summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--internal/data/networks.sql.go148
-rw-r--r--internal/packet/types.go29
-rw-r--r--internal/server/api/api.go93
-rw-r--r--internal/server/api/helpers.go23
-rw-r--r--internal/server/api/models.go15
-rw-r--r--internal/server/server.go3
-rw-r--r--query/networks.sql21
7 files changed, 271 insertions, 61 deletions
diff --git a/internal/data/networks.sql.go b/internal/data/networks.sql.go
index 8064fa9..5e1e0db 100644
--- a/internal/data/networks.sql.go
+++ b/internal/data/networks.sql.go
@@ -64,7 +64,7 @@ func (q *Queries) DeleteNetwork(ctx context.Context, id snowflake.ID) error {
return err
}
-const getBannedUsersInNetwork = `-- name: GetBannedUsersInNetwork :many
+const getNetworkBannedUsers = `-- name: GetNetworkBannedUsers :many
SELECT
users.id, users.name, users.public_key, users.description, users.is_public_dm, users.is_deleted,
users_networks.ban_reason
@@ -73,20 +73,20 @@ JOIN users ON users.id = users_networks.user_id
WHERE users_networks.network_id = ?
`
-type GetBannedUsersInNetworkRow struct {
+type GetNetworkBannedUsersRow struct {
User User
BanReason *string
}
-func (q *Queries) GetBannedUsersInNetwork(ctx context.Context, networkID snowflake.ID) ([]GetBannedUsersInNetworkRow, error) {
- rows, err := q.db.QueryContext(ctx, getBannedUsersInNetwork, networkID)
+func (q *Queries) GetNetworkBannedUsers(ctx context.Context, networkID snowflake.ID) ([]GetNetworkBannedUsersRow, error) {
+ rows, err := q.db.QueryContext(ctx, getNetworkBannedUsers, networkID)
if err != nil {
return nil, err
}
defer rows.Close()
- var items []GetBannedUsersInNetworkRow
+ var items []GetNetworkBannedUsersRow
for rows.Next() {
- var i GetBannedUsersInNetworkRow
+ var i GetNetworkBannedUsersRow
if err := rows.Scan(
&i.User.ID,
&i.User.Name,
@@ -129,43 +129,7 @@ func (q *Queries) GetNetworkById(ctx context.Context, id snowflake.ID) (Network,
return i, err
}
-const getPublicNetworks = `-- name: GetPublicNetworks :many
-SELECT id, owner_id, name, icon, bg_hex_color, fg_hex_color, is_public FROM networks
-WHERE is_public = true
-`
-
-func (q *Queries) GetPublicNetworks(ctx context.Context) ([]Network, error) {
- rows, err := q.db.QueryContext(ctx, getPublicNetworks)
- if err != nil {
- return nil, err
- }
- defer rows.Close()
- var items []Network
- for rows.Next() {
- var i Network
- if err := rows.Scan(
- &i.ID,
- &i.OwnerID,
- &i.Name,
- &i.Icon,
- &i.BgHexColor,
- &i.FgHexColor,
- &i.IsPublic,
- ); err != nil {
- return nil, err
- }
- items = append(items, i)
- }
- if err := rows.Close(); err != nil {
- return nil, err
- }
- if err := rows.Err(); err != nil {
- return nil, err
- }
- return items, nil
-}
-
-const getUsersInNetwork = `-- name: GetUsersInNetwork :many
+const getNetworkMembers = `-- name: GetNetworkMembers :many
SELECT
users.id, users.name, users.public_key, users.description, users.is_public_dm, users.is_deleted,
users_networks.joined_at,
@@ -176,22 +140,22 @@ JOIN users ON users.id = users_networks.user_id
WHERE users_networks.network_id = ? AND is_member = true
`
-type GetUsersInNetworkRow struct {
+type GetNetworkMembersRow struct {
User User
JoinedAt string
IsAdmin bool
IsMuted bool
}
-func (q *Queries) GetUsersInNetwork(ctx context.Context, networkID snowflake.ID) ([]GetUsersInNetworkRow, error) {
- rows, err := q.db.QueryContext(ctx, getUsersInNetwork, networkID)
+func (q *Queries) GetNetworkMembers(ctx context.Context, networkID snowflake.ID) ([]GetNetworkMembersRow, error) {
+ rows, err := q.db.QueryContext(ctx, getNetworkMembers, networkID)
if err != nil {
return nil, err
}
defer rows.Close()
- var items []GetUsersInNetworkRow
+ var items []GetNetworkMembersRow
for rows.Next() {
- var i GetUsersInNetworkRow
+ var i GetNetworkMembersRow
if err := rows.Scan(
&i.User.ID,
&i.User.Name,
@@ -216,6 +180,42 @@ func (q *Queries) GetUsersInNetwork(ctx context.Context, networkID snowflake.ID)
return items, nil
}
+const getPublicNetworks = `-- name: GetPublicNetworks :many
+SELECT id, owner_id, name, icon, bg_hex_color, fg_hex_color, is_public FROM networks
+WHERE is_public = true
+`
+
+func (q *Queries) GetPublicNetworks(ctx context.Context) ([]Network, error) {
+ rows, err := q.db.QueryContext(ctx, getPublicNetworks)
+ if err != nil {
+ return nil, err
+ }
+ defer rows.Close()
+ var items []Network
+ for rows.Next() {
+ var i Network
+ if err := rows.Scan(
+ &i.ID,
+ &i.OwnerID,
+ &i.Name,
+ &i.Icon,
+ &i.BgHexColor,
+ &i.FgHexColor,
+ &i.IsPublic,
+ ); err != nil {
+ return nil, err
+ }
+ items = append(items, i)
+ }
+ if err := rows.Close(); err != nil {
+ return nil, err
+ }
+ if err := rows.Err(); err != nil {
+ return nil, err
+ }
+ return items, nil
+}
+
const setNetworkIcon = `-- name: SetNetworkIcon :one
UPDATE networks SET
icon = ?,
@@ -306,6 +306,58 @@ func (q *Queries) SetNetworkName(ctx context.Context, arg SetNetworkNameParams)
return i, err
}
+const setNetworkUser = `-- name: SetNetworkUser :one
+INSERT INTO users_networks (
+ user_id, network_id,
+ is_member, is_admin, is_muted,
+ is_banned, ban_reason
+) VALUES (
+ ?, ?,
+ ?, ?, ?,
+ ?, ?
+)
+ON CONFLICT DO
+UPDATE SET
+ is_member = ?, is_admin = ?, is_muted = ?,
+ is_banned = ?, ban_reason = ?
+WHERE user_id = ? AND network_id = ?
+RETURNING user_id, network_id, joined_at, is_member, is_admin, is_muted, is_banned, ban_reason
+`
+
+type SetNetworkUserParams struct {
+ UserID snowflake.ID
+ NetworkID snowflake.ID
+ IsMember bool
+ IsAdmin bool
+ IsMuted bool
+ IsBanned bool
+ BanReason *string
+}
+
+func (q *Queries) SetNetworkUser(ctx context.Context, arg SetNetworkUserParams) (UsersNetwork, error) {
+ row := q.db.QueryRowContext(ctx, setNetworkUser,
+ arg.UserID,
+ arg.NetworkID,
+ arg.IsMember,
+ arg.IsAdmin,
+ arg.IsMuted,
+ arg.IsBanned,
+ arg.BanReason,
+ )
+ var i UsersNetwork
+ err := row.Scan(
+ &i.UserID,
+ &i.NetworkID,
+ &i.JoinedAt,
+ &i.IsMember,
+ &i.IsAdmin,
+ &i.IsMuted,
+ &i.IsBanned,
+ &i.BanReason,
+ )
+ return i, err
+}
+
const transferNetwork = `-- name: TransferNetwork :one
UPDATE networks SET
owner_id = ?
diff --git a/internal/packet/types.go b/internal/packet/types.go
index 405513e..031e983 100644
--- a/internal/packet/types.go
+++ b/internal/packet/types.go
@@ -26,8 +26,8 @@ func (m *CreateNetwork) Type() PacketType {
}
type UpdateNetwork struct {
- Network snowflake.ID
CreateNetwork
+ Network snowflake.ID
}
func (m *UpdateNetwork) Type() PacketType {
@@ -52,25 +52,34 @@ func (m *DeleteNetwork) Type() PacketType {
}
type SetNetworkUser struct {
- Network snowflake.ID
- User snowflake.ID
Member *bool
Admin *bool
Muted *bool
Banned *bool
BanReason *string
+ Network snowflake.ID
+ User snowflake.ID
}
func (m *SetNetworkUser) Type() PacketType {
return PacketSetNetworkUser
}
+type Member struct {
+ JoinedAt string
+ User data.User
+ IsAdmin bool
+ IsMuted bool
+}
+
+type FullNetwork struct {
+ Network data.Network
+ Frequencies []data.Frequency
+ Members []Member
+}
+
type NetworksInfo struct {
- Networks []struct {
- Network data.Network
- Frequencies []data.Frequency
- Members []data.User
- }
+ Networks []FullNetwork
}
func (m *NetworksInfo) Type() PacketType {
@@ -78,9 +87,9 @@ func (m *NetworksInfo) Type() PacketType {
}
type CreateFrequency struct {
- Network snowflake.ID
Name string
HexColor string
+ Network snowflake.ID
Perms int
}
@@ -89,9 +98,9 @@ func (m *CreateFrequency) Type() PacketType {
}
type UpdateFrequency struct {
- Frequency snowflake.ID
Name string
HexColor string
+ Frequency snowflake.ID
Perms byte
}
diff --git a/internal/server/api/api.go b/internal/server/api/api.go
index 6c8d51a..f320838 100644
--- a/internal/server/api/api.go
+++ b/internal/server/api/api.go
@@ -4,6 +4,7 @@ import (
"context"
"crypto/ed25519"
"database/sql"
+ "fmt"
"log"
"strconv"
"strings"
@@ -14,6 +15,8 @@ import (
"github.com/kyren223/eko/pkg/snowflake"
)
+var internalError = packet.Error{Error: "internal server error"}
+
func SendMessage(ctx context.Context, sess *session.Session, request *packet.SendMessage) packet.Payload {
if (request.ReceiverID != nil) == (request.FrequencyID != nil) {
return &packet.Error{Error: "either receiver id or frequency id must exist"}
@@ -34,7 +37,7 @@ func SendMessage(ctx context.Context, sess *session.Session, request *packet.Sen
})
if err != nil {
log.Println(sess.Addr(), "database error:", err, "in SendMessage")
- return &packet.Error{Error: "internal server error"}
+ return &internalError
}
return &packet.MessagesInfo{Messages: []data.Message{message}}
@@ -79,3 +82,91 @@ func CreateOrGetUser(ctx context.Context, node *snowflake.Node, pubKey ed25519.P
}
return user, nil
}
+
+func CreateNetwork(ctx context.Context, sess *session.Session, request packet.CreateNetwork) packet.Payload {
+ name := strings.TrimSpace(request.Name)
+ if name == "" {
+ return &packet.Error{Error: "server name must not be blank"}
+ }
+
+ if len(request.Icon) > MaxIconSize {
+ return &packet.Error{Error: fmt.Sprintf("icon is too large, must be smaller than %v bytes", MaxIconSize)}
+ }
+
+ if ok, err := isValidHexColor(request.BgHexColor); !ok {
+ return &packet.Error{Error: err}
+ }
+ if ok, err := isValidHexColor(request.FgHexColor); !ok {
+ return &packet.Error{Error: err}
+ }
+
+ tx, err := db.BeginTx(ctx, nil)
+ if err != nil {
+ log.Println("database error:", err)
+ return &internalError
+ }
+
+ queries := data.New(db)
+ qtx := queries.WithTx(tx)
+
+ network, err := qtx.CreateNetwork(ctx, data.CreateNetworkParams{
+ ID: sess.Manager().Node().Generate(),
+ OwnerID: sess.ID(),
+ Name: name,
+ IsPublic: request.IsPublic,
+ Icon: request.Icon,
+ BgHexColor: request.BgHexColor,
+ FgHexColor: request.FgHexColor,
+ })
+ if err != nil {
+ log.Println("database error:", err)
+ return &internalError
+ }
+
+ frequency, err := qtx.CreateFrequency(ctx, data.CreateFrequencyParams{
+ ID: sess.Manager().Node().Generate(),
+ NetworkID: network.ID,
+ Name: DefaultFrequencyName,
+ HexColor: nil,
+ Perms: PermReadWrite,
+ })
+ if err != nil {
+ log.Println("database error:", err)
+ return &internalError
+ }
+
+ networkUser, err := qtx.SetNetworkUser(ctx, data.SetNetworkUserParams{
+ UserID: network.OwnerID,
+ NetworkID: network.ID,
+ IsMember: true,
+ IsAdmin: true,
+ IsMuted: false,
+ IsBanned: false,
+ BanReason: nil,
+ })
+ if err != nil {
+ log.Println("database error:", err)
+ return &internalError
+ }
+
+ user, err := qtx.GetUserById(ctx, network.OwnerID)
+ if err != nil {
+ log.Println("database error:", err)
+ return &internalError
+ }
+
+ fullNetwork := packet.FullNetwork{
+ Network: network,
+ Frequencies: []data.Frequency{frequency},
+ Members: []packet.Member{{
+ JoinedAt: networkUser.JoinedAt,
+ User: user,
+ IsAdmin: networkUser.IsAdmin,
+ IsMuted: networkUser.IsMuted,
+ }},
+ }
+ return &packet.NetworksInfo{
+ Networks: []packet.FullNetwork{fullNetwork},
+ }
+}
+
diff --git a/internal/server/api/helpers.go b/internal/server/api/helpers.go
new file mode 100644
index 0000000..967f308
--- /dev/null
+++ b/internal/server/api/helpers.go
@@ -0,0 +1,23 @@
+package api
+
+import "strings"
+
+const hex = "0123456789abcdefABCDEF"
+
+func isValidHexColor(color string) (bool, string) {
+ if len(color) != 7 {
+ return false, "color must be hex with length of 7"
+ }
+
+ if color[0] != '#' {
+ return false, "color must start with '#'"
+ }
+
+ for _, c := range color {
+ if !strings.ContainsRune(hex, c) {
+ return false, "color must start with '#' and contain exactly 6 digits 0-9, a-f, A-F"
+ }
+ }
+
+ return true, ""
+}
diff --git a/internal/server/api/models.go b/internal/server/api/models.go
new file mode 100644
index 0000000..317ee6e
--- /dev/null
+++ b/internal/server/api/models.go
@@ -0,0 +1,15 @@
+package api
+
+const (
+ MaxIconSize = 16
+ DefaultFrequencyName = "main"
+)
+
+const (
+ PermNoAccess = 0 + iota
+ PermRead
+ PermReadWrite
+ PermMax
+)
+
+
diff --git a/internal/server/server.go b/internal/server/server.go
index 5f4376a..c15414f 100644
--- a/internal/server/server.go
+++ b/internal/server/server.go
@@ -289,6 +289,8 @@ func processRequest(ctx context.Context, sess *session.Session, request packet.P
// TODO: add a way to measure the time each request/response took and log it
// Potentially even separate time for code vs DB operations
switch request := request.(type) {
+ case *packet.CreateNetwork:
+ return timeout(20*time.Millisecond, api.CreateNetwork, ctx, sess, request)
case *packet.SendMessage:
return timeout(20*time.Millisecond, api.SendMessage, ctx, sess, request)
case *packet.RequestMessages:
@@ -303,6 +305,7 @@ func timeout[T packet.Payload](
apiRequest func(context.Context, *session.Session, T) packet.Payload,
ctx context.Context, sess *session.Session, request T,
) packet.Payload {
+ // TODO: Remove the channel and just wait directly?
responseChan := make(chan packet.Payload)
ctx, cancel := context.WithTimeout(ctx, timeoutDuration)
defer cancel()
diff --git a/query/networks.sql b/query/networks.sql
index dfa7b6b..0b0303f 100644
--- a/query/networks.sql
+++ b/query/networks.sql
@@ -16,7 +16,7 @@ INSERT INTO networks (
)
RETURNING *;
--- name: GetBannedUsersInNetwork :many
+-- name: GetNetworkBannedUsers :many
SELECT
sqlc.embed(users),
users_networks.ban_reason
@@ -24,7 +24,7 @@ FROM users_networks
JOIN users ON users.id = users_networks.user_id
WHERE users_networks.network_id = ?;
--- name: GetUsersInNetwork :many
+-- name: GetNetworkMembers :many
SELECT
sqlc.embed(users),
users_networks.joined_at,
@@ -62,3 +62,20 @@ RETURNING *;
-- name: DeleteNetwork :exec
DELETE FROM networks WHERE id = ?;
+
+-- name: SetNetworkUser :one
+INSERT INTO users_networks (
+ user_id, network_id,
+ is_member, is_admin, is_muted,
+ is_banned, ban_reason
+) VALUES (
+ ?, ?,
+ ?, ?, ?,
+ ?, ?
+)
+ON CONFLICT DO
+UPDATE SET
+ is_member = ?, is_admin = ?, is_muted = ?,
+ is_banned = ?, ban_reason = ?
+WHERE user_id = ? AND network_id = ?
+RETURNING *;