summaryrefslogtreecommitdiff
path: root/server/user
diff options
context:
space:
mode:
authorraven <citrons@mondecitronne.com>2025-10-20 18:06:13 -0500
committerraven <citrons@mondecitronne.com>2025-10-20 18:06:13 -0500
commit5320b561c592e875f0523760d4d20df8d66b30a7 (patch)
tree1b2f6cb8edc6a4b9cdd5d138b8528f04ff46971d /server/user
parent9e08d84af7d975ef540a67b54ecc9b3c0e4d084c (diff)
user and channel saving
Diffstat (limited to 'server/user')
-rw-r--r--server/user/command.go1
-rw-r--r--server/user/user.go124
2 files changed, 100 insertions, 25 deletions
diff --git a/server/user/command.go b/server/user/command.go
index 14a8b57..47a3efb 100644
--- a/server/user/command.go
+++ b/server/user/command.go
@@ -47,6 +47,7 @@ func (u *User) SendRequest(r session.Request) {
return
}
}
+ u.Save()
u.Stream.Event(r.Cmd)
r.ReplyOk()
diff --git a/server/user/user.go b/server/user/user.go
index 17063b4..46eda35 100644
--- a/server/user/user.go
+++ b/server/user/user.go
@@ -5,29 +5,31 @@ import (
"citrons.xyz/talk/server/session"
"citrons.xyz/talk/server/validate"
"citrons.xyz/talk/proto"
+ bolt "go.etcd.io/bbolt"
+ "log"
)
-type UserStore struct {
+type UserKind struct {
world *object.World
- byName map[string]*User
+ db *bolt.DB
}
type User struct {
- store *UserStore
+ kind *UserKind
name string
id string
status string
description string
Stream session.Stream
- Channels map[string]bool
- Anonymous bool
+ anonymous bool
+ Channels map[string]bool // TODO: remove
}
-func NewStore(world *object.World) *UserStore {
- return &UserStore {world, make(map[string]*User)}
+func Kind(world *object.World) *UserKind {
+ return &UserKind {world, world.DB()}
}
-func (us *UserStore) CreateUser(name string) (*User, *proto.Fail) {
+func (us *UserKind) CreateUser(name string) (*User, *proto.Fail) {
if us.ByName(name) != nil {
return nil, &proto.Fail {
"name-taken", "", map[string]string {"": name},
@@ -39,44 +41,107 @@ func (us *UserStore) CreateUser(name string) (*User, *proto.Fail) {
}
}
var u User
- u.store = us
+ u.kind = us
u.name = name
- us.byName[validate.Fold(name)] = &u
- u.id = us.world.NewObject(&u)
+ u.id = proto.GenId()
+ u.anonymous = true
u.Channels = make(map[string]bool)
+ u.Save()
return &u, nil
}
-func (us *UserStore) ByName(name string) *User {
- return us.byName[validate.Fold(name)]
+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 validate.Fold(name) == validate.Fold(u.name) {
- u.name = name
- return nil
- }
- if u.store.ByName(name) != nil {
+ if u.kind.ByName(name) != nil &&
+ validate.Fold(name) != validate.Fold(u.name) {
return &proto.Fail {
"name-taken", "", map[string]string {"": name},
}
}
- u.store.byName[validate.Fold(u.name)] = nil
- u.store.byName[validate.Fold(name)] = u
u.name = name
+ u.Save()
return nil
}
@@ -84,13 +149,18 @@ func (u *User) Delete() {
u.Stream.Event(proto.NewCmd("delete", u.id))
u.Stream.UnsubscribeAll()
- delete(u.store.byName, validate.Fold(u.name))
- u.store.world.RemoveObject(u.id)
-
gone := object.Tombstone {
u.id, map[string]string {"": u.name, "kind": "u"},
}
- u.store.world.PutObject(u.id, gone)
+ u.kind.world.PutObject(u.id, gone)
+ err := u.kind.db.Update(func(tx *bolt.Tx) error {
+ bucket, _ := tx.CreateBucketIfNotExists([]byte("anonymous users"))
+ bucket.Delete([]byte(u.id))
+ return nil
+ })
+ if err != nil {
+ log.Fatal("error updating database: ", err)
+ }
}
func (u *User) InfoFor(uid string) proto.Object {
@@ -98,10 +168,14 @@ func (u *User) InfoFor(uid string) proto.Object {
if u.status != "" {
i["status"] = u.status
}
- if u.Anonymous {
+ 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
+}