package user import ( "citrons.xyz/talk/server/object" "citrons.xyz/talk/server/session" "citrons.xyz/talk/server/validate" "citrons.xyz/talk/proto" bolt "go.etcd.io/bbolt" "log" ) type UserKind struct { world *object.World db *bolt.DB } type User struct { kind *UserKind name string id string status string description string Stream session.Stream PrivateStream session.Stream anonymous bool } func Kind(world *object.World) *UserKind { return &UserKind {world, world.DB()} } func (us *UserKind) CreateUser(name string) (*User, *proto.Fail) { if us.ByName(name) != nil { return nil, &proto.Fail { "name-taken", "", map[string]string {"": name}, } } if !validate.Name(name) { return nil, &proto.Fail { "invalid-name", "", map[string]string {"": name}, } } var u User u.kind = us u.name = name u.id = proto.GenId() u.anonymous = true u.Save() return &u, nil } func (us *UserKind) ByName(name string) *User { switch u := us.world.Lookup("u", name).(type) { case *User: return u default: return nil } } func (us *UserKind) DeleteAnonUsers() { var anon []string err := us.db.View(func(tx *bolt.Tx) error { bucket := tx.Bucket([]byte("anonymous users")) if bucket == nil { return nil } bucket.ForEach(func(k, v []byte) error { anon = append(anon, string(k)) return nil }) return nil }) if err != nil { log.Fatal("error reading database: ", err) } for _, id := range anon { switch u := us.world.GetObject(id).(type) { case *User: u.Delete() } } } func (us *UserKind) Undata(o proto.Object) object.Object { var u User u.kind = us u.id = o.Id u.name = o.Fields[""] u.status = o.Fields["status"] u.description = o.Fields["description"] u.anonymous = o.Fields["anonymous"] == "yes" return &u } func (u *User) Data() proto.Object { data := u.InfoFor("") data.Fields["description"] = u.description return data } func (u *User) Name() string { return u.name } func (u *User) Handle() string { return u.Name() } func (u *User) Id() string { return u.id } func (u *User) Save() { err := u.kind.db.Update(func(tx *bolt.Tx) error { bucket, _ := tx.CreateBucketIfNotExists([]byte("anonymous users")) if u.anonymous { bucket.Put([]byte(u.id), []byte("yes")) } else { bucket.Delete([]byte(u.id)) } return nil }) if err != nil { log.Fatal("error updating database: ", err) } u.kind.world.PutObject(u.id, u) } func (u *User) Rename(name string) *proto.Fail { if !validate.Name(name) { return &proto.Fail { "invalid-name", "", map[string]string {"": name}, } } if u.kind.ByName(name) != nil && validate.Fold(name) != validate.Fold(u.name) { return &proto.Fail { "name-taken", "", map[string]string {"": name}, } } u.name = name u.Save() return nil } func (u *User) Delete() { u.Stream.Event(proto.NewCmd("delete", u.id)) u.Stream.UnsubscribeAll() gone := object.Tombstone { u.id, map[string]string {"": u.name, "kind": "u"}, } u.kind.world.PutObject(u.id, gone) err := u.kind.db.Update(func(tx *bolt.Tx) error { anons, _ := tx.CreateBucketIfNotExists([]byte("anonymous users")) anons.Delete([]byte(u.id)) channels, _ := tx.CreateBucketIfNotExists([]byte("user channels")) channels.DeleteBucket([]byte(u.id)) return nil }) if err != nil { log.Fatal("error updating database: ", err) } } func (u *User) InfoFor(uid string) proto.Object { i := map[string]string {"": u.name} if u.status != "" { i["status"] = u.status } if u.anonymous { i["anonymous"] = "yes" } else { i["anonymous"] = "no" } return proto.Object {"u", u.id, i} } func (u *User) IsAnonymous() bool { return u.anonymous }