diff --git a/internal/client/api/api.go b/internal/client/api/api.go index d91de46..cfc4ce9 100644 --- a/internal/client/api/api.go +++ b/internal/client/api/api.go @@ -92,3 +92,39 @@ func GetUserById(id snowflake.ID) tea.Cmd { } } +func CreateServer( + name string, + icon string, + bgHexColor string, + fgHexColor string, + isPublic bool, +) tea.Cmd { + return func() tea.Msg { + requestName := "CreateServer" + log.Println("request", requestName, "sent") + request := packet.CreateNetwork{ + Name: name, + Icon: icon, + BgHexColor: bgHexColor, + FgHexColor: fgHexColor, + IsPublic: isPublic, + } + response, ok := <-gateway.Send(&request) + if !ok { + return fmt.Errorf("request %s timeout", requestName) + } + log.Println("request", requestName, "received response") + + switch response := response.(type) { + case *packet.ErrorMessage: + return errors.New(response.Error) + case *packet.Users: + if len(response.Users) == 0 { + return fmt.Errorf("requested user id %v not found", id) + } + assert.Assert(len(response.Users) == 1, "server must return only one user with the matching id") + return UserProfileUpdate(response.Users[0]) + } + return fmt.Errorf("received invalid response from server: %v", response.Type()) + } +} diff --git a/internal/data/models.go b/internal/data/models.go index 4bfb7a5..e6fac50 100644 --- a/internal/data/models.go +++ b/internal/data/models.go @@ -37,18 +37,11 @@ type Network struct { IsPublic bool } -type NetworkBannedUser struct { - NetworkID snowflake.ID - BannedUserID snowflake.ID - BannedAt string - Reason *string -} - type User struct { ID snowflake.ID Name string PublicKey ed25519.PublicKey - Description *string + Description string IsPublicDM bool IsDeleted bool } @@ -68,6 +61,9 @@ type UsersNetwork struct { UserID snowflake.ID NetworkID snowflake.ID JoinedAt string + IsMember bool IsAdmin bool IsMuted bool + IsBanned bool + BanReason *string } diff --git a/internal/data/networks.sql.go b/internal/data/networks.sql.go index d690b7b..8064fa9 100644 --- a/internal/data/networks.sql.go +++ b/internal/data/networks.sql.go @@ -67,17 +67,15 @@ func (q *Queries) DeleteNetwork(ctx context.Context, id snowflake.ID) error { const getBannedUsersInNetwork = `-- name: GetBannedUsersInNetwork :many SELECT users.id, users.name, users.public_key, users.description, users.is_public_dm, users.is_deleted, - network_banned_users.banned_at, - network_banned_users.reason -FROM network_banned_users -JOIN users ON users.id = network_banned_users.banned_user_id -WHERE network_banned_users.network_id = ? + users_networks.ban_reason +FROM users_networks +JOIN users ON users.id = users_networks.user_id +WHERE users_networks.network_id = ? ` type GetBannedUsersInNetworkRow struct { - User User - BannedAt string - Reason *string + User User + BanReason *string } func (q *Queries) GetBannedUsersInNetwork(ctx context.Context, networkID snowflake.ID) ([]GetBannedUsersInNetworkRow, error) { @@ -96,8 +94,7 @@ func (q *Queries) GetBannedUsersInNetwork(ctx context.Context, networkID snowfla &i.User.Description, &i.User.IsPublicDM, &i.User.IsDeleted, - &i.BannedAt, - &i.Reason, + &i.BanReason, ); err != nil { return nil, err } @@ -176,7 +173,7 @@ SELECT users_networks.is_muted FROM users_networks JOIN users ON users.id = users_networks.user_id -WHERE users_networks.network_id = ? +WHERE users_networks.network_id = ? AND is_member = true ` type GetUsersInNetworkRow struct { diff --git a/internal/data/users.sql.go b/internal/data/users.sql.go index 4411a80..33eab3f 100644 --- a/internal/data/users.sql.go +++ b/internal/data/users.sql.go @@ -117,7 +117,7 @@ RETURNING id, name, public_key, description, is_public_dm, is_deleted ` type SetUserDescriptionParams struct { - Description *string + Description string ID snowflake.ID } diff --git a/internal/packet/messages.go b/internal/packet/messages.go deleted file mode 100644 index 58d12e9..0000000 --- a/internal/packet/messages.go +++ /dev/null @@ -1,67 +0,0 @@ -package packet - -import ( - "github.com/kyren223/eko/internal/data" - "github.com/kyren223/eko/pkg/snowflake" -) - -type ErrorMessage struct { - Error string `msgpack:"error"` -} - -func (m *ErrorMessage) Type() PacketType { - return PacketError -} - -type SendMessage struct { - ReceiverID *snowflake.ID - FrequencyID *snowflake.ID - Content string -} - -func (m *SendMessage) Type() PacketType { - return PacketSendMessage -} - -type PushedMessages struct { - Messages []data.Message -} - -func (m *PushedMessages) Type() PacketType { - return PacketPushedMessages -} - -type Messages struct { - Messages []data.Message -} - -func (m *Messages) Type() PacketType { - return PacketMessages -} - -type GetMessagesRange struct { - FrequencyID *snowflake.ID - ReceiverID *snowflake.ID - From *int64 - To *int64 -} - -func (m *GetMessagesRange) Type() PacketType { - return PacketGetMessageRange -} - -type GetUserByID struct { - UserID snowflake.ID -} - -func (m *GetUserByID) Type() PacketType { - return PacketGetUserById -} - -type Users struct { - Users []data.User -} - -func (m *Users) Type() PacketType { - return PacketUsers -} diff --git a/internal/packet/packet.go b/internal/packet/packet.go index 58e83b4..8eb1a86 100644 --- a/internal/packet/packet.go +++ b/internal/packet/packet.go @@ -51,47 +51,34 @@ type PacketType uint8 const ( PacketError PacketType = iota + + PacketCreateNetwork + PacketUpdateNetwork + PacketTransferNetwork + PacketDeleteNetwork + PacketSetNetworkUser + PacketNetworksInfo + + PacketCreateFrequency + PacketUpdateFrequency + PacketDeleteFrequency + PacketSwapFrequencies + PacketSendMessage - PacketPushedMessages - PacketGetMessageRange - PacketMessages - PacketGetUserById - PacketUsers + PacketEditMessage + PacketDeleteMessage + PacketRequestMessages + PacketMessagesInfo + + PacketMax ) -func (t PacketType) String() string { - switch t { - case PacketError: - return "PacketError" - case PacketSendMessage: - return "PacketSendMessage" - case PacketPushedMessages: - return "PacketPushedMessages" - case PacketGetMessageRange: - return "PacketGetMessageRange" - case PacketMessages: - return "PacketMessages" - case PacketGetUserById: - return "PacketGetUserById" - case PacketUsers: - return "PacketUsers" - default: - return fmt.Sprintf("PacketInvalidType(%v)", byte(t)) - } +func Init() { + assert.Assert(PacketMax <= 64, "packet types exceeded allowed limit of 64 types") } func (e PacketType) IsSupported() bool { - return e <= PacketUsers -} - -// True for all packets that a server may push passively to the client. -func (e PacketType) IsPush() bool { - switch e { - case PacketPushedMessages: - return true - default: - return false - } + return e < PacketMax } const ( @@ -166,7 +153,7 @@ func (p Packet) Payload() []byte { } func (p Packet) String() string { - return fmt.Sprintf("Packet(v%v %v %v [%v bytes...])", p.Version(), p.Encoding().String(), p.Type().String(), p.PayloadLength()) + return fmt.Sprintf("Packet(v%v t%v %v [%v bytes...])", p.Version(), p.Encoding().String(), p.Type(), p.PayloadLength()) } func (p Packet) Into(writer io.Writer) (int, error) { @@ -196,21 +183,39 @@ func (p Packet) DecodedPayload() (Payload, error) { var payload Payload switch p.Type() { case PacketError: - payload = &ErrorMessage{} - case PacketPushedMessages: - payload = &PushedMessages{} + payload = &Error{} + case PacketCreateFrequency: + payload = &CreateFrequency{} + case PacketCreateNetwork: + payload = &CreateNetwork{} + case PacketDeleteFrequency: + payload = &DeleteFrequency{} + case PacketDeleteMessage: + payload = &DeleteMessage{} + case PacketDeleteNetwork: + payload = &DeleteNetwork{} + case PacketEditMessage: + payload = &EditMessage{} + case PacketMessagesInfo: + payload = &MessagesInfo{} + case PacketNetworksInfo: + payload = &NetworksInfo{} + case PacketRequestMessages: + payload = &RequestMessages{} case PacketSendMessage: payload = &SendMessage{} - case PacketGetMessageRange: - payload = &GetMessagesRange{} - case PacketMessages: - payload = &Messages{} - case PacketGetUserById: - payload = &GetUserByID{} - case PacketUsers: - payload = &Users{} + case PacketSetNetworkUser: + payload = &SetNetworkUser{} + case PacketSwapFrequencies: + payload = &SwapFrequencies{} + case PacketTransferNetwork: + payload = &TransferNetwork{} + case PacketUpdateFrequency: + payload = &UpdateFrequency{} + case PacketUpdateNetwork: + payload = &UpdateNetwork{} default: - assert.Never("packet type of a packet struct must always be valid") + assert.Never("unexpected packet.PacketType", "type", p.Type()) } err := p.DecodePayloadInto(payload) return payload, err diff --git a/internal/packet/types.go b/internal/packet/types.go new file mode 100644 index 0000000..405513e --- /dev/null +++ b/internal/packet/types.go @@ -0,0 +1,162 @@ +package packet + +import ( + "github.com/kyren223/eko/internal/data" + "github.com/kyren223/eko/pkg/snowflake" +) + +type Error struct { + Error string +} + +func (m *Error) Type() PacketType { + return PacketError +} + +type CreateNetwork struct { + Name string + Icon string + BgHexColor string + FgHexColor string + IsPublic bool +} + +func (m *CreateNetwork) Type() PacketType { + return PacketCreateNetwork +} + +type UpdateNetwork struct { + Network snowflake.ID + CreateNetwork +} + +func (m *UpdateNetwork) Type() PacketType { + return PacketUpdateNetwork +} + +type TransferNetwork struct { + Network snowflake.ID + User snowflake.ID +} + +func (m *TransferNetwork) Type() PacketType { + return PacketTransferNetwork +} + +type DeleteNetwork struct { + Network snowflake.ID +} + +func (m *DeleteNetwork) Type() PacketType { + return PacketDeleteNetwork +} + +type SetNetworkUser struct { + Network snowflake.ID + User snowflake.ID + Member *bool + Admin *bool + Muted *bool + Banned *bool + BanReason *string +} + +func (m *SetNetworkUser) Type() PacketType { + return PacketSetNetworkUser +} + +type NetworksInfo struct { + Networks []struct { + Network data.Network + Frequencies []data.Frequency + Members []data.User + } +} + +func (m *NetworksInfo) Type() PacketType { + return PacketNetworksInfo +} + +type CreateFrequency struct { + Network snowflake.ID + Name string + HexColor string + Perms int +} + +func (m *CreateFrequency) Type() PacketType { + return PacketCreateFrequency +} + +type UpdateFrequency struct { + Frequency snowflake.ID + Name string + HexColor string + Perms byte +} + +func (m *UpdateFrequency) Type() PacketType { + return PacketUpdateFrequency +} + +type DeleteFrequency struct { + Frequency snowflake.ID +} + +func (m *DeleteFrequency) Type() PacketType { + return PacketDeleteFrequency +} + +type SwapFrequencies struct { + Network snowflake.ID + Pos1 int + Pos2 int +} + +func (m *SwapFrequencies) Type() PacketType { + return PacketSwapFrequencies +} + +type SendMessage struct { + ReceiverID *snowflake.ID + FrequencyID *snowflake.ID + Content string +} + +func (m *SendMessage) Type() PacketType { + return PacketSendMessage +} + +type EditMessage struct { + Message snowflake.ID + Content string +} + +func (m *EditMessage) Type() PacketType { + return PacketEditMessage +} + +type DeleteMessage struct { + Message snowflake.ID +} + +func (m *DeleteMessage) Type() PacketType { + return PacketDeleteMessage +} + +type RequestMessages struct { + ReceiverID *snowflake.ID + FrequencyID *snowflake.ID +} + +func (m *RequestMessages) Type() PacketType { + return PacketRequestMessages +} + +type MessagesInfo struct { + Messages []data.Message +} + +func (m *MessagesInfo) Type() PacketType { + return PacketMessagesInfo +} diff --git a/migrations/20241026174345_init.sql b/migrations/20241026174345_init.sql index d20898a..f7cedd8 100644 --- a/migrations/20241026174345_init.sql +++ b/migrations/20241026174345_init.sql @@ -3,7 +3,7 @@ CREATE TABLE messages ( id INTEGER PRIMARY KEY, sender_id INT NOT NULL REFERENCES users (id), content TEXT NOT NULL, - edited BOOLEAN NOT NULL CHECK (edited IN (0, 1)) DEFAULT 0, + edited BOOLEAN NOT NULL CHECK (edited IN (false, true)) DEFAULT false, frequency_id INT REFERENCES frequencies (id) ON DELETE CASCADE, receiver_id INT REFERENCES users (id), @@ -18,9 +18,9 @@ CREATE TABLE users ( id INTEGER PRIMARY KEY, name TEXT NOT NULL, public_key BLOB NOT NULL UNIQUE, - description TEXT, - is_public_dm BOOLEAN NOT NULL CHECK (is_public_dm IN (0, 1)) DEFAULT 1, - is_deleted BOOLEAN NOT NULL CHECK (is_deleted IN (0, 1)) DEFAULT 0 + description TEXT NOT NULL, + is_public_dm BOOLEAN NOT NULL CHECK (is_public_dm IN (false, true)) DEFAULT true, + is_deleted BOOLEAN NOT NULL CHECK (is_deleted IN (false, true)) DEFAULT false ); CREATE TABLE networks ( @@ -30,7 +30,7 @@ CREATE TABLE networks ( icon TEXT NOT NULL, bg_hex_color TEXT NOT NULL, fg_hex_color TEXT NOT NULL, - is_public BOOLEAN NOT NULL CHECK (is_public IN (0, 1)) + is_public BOOLEAN NOT NULL CHECK (is_public IN (false, true)) ); CREATE TABLE frequencies ( @@ -39,7 +39,7 @@ CREATE TABLE frequencies ( name TEXT NOT NULL, hex_color TEXT, perms INT NOT NULL CHECK (perms IN (0, 1, 2)), - -- 0 rw admin | 1 r all w admin | 2 rw all + -- 0 no access | 1 read | 2 read & write position INT NOT NULL ); @@ -47,8 +47,11 @@ CREATE TABLE users_networks ( user_id INT NOT NULL REFERENCES users (id), network_id INT NOT NULL REFERENCES networks (id) ON DELETE CASCADE, joined_at TEXT NOT NULL DEFAULT current_timestamp, - is_admin BOOLEAN NOT NULL CHECK (is_admin IN (0, 1)) DEFAULT 0, - is_muted BOOLEAN NOT NULL CHECK (is_muted IN (0, 1)) DEFAULT 0, + is_member BOOLEAN NOT NULL CHECK (is_member IN (false, true)) DEFAULT true, + is_admin BOOLEAN NOT NULL CHECK (is_admin IN (false, true)) DEFAULT false, + is_muted BOOLEAN NOT NULL CHECK (is_muted IN (false, true)) DEFAULT false, + is_banned BOOLEAN NOT NULL CHECK (is_banned IN (false, true)) DEFAULT false, + ban_reason TEXT, PRIMARY KEY (user_id, network_id) ); @@ -65,18 +68,10 @@ CREATE TABLE user_blocked_users ( PRIMARY KEY (blocker_user_id, blocked_user_id) ); -CREATE TABLE network_banned_users ( - network_id INT NOT NULL REFERENCES networks (id) ON DELETE CASCADE, - banned_user_id INT NOT NULL REFERENCES users (id), - banned_at TEXT NOT NULL DEFAULT current_timestamp, - reason TEXT, - PRIMARY KEY (network_id, banned_user_id) -); - -CREATE INDEX idx_network_name ON networks (name) WHERE is_public = 1; +CREATE INDEX idx_network_name ON networks (name) WHERE is_public = true; CREATE INDEX idx_frequency_network ON frequencies (network_id); CREATE INDEX idx_blocked_by_user ON user_blocked_users (blocker_user_id); -CREATE INDEX idx_banned_by_network ON network_banned_users (network_id); +CREATE INDEX idx_banned_by_network ON users_networks (network_id) WHERE is_banned = true; CREATE INDEX idx_direct_messages ON messages (sender_id, receiver_id); CREATE INDEX idx_frequency_messages ON messages (frequency_id); diff --git a/query/networks.sql b/query/networks.sql index 9ff350e..dfa7b6b 100644 --- a/query/networks.sql +++ b/query/networks.sql @@ -19,11 +19,10 @@ RETURNING *; -- name: GetBannedUsersInNetwork :many SELECT sqlc.embed(users), - network_banned_users.banned_at, - network_banned_users.reason -FROM network_banned_users -JOIN users ON users.id = network_banned_users.banned_user_id -WHERE network_banned_users.network_id = ?; + users_networks.ban_reason +FROM users_networks +JOIN users ON users.id = users_networks.user_id +WHERE users_networks.network_id = ?; -- name: GetUsersInNetwork :many SELECT @@ -33,7 +32,7 @@ SELECT users_networks.is_muted FROM users_networks JOIN users ON users.id = users_networks.user_id -WHERE users_networks.network_id = ?; +WHERE users_networks.network_id = ? AND is_member = true; -- name: SetNetworkName :one UPDATE networks SET