From 0c093e8403f0ae7945e7eb3162d17bd8117afdc2 Mon Sep 17 00:00:00 2001 From: J3vb <192430104+J3vb@users.noreply.github.com> Date: Sun, 19 Jul 2026 08:15:58 +0200 Subject: [PATCH] chore(server): delete unfinished Postgres scaffolding PostgresStore was 86% stubs behind a build tag nothing enables, pgdbgen carried hand-added build tags that fought sqlc-verify, and the runtime never threaded store.Store through the handler boundary. Single-engine reality shrinks the W1-3 attachment-ownership fix and ends the pgdbgen churn. Removed: store/postgres.go, db/pgdbgen/, db/queries/postgres/, migrations/postgres/, the sqlc postgres block, pgx from go.mod, the startup-refusal branch, and the dead Postgres config surface. Co-Authored-By: Claude Fable 5 --- Server/Makefile | 10 +- Server/config/config.go | 32 +- Server/db/pgdbgen/admin.sql.go | 307 ------- Server/db/pgdbgen/attachments.sql.go | 169 ---- Server/db/pgdbgen/blocks.sql.go | 86 -- Server/db/pgdbgen/channels.sql.go | 381 -------- Server/db/pgdbgen/db.go | 34 - Server/db/pgdbgen/dm.sql.go | 241 ----- Server/db/pgdbgen/events.sql.go | 109 --- Server/db/pgdbgen/invites.sql.go | 140 --- Server/db/pgdbgen/lockouts.sql.go | 74 -- Server/db/pgdbgen/messages.sql.go | 479 ---------- Server/db/pgdbgen/models.go | 218 ----- Server/db/pgdbgen/plugins.sql.go | 175 ---- Server/db/pgdbgen/profile.sql.go | 48 - Server/db/pgdbgen/querier.go | 197 ----- Server/db/pgdbgen/reactions.sql.go | 78 -- Server/db/pgdbgen/roles.sql.go | 165 ---- Server/db/pgdbgen/sessions.sql.go | 221 ----- Server/db/pgdbgen/users.sql.go | 219 ----- Server/db/pgdbgen/voice.sql.go | 371 -------- Server/db/queries/postgres/admin.sql | 55 -- Server/db/queries/postgres/attachments.sql | 27 - Server/db/queries/postgres/blocks.sql | 18 - Server/db/queries/postgres/channels.sql | 69 -- Server/db/queries/postgres/dm.sql | 64 -- Server/db/queries/postgres/events.sql | 19 - Server/db/queries/postgres/invites.sql | 23 - Server/db/queries/postgres/lockouts.sql | 15 - Server/db/queries/postgres/messages.sql | 79 -- Server/db/queries/postgres/plugins.sql | 36 - Server/db/queries/postgres/profile.sql | 9 - Server/db/queries/postgres/reactions.sql | 12 - Server/db/queries/postgres/roles.sql | 29 - Server/db/queries/postgres/sessions.sql | 49 -- Server/db/queries/postgres/users.sql | 51 -- Server/db/queries/postgres/voice.sql | 88 -- Server/go.mod | 4 - Server/go.sum | 15 +- Server/main.go | 29 +- .../postgres/001_initial_schema.sql | 336 ------- Server/migrations/postgres/migrations.go | 17 - Server/plugin/manifest.go | 2 +- Server/plugin/sandbox_wazero.go | 2 +- Server/sqlc.yaml | 20 - Server/store/postgres.go | 833 ------------------ Server/telemetry/telemetry_otel.go | 2 +- 47 files changed, 22 insertions(+), 5635 deletions(-) delete mode 100644 Server/db/pgdbgen/admin.sql.go delete mode 100644 Server/db/pgdbgen/attachments.sql.go delete mode 100644 Server/db/pgdbgen/blocks.sql.go delete mode 100644 Server/db/pgdbgen/channels.sql.go delete mode 100644 Server/db/pgdbgen/db.go delete mode 100644 Server/db/pgdbgen/dm.sql.go delete mode 100644 Server/db/pgdbgen/events.sql.go delete mode 100644 Server/db/pgdbgen/invites.sql.go delete mode 100644 Server/db/pgdbgen/lockouts.sql.go delete mode 100644 Server/db/pgdbgen/messages.sql.go delete mode 100644 Server/db/pgdbgen/models.go delete mode 100644 Server/db/pgdbgen/plugins.sql.go delete mode 100644 Server/db/pgdbgen/profile.sql.go delete mode 100644 Server/db/pgdbgen/querier.go delete mode 100644 Server/db/pgdbgen/reactions.sql.go delete mode 100644 Server/db/pgdbgen/roles.sql.go delete mode 100644 Server/db/pgdbgen/sessions.sql.go delete mode 100644 Server/db/pgdbgen/users.sql.go delete mode 100644 Server/db/pgdbgen/voice.sql.go delete mode 100644 Server/db/queries/postgres/admin.sql delete mode 100644 Server/db/queries/postgres/attachments.sql delete mode 100644 Server/db/queries/postgres/blocks.sql delete mode 100644 Server/db/queries/postgres/channels.sql delete mode 100644 Server/db/queries/postgres/dm.sql delete mode 100644 Server/db/queries/postgres/events.sql delete mode 100644 Server/db/queries/postgres/invites.sql delete mode 100644 Server/db/queries/postgres/lockouts.sql delete mode 100644 Server/db/queries/postgres/messages.sql delete mode 100644 Server/db/queries/postgres/plugins.sql delete mode 100644 Server/db/queries/postgres/profile.sql delete mode 100644 Server/db/queries/postgres/reactions.sql delete mode 100644 Server/db/queries/postgres/roles.sql delete mode 100644 Server/db/queries/postgres/sessions.sql delete mode 100644 Server/db/queries/postgres/users.sql delete mode 100644 Server/db/queries/postgres/voice.sql delete mode 100644 Server/migrations/postgres/001_initial_schema.sql delete mode 100644 Server/migrations/postgres/migrations.go delete mode 100644 Server/store/postgres.go diff --git a/Server/Makefile b/Server/Makefile index 54d2ca89..5eb9012b 100644 --- a/Server/Makefile +++ b/Server/Makefile @@ -1,8 +1,7 @@ # OwnCord Server — developer convenience targets # -# sqlc-generate Regenerate type-safe Go for both the sqlite and postgres -# engines defined in sqlc.yaml (db/dbgen + db/pgdbgen). -# sqlc-verify Fail if either committed dbgen output is stale (used by CI). +# sqlc-generate Regenerate type-safe Go from sqlc.yaml (db/dbgen). +# sqlc-verify Fail if the committed dbgen output is stale (used by CI). # sqlc-install Install the pinned sqlc version into $GOBIN. # otel-up Start Jaeger + Prometheus for local tracing development. # otel-down Stop and remove the OTel dev containers. @@ -17,13 +16,8 @@ sqlc-install: sqlc-generate: sqlc generate -# Verify only db/dbgen: the committed db/pgdbgen files carry hand-added -# `//go:build postgres` tags that `sqlc generate` strips, so a pgdbgen diff -# is expected noise. pgdbgen is scheduled for removal with the Postgres -# scaffolding; restore it after generating so verify leaves a clean tree. sqlc-verify: sqlc generate - @git checkout -- db/pgdbgen @git diff --exit-code db/dbgen || ( \ echo "ERROR: generated sqlc output is stale. Run 'make sqlc-generate' and commit the result." ; \ exit 1 ; \ diff --git a/Server/config/config.go b/Server/config/config.go index bcba0651..69a32c37 100644 --- a/Server/config/config.go +++ b/Server/config/config.go @@ -123,20 +123,12 @@ type ServerConfig struct { // cause the server to refuse to start with a clear error pointing at the // follow-up work — see Server/main.go. type DatabaseConfig struct { - // Type is "sqlite" or "postgres". Empty defaults to "sqlite". + // Type selects the database backend. "sqlite" (or empty, which defaults + // to it) is the only supported value. Type string `koanf:"type"` - // Path is the SQLite database file path. Only used when Type == "sqlite". + // Path is the SQLite database file path. Path string `koanf:"path"` - - // PostgreSQL connection settings. Only used when Type == "postgres". - Host string `koanf:"host"` - Port int `koanf:"port"` - User string `koanf:"user"` - Password string `koanf:"password"` - Name string `koanf:"name"` - SSLMode string `koanf:"sslmode"` // disable | require | verify-ca | verify-full - MaxConns int `koanf:"max_conns"` // pgxpool max connections; 0 = pgxpool default } // TLSConfig holds TLS/certificate settings. @@ -173,12 +165,8 @@ func defaults() Config { }, }, Database: DatabaseConfig{ - Type: "sqlite", - Path: "data/chatserver.db", - Host: "localhost", - Port: 5432, - Name: "owncord", - SSLMode: "disable", + Type: "sqlite", + Path: "data/chatserver.db", }, TLS: TLSConfig{ Mode: "self_signed", @@ -236,16 +224,8 @@ server: # - "192.168.0.0/16" database: - type: "sqlite" # "sqlite" (default, zero-config) or "postgres" + type: "sqlite" # "sqlite" is the only supported backend path: "data/chatserver.db" - # PostgreSQL settings (only used when type: "postgres"): - # host: "localhost" - # port: 5432 - # user: "owncord" - # password: "" - # name: "owncord" - # sslmode: "disable" # disable | require | verify-ca | verify-full - # max_conns: 0 # pgxpool connection cap (0 = pgx default) tls: mode: "self_signed" # self_signed, acme, manual, off diff --git a/Server/db/pgdbgen/admin.sql.go b/Server/db/pgdbgen/admin.sql.go deleted file mode 100644 index e3e4c257..00000000 --- a/Server/db/pgdbgen/admin.sql.go +++ /dev/null @@ -1,307 +0,0 @@ -//go:build postgres - -// Code generated by sqlc. DO NOT EDIT. -// versions: -// sqlc v1.30.0 -// source: admin.sql - -package pgdbgen - -import ( - "context" - - "github.com/jackc/pgx/v5/pgtype" -) - -const countActiveInvites = `-- name: CountActiveInvites :one -SELECT COUNT(*) FROM invites WHERE revoked = FALSE -` - -func (q *Queries) CountActiveInvites(ctx context.Context) (int64, error) { - row := q.db.QueryRow(ctx, countActiveInvites) - var count int64 - err := row.Scan(&count) - return count, err -} - -const countActiveMessages = `-- name: CountActiveMessages :one -SELECT COUNT(*) FROM messages WHERE deleted = FALSE -` - -func (q *Queries) CountActiveMessages(ctx context.Context) (int64, error) { - row := q.db.QueryRow(ctx, countActiveMessages) - var count int64 - err := row.Scan(&count) - return count, err -} - -const countChannels = `-- name: CountChannels :one -SELECT COUNT(*) FROM channels -` - -func (q *Queries) CountChannels(ctx context.Context) (int64, error) { - row := q.db.QueryRow(ctx, countChannels) - var count int64 - err := row.Scan(&count) - return count, err -} - -const forceLogoutUser = `-- name: ForceLogoutUser :exec -DELETE FROM sessions WHERE user_id = $1 -` - -func (q *Queries) ForceLogoutUser(ctx context.Context, userID int64) error { - _, err := q.db.Exec(ctx, forceLogoutUser, userID) - return err -} - -const getAllSettings = `-- name: GetAllSettings :many -SELECT key, value FROM settings -` - -func (q *Queries) GetAllSettings(ctx context.Context) ([]Setting, error) { - rows, err := q.db.Query(ctx, getAllSettings) - if err != nil { - return nil, err - } - defer rows.Close() - items := []Setting{} - for rows.Next() { - var i Setting - if err := rows.Scan(&i.Key, &i.Value); err != nil { - return nil, err - } - items = append(items, i) - } - if err := rows.Err(); err != nil { - return nil, err - } - return items, nil -} - -const getAuditLog = `-- name: GetAuditLog :many -SELECT a.id, a.actor_id, COALESCE(u.username, '') AS actor_name, a.action, - a.target_type, a.target_id, a.detail, a.created_at -FROM audit_log a -LEFT JOIN users u ON u.id = a.actor_id -ORDER BY a.id DESC -LIMIT $1 OFFSET $2 -` - -type GetAuditLogParams struct { - Limit int32 `json:"limit"` - Offset int32 `json:"offset"` -} - -type GetAuditLogRow struct { - ID int64 `json:"id"` - ActorID int64 `json:"actorId"` - ActorName string `json:"actorName"` - Action string `json:"action"` - TargetType string `json:"targetType"` - TargetID int64 `json:"targetId"` - Detail string `json:"detail"` - CreatedAt pgtype.Timestamptz `json:"createdAt"` -} - -func (q *Queries) GetAuditLog(ctx context.Context, arg GetAuditLogParams) ([]GetAuditLogRow, error) { - rows, err := q.db.Query(ctx, getAuditLog, arg.Limit, arg.Offset) - if err != nil { - return nil, err - } - defer rows.Close() - items := []GetAuditLogRow{} - for rows.Next() { - var i GetAuditLogRow - if err := rows.Scan( - &i.ID, - &i.ActorID, - &i.ActorName, - &i.Action, - &i.TargetType, - &i.TargetID, - &i.Detail, - &i.CreatedAt, - ); err != nil { - return nil, err - } - items = append(items, i) - } - if err := rows.Err(); err != nil { - return nil, err - } - return items, nil -} - -const getSetting = `-- name: GetSetting :one -SELECT value FROM settings WHERE key = $1 -` - -func (q *Queries) GetSetting(ctx context.Context, key string) (string, error) { - row := q.db.QueryRow(ctx, getSetting, key) - var value string - err := row.Scan(&value) - return value, err -} - -const getUserSessions = `-- name: GetUserSessions :many -SELECT id, user_id, token, device, ip_address, created_at, last_used, expires_at -FROM sessions WHERE user_id = $1 -ORDER BY created_at DESC -` - -func (q *Queries) GetUserSessions(ctx context.Context, userID int64) ([]Session, error) { - rows, err := q.db.Query(ctx, getUserSessions, userID) - if err != nil { - return nil, err - } - defer rows.Close() - items := []Session{} - for rows.Next() { - var i Session - if err := rows.Scan( - &i.ID, - &i.UserID, - &i.Token, - &i.Device, - &i.IpAddress, - &i.CreatedAt, - &i.LastUsed, - &i.ExpiresAt, - ); err != nil { - return nil, err - } - items = append(items, i) - } - if err := rows.Err(); err != nil { - return nil, err - } - return items, nil -} - -const listAllUsers = `-- name: ListAllUsers :many -SELECT u.id, u.username, u.avatar, u.role_id, - u.status, u.created_at, u.last_seen, u.banned, u.ban_reason, u.ban_expires, - COALESCE(r.name, '') AS role_name -FROM users u -LEFT JOIN roles r ON r.id = u.role_id -ORDER BY u.id ASC -LIMIT $1 OFFSET $2 -` - -type ListAllUsersParams struct { - Limit int32 `json:"limit"` - Offset int32 `json:"offset"` -} - -type ListAllUsersRow struct { - ID int64 `json:"id"` - Username string `json:"username"` - Avatar *string `json:"avatar"` - RoleID int64 `json:"roleId"` - Status string `json:"status"` - CreatedAt pgtype.Timestamptz `json:"createdAt"` - LastSeen pgtype.Timestamptz `json:"lastSeen"` - Banned bool `json:"banned"` - BanReason *string `json:"banReason"` - BanExpires pgtype.Timestamptz `json:"banExpires"` - RoleName string `json:"roleName"` -} - -func (q *Queries) ListAllUsers(ctx context.Context, arg ListAllUsersParams) ([]ListAllUsersRow, error) { - rows, err := q.db.Query(ctx, listAllUsers, arg.Limit, arg.Offset) - if err != nil { - return nil, err - } - defer rows.Close() - items := []ListAllUsersRow{} - for rows.Next() { - var i ListAllUsersRow - if err := rows.Scan( - &i.ID, - &i.Username, - &i.Avatar, - &i.RoleID, - &i.Status, - &i.CreatedAt, - &i.LastSeen, - &i.Banned, - &i.BanReason, - &i.BanExpires, - &i.RoleName, - ); err != nil { - return nil, err - } - items = append(items, i) - } - if err := rows.Err(); err != nil { - return nil, err - } - return items, nil -} - -const logAudit = `-- name: LogAudit :exec -INSERT INTO audit_log (actor_id, action, target_type, target_id, detail) -VALUES ($1, $2, $3, $4, $5) -` - -type LogAuditParams struct { - ActorID int64 `json:"actorId"` - Action string `json:"action"` - TargetType string `json:"targetType"` - TargetID int64 `json:"targetId"` - Detail string `json:"detail"` -} - -func (q *Queries) LogAudit(ctx context.Context, arg LogAuditParams) error { - _, err := q.db.Exec(ctx, logAudit, - arg.ActorID, - arg.Action, - arg.TargetType, - arg.TargetID, - arg.Detail, - ) - return err -} - -const setSetting = `-- name: SetSetting :exec -INSERT INTO settings (key, value) VALUES ($1, $2) -ON CONFLICT (key) DO UPDATE SET value = EXCLUDED.value -` - -type SetSettingParams struct { - Key string `json:"key"` - Value string `json:"value"` -} - -func (q *Queries) SetSetting(ctx context.Context, arg SetSettingParams) error { - _, err := q.db.Exec(ctx, setSetting, arg.Key, arg.Value) - return err -} - -const updateUserRole = `-- name: UpdateUserRole :exec -UPDATE users SET role_id = $1 WHERE id = $2 -` - -type UpdateUserRoleParams struct { - RoleID int64 `json:"roleId"` - ID int64 `json:"id"` -} - -func (q *Queries) UpdateUserRole(ctx context.Context, arg UpdateUserRoleParams) error { - _, err := q.db.Exec(ctx, updateUserRole, arg.RoleID, arg.ID) - return err -} - -const userCount = `-- name: UserCount :one - -SELECT COUNT(*) FROM users -` - -// PostgreSQL variants of the sqlite admin queries. -func (q *Queries) UserCount(ctx context.Context) (int64, error) { - row := q.db.QueryRow(ctx, userCount) - var count int64 - err := row.Scan(&count) - return count, err -} diff --git a/Server/db/pgdbgen/attachments.sql.go b/Server/db/pgdbgen/attachments.sql.go deleted file mode 100644 index f483e264..00000000 --- a/Server/db/pgdbgen/attachments.sql.go +++ /dev/null @@ -1,169 +0,0 @@ -//go:build postgres - -// Code generated by sqlc. DO NOT EDIT. -// versions: -// sqlc v1.30.0 -// source: attachments.sql - -package pgdbgen - -import ( - "context" - - "github.com/jackc/pgx/v5/pgtype" -) - -const createAttachment = `-- name: CreateAttachment :exec - -INSERT INTO attachments (id, uploader_id, filename, stored_as, mime_type, size, width, height) -VALUES ($1, $2, $3, $4, $5, $6, $7, $8) -` - -type CreateAttachmentParams struct { - ID string `json:"id"` - UploaderID *int64 `json:"uploaderId"` - Filename string `json:"filename"` - StoredAs string `json:"storedAs"` - MimeType string `json:"mimeType"` - Size int64 `json:"size"` - Width *int32 `json:"width"` - Height *int32 `json:"height"` -} - -// PostgreSQL variants of the sqlite attachments queries. -func (q *Queries) CreateAttachment(ctx context.Context, arg CreateAttachmentParams) error { - _, err := q.db.Exec(ctx, createAttachment, - arg.ID, - arg.UploaderID, - arg.Filename, - arg.StoredAs, - arg.MimeType, - arg.Size, - arg.Width, - arg.Height, - ) - return err -} - -const deleteAttachment = `-- name: DeleteAttachment :exec -DELETE FROM attachments WHERE id = $1 -` - -func (q *Queries) DeleteAttachment(ctx context.Context, id string) error { - _, err := q.db.Exec(ctx, deleteAttachment, id) - return err -} - -const deleteOrphanedAttachments = `-- name: DeleteOrphanedAttachments :many -DELETE FROM attachments WHERE message_id IS NULL AND uploaded_at < $1 RETURNING stored_as -` - -// Postgres timestamptz comparison — the caller passes a wall-clock time. -func (q *Queries) DeleteOrphanedAttachments(ctx context.Context, uploadedAt pgtype.Timestamptz) ([]string, error) { - rows, err := q.db.Query(ctx, deleteOrphanedAttachments, uploadedAt) - if err != nil { - return nil, err - } - defer rows.Close() - items := []string{} - for rows.Next() { - var stored_as string - if err := rows.Scan(&stored_as); err != nil { - return nil, err - } - items = append(items, stored_as) - } - if err := rows.Err(); err != nil { - return nil, err - } - return items, nil -} - -const getAttachmentByID = `-- name: GetAttachmentByID :one -SELECT id, message_id, filename, stored_as, mime_type, size, uploaded_at, uploader_id -FROM attachments WHERE id = $1 -` - -type GetAttachmentByIDRow struct { - ID string `json:"id"` - MessageID *int64 `json:"messageId"` - Filename string `json:"filename"` - StoredAs string `json:"storedAs"` - MimeType string `json:"mimeType"` - Size int64 `json:"size"` - UploadedAt pgtype.Timestamptz `json:"uploadedAt"` - UploaderID *int64 `json:"uploaderId"` -} - -func (q *Queries) GetAttachmentByID(ctx context.Context, id string) (GetAttachmentByIDRow, error) { - row := q.db.QueryRow(ctx, getAttachmentByID, id) - var i GetAttachmentByIDRow - err := row.Scan( - &i.ID, - &i.MessageID, - &i.Filename, - &i.StoredAs, - &i.MimeType, - &i.Size, - &i.UploadedAt, - &i.UploaderID, - ) - return i, err -} - -const getAttachmentWithChannel = `-- name: GetAttachmentWithChannel :one -SELECT a.id, a.message_id, a.filename, a.stored_as, a.mime_type, a.size, - a.uploaded_at, a.uploader_id, m.channel_id, c.type -FROM attachments a -LEFT JOIN messages m ON m.id = a.message_id -LEFT JOIN channels c ON c.id = m.channel_id -WHERE a.id = $1 -` - -type GetAttachmentWithChannelRow struct { - ID string `json:"id"` - MessageID *int64 `json:"messageId"` - Filename string `json:"filename"` - StoredAs string `json:"storedAs"` - MimeType string `json:"mimeType"` - Size int64 `json:"size"` - UploadedAt pgtype.Timestamptz `json:"uploadedAt"` - UploaderID *int64 `json:"uploaderId"` - ChannelID *int64 `json:"channelId"` - Type *string `json:"type"` -} - -func (q *Queries) GetAttachmentWithChannel(ctx context.Context, id string) (GetAttachmentWithChannelRow, error) { - row := q.db.QueryRow(ctx, getAttachmentWithChannel, id) - var i GetAttachmentWithChannelRow - err := row.Scan( - &i.ID, - &i.MessageID, - &i.Filename, - &i.StoredAs, - &i.MimeType, - &i.Size, - &i.UploadedAt, - &i.UploaderID, - &i.ChannelID, - &i.Type, - ) - return i, err -} - -const linkAttachmentToMessage = `-- name: LinkAttachmentToMessage :execrows -UPDATE attachments SET message_id = $1 WHERE id = $2 AND message_id IS NULL -` - -type LinkAttachmentToMessageParams struct { - MessageID *int64 `json:"messageId"` - ID string `json:"id"` -} - -func (q *Queries) LinkAttachmentToMessage(ctx context.Context, arg LinkAttachmentToMessageParams) (int64, error) { - result, err := q.db.Exec(ctx, linkAttachmentToMessage, arg.MessageID, arg.ID) - if err != nil { - return 0, err - } - return result.RowsAffected(), nil -} diff --git a/Server/db/pgdbgen/blocks.sql.go b/Server/db/pgdbgen/blocks.sql.go deleted file mode 100644 index 345a8812..00000000 --- a/Server/db/pgdbgen/blocks.sql.go +++ /dev/null @@ -1,86 +0,0 @@ -//go:build postgres - -// Code generated by sqlc. DO NOT EDIT. -// versions: -// sqlc v1.30.0 -// source: blocks.sql - -package pgdbgen - -import ( - "context" -) - -const blockUser = `-- name: BlockUser :exec - -INSERT INTO user_blocks (blocker_id, blocked_id) VALUES ($1, $2) -ON CONFLICT (blocker_id, blocked_id) DO NOTHING -` - -type BlockUserParams struct { - BlockerID int64 `json:"blockerId"` - BlockedID int64 `json:"blockedId"` -} - -// PostgreSQL variants of the sqlite user block queries. -// `INSERT OR IGNORE` becomes `INSERT ... ON CONFLICT DO NOTHING`. -func (q *Queries) BlockUser(ctx context.Context, arg BlockUserParams) error { - _, err := q.db.Exec(ctx, blockUser, arg.BlockerID, arg.BlockedID) - return err -} - -const isBlocked = `-- name: IsBlocked :one -SELECT 1 FROM user_blocks WHERE blocker_id = $1 AND blocked_id = $2 LIMIT 1 -` - -type IsBlockedParams struct { - BlockerID int64 `json:"blockerId"` - BlockedID int64 `json:"blockedId"` -} - -func (q *Queries) IsBlocked(ctx context.Context, arg IsBlockedParams) (int32, error) { - row := q.db.QueryRow(ctx, isBlocked, arg.BlockerID, arg.BlockedID) - var column_1 int32 - err := row.Scan(&column_1) - return column_1, err -} - -const isEitherBlocked = `-- name: IsEitherBlocked :one -SELECT 1 FROM user_blocks -WHERE (blocker_id = $1 AND blocked_id = $2) - OR (blocker_id = $3 AND blocked_id = $4) -LIMIT 1 -` - -type IsEitherBlockedParams struct { - BlockerID int64 `json:"blockerId"` - BlockedID int64 `json:"blockedId"` - BlockerID_2 int64 `json:"blockerId2"` - BlockedID_2 int64 `json:"blockedId2"` -} - -func (q *Queries) IsEitherBlocked(ctx context.Context, arg IsEitherBlockedParams) (int32, error) { - row := q.db.QueryRow(ctx, isEitherBlocked, - arg.BlockerID, - arg.BlockedID, - arg.BlockerID_2, - arg.BlockedID_2, - ) - var column_1 int32 - err := row.Scan(&column_1) - return column_1, err -} - -const unblockUser = `-- name: UnblockUser :exec -DELETE FROM user_blocks WHERE blocker_id = $1 AND blocked_id = $2 -` - -type UnblockUserParams struct { - BlockerID int64 `json:"blockerId"` - BlockedID int64 `json:"blockedId"` -} - -func (q *Queries) UnblockUser(ctx context.Context, arg UnblockUserParams) error { - _, err := q.db.Exec(ctx, unblockUser, arg.BlockerID, arg.BlockedID) - return err -} diff --git a/Server/db/pgdbgen/channels.sql.go b/Server/db/pgdbgen/channels.sql.go deleted file mode 100644 index 4b2c96fc..00000000 --- a/Server/db/pgdbgen/channels.sql.go +++ /dev/null @@ -1,381 +0,0 @@ -//go:build postgres - -// Code generated by sqlc. DO NOT EDIT. -// versions: -// sqlc v1.30.0 -// source: channels.sql - -package pgdbgen - -import ( - "context" - - "github.com/jackc/pgx/v5/pgtype" -) - -const adminUpdateChannel = `-- name: AdminUpdateChannel :exec -UPDATE channels -SET name = $1, topic = $2, slow_mode = $3, position = $4, archived = $5 -WHERE id = $6 -` - -type AdminUpdateChannelParams struct { - Name string `json:"name"` - Topic *string `json:"topic"` - SlowMode int32 `json:"slowMode"` - Position int32 `json:"position"` - Archived bool `json:"archived"` - ID int64 `json:"id"` -} - -func (q *Queries) AdminUpdateChannel(ctx context.Context, arg AdminUpdateChannelParams) error { - _, err := q.db.Exec(ctx, adminUpdateChannel, - arg.Name, - arg.Topic, - arg.SlowMode, - arg.Position, - arg.Archived, - arg.ID, - ) - return err -} - -const archiveChannel = `-- name: ArchiveChannel :exec -UPDATE channels SET archived = $1 WHERE id = $2 -` - -type ArchiveChannelParams struct { - Archived bool `json:"archived"` - ID int64 `json:"id"` -} - -func (q *Queries) ArchiveChannel(ctx context.Context, arg ArchiveChannelParams) error { - _, err := q.db.Exec(ctx, archiveChannel, arg.Archived, arg.ID) - return err -} - -const createChannel = `-- name: CreateChannel :one -INSERT INTO channels (name, type, category, topic, position) -VALUES ($1, $2, $3, $4, $5) -RETURNING id -` - -type CreateChannelParams struct { - Name string `json:"name"` - Type string `json:"type"` - Category *string `json:"category"` - Topic *string `json:"topic"` - Position int32 `json:"position"` -} - -func (q *Queries) CreateChannel(ctx context.Context, arg CreateChannelParams) (int64, error) { - row := q.db.QueryRow(ctx, createChannel, - arg.Name, - arg.Type, - arg.Category, - arg.Topic, - arg.Position, - ) - var id int64 - err := row.Scan(&id) - return id, err -} - -const deleteChannel = `-- name: DeleteChannel :exec -DELETE FROM channels WHERE id = $1 -` - -func (q *Queries) DeleteChannel(ctx context.Context, id int64) error { - _, err := q.db.Exec(ctx, deleteChannel, id) - return err -} - -const deleteChannelPermission = `-- name: DeleteChannelPermission :exec -DELETE FROM channel_overrides WHERE channel_id = $1 AND role_id = $2 -` - -type DeleteChannelPermissionParams struct { - ChannelID int64 `json:"channelId"` - RoleID int64 `json:"roleId"` -} - -func (q *Queries) DeleteChannelPermission(ctx context.Context, arg DeleteChannelPermissionParams) error { - _, err := q.db.Exec(ctx, deleteChannelPermission, arg.ChannelID, arg.RoleID) - return err -} - -const getChannel = `-- name: GetChannel :one -SELECT id, name, type, COALESCE(category, '') AS category, COALESCE(topic, '') AS topic, - position, slow_mode, archived, created_at, - COALESCE(voice_max_users, 0) AS voice_max_users, - voice_quality, - mixing_threshold, - COALESCE(voice_max_video, 0) AS voice_max_video -FROM channels WHERE id = $1 -` - -type GetChannelRow struct { - ID int64 `json:"id"` - Name string `json:"name"` - Type string `json:"type"` - Category string `json:"category"` - Topic string `json:"topic"` - Position int32 `json:"position"` - SlowMode int32 `json:"slowMode"` - Archived bool `json:"archived"` - CreatedAt pgtype.Timestamptz `json:"createdAt"` - VoiceMaxUsers int32 `json:"voiceMaxUsers"` - VoiceQuality *string `json:"voiceQuality"` - MixingThreshold *int32 `json:"mixingThreshold"` - VoiceMaxVideo int32 `json:"voiceMaxVideo"` -} - -func (q *Queries) GetChannel(ctx context.Context, id int64) (GetChannelRow, error) { - row := q.db.QueryRow(ctx, getChannel, id) - var i GetChannelRow - err := row.Scan( - &i.ID, - &i.Name, - &i.Type, - &i.Category, - &i.Topic, - &i.Position, - &i.SlowMode, - &i.Archived, - &i.CreatedAt, - &i.VoiceMaxUsers, - &i.VoiceQuality, - &i.MixingThreshold, - &i.VoiceMaxVideo, - ) - return i, err -} - -const getChannelPermission = `-- name: GetChannelPermission :one -SELECT allow, deny FROM channel_overrides WHERE channel_id = $1 AND role_id = $2 -` - -type GetChannelPermissionParams struct { - ChannelID int64 `json:"channelId"` - RoleID int64 `json:"roleId"` -} - -type GetChannelPermissionRow struct { - Allow int64 `json:"allow"` - Deny int64 `json:"deny"` -} - -func (q *Queries) GetChannelPermission(ctx context.Context, arg GetChannelPermissionParams) (GetChannelPermissionRow, error) { - row := q.db.QueryRow(ctx, getChannelPermission, arg.ChannelID, arg.RoleID) - var i GetChannelPermissionRow - err := row.Scan(&i.Allow, &i.Deny) - return i, err -} - -const getRoleChannelPermissions = `-- name: GetRoleChannelPermissions :many -SELECT channel_id, allow, deny FROM channel_overrides WHERE role_id = $1 -` - -type GetRoleChannelPermissionsRow struct { - ChannelID int64 `json:"channelId"` - Allow int64 `json:"allow"` - Deny int64 `json:"deny"` -} - -func (q *Queries) GetRoleChannelPermissions(ctx context.Context, roleID int64) ([]GetRoleChannelPermissionsRow, error) { - rows, err := q.db.Query(ctx, getRoleChannelPermissions, roleID) - if err != nil { - return nil, err - } - defer rows.Close() - items := []GetRoleChannelPermissionsRow{} - for rows.Next() { - var i GetRoleChannelPermissionsRow - if err := rows.Scan(&i.ChannelID, &i.Allow, &i.Deny); err != nil { - return nil, err - } - items = append(items, i) - } - if err := rows.Err(); err != nil { - return nil, err - } - return items, nil -} - -const listChannels = `-- name: ListChannels :many - -SELECT id, name, type, COALESCE(category, '') AS category, COALESCE(topic, '') AS topic, - position, slow_mode, archived, created_at, - COALESCE(voice_max_users, 0) AS voice_max_users, - voice_quality, - mixing_threshold, - COALESCE(voice_max_video, 0) AS voice_max_video -FROM channels ORDER BY position ASC, id ASC -` - -type ListChannelsRow struct { - ID int64 `json:"id"` - Name string `json:"name"` - Type string `json:"type"` - Category string `json:"category"` - Topic string `json:"topic"` - Position int32 `json:"position"` - SlowMode int32 `json:"slowMode"` - Archived bool `json:"archived"` - CreatedAt pgtype.Timestamptz `json:"createdAt"` - VoiceMaxUsers int32 `json:"voiceMaxUsers"` - VoiceQuality *string `json:"voiceQuality"` - MixingThreshold *int32 `json:"mixingThreshold"` - VoiceMaxVideo int32 `json:"voiceMaxVideo"` -} - -// PostgreSQL variants of the sqlite channels queries. -func (q *Queries) ListChannels(ctx context.Context) ([]ListChannelsRow, error) { - rows, err := q.db.Query(ctx, listChannels) - if err != nil { - return nil, err - } - defer rows.Close() - items := []ListChannelsRow{} - for rows.Next() { - var i ListChannelsRow - if err := rows.Scan( - &i.ID, - &i.Name, - &i.Type, - &i.Category, - &i.Topic, - &i.Position, - &i.SlowMode, - &i.Archived, - &i.CreatedAt, - &i.VoiceMaxUsers, - &i.VoiceQuality, - &i.MixingThreshold, - &i.VoiceMaxVideo, - ); err != nil { - return nil, err - } - items = append(items, i) - } - if err := rows.Err(); err != nil { - return nil, err - } - return items, nil -} - -const setChannelMixingThreshold = `-- name: SetChannelMixingThreshold :exec -UPDATE channels SET mixing_threshold = $1 WHERE id = $2 -` - -type SetChannelMixingThresholdParams struct { - MixingThreshold *int32 `json:"mixingThreshold"` - ID int64 `json:"id"` -} - -func (q *Queries) SetChannelMixingThreshold(ctx context.Context, arg SetChannelMixingThresholdParams) error { - _, err := q.db.Exec(ctx, setChannelMixingThreshold, arg.MixingThreshold, arg.ID) - return err -} - -const setChannelSlowMode = `-- name: SetChannelSlowMode :exec -UPDATE channels SET slow_mode = $1 WHERE id = $2 -` - -type SetChannelSlowModeParams struct { - SlowMode int32 `json:"slowMode"` - ID int64 `json:"id"` -} - -func (q *Queries) SetChannelSlowMode(ctx context.Context, arg SetChannelSlowModeParams) error { - _, err := q.db.Exec(ctx, setChannelSlowMode, arg.SlowMode, arg.ID) - return err -} - -const setChannelVoiceMaxUsers = `-- name: SetChannelVoiceMaxUsers :exec -UPDATE channels SET voice_max_users = $1 WHERE id = $2 -` - -type SetChannelVoiceMaxUsersParams struct { - VoiceMaxUsers int32 `json:"voiceMaxUsers"` - ID int64 `json:"id"` -} - -func (q *Queries) SetChannelVoiceMaxUsers(ctx context.Context, arg SetChannelVoiceMaxUsersParams) error { - _, err := q.db.Exec(ctx, setChannelVoiceMaxUsers, arg.VoiceMaxUsers, arg.ID) - return err -} - -const setChannelVoiceMaxVideo = `-- name: SetChannelVoiceMaxVideo :exec -UPDATE channels SET voice_max_video = $1 WHERE id = $2 -` - -type SetChannelVoiceMaxVideoParams struct { - VoiceMaxVideo int32 `json:"voiceMaxVideo"` - ID int64 `json:"id"` -} - -func (q *Queries) SetChannelVoiceMaxVideo(ctx context.Context, arg SetChannelVoiceMaxVideoParams) error { - _, err := q.db.Exec(ctx, setChannelVoiceMaxVideo, arg.VoiceMaxVideo, arg.ID) - return err -} - -const setChannelVoiceQuality = `-- name: SetChannelVoiceQuality :exec -UPDATE channels SET voice_quality = $1 WHERE id = $2 -` - -type SetChannelVoiceQualityParams struct { - VoiceQuality *string `json:"voiceQuality"` - ID int64 `json:"id"` -} - -func (q *Queries) SetChannelVoiceQuality(ctx context.Context, arg SetChannelVoiceQualityParams) error { - _, err := q.db.Exec(ctx, setChannelVoiceQuality, arg.VoiceQuality, arg.ID) - return err -} - -const updateChannel = `-- name: UpdateChannel :exec -UPDATE channels SET name = $1, topic = $2, slow_mode = $3 WHERE id = $4 -` - -type UpdateChannelParams struct { - Name string `json:"name"` - Topic *string `json:"topic"` - SlowMode int32 `json:"slowMode"` - ID int64 `json:"id"` -} - -func (q *Queries) UpdateChannel(ctx context.Context, arg UpdateChannelParams) error { - _, err := q.db.Exec(ctx, updateChannel, - arg.Name, - arg.Topic, - arg.SlowMode, - arg.ID, - ) - return err -} - -const upsertChannelPermission = `-- name: UpsertChannelPermission :exec -INSERT INTO channel_overrides (channel_id, role_id, allow, deny) -VALUES ($1, $2, $3, $4) -ON CONFLICT (channel_id, role_id) DO UPDATE SET - allow = EXCLUDED.allow, - deny = EXCLUDED.deny -` - -type UpsertChannelPermissionParams struct { - ChannelID int64 `json:"channelId"` - RoleID int64 `json:"roleId"` - Allow int64 `json:"allow"` - Deny int64 `json:"deny"` -} - -func (q *Queries) UpsertChannelPermission(ctx context.Context, arg UpsertChannelPermissionParams) error { - _, err := q.db.Exec(ctx, upsertChannelPermission, - arg.ChannelID, - arg.RoleID, - arg.Allow, - arg.Deny, - ) - return err -} diff --git a/Server/db/pgdbgen/db.go b/Server/db/pgdbgen/db.go deleted file mode 100644 index 09808de2..00000000 --- a/Server/db/pgdbgen/db.go +++ /dev/null @@ -1,34 +0,0 @@ -//go:build postgres - -// Code generated by sqlc. DO NOT EDIT. -// versions: -// sqlc v1.30.0 - -package pgdbgen - -import ( - "context" - - "github.com/jackc/pgx/v5" - "github.com/jackc/pgx/v5/pgconn" -) - -type DBTX interface { - Exec(context.Context, string, ...interface{}) (pgconn.CommandTag, error) - Query(context.Context, string, ...interface{}) (pgx.Rows, error) - QueryRow(context.Context, string, ...interface{}) pgx.Row -} - -func New(db DBTX) *Queries { - return &Queries{db: db} -} - -type Queries struct { - db DBTX -} - -func (q *Queries) WithTx(tx pgx.Tx) *Queries { - return &Queries{ - db: tx, - } -} diff --git a/Server/db/pgdbgen/dm.sql.go b/Server/db/pgdbgen/dm.sql.go deleted file mode 100644 index ab383391..00000000 --- a/Server/db/pgdbgen/dm.sql.go +++ /dev/null @@ -1,241 +0,0 @@ -//go:build postgres - -// Code generated by sqlc. DO NOT EDIT. -// versions: -// sqlc v1.30.0 -// source: dm.sql - -package pgdbgen - -import ( - "context" - - "github.com/jackc/pgx/v5/pgtype" -) - -const closeDM = `-- name: CloseDM :exec -DELETE FROM dm_open_state WHERE user_id = $1 AND channel_id = $2 -` - -type CloseDMParams struct { - UserID int64 `json:"userId"` - ChannelID int64 `json:"channelId"` -} - -func (q *Queries) CloseDM(ctx context.Context, arg CloseDMParams) error { - _, err := q.db.Exec(ctx, closeDM, arg.UserID, arg.ChannelID) - return err -} - -const findExistingDMChannel = `-- name: FindExistingDMChannel :one -SELECT dp1.channel_id -FROM dm_participants dp1 -JOIN dm_participants dp2 ON dp1.channel_id = dp2.channel_id -JOIN channels c ON c.id = dp1.channel_id -WHERE dp1.user_id = $1 AND dp2.user_id = $2 AND c.type = 'dm' -LIMIT 1 -` - -type FindExistingDMChannelParams struct { - UserID int64 `json:"userId"` - UserID_2 int64 `json:"userId2"` -} - -func (q *Queries) FindExistingDMChannel(ctx context.Context, arg FindExistingDMChannelParams) (int64, error) { - row := q.db.QueryRow(ctx, findExistingDMChannel, arg.UserID, arg.UserID_2) - var channel_id int64 - err := row.Scan(&channel_id) - return channel_id, err -} - -const getDMParticipantIDs = `-- name: GetDMParticipantIDs :many -SELECT user_id FROM dm_participants WHERE channel_id = $1 -` - -func (q *Queries) GetDMParticipantIDs(ctx context.Context, channelID int64) ([]int64, error) { - rows, err := q.db.Query(ctx, getDMParticipantIDs, channelID) - if err != nil { - return nil, err - } - defer rows.Close() - items := []int64{} - for rows.Next() { - var user_id int64 - if err := rows.Scan(&user_id); err != nil { - return nil, err - } - items = append(items, user_id) - } - if err := rows.Err(); err != nil { - return nil, err - } - return items, nil -} - -const getUserDMChannels = `-- name: GetUserDMChannels :many -SELECT - c.id AS channel_id, - u.id AS recipient_id, - u.username AS recipient_username, - COALESCE(u.avatar, '') AS recipient_avatar, - u.status AS recipient_status, - lm.id AS last_message_id, - COALESCE(lm.content, '') AS last_message, - COALESCE(lm.timestamp, dos.opened_at) AS last_message_at, - COUNT(CASE WHEN m_unread.id > COALESCE(rs.last_message_id, 0) - AND m_unread.deleted = FALSE THEN 1 END) AS unread_count -FROM dm_open_state dos -JOIN channels c ON c.id = dos.channel_id AND c.type = 'dm' -JOIN dm_participants dp ON dp.channel_id = c.id AND dp.user_id != $1 -JOIN users u ON u.id = dp.user_id -LEFT JOIN messages lm ON lm.id = ( - SELECT MAX(id) FROM messages WHERE channel_id = c.id AND deleted = FALSE -) -LEFT JOIN messages m_unread ON m_unread.channel_id = c.id -LEFT JOIN read_states rs ON rs.channel_id = c.id AND rs.user_id = $2 -WHERE dos.user_id = $3 -GROUP BY c.id, u.id, lm.id, lm.content, lm.timestamp, dos.opened_at -ORDER BY COALESCE(lm.timestamp, dos.opened_at) DESC -` - -type GetUserDMChannelsParams struct { - UserID int64 `json:"userId"` - UserID_2 int64 `json:"userId2"` - UserID_3 int64 `json:"userId3"` -} - -type GetUserDMChannelsRow struct { - ChannelID int64 `json:"channelId"` - RecipientID int64 `json:"recipientId"` - RecipientUsername string `json:"recipientUsername"` - RecipientAvatar string `json:"recipientAvatar"` - RecipientStatus string `json:"recipientStatus"` - LastMessageID *int64 `json:"lastMessageId"` - LastMessage string `json:"lastMessage"` - LastMessageAt pgtype.Timestamptz `json:"lastMessageAt"` - UnreadCount int64 `json:"unreadCount"` -} - -// For the "last message at" and "last message content" columns, sqlite -// COALESCEs to ” (empty string). Postgres TIMESTAMPTZ cannot COALESCE to an -// empty string, so we COALESCE to the dm_open_state.opened_at fallback and -// leave conversion to the store wrapper. -func (q *Queries) GetUserDMChannels(ctx context.Context, arg GetUserDMChannelsParams) ([]GetUserDMChannelsRow, error) { - rows, err := q.db.Query(ctx, getUserDMChannels, arg.UserID, arg.UserID_2, arg.UserID_3) - if err != nil { - return nil, err - } - defer rows.Close() - items := []GetUserDMChannelsRow{} - for rows.Next() { - var i GetUserDMChannelsRow - if err := rows.Scan( - &i.ChannelID, - &i.RecipientID, - &i.RecipientUsername, - &i.RecipientAvatar, - &i.RecipientStatus, - &i.LastMessageID, - &i.LastMessage, - &i.LastMessageAt, - &i.UnreadCount, - ); err != nil { - return nil, err - } - items = append(items, i) - } - if err := rows.Err(); err != nil { - return nil, err - } - return items, nil -} - -const insertDMChannel = `-- name: InsertDMChannel :one - -INSERT INTO channels (name, type) VALUES ('', 'dm') RETURNING id -` - -// PostgreSQL variants of the sqlite DM queries. -// InsertDMChannel: sqlite uses :execresult (LastInsertId); postgres uses -// :one with RETURNING id. -// `INSERT OR IGNORE` becomes `INSERT ... ON CONFLICT DO NOTHING`. -func (q *Queries) InsertDMChannel(ctx context.Context) (int64, error) { - row := q.db.QueryRow(ctx, insertDMChannel) - var id int64 - err := row.Scan(&id) - return id, err -} - -const insertDMOpenState = `-- name: InsertDMOpenState :exec -INSERT INTO dm_open_state (user_id, channel_id) VALUES ($1, $2), ($3, $4) -ON CONFLICT (user_id, channel_id) DO NOTHING -` - -type InsertDMOpenStateParams struct { - UserID int64 `json:"userId"` - ChannelID int64 `json:"channelId"` - UserID_2 int64 `json:"userId2"` - ChannelID_2 int64 `json:"channelId2"` -} - -func (q *Queries) InsertDMOpenState(ctx context.Context, arg InsertDMOpenStateParams) error { - _, err := q.db.Exec(ctx, insertDMOpenState, - arg.UserID, - arg.ChannelID, - arg.UserID_2, - arg.ChannelID_2, - ) - return err -} - -const insertDMParticipants = `-- name: InsertDMParticipants :exec -INSERT INTO dm_participants (channel_id, user_id) VALUES ($1, $2), ($3, $4) -` - -type InsertDMParticipantsParams struct { - ChannelID int64 `json:"channelId"` - UserID int64 `json:"userId"` - ChannelID_2 int64 `json:"channelId2"` - UserID_2 int64 `json:"userId2"` -} - -func (q *Queries) InsertDMParticipants(ctx context.Context, arg InsertDMParticipantsParams) error { - _, err := q.db.Exec(ctx, insertDMParticipants, - arg.ChannelID, - arg.UserID, - arg.ChannelID_2, - arg.UserID_2, - ) - return err -} - -const isDMParticipant = `-- name: IsDMParticipant :one -SELECT user_id FROM dm_participants WHERE user_id = $1 AND channel_id = $2 -` - -type IsDMParticipantParams struct { - UserID int64 `json:"userId"` - ChannelID int64 `json:"channelId"` -} - -func (q *Queries) IsDMParticipant(ctx context.Context, arg IsDMParticipantParams) (int64, error) { - row := q.db.QueryRow(ctx, isDMParticipant, arg.UserID, arg.ChannelID) - var user_id int64 - err := row.Scan(&user_id) - return user_id, err -} - -const openDM = `-- name: OpenDM :exec -INSERT INTO dm_open_state (user_id, channel_id) VALUES ($1, $2) -ON CONFLICT (user_id, channel_id) DO NOTHING -` - -type OpenDMParams struct { - UserID int64 `json:"userId"` - ChannelID int64 `json:"channelId"` -} - -func (q *Queries) OpenDM(ctx context.Context, arg OpenDMParams) error { - _, err := q.db.Exec(ctx, openDM, arg.UserID, arg.ChannelID) - return err -} diff --git a/Server/db/pgdbgen/events.sql.go b/Server/db/pgdbgen/events.sql.go deleted file mode 100644 index d02eb71b..00000000 --- a/Server/db/pgdbgen/events.sql.go +++ /dev/null @@ -1,109 +0,0 @@ -//go:build postgres - -// Code generated by sqlc. DO NOT EDIT. -// versions: -// sqlc v1.30.0 -// source: events.sql - -package pgdbgen - -import ( - "context" - - "github.com/jackc/pgx/v5/pgtype" -) - -const getEventsSince = `-- name: GetEventsSince :many -SELECT seq, event_type, channel_id, payload, created_at -FROM events -WHERE seq > $1 -ORDER BY seq ASC -LIMIT $2 -` - -type GetEventsSinceParams struct { - Seq int64 `json:"seq"` - Limit int32 `json:"limit"` -} - -type GetEventsSinceRow struct { - Seq int64 `json:"seq"` - EventType string `json:"eventType"` - ChannelID int64 `json:"channelId"` - Payload []byte `json:"payload"` - CreatedAt pgtype.Timestamptz `json:"createdAt"` -} - -func (q *Queries) GetEventsSince(ctx context.Context, arg GetEventsSinceParams) ([]GetEventsSinceRow, error) { - rows, err := q.db.Query(ctx, getEventsSince, arg.Seq, arg.Limit) - if err != nil { - return nil, err - } - defer rows.Close() - items := []GetEventsSinceRow{} - for rows.Next() { - var i GetEventsSinceRow - if err := rows.Scan( - &i.Seq, - &i.EventType, - &i.ChannelID, - &i.Payload, - &i.CreatedAt, - ); err != nil { - return nil, err - } - items = append(items, i) - } - if err := rows.Err(); err != nil { - return nil, err - } - return items, nil -} - -const getMaxEventSeq = `-- name: GetMaxEventSeq :one -SELECT COALESCE(MAX(seq), 0)::BIGINT FROM events -` - -func (q *Queries) GetMaxEventSeq(ctx context.Context) (int64, error) { - row := q.db.QueryRow(ctx, getMaxEventSeq) - var column_1 int64 - err := row.Scan(&column_1) - return column_1, err -} - -const persistEvent = `-- name: PersistEvent :exec -INSERT INTO events (seq, event_type, channel_id, payload) -VALUES ($1, $2, $3, $4) -` - -type PersistEventParams struct { - Seq int64 `json:"seq"` - EventType string `json:"eventType"` - ChannelID int64 `json:"channelId"` - Payload []byte `json:"payload"` -} - -// seq is supplied by the hub so the row seq matches the wrapped-payload seq. -// The schema's BIGSERIAL still owns the id column for inserts that omit seq, -// but PersistEvent always supplies an explicit value. -func (q *Queries) PersistEvent(ctx context.Context, arg PersistEventParams) error { - _, err := q.db.Exec(ctx, persistEvent, - arg.Seq, - arg.EventType, - arg.ChannelID, - arg.Payload, - ) - return err -} - -const pruneEventsOlderThan = `-- name: PruneEventsOlderThan :execrows -DELETE FROM events WHERE created_at < $1 -` - -func (q *Queries) PruneEventsOlderThan(ctx context.Context, createdAt pgtype.Timestamptz) (int64, error) { - result, err := q.db.Exec(ctx, pruneEventsOlderThan, createdAt) - if err != nil { - return 0, err - } - return result.RowsAffected(), nil -} diff --git a/Server/db/pgdbgen/invites.sql.go b/Server/db/pgdbgen/invites.sql.go deleted file mode 100644 index 32bac5a1..00000000 --- a/Server/db/pgdbgen/invites.sql.go +++ /dev/null @@ -1,140 +0,0 @@ -//go:build postgres - -// Code generated by sqlc. DO NOT EDIT. -// versions: -// sqlc v1.30.0 -// source: invites.sql - -package pgdbgen - -import ( - "context" - - "github.com/jackc/pgx/v5/pgtype" -) - -const createInvite = `-- name: CreateInvite :exec - -INSERT INTO invites (code, created_by, max_uses, expires_at) VALUES ($1, $2, $3, $4) -` - -type CreateInviteParams struct { - Code string `json:"code"` - CreatedBy int64 `json:"createdBy"` - MaxUses *int32 `json:"maxUses"` - ExpiresAt pgtype.Timestamptz `json:"expiresAt"` -} - -// PostgreSQL variants of the sqlite invites queries. -// The expiry check uses native timestamp comparison instead of sqlite's -// strftime('%s', …) trick. -func (q *Queries) CreateInvite(ctx context.Context, arg CreateInviteParams) error { - _, err := q.db.Exec(ctx, createInvite, - arg.Code, - arg.CreatedBy, - arg.MaxUses, - arg.ExpiresAt, - ) - return err -} - -const getInvite = `-- name: GetInvite :one -SELECT id, code, created_by, max_uses, use_count, expires_at, revoked, created_at -FROM invites WHERE code = $1 -` - -type GetInviteRow struct { - ID int64 `json:"id"` - Code string `json:"code"` - CreatedBy int64 `json:"createdBy"` - MaxUses *int32 `json:"maxUses"` - UseCount int32 `json:"useCount"` - ExpiresAt pgtype.Timestamptz `json:"expiresAt"` - Revoked bool `json:"revoked"` - CreatedAt pgtype.Timestamptz `json:"createdAt"` -} - -func (q *Queries) GetInvite(ctx context.Context, code string) (GetInviteRow, error) { - row := q.db.QueryRow(ctx, getInvite, code) - var i GetInviteRow - err := row.Scan( - &i.ID, - &i.Code, - &i.CreatedBy, - &i.MaxUses, - &i.UseCount, - &i.ExpiresAt, - &i.Revoked, - &i.CreatedAt, - ) - return i, err -} - -const listInvites = `-- name: ListInvites :many -SELECT id, code, created_by, max_uses, use_count, expires_at, revoked, created_at -FROM invites ORDER BY created_at DESC LIMIT 200 -` - -type ListInvitesRow struct { - ID int64 `json:"id"` - Code string `json:"code"` - CreatedBy int64 `json:"createdBy"` - MaxUses *int32 `json:"maxUses"` - UseCount int32 `json:"useCount"` - ExpiresAt pgtype.Timestamptz `json:"expiresAt"` - Revoked bool `json:"revoked"` - CreatedAt pgtype.Timestamptz `json:"createdAt"` -} - -func (q *Queries) ListInvites(ctx context.Context) ([]ListInvitesRow, error) { - rows, err := q.db.Query(ctx, listInvites) - if err != nil { - return nil, err - } - defer rows.Close() - items := []ListInvitesRow{} - for rows.Next() { - var i ListInvitesRow - if err := rows.Scan( - &i.ID, - &i.Code, - &i.CreatedBy, - &i.MaxUses, - &i.UseCount, - &i.ExpiresAt, - &i.Revoked, - &i.CreatedAt, - ); err != nil { - return nil, err - } - items = append(items, i) - } - if err := rows.Err(); err != nil { - return nil, err - } - return items, nil -} - -const revokeInvite = `-- name: RevokeInvite :exec -UPDATE invites SET revoked = TRUE WHERE code = $1 -` - -func (q *Queries) RevokeInvite(ctx context.Context, code string) error { - _, err := q.db.Exec(ctx, revokeInvite, code) - return err -} - -const useInviteAtomic = `-- name: UseInviteAtomic :execrows -UPDATE invites SET use_count = use_count + 1 -WHERE code = $1 AND revoked = FALSE - AND (max_uses IS NULL OR use_count < max_uses) - AND (expires_at IS NULL OR expires_at > NOW()) -` - -func (q *Queries) UseInviteAtomic(ctx context.Context, code string) (int64, error) { - result, err := q.db.Exec(ctx, useInviteAtomic, code) - if err != nil { - return 0, err - } - return result.RowsAffected(), nil -} diff --git a/Server/db/pgdbgen/lockouts.sql.go b/Server/db/pgdbgen/lockouts.sql.go deleted file mode 100644 index d98de3ad..00000000 --- a/Server/db/pgdbgen/lockouts.sql.go +++ /dev/null @@ -1,74 +0,0 @@ -//go:build postgres - -// Code generated by sqlc. DO NOT EDIT. -// versions: -// sqlc v1.30.0 -// source: lockouts.sql - -package pgdbgen - -import ( - "context" - - "github.com/jackc/pgx/v5/pgtype" -) - -const cleanupExpiredLockouts = `-- name: CleanupExpiredLockouts :exec -DELETE FROM rate_lockouts WHERE expires_at <= $1 -` - -func (q *Queries) CleanupExpiredLockouts(ctx context.Context, expiresAt pgtype.Timestamptz) error { - _, err := q.db.Exec(ctx, cleanupExpiredLockouts, expiresAt) - return err -} - -const deleteLockout = `-- name: DeleteLockout :exec -DELETE FROM rate_lockouts WHERE key = $1 -` - -func (q *Queries) DeleteLockout(ctx context.Context, key string) error { - _, err := q.db.Exec(ctx, deleteLockout, key) - return err -} - -const loadActiveLockouts = `-- name: LoadActiveLockouts :many -SELECT key, expires_at FROM rate_lockouts WHERE expires_at > $1 -` - -func (q *Queries) LoadActiveLockouts(ctx context.Context, expiresAt pgtype.Timestamptz) ([]RateLockout, error) { - rows, err := q.db.Query(ctx, loadActiveLockouts, expiresAt) - if err != nil { - return nil, err - } - defer rows.Close() - items := []RateLockout{} - for rows.Next() { - var i RateLockout - if err := rows.Scan(&i.Key, &i.ExpiresAt); err != nil { - return nil, err - } - items = append(items, i) - } - if err := rows.Err(); err != nil { - return nil, err - } - return items, nil -} - -const upsertLockout = `-- name: UpsertLockout :exec - -INSERT INTO rate_lockouts (key, expires_at) VALUES ($1, $2) -ON CONFLICT (key) DO UPDATE SET expires_at = EXCLUDED.expires_at -` - -type UpsertLockoutParams struct { - Key string `json:"key"` - ExpiresAt pgtype.Timestamptz `json:"expiresAt"` -} - -// PostgreSQL variants of the sqlite rate-lockout queries. -// `INSERT OR REPLACE` becomes `INSERT ... ON CONFLICT (key) DO UPDATE`. -func (q *Queries) UpsertLockout(ctx context.Context, arg UpsertLockoutParams) error { - _, err := q.db.Exec(ctx, upsertLockout, arg.Key, arg.ExpiresAt) - return err -} diff --git a/Server/db/pgdbgen/messages.sql.go b/Server/db/pgdbgen/messages.sql.go deleted file mode 100644 index bfdf17f1..00000000 --- a/Server/db/pgdbgen/messages.sql.go +++ /dev/null @@ -1,479 +0,0 @@ -//go:build postgres - -// Code generated by sqlc. DO NOT EDIT. -// versions: -// sqlc v1.30.0 -// source: messages.sql - -package pgdbgen - -import ( - "context" - - "github.com/jackc/pgx/v5/pgtype" -) - -const createMessage = `-- name: CreateMessage :one - -INSERT INTO messages (channel_id, user_id, content, reply_to) -VALUES ($1, $2, $3, $4) -RETURNING id -` - -type CreateMessageParams struct { - ChannelID int64 `json:"channelId"` - UserID int64 `json:"userId"` - Content string `json:"content"` - ReplyTo *int64 `json:"replyTo"` -} - -// PostgreSQL variants of the sqlite messages queries. -// `deleted = 0/1` and `pinned = 0/1` become FALSE/TRUE (columns are BOOLEAN). -// The FTS search queries are NOT included here: on postgres, messages.fts is -// a tsvector column with a GIN index (see migrations/postgres/001_initial_schema.sql) -// and FTS queries are hand-written in the postgres-specific store dispatch, -// mirroring how sqlite's FTS5 queries live in message_queries.go. -func (q *Queries) CreateMessage(ctx context.Context, arg CreateMessageParams) (int64, error) { - row := q.db.QueryRow(ctx, createMessage, - arg.ChannelID, - arg.UserID, - arg.Content, - arg.ReplyTo, - ) - var id int64 - err := row.Scan(&id) - return id, err -} - -const editMessageContent = `-- name: EditMessageContent :exec -UPDATE messages SET content = $1, edited_at = NOW() WHERE id = $2 -` - -type EditMessageContentParams struct { - Content string `json:"content"` - ID int64 `json:"id"` -} - -func (q *Queries) EditMessageContent(ctx context.Context, arg EditMessageContentParams) error { - _, err := q.db.Exec(ctx, editMessageContent, arg.Content, arg.ID) - return err -} - -const getChannelUnreadCounts = `-- name: GetChannelUnreadCounts :many -SELECT c.id, - COALESCE(MAX(m.id), 0)::BIGINT AS last_msg_id, - COUNT(CASE WHEN m.id > COALESCE(rs.last_message_id, 0) AND m.deleted = FALSE THEN 1 END) AS unread -FROM channels c -LEFT JOIN messages m ON m.channel_id = c.id AND m.deleted = FALSE -LEFT JOIN read_states rs ON rs.channel_id = c.id AND rs.user_id = $1 -WHERE c.type = 'text' -GROUP BY c.id -` - -type GetChannelUnreadCountsRow struct { - ID int64 `json:"id"` - LastMsgID int64 `json:"lastMsgId"` - Unread int64 `json:"unread"` -} - -func (q *Queries) GetChannelUnreadCounts(ctx context.Context, userID int64) ([]GetChannelUnreadCountsRow, error) { - rows, err := q.db.Query(ctx, getChannelUnreadCounts, userID) - if err != nil { - return nil, err - } - defer rows.Close() - items := []GetChannelUnreadCountsRow{} - for rows.Next() { - var i GetChannelUnreadCountsRow - if err := rows.Scan(&i.ID, &i.LastMsgID, &i.Unread); err != nil { - return nil, err - } - items = append(items, i) - } - if err := rows.Err(); err != nil { - return nil, err - } - return items, nil -} - -const getLatestMessageID = `-- name: GetLatestMessageID :one -SELECT COALESCE(MAX(id), 0)::BIGINT FROM messages WHERE channel_id = $1 AND deleted = FALSE -` - -func (q *Queries) GetLatestMessageID(ctx context.Context, channelID int64) (int64, error) { - row := q.db.QueryRow(ctx, getLatestMessageID, channelID) - var column_1 int64 - err := row.Scan(&column_1) - return column_1, err -} - -const getMessage = `-- name: GetMessage :one -SELECT id, channel_id, user_id, content, reply_to, edited_at, deleted, pinned, timestamp -FROM messages WHERE id = $1 -` - -type GetMessageRow struct { - ID int64 `json:"id"` - ChannelID int64 `json:"channelId"` - UserID int64 `json:"userId"` - Content string `json:"content"` - ReplyTo *int64 `json:"replyTo"` - EditedAt pgtype.Timestamptz `json:"editedAt"` - Deleted bool `json:"deleted"` - Pinned bool `json:"pinned"` - Timestamp pgtype.Timestamptz `json:"timestamp"` -} - -func (q *Queries) GetMessage(ctx context.Context, id int64) (GetMessageRow, error) { - row := q.db.QueryRow(ctx, getMessage, id) - var i GetMessageRow - err := row.Scan( - &i.ID, - &i.ChannelID, - &i.UserID, - &i.Content, - &i.ReplyTo, - &i.EditedAt, - &i.Deleted, - &i.Pinned, - &i.Timestamp, - ) - return i, err -} - -const getMessagesByChannel = `-- name: GetMessagesByChannel :many -SELECT m.id, m.channel_id, m.user_id, m.content, m.reply_to, - m.edited_at, m.deleted, m.pinned, m.timestamp, - u.username, u.avatar -FROM messages m JOIN users u ON m.user_id = u.id -WHERE m.channel_id = $1 AND m.deleted = FALSE -ORDER BY m.id DESC LIMIT $2 -` - -type GetMessagesByChannelParams struct { - ChannelID int64 `json:"channelId"` - Limit int32 `json:"limit"` -} - -type GetMessagesByChannelRow struct { - ID int64 `json:"id"` - ChannelID int64 `json:"channelId"` - UserID int64 `json:"userId"` - Content string `json:"content"` - ReplyTo *int64 `json:"replyTo"` - EditedAt pgtype.Timestamptz `json:"editedAt"` - Deleted bool `json:"deleted"` - Pinned bool `json:"pinned"` - Timestamp pgtype.Timestamptz `json:"timestamp"` - Username string `json:"username"` - Avatar *string `json:"avatar"` -} - -func (q *Queries) GetMessagesByChannel(ctx context.Context, arg GetMessagesByChannelParams) ([]GetMessagesByChannelRow, error) { - rows, err := q.db.Query(ctx, getMessagesByChannel, arg.ChannelID, arg.Limit) - if err != nil { - return nil, err - } - defer rows.Close() - items := []GetMessagesByChannelRow{} - for rows.Next() { - var i GetMessagesByChannelRow - if err := rows.Scan( - &i.ID, - &i.ChannelID, - &i.UserID, - &i.Content, - &i.ReplyTo, - &i.EditedAt, - &i.Deleted, - &i.Pinned, - &i.Timestamp, - &i.Username, - &i.Avatar, - ); err != nil { - return nil, err - } - items = append(items, i) - } - if err := rows.Err(); err != nil { - return nil, err - } - return items, nil -} - -const getMessagesByChannelBeforeCursor = `-- name: GetMessagesByChannelBeforeCursor :many -SELECT m.id, m.channel_id, m.user_id, m.content, m.reply_to, - m.edited_at, m.deleted, m.pinned, m.timestamp, - u.username, u.avatar -FROM messages m JOIN users u ON m.user_id = u.id -WHERE m.channel_id = $1 AND m.id < $2 AND m.deleted = FALSE -ORDER BY m.id DESC LIMIT $3 -` - -type GetMessagesByChannelBeforeCursorParams struct { - ChannelID int64 `json:"channelId"` - ID int64 `json:"id"` - Limit int32 `json:"limit"` -} - -type GetMessagesByChannelBeforeCursorRow struct { - ID int64 `json:"id"` - ChannelID int64 `json:"channelId"` - UserID int64 `json:"userId"` - Content string `json:"content"` - ReplyTo *int64 `json:"replyTo"` - EditedAt pgtype.Timestamptz `json:"editedAt"` - Deleted bool `json:"deleted"` - Pinned bool `json:"pinned"` - Timestamp pgtype.Timestamptz `json:"timestamp"` - Username string `json:"username"` - Avatar *string `json:"avatar"` -} - -func (q *Queries) GetMessagesByChannelBeforeCursor(ctx context.Context, arg GetMessagesByChannelBeforeCursorParams) ([]GetMessagesByChannelBeforeCursorRow, error) { - rows, err := q.db.Query(ctx, getMessagesByChannelBeforeCursor, arg.ChannelID, arg.ID, arg.Limit) - if err != nil { - return nil, err - } - defer rows.Close() - items := []GetMessagesByChannelBeforeCursorRow{} - for rows.Next() { - var i GetMessagesByChannelBeforeCursorRow - if err := rows.Scan( - &i.ID, - &i.ChannelID, - &i.UserID, - &i.Content, - &i.ReplyTo, - &i.EditedAt, - &i.Deleted, - &i.Pinned, - &i.Timestamp, - &i.Username, - &i.Avatar, - ); err != nil { - return nil, err - } - items = append(items, i) - } - if err := rows.Err(); err != nil { - return nil, err - } - return items, nil -} - -const getMessagesForAPI = `-- name: GetMessagesForAPI :many -SELECT m.id, m.channel_id, m.user_id, u.username, u.avatar, - m.content, m.reply_to, m.edited_at, m.deleted, m.pinned, m.timestamp -FROM messages m JOIN users u ON m.user_id = u.id -WHERE m.channel_id = $1 AND m.deleted = FALSE -ORDER BY m.id DESC LIMIT $2 -` - -type GetMessagesForAPIParams struct { - ChannelID int64 `json:"channelId"` - Limit int32 `json:"limit"` -} - -type GetMessagesForAPIRow struct { - ID int64 `json:"id"` - ChannelID int64 `json:"channelId"` - UserID int64 `json:"userId"` - Username string `json:"username"` - Avatar *string `json:"avatar"` - Content string `json:"content"` - ReplyTo *int64 `json:"replyTo"` - EditedAt pgtype.Timestamptz `json:"editedAt"` - Deleted bool `json:"deleted"` - Pinned bool `json:"pinned"` - Timestamp pgtype.Timestamptz `json:"timestamp"` -} - -func (q *Queries) GetMessagesForAPI(ctx context.Context, arg GetMessagesForAPIParams) ([]GetMessagesForAPIRow, error) { - rows, err := q.db.Query(ctx, getMessagesForAPI, arg.ChannelID, arg.Limit) - if err != nil { - return nil, err - } - defer rows.Close() - items := []GetMessagesForAPIRow{} - for rows.Next() { - var i GetMessagesForAPIRow - if err := rows.Scan( - &i.ID, - &i.ChannelID, - &i.UserID, - &i.Username, - &i.Avatar, - &i.Content, - &i.ReplyTo, - &i.EditedAt, - &i.Deleted, - &i.Pinned, - &i.Timestamp, - ); err != nil { - return nil, err - } - items = append(items, i) - } - if err := rows.Err(); err != nil { - return nil, err - } - return items, nil -} - -const getMessagesForAPIBeforeCursor = `-- name: GetMessagesForAPIBeforeCursor :many -SELECT m.id, m.channel_id, m.user_id, u.username, u.avatar, - m.content, m.reply_to, m.edited_at, m.deleted, m.pinned, m.timestamp -FROM messages m JOIN users u ON m.user_id = u.id -WHERE m.channel_id = $1 AND m.id < $2 AND m.deleted = FALSE -ORDER BY m.id DESC LIMIT $3 -` - -type GetMessagesForAPIBeforeCursorParams struct { - ChannelID int64 `json:"channelId"` - ID int64 `json:"id"` - Limit int32 `json:"limit"` -} - -type GetMessagesForAPIBeforeCursorRow struct { - ID int64 `json:"id"` - ChannelID int64 `json:"channelId"` - UserID int64 `json:"userId"` - Username string `json:"username"` - Avatar *string `json:"avatar"` - Content string `json:"content"` - ReplyTo *int64 `json:"replyTo"` - EditedAt pgtype.Timestamptz `json:"editedAt"` - Deleted bool `json:"deleted"` - Pinned bool `json:"pinned"` - Timestamp pgtype.Timestamptz `json:"timestamp"` -} - -func (q *Queries) GetMessagesForAPIBeforeCursor(ctx context.Context, arg GetMessagesForAPIBeforeCursorParams) ([]GetMessagesForAPIBeforeCursorRow, error) { - rows, err := q.db.Query(ctx, getMessagesForAPIBeforeCursor, arg.ChannelID, arg.ID, arg.Limit) - if err != nil { - return nil, err - } - defer rows.Close() - items := []GetMessagesForAPIBeforeCursorRow{} - for rows.Next() { - var i GetMessagesForAPIBeforeCursorRow - if err := rows.Scan( - &i.ID, - &i.ChannelID, - &i.UserID, - &i.Username, - &i.Avatar, - &i.Content, - &i.ReplyTo, - &i.EditedAt, - &i.Deleted, - &i.Pinned, - &i.Timestamp, - ); err != nil { - return nil, err - } - items = append(items, i) - } - if err := rows.Err(); err != nil { - return nil, err - } - return items, nil -} - -const getPinnedMessageRows = `-- name: GetPinnedMessageRows :many -SELECT m.id, m.channel_id, m.user_id, u.username, u.avatar, - m.content, m.reply_to, m.edited_at, m.deleted, m.pinned, m.timestamp -FROM messages m JOIN users u ON m.user_id = u.id -WHERE m.channel_id = $1 AND m.pinned = TRUE AND m.deleted = FALSE -ORDER BY m.id DESC -` - -type GetPinnedMessageRowsRow struct { - ID int64 `json:"id"` - ChannelID int64 `json:"channelId"` - UserID int64 `json:"userId"` - Username string `json:"username"` - Avatar *string `json:"avatar"` - Content string `json:"content"` - ReplyTo *int64 `json:"replyTo"` - EditedAt pgtype.Timestamptz `json:"editedAt"` - Deleted bool `json:"deleted"` - Pinned bool `json:"pinned"` - Timestamp pgtype.Timestamptz `json:"timestamp"` -} - -func (q *Queries) GetPinnedMessageRows(ctx context.Context, channelID int64) ([]GetPinnedMessageRowsRow, error) { - rows, err := q.db.Query(ctx, getPinnedMessageRows, channelID) - if err != nil { - return nil, err - } - defer rows.Close() - items := []GetPinnedMessageRowsRow{} - for rows.Next() { - var i GetPinnedMessageRowsRow - if err := rows.Scan( - &i.ID, - &i.ChannelID, - &i.UserID, - &i.Username, - &i.Avatar, - &i.Content, - &i.ReplyTo, - &i.EditedAt, - &i.Deleted, - &i.Pinned, - &i.Timestamp, - ); err != nil { - return nil, err - } - items = append(items, i) - } - if err := rows.Err(); err != nil { - return nil, err - } - return items, nil -} - -const setMessagePinned = `-- name: SetMessagePinned :execrows -UPDATE messages SET pinned = $1 WHERE id = $2 AND deleted = FALSE -` - -type SetMessagePinnedParams struct { - Pinned bool `json:"pinned"` - ID int64 `json:"id"` -} - -func (q *Queries) SetMessagePinned(ctx context.Context, arg SetMessagePinnedParams) (int64, error) { - result, err := q.db.Exec(ctx, setMessagePinned, arg.Pinned, arg.ID) - if err != nil { - return 0, err - } - return result.RowsAffected(), nil -} - -const softDeleteMessage = `-- name: SoftDeleteMessage :exec -UPDATE messages SET deleted = TRUE WHERE id = $1 -` - -func (q *Queries) SoftDeleteMessage(ctx context.Context, id int64) error { - _, err := q.db.Exec(ctx, softDeleteMessage, id) - return err -} - -const updateReadState = `-- name: UpdateReadState :exec -INSERT INTO read_states (user_id, channel_id, last_message_id) -VALUES ($1, $2, $3) -ON CONFLICT (user_id, channel_id) DO UPDATE SET last_message_id = EXCLUDED.last_message_id -` - -type UpdateReadStateParams struct { - UserID int64 `json:"userId"` - ChannelID int64 `json:"channelId"` - LastMessageID int64 `json:"lastMessageId"` -} - -func (q *Queries) UpdateReadState(ctx context.Context, arg UpdateReadStateParams) error { - _, err := q.db.Exec(ctx, updateReadState, arg.UserID, arg.ChannelID, arg.LastMessageID) - return err -} diff --git a/Server/db/pgdbgen/models.go b/Server/db/pgdbgen/models.go deleted file mode 100644 index c689ed8b..00000000 --- a/Server/db/pgdbgen/models.go +++ /dev/null @@ -1,218 +0,0 @@ -//go:build postgres - -// Code generated by sqlc. DO NOT EDIT. -// versions: -// sqlc v1.30.0 - -package pgdbgen - -import ( - "github.com/jackc/pgx/v5/pgtype" -) - -type Attachment struct { - ID string `json:"id"` - MessageID *int64 `json:"messageId"` - Filename string `json:"filename"` - StoredAs string `json:"storedAs"` - MimeType string `json:"mimeType"` - Size int64 `json:"size"` - UploadedAt pgtype.Timestamptz `json:"uploadedAt"` - Width *int32 `json:"width"` - Height *int32 `json:"height"` - UploaderID *int64 `json:"uploaderId"` -} - -type AuditLog struct { - ID int64 `json:"id"` - ActorID int64 `json:"actorId"` - Action string `json:"action"` - TargetType string `json:"targetType"` - TargetID int64 `json:"targetId"` - Detail string `json:"detail"` - CreatedAt pgtype.Timestamptz `json:"createdAt"` -} - -type Channel struct { - ID int64 `json:"id"` - Name string `json:"name"` - Type string `json:"type"` - Category *string `json:"category"` - Topic *string `json:"topic"` - Position int32 `json:"position"` - SlowMode int32 `json:"slowMode"` - Archived bool `json:"archived"` - CreatedAt pgtype.Timestamptz `json:"createdAt"` - VoiceMaxUsers int32 `json:"voiceMaxUsers"` - VoiceQuality *string `json:"voiceQuality"` - MixingThreshold *int32 `json:"mixingThreshold"` - VoiceMaxVideo int32 `json:"voiceMaxVideo"` -} - -type ChannelOverride struct { - ID int64 `json:"id"` - ChannelID int64 `json:"channelId"` - RoleID int64 `json:"roleId"` - Allow int64 `json:"allow"` - Deny int64 `json:"deny"` -} - -type DmOpenState struct { - UserID int64 `json:"userId"` - ChannelID int64 `json:"channelId"` - OpenedAt pgtype.Timestamptz `json:"openedAt"` -} - -type DmParticipant struct { - ChannelID int64 `json:"channelId"` - UserID int64 `json:"userId"` -} - -type Emoji struct { - ID int64 `json:"id"` - Shortcode string `json:"shortcode"` - Filename string `json:"filename"` - UploadedBy int64 `json:"uploadedBy"` - CreatedAt pgtype.Timestamptz `json:"createdAt"` -} - -type Event struct { - Seq int64 `json:"seq"` - EventType string `json:"eventType"` - Payload []byte `json:"payload"` - ChannelID int64 `json:"channelId"` - CreatedAt pgtype.Timestamptz `json:"createdAt"` -} - -type Invite struct { - ID int64 `json:"id"` - Code string `json:"code"` - CreatedBy int64 `json:"createdBy"` - RedeemedBy *int64 `json:"redeemedBy"` - MaxUses *int32 `json:"maxUses"` - UseCount int32 `json:"useCount"` - ExpiresAt pgtype.Timestamptz `json:"expiresAt"` - CreatedAt pgtype.Timestamptz `json:"createdAt"` - Revoked bool `json:"revoked"` -} - -type LoginAttempt struct { - ID int64 `json:"id"` - IpAddress string `json:"ipAddress"` - Username *string `json:"username"` - Success bool `json:"success"` - Timestamp pgtype.Timestamptz `json:"timestamp"` -} - -type Message struct { - ID int64 `json:"id"` - ChannelID int64 `json:"channelId"` - UserID int64 `json:"userId"` - Content string `json:"content"` - ReplyTo *int64 `json:"replyTo"` - EditedAt pgtype.Timestamptz `json:"editedAt"` - Deleted bool `json:"deleted"` - Pinned bool `json:"pinned"` - Timestamp pgtype.Timestamptz `json:"timestamp"` - Fts interface{} `json:"fts"` -} - -type Plugin struct { - ID int64 `json:"id"` - Name string `json:"name"` - Version string `json:"version"` - Enabled bool `json:"enabled"` - ManifestJson string `json:"manifestJson"` - InstalledAt pgtype.Timestamptz `json:"installedAt"` -} - -type PluginKv struct { - PluginID int64 `json:"pluginId"` - Key string `json:"key"` - Value []byte `json:"value"` -} - -type RateLockout struct { - Key string `json:"key"` - ExpiresAt pgtype.Timestamptz `json:"expiresAt"` -} - -type Reaction struct { - ID int64 `json:"id"` - MessageID int64 `json:"messageId"` - UserID int64 `json:"userId"` - Emoji string `json:"emoji"` -} - -type ReadState struct { - UserID int64 `json:"userId"` - ChannelID int64 `json:"channelId"` - LastMessageID int64 `json:"lastMessageId"` - MentionCount int32 `json:"mentionCount"` -} - -type Role struct { - ID int64 `json:"id"` - Name string `json:"name"` - Color *string `json:"color"` - Permissions int64 `json:"permissions"` - Position int32 `json:"position"` - IsDefault bool `json:"isDefault"` -} - -type Session struct { - ID int64 `json:"id"` - UserID int64 `json:"userId"` - Token string `json:"token"` - Device *string `json:"device"` - IpAddress *string `json:"ipAddress"` - CreatedAt pgtype.Timestamptz `json:"createdAt"` - LastUsed pgtype.Timestamptz `json:"lastUsed"` - ExpiresAt pgtype.Timestamptz `json:"expiresAt"` -} - -type Setting struct { - Key string `json:"key"` - Value string `json:"value"` -} - -type Sound struct { - ID int64 `json:"id"` - Name string `json:"name"` - Filename string `json:"filename"` - DurationMs int32 `json:"durationMs"` - UploadedBy int64 `json:"uploadedBy"` - CreatedAt pgtype.Timestamptz `json:"createdAt"` -} - -type User struct { - ID int64 `json:"id"` - Username string `json:"username"` - Password string `json:"password"` - Avatar *string `json:"avatar"` - RoleID int64 `json:"roleId"` - TotpSecret *string `json:"totpSecret"` - Status string `json:"status"` - CreatedAt pgtype.Timestamptz `json:"createdAt"` - LastSeen pgtype.Timestamptz `json:"lastSeen"` - Banned bool `json:"banned"` - BanReason *string `json:"banReason"` - BanExpires pgtype.Timestamptz `json:"banExpires"` -} - -type UserBlock struct { - BlockerID int64 `json:"blockerId"` - BlockedID int64 `json:"blockedId"` - CreatedAt pgtype.Timestamptz `json:"createdAt"` -} - -type VoiceState struct { - UserID int64 `json:"userId"` - ChannelID int64 `json:"channelId"` - Muted bool `json:"muted"` - Deafened bool `json:"deafened"` - Speaking bool `json:"speaking"` - JoinedAt pgtype.Timestamptz `json:"joinedAt"` - Camera bool `json:"camera"` - Screenshare bool `json:"screenshare"` -} diff --git a/Server/db/pgdbgen/plugins.sql.go b/Server/db/pgdbgen/plugins.sql.go deleted file mode 100644 index 3f24e863..00000000 --- a/Server/db/pgdbgen/plugins.sql.go +++ /dev/null @@ -1,175 +0,0 @@ -//go:build postgres - -// Code generated by sqlc. DO NOT EDIT. -// versions: -// sqlc v1.30.0 -// source: plugins.sql - -package pgdbgen - -import ( - "context" -) - -const disablePlugin = `-- name: DisablePlugin :exec -UPDATE plugins SET enabled = FALSE WHERE id = $1 -` - -func (q *Queries) DisablePlugin(ctx context.Context, id int64) error { - _, err := q.db.Exec(ctx, disablePlugin, id) - return err -} - -const enablePlugin = `-- name: EnablePlugin :exec -UPDATE plugins SET enabled = TRUE WHERE id = $1 -` - -func (q *Queries) EnablePlugin(ctx context.Context, id int64) error { - _, err := q.db.Exec(ctx, enablePlugin, id) - return err -} - -const getPlugin = `-- name: GetPlugin :one -SELECT id, name, version, enabled, manifest_json, installed_at FROM plugins WHERE id = $1 -` - -func (q *Queries) GetPlugin(ctx context.Context, id int64) (Plugin, error) { - row := q.db.QueryRow(ctx, getPlugin, id) - var i Plugin - err := row.Scan( - &i.ID, - &i.Name, - &i.Version, - &i.Enabled, - &i.ManifestJson, - &i.InstalledAt, - ) - return i, err -} - -const getPluginByName = `-- name: GetPluginByName :one -SELECT id, name, version, enabled, manifest_json, installed_at FROM plugins WHERE name = $1 -` - -func (q *Queries) GetPluginByName(ctx context.Context, name string) (Plugin, error) { - row := q.db.QueryRow(ctx, getPluginByName, name) - var i Plugin - err := row.Scan( - &i.ID, - &i.Name, - &i.Version, - &i.Enabled, - &i.ManifestJson, - &i.InstalledAt, - ) - return i, err -} - -const installPlugin = `-- name: InstallPlugin :one -INSERT INTO plugins (name, version, manifest_json) -VALUES ($1, $2, $3) -ON CONFLICT (name) DO UPDATE - SET version = excluded.version, - manifest_json = excluded.manifest_json -RETURNING id -` - -type InstallPluginParams struct { - Name string `json:"name"` - Version string `json:"version"` - ManifestJson string `json:"manifestJson"` -} - -func (q *Queries) InstallPlugin(ctx context.Context, arg InstallPluginParams) (int64, error) { - row := q.db.QueryRow(ctx, installPlugin, arg.Name, arg.Version, arg.ManifestJson) - var id int64 - err := row.Scan(&id) - return id, err -} - -const listPlugins = `-- name: ListPlugins :many -SELECT id, name, version, enabled, manifest_json, installed_at FROM plugins ORDER BY name -` - -func (q *Queries) ListPlugins(ctx context.Context) ([]Plugin, error) { - rows, err := q.db.Query(ctx, listPlugins) - if err != nil { - return nil, err - } - defer rows.Close() - items := []Plugin{} - for rows.Next() { - var i Plugin - if err := rows.Scan( - &i.ID, - &i.Name, - &i.Version, - &i.Enabled, - &i.ManifestJson, - &i.InstalledAt, - ); err != nil { - return nil, err - } - items = append(items, i) - } - if err := rows.Err(); err != nil { - return nil, err - } - return items, nil -} - -const pluginKVDelete = `-- name: PluginKVDelete :exec -DELETE FROM plugin_kv WHERE plugin_id = $1 AND key = $2 -` - -type PluginKVDeleteParams struct { - PluginID int64 `json:"pluginId"` - Key string `json:"key"` -} - -func (q *Queries) PluginKVDelete(ctx context.Context, arg PluginKVDeleteParams) error { - _, err := q.db.Exec(ctx, pluginKVDelete, arg.PluginID, arg.Key) - return err -} - -const pluginKVGet = `-- name: PluginKVGet :one -SELECT value FROM plugin_kv WHERE plugin_id = $1 AND key = $2 -` - -type PluginKVGetParams struct { - PluginID int64 `json:"pluginId"` - Key string `json:"key"` -} - -func (q *Queries) PluginKVGet(ctx context.Context, arg PluginKVGetParams) ([]byte, error) { - row := q.db.QueryRow(ctx, pluginKVGet, arg.PluginID, arg.Key) - var value []byte - err := row.Scan(&value) - return value, err -} - -const pluginKVSet = `-- name: PluginKVSet :exec -INSERT INTO plugin_kv (plugin_id, key, value) -VALUES ($1, $2, $3) -ON CONFLICT (plugin_id, key) DO UPDATE SET value = excluded.value -` - -type PluginKVSetParams struct { - PluginID int64 `json:"pluginId"` - Key string `json:"key"` - Value []byte `json:"value"` -} - -func (q *Queries) PluginKVSet(ctx context.Context, arg PluginKVSetParams) error { - _, err := q.db.Exec(ctx, pluginKVSet, arg.PluginID, arg.Key, arg.Value) - return err -} - -const uninstallPlugin = `-- name: UninstallPlugin :exec -DELETE FROM plugins WHERE id = $1 -` - -func (q *Queries) UninstallPlugin(ctx context.Context, id int64) error { - _, err := q.db.Exec(ctx, uninstallPlugin, id) - return err -} diff --git a/Server/db/pgdbgen/profile.sql.go b/Server/db/pgdbgen/profile.sql.go deleted file mode 100644 index 4da8d7d3..00000000 --- a/Server/db/pgdbgen/profile.sql.go +++ /dev/null @@ -1,48 +0,0 @@ -//go:build postgres - -// Code generated by sqlc. DO NOT EDIT. -// versions: -// sqlc v1.30.0 -// source: profile.sql - -package pgdbgen - -import ( - "context" -) - -const updateUserPassword = `-- name: UpdateUserPassword :exec -UPDATE users SET password = $1 WHERE id = $2 -` - -type UpdateUserPasswordParams struct { - Password string `json:"password"` - ID int64 `json:"id"` -} - -func (q *Queries) UpdateUserPassword(ctx context.Context, arg UpdateUserPasswordParams) error { - _, err := q.db.Exec(ctx, updateUserPassword, arg.Password, arg.ID) - return err -} - -const updateUserProfile = `-- name: UpdateUserProfile :execrows - -UPDATE users SET username = $1, avatar = $2 WHERE id = $3 -` - -type UpdateUserProfileParams struct { - Username string `json:"username"` - Avatar *string `json:"avatar"` - ID int64 `json:"id"` -} - -// PostgreSQL variants of the sqlite profile queries. -// UpdateUserProfile uses :execrows because postgres has no LastInsertId; -// the caller checks rows-affected for existence. -func (q *Queries) UpdateUserProfile(ctx context.Context, arg UpdateUserProfileParams) (int64, error) { - result, err := q.db.Exec(ctx, updateUserProfile, arg.Username, arg.Avatar, arg.ID) - if err != nil { - return 0, err - } - return result.RowsAffected(), nil -} diff --git a/Server/db/pgdbgen/querier.go b/Server/db/pgdbgen/querier.go deleted file mode 100644 index cf97acc1..00000000 --- a/Server/db/pgdbgen/querier.go +++ /dev/null @@ -1,197 +0,0 @@ -//go:build postgres - -// Code generated by sqlc. DO NOT EDIT. -// versions: -// sqlc v1.30.0 - -package pgdbgen - -import ( - "context" - - "github.com/jackc/pgx/v5/pgtype" -) - -type Querier interface { - // PostgreSQL variants of the sqlite reactions queries. - AddReaction(ctx context.Context, arg AddReactionParams) error - AdminUpdateChannel(ctx context.Context, arg AdminUpdateChannelParams) error - ArchiveChannel(ctx context.Context, arg ArchiveChannelParams) error - BanUser(ctx context.Context, arg BanUserParams) error - // PostgreSQL variants of the sqlite user block queries. - // `INSERT OR IGNORE` becomes `INSERT ... ON CONFLICT DO NOTHING`. - BlockUser(ctx context.Context, arg BlockUserParams) error - CleanupExpiredLockouts(ctx context.Context, expiresAt pgtype.Timestamptz) error - ClearAllVoiceStates(ctx context.Context) error - ClearVoiceState(ctx context.Context, userID int64) error - CloseDM(ctx context.Context, arg CloseDMParams) error - CountActiveCameras(ctx context.Context, channelID int64) (int64, error) - CountActiveInvites(ctx context.Context) (int64, error) - CountActiveMessages(ctx context.Context) (int64, error) - CountChannels(ctx context.Context) (int64, error) - CountUsers(ctx context.Context) (int64, error) - CountUsersWithoutTOTP(ctx context.Context) (int64, error) - // PostgreSQL variants of the sqlite attachments queries. - CreateAttachment(ctx context.Context, arg CreateAttachmentParams) error - CreateChannel(ctx context.Context, arg CreateChannelParams) (int64, error) - // PostgreSQL variants of the sqlite invites queries. - // The expiry check uses native timestamp comparison instead of sqlite's - // strftime('%s', …) trick. - CreateInvite(ctx context.Context, arg CreateInviteParams) error - // PostgreSQL variants of the sqlite messages queries. - // `deleted = 0/1` and `pinned = 0/1` become FALSE/TRUE (columns are BOOLEAN). - // The FTS search queries are NOT included here: on postgres, messages.fts is - // a tsvector column with a GIN index (see migrations/postgres/001_initial_schema.sql) - // and FTS queries are hand-written in the postgres-specific store dispatch, - // mirroring how sqlite's FTS5 queries live in message_queries.go. - CreateMessage(ctx context.Context, arg CreateMessageParams) (int64, error) - CreateUser(ctx context.Context, arg CreateUserParams) (int64, error) - DeleteAttachment(ctx context.Context, id string) error - DeleteChannel(ctx context.Context, id int64) error - DeleteChannelPermission(ctx context.Context, arg DeleteChannelPermissionParams) error - // Use native timestamp comparison instead of sqlite's strftime trick. - DeleteExpiredSessions(ctx context.Context) error - DeleteLockout(ctx context.Context, key string) error - // Postgres timestamptz comparison — the caller passes a wall-clock time. - DeleteOrphanedAttachments(ctx context.Context, uploadedAt pgtype.Timestamptz) ([]string, error) - DeleteOtherSessions(ctx context.Context, arg DeleteOtherSessionsParams) (int64, error) - DeleteSessionByID(ctx context.Context, arg DeleteSessionByIDParams) error - DeleteSessionByToken(ctx context.Context, token string) error - DisablePlugin(ctx context.Context, id int64) error - EditMessageContent(ctx context.Context, arg EditMessageContentParams) error - EnableCameraIfUnderLimit(ctx context.Context, arg EnableCameraIfUnderLimitParams) (int64, error) - EnablePlugin(ctx context.Context, id int64) error - // Delete all but the N most recent sessions for a user. Postgres replaces - // sqlite's `LIMIT -1 OFFSET ?` with `OFFSET $2`. - EvictOldestSessions(ctx context.Context, arg EvictOldestSessionsParams) error - FindExistingDMChannel(ctx context.Context, arg FindExistingDMChannelParams) (int64, error) - ForceLogoutUser(ctx context.Context, userID int64) error - GetAllSettings(ctx context.Context) ([]Setting, error) - GetAllVoiceStates(ctx context.Context) ([]GetAllVoiceStatesRow, error) - GetAttachmentByID(ctx context.Context, id string) (GetAttachmentByIDRow, error) - GetAttachmentWithChannel(ctx context.Context, id string) (GetAttachmentWithChannelRow, error) - GetAuditLog(ctx context.Context, arg GetAuditLogParams) ([]GetAuditLogRow, error) - GetChannel(ctx context.Context, id int64) (GetChannelRow, error) - GetChannelPermission(ctx context.Context, arg GetChannelPermissionParams) (GetChannelPermissionRow, error) - GetChannelUnreadCounts(ctx context.Context, userID int64) ([]GetChannelUnreadCountsRow, error) - GetChannelVoiceStates(ctx context.Context, channelID int64) ([]GetChannelVoiceStatesRow, error) - GetDMParticipantIDs(ctx context.Context, channelID int64) ([]int64, error) - GetDefaultRole(ctx context.Context) (Role, error) - GetEventsSince(ctx context.Context, arg GetEventsSinceParams) ([]GetEventsSinceRow, error) - GetInvite(ctx context.Context, code string) (GetInviteRow, error) - GetLatestMessageID(ctx context.Context, channelID int64) (int64, error) - GetMaxEventSeq(ctx context.Context) (int64, error) - GetMessage(ctx context.Context, id int64) (GetMessageRow, error) - GetMessagesByChannel(ctx context.Context, arg GetMessagesByChannelParams) ([]GetMessagesByChannelRow, error) - GetMessagesByChannelBeforeCursor(ctx context.Context, arg GetMessagesByChannelBeforeCursorParams) ([]GetMessagesByChannelBeforeCursorRow, error) - GetMessagesForAPI(ctx context.Context, arg GetMessagesForAPIParams) ([]GetMessagesForAPIRow, error) - GetMessagesForAPIBeforeCursor(ctx context.Context, arg GetMessagesForAPIBeforeCursorParams) ([]GetMessagesForAPIBeforeCursorRow, error) - GetPinnedMessageRows(ctx context.Context, channelID int64) ([]GetPinnedMessageRowsRow, error) - GetPlugin(ctx context.Context, id int64) (Plugin, error) - GetPluginByName(ctx context.Context, name string) (Plugin, error) - GetReactionCounts(ctx context.Context, messageID int64) ([]GetReactionCountsRow, error) - // PostgreSQL variants of the sqlite roles queries. - // `is_default = 1` becomes `is_default = TRUE` since the column is BOOLEAN. - GetRoleByID(ctx context.Context, id int64) (Role, error) - GetRoleChannelPermissions(ctx context.Context, roleID int64) ([]GetRoleChannelPermissionsRow, error) - GetRoleForUser(ctx context.Context, id int64) (Role, error) - GetSessionByTokenHash(ctx context.Context, token string) (Session, error) - GetSessionWithBanStatus(ctx context.Context, token string) (GetSessionWithBanStatusRow, error) - GetSetting(ctx context.Context, key string) (string, error) - GetUserByID(ctx context.Context, id int64) (User, error) - // PostgreSQL variants of the sqlite users queries. - // Differences from sqlite: - // - `?` -> `$1`, `$2`, … - // - `COLLATE NOCASE` -> removed; the `username` column is CITEXT. - // - `datetime('now')` -> `NOW()` - // - `banned = 0/1` -> `banned = FALSE/TRUE` (column is BOOLEAN) - // - `:execresult INSERT` -> `:one ... RETURNING id` (pgx has no LastInsertId) - GetUserByUsername(ctx context.Context, username string) (User, error) - // For the "last message at" and "last message content" columns, sqlite - // COALESCEs to '' (empty string). Postgres TIMESTAMPTZ cannot COALESCE to an - // empty string, so we COALESCE to the dm_open_state.opened_at fallback and - // leave conversion to the store wrapper. - GetUserDMChannels(ctx context.Context, arg GetUserDMChannelsParams) ([]GetUserDMChannelsRow, error) - GetUserSessions(ctx context.Context, userID int64) ([]Session, error) - GetUserVoiceState(ctx context.Context, userID int64) (GetUserVoiceStateRow, error) - GetUserWithRole(ctx context.Context, id int64) (GetUserWithRoleRow, error) - // PostgreSQL variants of the sqlite DM queries. - // InsertDMChannel: sqlite uses :execresult (LastInsertId); postgres uses - // :one with RETURNING id. - // `INSERT OR IGNORE` becomes `INSERT ... ON CONFLICT DO NOTHING`. - InsertDMChannel(ctx context.Context) (int64, error) - InsertDMOpenState(ctx context.Context, arg InsertDMOpenStateParams) error - InsertDMParticipants(ctx context.Context, arg InsertDMParticipantsParams) error - // PostgreSQL variants of the sqlite sessions queries. - InsertSession(ctx context.Context, arg InsertSessionParams) (int64, error) - InstallPlugin(ctx context.Context, arg InstallPluginParams) (int64, error) - IsBlocked(ctx context.Context, arg IsBlockedParams) (int32, error) - IsDMParticipant(ctx context.Context, arg IsDMParticipantParams) (int64, error) - IsEitherBlocked(ctx context.Context, arg IsEitherBlockedParams) (int32, error) - // PostgreSQL variants of the sqlite voice queries. - // voice_states boolean columns (muted, deafened, speaking, camera, - // screenshare) use FALSE/TRUE instead of 0/1. - JoinVoiceChannel(ctx context.Context, arg JoinVoiceChannelParams) error - JoinVoiceChannelIfCapacity(ctx context.Context, arg JoinVoiceChannelIfCapacityParams) (int64, error) - LeaveVoiceChannel(ctx context.Context, userID int64) error - LeaveVoiceChannelIfMatch(ctx context.Context, arg LeaveVoiceChannelIfMatchParams) (int64, error) - LinkAttachmentToMessage(ctx context.Context, arg LinkAttachmentToMessageParams) (int64, error) - ListAllUsers(ctx context.Context, arg ListAllUsersParams) ([]ListAllUsersRow, error) - // PostgreSQL variants of the sqlite channels queries. - ListChannels(ctx context.Context) ([]ListChannelsRow, error) - ListInvites(ctx context.Context) ([]ListInvitesRow, error) - ListMembers(ctx context.Context) ([]ListMembersRow, error) - ListPlugins(ctx context.Context) ([]Plugin, error) - ListRoles(ctx context.Context) ([]Role, error) - ListUserSessions(ctx context.Context, userID int64) ([]Session, error) - LoadActiveLockouts(ctx context.Context, expiresAt pgtype.Timestamptz) ([]RateLockout, error) - LogAudit(ctx context.Context, arg LogAuditParams) error - OpenDM(ctx context.Context, arg OpenDMParams) error - // seq is supplied by the hub so the row seq matches the wrapped-payload seq. - // The schema's BIGSERIAL still owns the id column for inserts that omit seq, - // but PersistEvent always supplies an explicit value. - PersistEvent(ctx context.Context, arg PersistEventParams) error - PluginKVDelete(ctx context.Context, arg PluginKVDeleteParams) error - PluginKVGet(ctx context.Context, arg PluginKVGetParams) ([]byte, error) - PluginKVSet(ctx context.Context, arg PluginKVSetParams) error - PruneEventsOlderThan(ctx context.Context, createdAt pgtype.Timestamptz) (int64, error) - RemoveReaction(ctx context.Context, arg RemoveReactionParams) (int64, error) - ResetAllUserStatuses(ctx context.Context) error - RevokeInvite(ctx context.Context, code string) error - SetChannelMixingThreshold(ctx context.Context, arg SetChannelMixingThresholdParams) error - SetChannelSlowMode(ctx context.Context, arg SetChannelSlowModeParams) error - SetChannelVoiceMaxUsers(ctx context.Context, arg SetChannelVoiceMaxUsersParams) error - SetChannelVoiceMaxVideo(ctx context.Context, arg SetChannelVoiceMaxVideoParams) error - SetChannelVoiceQuality(ctx context.Context, arg SetChannelVoiceQualityParams) error - SetMessagePinned(ctx context.Context, arg SetMessagePinnedParams) (int64, error) - SetSetting(ctx context.Context, arg SetSettingParams) error - SoftDeleteMessage(ctx context.Context, id int64) error - TouchSession(ctx context.Context, token string) error - UnbanUser(ctx context.Context, id int64) error - UnblockUser(ctx context.Context, arg UnblockUserParams) error - UninstallPlugin(ctx context.Context, id int64) error - UpdateChannel(ctx context.Context, arg UpdateChannelParams) error - UpdateReadState(ctx context.Context, arg UpdateReadStateParams) error - UpdateUserPassword(ctx context.Context, arg UpdateUserPasswordParams) error - // PostgreSQL variants of the sqlite profile queries. - // UpdateUserProfile uses :execrows because postgres has no LastInsertId; - // the caller checks rows-affected for existence. - UpdateUserProfile(ctx context.Context, arg UpdateUserProfileParams) (int64, error) - UpdateUserRole(ctx context.Context, arg UpdateUserRoleParams) error - UpdateUserStatus(ctx context.Context, arg UpdateUserStatusParams) error - UpdateUserTOTPSecret(ctx context.Context, arg UpdateUserTOTPSecretParams) error - UpdateVoiceCamera(ctx context.Context, arg UpdateVoiceCameraParams) error - UpdateVoiceDeafen(ctx context.Context, arg UpdateVoiceDeafenParams) error - UpdateVoiceMute(ctx context.Context, arg UpdateVoiceMuteParams) error - UpdateVoiceScreenshare(ctx context.Context, arg UpdateVoiceScreenshareParams) error - UpdateVoiceSpeaking(ctx context.Context, arg UpdateVoiceSpeakingParams) error - UpsertChannelPermission(ctx context.Context, arg UpsertChannelPermissionParams) error - // PostgreSQL variants of the sqlite rate-lockout queries. - // `INSERT OR REPLACE` becomes `INSERT ... ON CONFLICT (key) DO UPDATE`. - UpsertLockout(ctx context.Context, arg UpsertLockoutParams) error - UseInviteAtomic(ctx context.Context, code string) (int64, error) - // PostgreSQL variants of the sqlite admin queries. - UserCount(ctx context.Context) (int64, error) -} - -var _ Querier = (*Queries)(nil) diff --git a/Server/db/pgdbgen/reactions.sql.go b/Server/db/pgdbgen/reactions.sql.go deleted file mode 100644 index 69927d98..00000000 --- a/Server/db/pgdbgen/reactions.sql.go +++ /dev/null @@ -1,78 +0,0 @@ -//go:build postgres - -// Code generated by sqlc. DO NOT EDIT. -// versions: -// sqlc v1.30.0 -// source: reactions.sql - -package pgdbgen - -import ( - "context" -) - -const addReaction = `-- name: AddReaction :exec - -INSERT INTO reactions (message_id, user_id, emoji) VALUES ($1, $2, $3) -` - -type AddReactionParams struct { - MessageID int64 `json:"messageId"` - UserID int64 `json:"userId"` - Emoji string `json:"emoji"` -} - -// PostgreSQL variants of the sqlite reactions queries. -func (q *Queries) AddReaction(ctx context.Context, arg AddReactionParams) error { - _, err := q.db.Exec(ctx, addReaction, arg.MessageID, arg.UserID, arg.Emoji) - return err -} - -const getReactionCounts = `-- name: GetReactionCounts :many -SELECT emoji, COUNT(*) AS count -FROM reactions WHERE message_id = $1 -GROUP BY emoji -` - -type GetReactionCountsRow struct { - Emoji string `json:"emoji"` - Count int64 `json:"count"` -} - -func (q *Queries) GetReactionCounts(ctx context.Context, messageID int64) ([]GetReactionCountsRow, error) { - rows, err := q.db.Query(ctx, getReactionCounts, messageID) - if err != nil { - return nil, err - } - defer rows.Close() - items := []GetReactionCountsRow{} - for rows.Next() { - var i GetReactionCountsRow - if err := rows.Scan(&i.Emoji, &i.Count); err != nil { - return nil, err - } - items = append(items, i) - } - if err := rows.Err(); err != nil { - return nil, err - } - return items, nil -} - -const removeReaction = `-- name: RemoveReaction :execrows -DELETE FROM reactions WHERE message_id = $1 AND user_id = $2 AND emoji = $3 -` - -type RemoveReactionParams struct { - MessageID int64 `json:"messageId"` - UserID int64 `json:"userId"` - Emoji string `json:"emoji"` -} - -func (q *Queries) RemoveReaction(ctx context.Context, arg RemoveReactionParams) (int64, error) { - result, err := q.db.Exec(ctx, removeReaction, arg.MessageID, arg.UserID, arg.Emoji) - if err != nil { - return 0, err - } - return result.RowsAffected(), nil -} diff --git a/Server/db/pgdbgen/roles.sql.go b/Server/db/pgdbgen/roles.sql.go deleted file mode 100644 index 2da88cd1..00000000 --- a/Server/db/pgdbgen/roles.sql.go +++ /dev/null @@ -1,165 +0,0 @@ -//go:build postgres - -// Code generated by sqlc. DO NOT EDIT. -// versions: -// sqlc v1.30.0 -// source: roles.sql - -package pgdbgen - -import ( - "context" - - "github.com/jackc/pgx/v5/pgtype" -) - -const getDefaultRole = `-- name: GetDefaultRole :one -SELECT id, name, color, permissions, position, is_default -FROM roles WHERE is_default = TRUE LIMIT 1 -` - -func (q *Queries) GetDefaultRole(ctx context.Context) (Role, error) { - row := q.db.QueryRow(ctx, getDefaultRole) - var i Role - err := row.Scan( - &i.ID, - &i.Name, - &i.Color, - &i.Permissions, - &i.Position, - &i.IsDefault, - ) - return i, err -} - -const getRoleByID = `-- name: GetRoleByID :one - -SELECT id, name, color, permissions, position, is_default -FROM roles WHERE id = $1 -` - -// PostgreSQL variants of the sqlite roles queries. -// `is_default = 1` becomes `is_default = TRUE` since the column is BOOLEAN. -func (q *Queries) GetRoleByID(ctx context.Context, id int64) (Role, error) { - row := q.db.QueryRow(ctx, getRoleByID, id) - var i Role - err := row.Scan( - &i.ID, - &i.Name, - &i.Color, - &i.Permissions, - &i.Position, - &i.IsDefault, - ) - return i, err -} - -const getRoleForUser = `-- name: GetRoleForUser :one -SELECT r.id, r.name, r.color, r.permissions, r.position, r.is_default -FROM users u -JOIN roles r ON u.role_id = r.id -WHERE u.id = $1 -` - -func (q *Queries) GetRoleForUser(ctx context.Context, id int64) (Role, error) { - row := q.db.QueryRow(ctx, getRoleForUser, id) - var i Role - err := row.Scan( - &i.ID, - &i.Name, - &i.Color, - &i.Permissions, - &i.Position, - &i.IsDefault, - ) - return i, err -} - -const getUserWithRole = `-- name: GetUserWithRole :one -SELECT u.id, u.username, u.password, u.avatar, u.role_id, - u.totp_secret, u.status, u.created_at, u.last_seen, - u.banned, u.ban_reason, u.ban_expires, - r.id, r.name, r.color, r.permissions, r.position, r.is_default -FROM users u -JOIN roles r ON r.id = u.role_id -WHERE u.id = $1 -` - -type GetUserWithRoleRow struct { - ID int64 `json:"id"` - Username string `json:"username"` - Password string `json:"password"` - Avatar *string `json:"avatar"` - RoleID int64 `json:"roleId"` - TotpSecret *string `json:"totpSecret"` - Status string `json:"status"` - CreatedAt pgtype.Timestamptz `json:"createdAt"` - LastSeen pgtype.Timestamptz `json:"lastSeen"` - Banned bool `json:"banned"` - BanReason *string `json:"banReason"` - BanExpires pgtype.Timestamptz `json:"banExpires"` - ID_2 int64 `json:"id2"` - Name string `json:"name"` - Color *string `json:"color"` - Permissions int64 `json:"permissions"` - Position int32 `json:"position"` - IsDefault bool `json:"isDefault"` -} - -func (q *Queries) GetUserWithRole(ctx context.Context, id int64) (GetUserWithRoleRow, error) { - row := q.db.QueryRow(ctx, getUserWithRole, id) - var i GetUserWithRoleRow - err := row.Scan( - &i.ID, - &i.Username, - &i.Password, - &i.Avatar, - &i.RoleID, - &i.TotpSecret, - &i.Status, - &i.CreatedAt, - &i.LastSeen, - &i.Banned, - &i.BanReason, - &i.BanExpires, - &i.ID_2, - &i.Name, - &i.Color, - &i.Permissions, - &i.Position, - &i.IsDefault, - ) - return i, err -} - -const listRoles = `-- name: ListRoles :many -SELECT id, name, color, permissions, position, is_default -FROM roles ORDER BY position DESC -` - -func (q *Queries) ListRoles(ctx context.Context) ([]Role, error) { - rows, err := q.db.Query(ctx, listRoles) - if err != nil { - return nil, err - } - defer rows.Close() - items := []Role{} - for rows.Next() { - var i Role - if err := rows.Scan( - &i.ID, - &i.Name, - &i.Color, - &i.Permissions, - &i.Position, - &i.IsDefault, - ); err != nil { - return nil, err - } - items = append(items, i) - } - if err := rows.Err(); err != nil { - return nil, err - } - return items, nil -} diff --git a/Server/db/pgdbgen/sessions.sql.go b/Server/db/pgdbgen/sessions.sql.go deleted file mode 100644 index feeccda1..00000000 --- a/Server/db/pgdbgen/sessions.sql.go +++ /dev/null @@ -1,221 +0,0 @@ -//go:build postgres - -// Code generated by sqlc. DO NOT EDIT. -// versions: -// sqlc v1.30.0 -// source: sessions.sql - -package pgdbgen - -import ( - "context" - - "github.com/jackc/pgx/v5/pgtype" -) - -const deleteExpiredSessions = `-- name: DeleteExpiredSessions :exec -DELETE FROM sessions WHERE expires_at < NOW() -` - -// Use native timestamp comparison instead of sqlite's strftime trick. -func (q *Queries) DeleteExpiredSessions(ctx context.Context) error { - _, err := q.db.Exec(ctx, deleteExpiredSessions) - return err -} - -const deleteOtherSessions = `-- name: DeleteOtherSessions :execrows -DELETE FROM sessions WHERE user_id = $1 AND id != $2 -` - -type DeleteOtherSessionsParams struct { - UserID int64 `json:"userId"` - ID int64 `json:"id"` -} - -func (q *Queries) DeleteOtherSessions(ctx context.Context, arg DeleteOtherSessionsParams) (int64, error) { - result, err := q.db.Exec(ctx, deleteOtherSessions, arg.UserID, arg.ID) - if err != nil { - return 0, err - } - return result.RowsAffected(), nil -} - -const deleteSessionByID = `-- name: DeleteSessionByID :exec -DELETE FROM sessions WHERE id = $1 AND user_id = $2 -` - -type DeleteSessionByIDParams struct { - ID int64 `json:"id"` - UserID int64 `json:"userId"` -} - -func (q *Queries) DeleteSessionByID(ctx context.Context, arg DeleteSessionByIDParams) error { - _, err := q.db.Exec(ctx, deleteSessionByID, arg.ID, arg.UserID) - return err -} - -const deleteSessionByToken = `-- name: DeleteSessionByToken :exec -DELETE FROM sessions WHERE token = $1 -` - -func (q *Queries) DeleteSessionByToken(ctx context.Context, token string) error { - _, err := q.db.Exec(ctx, deleteSessionByToken, token) - return err -} - -const evictOldestSessions = `-- name: EvictOldestSessions :exec -DELETE FROM sessions WHERE id IN ( - SELECT s2.id FROM sessions AS s2 WHERE s2.user_id = $1 - ORDER BY s2.created_at DESC - OFFSET $2 -) -` - -type EvictOldestSessionsParams struct { - UserID int64 `json:"userId"` - Offset int32 `json:"offset"` -} - -// Delete all but the N most recent sessions for a user. Postgres replaces -// sqlite's `LIMIT -1 OFFSET ?` with `OFFSET $2`. -func (q *Queries) EvictOldestSessions(ctx context.Context, arg EvictOldestSessionsParams) error { - _, err := q.db.Exec(ctx, evictOldestSessions, arg.UserID, arg.Offset) - return err -} - -const getSessionByTokenHash = `-- name: GetSessionByTokenHash :one -SELECT id, user_id, token, device, ip_address, created_at, last_used, expires_at -FROM sessions WHERE token = $1 -` - -func (q *Queries) GetSessionByTokenHash(ctx context.Context, token string) (Session, error) { - row := q.db.QueryRow(ctx, getSessionByTokenHash, token) - var i Session - err := row.Scan( - &i.ID, - &i.UserID, - &i.Token, - &i.Device, - &i.IpAddress, - &i.CreatedAt, - &i.LastUsed, - &i.ExpiresAt, - ) - return i, err -} - -const getSessionWithBanStatus = `-- name: GetSessionWithBanStatus :one -SELECT s.id, s.user_id, s.token, s.device, s.ip_address, - s.created_at, s.last_used, s.expires_at, - u.banned, u.ban_reason, u.ban_expires -FROM sessions s -JOIN users u ON s.user_id = u.id -WHERE s.token = $1 -` - -type GetSessionWithBanStatusRow struct { - ID int64 `json:"id"` - UserID int64 `json:"userId"` - Token string `json:"token"` - Device *string `json:"device"` - IpAddress *string `json:"ipAddress"` - CreatedAt pgtype.Timestamptz `json:"createdAt"` - LastUsed pgtype.Timestamptz `json:"lastUsed"` - ExpiresAt pgtype.Timestamptz `json:"expiresAt"` - Banned bool `json:"banned"` - BanReason *string `json:"banReason"` - BanExpires pgtype.Timestamptz `json:"banExpires"` -} - -func (q *Queries) GetSessionWithBanStatus(ctx context.Context, token string) (GetSessionWithBanStatusRow, error) { - row := q.db.QueryRow(ctx, getSessionWithBanStatus, token) - var i GetSessionWithBanStatusRow - err := row.Scan( - &i.ID, - &i.UserID, - &i.Token, - &i.Device, - &i.IpAddress, - &i.CreatedAt, - &i.LastUsed, - &i.ExpiresAt, - &i.Banned, - &i.BanReason, - &i.BanExpires, - ) - return i, err -} - -const insertSession = `-- name: InsertSession :one - -INSERT INTO sessions (user_id, token, device, ip_address, expires_at) -VALUES ($1, $2, $3, $4, $5) -RETURNING id -` - -type InsertSessionParams struct { - UserID int64 `json:"userId"` - Token string `json:"token"` - Device *string `json:"device"` - IpAddress *string `json:"ipAddress"` - ExpiresAt pgtype.Timestamptz `json:"expiresAt"` -} - -// PostgreSQL variants of the sqlite sessions queries. -func (q *Queries) InsertSession(ctx context.Context, arg InsertSessionParams) (int64, error) { - row := q.db.QueryRow(ctx, insertSession, - arg.UserID, - arg.Token, - arg.Device, - arg.IpAddress, - arg.ExpiresAt, - ) - var id int64 - err := row.Scan(&id) - return id, err -} - -const listUserSessions = `-- name: ListUserSessions :many -SELECT id, user_id, token, device, ip_address, created_at, last_used, expires_at -FROM sessions -WHERE user_id = $1 -ORDER BY created_at DESC -` - -func (q *Queries) ListUserSessions(ctx context.Context, userID int64) ([]Session, error) { - rows, err := q.db.Query(ctx, listUserSessions, userID) - if err != nil { - return nil, err - } - defer rows.Close() - items := []Session{} - for rows.Next() { - var i Session - if err := rows.Scan( - &i.ID, - &i.UserID, - &i.Token, - &i.Device, - &i.IpAddress, - &i.CreatedAt, - &i.LastUsed, - &i.ExpiresAt, - ); err != nil { - return nil, err - } - items = append(items, i) - } - if err := rows.Err(); err != nil { - return nil, err - } - return items, nil -} - -const touchSession = `-- name: TouchSession :exec -UPDATE sessions SET last_used = NOW() WHERE token = $1 -` - -func (q *Queries) TouchSession(ctx context.Context, token string) error { - _, err := q.db.Exec(ctx, touchSession, token) - return err -} diff --git a/Server/db/pgdbgen/users.sql.go b/Server/db/pgdbgen/users.sql.go deleted file mode 100644 index d4878c0d..00000000 --- a/Server/db/pgdbgen/users.sql.go +++ /dev/null @@ -1,219 +0,0 @@ -//go:build postgres - -// Code generated by sqlc. DO NOT EDIT. -// versions: -// sqlc v1.30.0 -// source: users.sql - -package pgdbgen - -import ( - "context" - - "github.com/jackc/pgx/v5/pgtype" -) - -const banUser = `-- name: BanUser :exec -UPDATE users SET banned = TRUE, ban_reason = $1, ban_expires = $2 WHERE id = $3 -` - -type BanUserParams struct { - BanReason *string `json:"banReason"` - BanExpires pgtype.Timestamptz `json:"banExpires"` - ID int64 `json:"id"` -} - -func (q *Queries) BanUser(ctx context.Context, arg BanUserParams) error { - _, err := q.db.Exec(ctx, banUser, arg.BanReason, arg.BanExpires, arg.ID) - return err -} - -const countUsers = `-- name: CountUsers :one -SELECT COUNT(*) FROM users -` - -func (q *Queries) CountUsers(ctx context.Context) (int64, error) { - row := q.db.QueryRow(ctx, countUsers) - var count int64 - err := row.Scan(&count) - return count, err -} - -const countUsersWithoutTOTP = `-- name: CountUsersWithoutTOTP :one -SELECT COUNT(*) FROM users WHERE banned = FALSE AND totp_secret IS NULL -` - -func (q *Queries) CountUsersWithoutTOTP(ctx context.Context) (int64, error) { - row := q.db.QueryRow(ctx, countUsersWithoutTOTP) - var count int64 - err := row.Scan(&count) - return count, err -} - -const createUser = `-- name: CreateUser :one -INSERT INTO users (username, password, role_id) -VALUES ($1, $2, $3) -RETURNING id -` - -type CreateUserParams struct { - Username string `json:"username"` - Password string `json:"password"` - RoleID int64 `json:"roleId"` -} - -func (q *Queries) CreateUser(ctx context.Context, arg CreateUserParams) (int64, error) { - row := q.db.QueryRow(ctx, createUser, arg.Username, arg.Password, arg.RoleID) - var id int64 - err := row.Scan(&id) - return id, err -} - -const getUserByID = `-- name: GetUserByID :one -SELECT id, username, password, avatar, role_id, totp_secret, status, - created_at, last_seen, banned, ban_reason, ban_expires -FROM users WHERE id = $1 -` - -func (q *Queries) GetUserByID(ctx context.Context, id int64) (User, error) { - row := q.db.QueryRow(ctx, getUserByID, id) - var i User - err := row.Scan( - &i.ID, - &i.Username, - &i.Password, - &i.Avatar, - &i.RoleID, - &i.TotpSecret, - &i.Status, - &i.CreatedAt, - &i.LastSeen, - &i.Banned, - &i.BanReason, - &i.BanExpires, - ) - return i, err -} - -const getUserByUsername = `-- name: GetUserByUsername :one - -SELECT id, username, password, avatar, role_id, totp_secret, status, - created_at, last_seen, banned, ban_reason, ban_expires -FROM users WHERE username = $1 -` - -// PostgreSQL variants of the sqlite users queries. -// Differences from sqlite: -// - `?` -> `$1`, `$2`, … -// - `COLLATE NOCASE` -> removed; the `username` column is CITEXT. -// - `datetime('now')` -> `NOW()` -// - `banned = 0/1` -> `banned = FALSE/TRUE` (column is BOOLEAN) -// - `:execresult INSERT` -> `:one ... RETURNING id` (pgx has no LastInsertId) -func (q *Queries) GetUserByUsername(ctx context.Context, username string) (User, error) { - row := q.db.QueryRow(ctx, getUserByUsername, username) - var i User - err := row.Scan( - &i.ID, - &i.Username, - &i.Password, - &i.Avatar, - &i.RoleID, - &i.TotpSecret, - &i.Status, - &i.CreatedAt, - &i.LastSeen, - &i.Banned, - &i.BanReason, - &i.BanExpires, - ) - return i, err -} - -const listMembers = `-- name: ListMembers :many -SELECT u.id, u.username, u.avatar, u.status, LOWER(r.name) -FROM users u -JOIN roles r ON u.role_id = r.id -WHERE u.banned = FALSE -ORDER BY u.username ASC -LIMIT 1000 -` - -type ListMembersRow struct { - ID int64 `json:"id"` - Username string `json:"username"` - Avatar *string `json:"avatar"` - Status string `json:"status"` - Lower string `json:"lower"` -} - -func (q *Queries) ListMembers(ctx context.Context) ([]ListMembersRow, error) { - rows, err := q.db.Query(ctx, listMembers) - if err != nil { - return nil, err - } - defer rows.Close() - items := []ListMembersRow{} - for rows.Next() { - var i ListMembersRow - if err := rows.Scan( - &i.ID, - &i.Username, - &i.Avatar, - &i.Status, - &i.Lower, - ); err != nil { - return nil, err - } - items = append(items, i) - } - if err := rows.Err(); err != nil { - return nil, err - } - return items, nil -} - -const resetAllUserStatuses = `-- name: ResetAllUserStatuses :exec -UPDATE users SET status = 'offline' WHERE status != 'offline' -` - -func (q *Queries) ResetAllUserStatuses(ctx context.Context) error { - _, err := q.db.Exec(ctx, resetAllUserStatuses) - return err -} - -const unbanUser = `-- name: UnbanUser :exec -UPDATE users SET banned = FALSE, ban_reason = NULL, ban_expires = NULL WHERE id = $1 -` - -func (q *Queries) UnbanUser(ctx context.Context, id int64) error { - _, err := q.db.Exec(ctx, unbanUser, id) - return err -} - -const updateUserStatus = `-- name: UpdateUserStatus :exec -UPDATE users SET status = $1, last_seen = NOW() WHERE id = $2 -` - -type UpdateUserStatusParams struct { - Status string `json:"status"` - ID int64 `json:"id"` -} - -func (q *Queries) UpdateUserStatus(ctx context.Context, arg UpdateUserStatusParams) error { - _, err := q.db.Exec(ctx, updateUserStatus, arg.Status, arg.ID) - return err -} - -const updateUserTOTPSecret = `-- name: UpdateUserTOTPSecret :exec -UPDATE users SET totp_secret = $1 WHERE id = $2 -` - -type UpdateUserTOTPSecretParams struct { - TotpSecret *string `json:"totpSecret"` - ID int64 `json:"id"` -} - -func (q *Queries) UpdateUserTOTPSecret(ctx context.Context, arg UpdateUserTOTPSecretParams) error { - _, err := q.db.Exec(ctx, updateUserTOTPSecret, arg.TotpSecret, arg.ID) - return err -} diff --git a/Server/db/pgdbgen/voice.sql.go b/Server/db/pgdbgen/voice.sql.go deleted file mode 100644 index 718218c4..00000000 --- a/Server/db/pgdbgen/voice.sql.go +++ /dev/null @@ -1,371 +0,0 @@ -//go:build postgres - -// Code generated by sqlc. DO NOT EDIT. -// versions: -// sqlc v1.30.0 -// source: voice.sql - -package pgdbgen - -import ( - "context" - - "github.com/jackc/pgx/v5/pgtype" -) - -const clearAllVoiceStates = `-- name: ClearAllVoiceStates :exec -DELETE FROM voice_states -` - -func (q *Queries) ClearAllVoiceStates(ctx context.Context) error { - _, err := q.db.Exec(ctx, clearAllVoiceStates) - return err -} - -const clearVoiceState = `-- name: ClearVoiceState :exec -DELETE FROM voice_states WHERE user_id = $1 -` - -func (q *Queries) ClearVoiceState(ctx context.Context, userID int64) error { - _, err := q.db.Exec(ctx, clearVoiceState, userID) - return err -} - -const countActiveCameras = `-- name: CountActiveCameras :one -SELECT COUNT(*) FROM voice_states WHERE channel_id = $1 AND camera = TRUE -` - -func (q *Queries) CountActiveCameras(ctx context.Context, channelID int64) (int64, error) { - row := q.db.QueryRow(ctx, countActiveCameras, channelID) - var count int64 - err := row.Scan(&count) - return count, err -} - -const enableCameraIfUnderLimit = `-- name: EnableCameraIfUnderLimit :execrows -UPDATE voice_states SET camera = TRUE -WHERE voice_states.user_id = $1 AND voice_states.channel_id = $2 - AND (SELECT COUNT(*) FROM voice_states AS vs2 WHERE vs2.channel_id = $3 AND vs2.camera = TRUE) < $4 -` - -type EnableCameraIfUnderLimitParams struct { - UserID int64 `json:"userId"` - ChannelID int64 `json:"channelId"` - ChannelID_2 int64 `json:"channelId2"` - ChannelID_3 int64 `json:"channelId3"` -} - -func (q *Queries) EnableCameraIfUnderLimit(ctx context.Context, arg EnableCameraIfUnderLimitParams) (int64, error) { - result, err := q.db.Exec(ctx, enableCameraIfUnderLimit, - arg.UserID, - arg.ChannelID, - arg.ChannelID_2, - arg.ChannelID_3, - ) - if err != nil { - return 0, err - } - return result.RowsAffected(), nil -} - -const getAllVoiceStates = `-- name: GetAllVoiceStates :many -SELECT vs.user_id, vs.channel_id, u.username, - vs.muted, vs.deafened, vs.speaking, - vs.camera, vs.screenshare, vs.joined_at -FROM voice_states vs -JOIN users u ON u.id = vs.user_id -ORDER BY vs.channel_id, vs.joined_at ASC -` - -type GetAllVoiceStatesRow struct { - UserID int64 `json:"userId"` - ChannelID int64 `json:"channelId"` - Username string `json:"username"` - Muted bool `json:"muted"` - Deafened bool `json:"deafened"` - Speaking bool `json:"speaking"` - Camera bool `json:"camera"` - Screenshare bool `json:"screenshare"` - JoinedAt pgtype.Timestamptz `json:"joinedAt"` -} - -func (q *Queries) GetAllVoiceStates(ctx context.Context) ([]GetAllVoiceStatesRow, error) { - rows, err := q.db.Query(ctx, getAllVoiceStates) - if err != nil { - return nil, err - } - defer rows.Close() - items := []GetAllVoiceStatesRow{} - for rows.Next() { - var i GetAllVoiceStatesRow - if err := rows.Scan( - &i.UserID, - &i.ChannelID, - &i.Username, - &i.Muted, - &i.Deafened, - &i.Speaking, - &i.Camera, - &i.Screenshare, - &i.JoinedAt, - ); err != nil { - return nil, err - } - items = append(items, i) - } - if err := rows.Err(); err != nil { - return nil, err - } - return items, nil -} - -const getChannelVoiceStates = `-- name: GetChannelVoiceStates :many -SELECT vs.user_id, vs.channel_id, u.username, - vs.muted, vs.deafened, vs.speaking, - vs.camera, vs.screenshare, vs.joined_at -FROM voice_states vs -JOIN users u ON u.id = vs.user_id -WHERE vs.channel_id = $1 -ORDER BY vs.joined_at ASC -` - -type GetChannelVoiceStatesRow struct { - UserID int64 `json:"userId"` - ChannelID int64 `json:"channelId"` - Username string `json:"username"` - Muted bool `json:"muted"` - Deafened bool `json:"deafened"` - Speaking bool `json:"speaking"` - Camera bool `json:"camera"` - Screenshare bool `json:"screenshare"` - JoinedAt pgtype.Timestamptz `json:"joinedAt"` -} - -func (q *Queries) GetChannelVoiceStates(ctx context.Context, channelID int64) ([]GetChannelVoiceStatesRow, error) { - rows, err := q.db.Query(ctx, getChannelVoiceStates, channelID) - if err != nil { - return nil, err - } - defer rows.Close() - items := []GetChannelVoiceStatesRow{} - for rows.Next() { - var i GetChannelVoiceStatesRow - if err := rows.Scan( - &i.UserID, - &i.ChannelID, - &i.Username, - &i.Muted, - &i.Deafened, - &i.Speaking, - &i.Camera, - &i.Screenshare, - &i.JoinedAt, - ); err != nil { - return nil, err - } - items = append(items, i) - } - if err := rows.Err(); err != nil { - return nil, err - } - return items, nil -} - -const getUserVoiceState = `-- name: GetUserVoiceState :one -SELECT vs.user_id, vs.channel_id, u.username, - vs.muted, vs.deafened, vs.speaking, - vs.camera, vs.screenshare, vs.joined_at -FROM voice_states vs -JOIN users u ON u.id = vs.user_id -WHERE vs.user_id = $1 -` - -type GetUserVoiceStateRow struct { - UserID int64 `json:"userId"` - ChannelID int64 `json:"channelId"` - Username string `json:"username"` - Muted bool `json:"muted"` - Deafened bool `json:"deafened"` - Speaking bool `json:"speaking"` - Camera bool `json:"camera"` - Screenshare bool `json:"screenshare"` - JoinedAt pgtype.Timestamptz `json:"joinedAt"` -} - -func (q *Queries) GetUserVoiceState(ctx context.Context, userID int64) (GetUserVoiceStateRow, error) { - row := q.db.QueryRow(ctx, getUserVoiceState, userID) - var i GetUserVoiceStateRow - err := row.Scan( - &i.UserID, - &i.ChannelID, - &i.Username, - &i.Muted, - &i.Deafened, - &i.Speaking, - &i.Camera, - &i.Screenshare, - &i.JoinedAt, - ) - return i, err -} - -const joinVoiceChannel = `-- name: JoinVoiceChannel :exec - -INSERT INTO voice_states (user_id, channel_id, muted, deafened, speaking, camera, screenshare, joined_at) -VALUES ($1, $2, FALSE, FALSE, FALSE, FALSE, FALSE, $3) -ON CONFLICT (user_id) DO UPDATE SET - channel_id = EXCLUDED.channel_id, - muted = FALSE, - deafened = FALSE, - speaking = FALSE, - camera = FALSE, - screenshare = FALSE, - joined_at = EXCLUDED.joined_at -` - -type JoinVoiceChannelParams struct { - UserID int64 `json:"userId"` - ChannelID int64 `json:"channelId"` - JoinedAt pgtype.Timestamptz `json:"joinedAt"` -} - -// PostgreSQL variants of the sqlite voice queries. -// voice_states boolean columns (muted, deafened, speaking, camera, -// screenshare) use FALSE/TRUE instead of 0/1. -func (q *Queries) JoinVoiceChannel(ctx context.Context, arg JoinVoiceChannelParams) error { - _, err := q.db.Exec(ctx, joinVoiceChannel, arg.UserID, arg.ChannelID, arg.JoinedAt) - return err -} - -const joinVoiceChannelIfCapacity = `-- name: JoinVoiceChannelIfCapacity :execrows -INSERT INTO voice_states (user_id, channel_id, muted, deafened, speaking, camera, screenshare, joined_at) -SELECT $1, $2, FALSE, FALSE, FALSE, FALSE, FALSE, $3 -WHERE (SELECT COUNT(*) FROM voice_states AS vs2 WHERE vs2.channel_id = $4) < $5 -ON CONFLICT (user_id) DO UPDATE SET - channel_id = EXCLUDED.channel_id, - muted = FALSE, - deafened = FALSE, - speaking = FALSE, - camera = FALSE, - screenshare = FALSE, - joined_at = EXCLUDED.joined_at -` - -type JoinVoiceChannelIfCapacityParams struct { - UserID int64 `json:"userId"` - ChannelID int64 `json:"channelId"` - JoinedAt pgtype.Timestamptz `json:"joinedAt"` - ChannelID_2 int64 `json:"channelId2"` - ChannelID_3 int64 `json:"channelId3"` -} - -func (q *Queries) JoinVoiceChannelIfCapacity(ctx context.Context, arg JoinVoiceChannelIfCapacityParams) (int64, error) { - result, err := q.db.Exec(ctx, joinVoiceChannelIfCapacity, - arg.UserID, - arg.ChannelID, - arg.JoinedAt, - arg.ChannelID_2, - arg.ChannelID_3, - ) - if err != nil { - return 0, err - } - return result.RowsAffected(), nil -} - -const leaveVoiceChannel = `-- name: LeaveVoiceChannel :exec -DELETE FROM voice_states WHERE user_id = $1 -` - -func (q *Queries) LeaveVoiceChannel(ctx context.Context, userID int64) error { - _, err := q.db.Exec(ctx, leaveVoiceChannel, userID) - return err -} - -const leaveVoiceChannelIfMatch = `-- name: LeaveVoiceChannelIfMatch :execrows -DELETE FROM voice_states WHERE user_id = $1 AND channel_id = $2 AND joined_at = $3 -` - -type LeaveVoiceChannelIfMatchParams struct { - UserID int64 `json:"userId"` - ChannelID int64 `json:"channelId"` - JoinedAt pgtype.Timestamptz `json:"joinedAt"` -} - -func (q *Queries) LeaveVoiceChannelIfMatch(ctx context.Context, arg LeaveVoiceChannelIfMatchParams) (int64, error) { - result, err := q.db.Exec(ctx, leaveVoiceChannelIfMatch, arg.UserID, arg.ChannelID, arg.JoinedAt) - if err != nil { - return 0, err - } - return result.RowsAffected(), nil -} - -const updateVoiceCamera = `-- name: UpdateVoiceCamera :exec -UPDATE voice_states SET camera = $1 WHERE user_id = $2 -` - -type UpdateVoiceCameraParams struct { - Camera bool `json:"camera"` - UserID int64 `json:"userId"` -} - -func (q *Queries) UpdateVoiceCamera(ctx context.Context, arg UpdateVoiceCameraParams) error { - _, err := q.db.Exec(ctx, updateVoiceCamera, arg.Camera, arg.UserID) - return err -} - -const updateVoiceDeafen = `-- name: UpdateVoiceDeafen :exec -UPDATE voice_states SET deafened = $1 WHERE user_id = $2 -` - -type UpdateVoiceDeafenParams struct { - Deafened bool `json:"deafened"` - UserID int64 `json:"userId"` -} - -func (q *Queries) UpdateVoiceDeafen(ctx context.Context, arg UpdateVoiceDeafenParams) error { - _, err := q.db.Exec(ctx, updateVoiceDeafen, arg.Deafened, arg.UserID) - return err -} - -const updateVoiceMute = `-- name: UpdateVoiceMute :exec -UPDATE voice_states SET muted = $1 WHERE user_id = $2 -` - -type UpdateVoiceMuteParams struct { - Muted bool `json:"muted"` - UserID int64 `json:"userId"` -} - -func (q *Queries) UpdateVoiceMute(ctx context.Context, arg UpdateVoiceMuteParams) error { - _, err := q.db.Exec(ctx, updateVoiceMute, arg.Muted, arg.UserID) - return err -} - -const updateVoiceScreenshare = `-- name: UpdateVoiceScreenshare :exec -UPDATE voice_states SET screenshare = $1 WHERE user_id = $2 -` - -type UpdateVoiceScreenshareParams struct { - Screenshare bool `json:"screenshare"` - UserID int64 `json:"userId"` -} - -func (q *Queries) UpdateVoiceScreenshare(ctx context.Context, arg UpdateVoiceScreenshareParams) error { - _, err := q.db.Exec(ctx, updateVoiceScreenshare, arg.Screenshare, arg.UserID) - return err -} - -const updateVoiceSpeaking = `-- name: UpdateVoiceSpeaking :exec -UPDATE voice_states SET speaking = $1 WHERE user_id = $2 -` - -type UpdateVoiceSpeakingParams struct { - Speaking bool `json:"speaking"` - UserID int64 `json:"userId"` -} - -func (q *Queries) UpdateVoiceSpeaking(ctx context.Context, arg UpdateVoiceSpeakingParams) error { - _, err := q.db.Exec(ctx, updateVoiceSpeaking, arg.Speaking, arg.UserID) - return err -} diff --git a/Server/db/queries/postgres/admin.sql b/Server/db/queries/postgres/admin.sql deleted file mode 100644 index 614ed13e..00000000 --- a/Server/db/queries/postgres/admin.sql +++ /dev/null @@ -1,55 +0,0 @@ --- PostgreSQL variants of the sqlite admin queries. - --- name: UserCount :one -SELECT COUNT(*) FROM users; - --- name: CountActiveMessages :one -SELECT COUNT(*) FROM messages WHERE deleted = FALSE; - --- name: CountChannels :one -SELECT COUNT(*) FROM channels; - --- name: CountActiveInvites :one -SELECT COUNT(*) FROM invites WHERE revoked = FALSE; - --- name: ListAllUsers :many -SELECT u.id, u.username, u.avatar, u.role_id, - u.status, u.created_at, u.last_seen, u.banned, u.ban_reason, u.ban_expires, - COALESCE(r.name, '') AS role_name -FROM users u -LEFT JOIN roles r ON r.id = u.role_id -ORDER BY u.id ASC -LIMIT $1 OFFSET $2; - --- name: UpdateUserRole :exec -UPDATE users SET role_id = $1 WHERE id = $2; - --- name: ForceLogoutUser :exec -DELETE FROM sessions WHERE user_id = $1; - --- name: GetUserSessions :many -SELECT id, user_id, token, device, ip_address, created_at, last_used, expires_at -FROM sessions WHERE user_id = $1 -ORDER BY created_at DESC; - --- name: LogAudit :exec -INSERT INTO audit_log (actor_id, action, target_type, target_id, detail) -VALUES ($1, $2, $3, $4, $5); - --- name: GetAuditLog :many -SELECT a.id, a.actor_id, COALESCE(u.username, '') AS actor_name, a.action, - a.target_type, a.target_id, a.detail, a.created_at -FROM audit_log a -LEFT JOIN users u ON u.id = a.actor_id -ORDER BY a.id DESC -LIMIT $1 OFFSET $2; - --- name: GetSetting :one -SELECT value FROM settings WHERE key = $1; - --- name: SetSetting :exec -INSERT INTO settings (key, value) VALUES ($1, $2) -ON CONFLICT (key) DO UPDATE SET value = EXCLUDED.value; - --- name: GetAllSettings :many -SELECT key, value FROM settings; diff --git a/Server/db/queries/postgres/attachments.sql b/Server/db/queries/postgres/attachments.sql deleted file mode 100644 index cf3e0f06..00000000 --- a/Server/db/queries/postgres/attachments.sql +++ /dev/null @@ -1,27 +0,0 @@ --- PostgreSQL variants of the sqlite attachments queries. - --- name: CreateAttachment :exec -INSERT INTO attachments (id, uploader_id, filename, stored_as, mime_type, size, width, height) -VALUES ($1, $2, $3, $4, $5, $6, $7, $8); - --- name: GetAttachmentByID :one -SELECT id, message_id, filename, stored_as, mime_type, size, uploaded_at, uploader_id -FROM attachments WHERE id = $1; - --- name: GetAttachmentWithChannel :one -SELECT a.id, a.message_id, a.filename, a.stored_as, a.mime_type, a.size, - a.uploaded_at, a.uploader_id, m.channel_id, c.type -FROM attachments a -LEFT JOIN messages m ON m.id = a.message_id -LEFT JOIN channels c ON c.id = m.channel_id -WHERE a.id = $1; - --- name: LinkAttachmentToMessage :execrows -UPDATE attachments SET message_id = $1 WHERE id = $2 AND message_id IS NULL; - --- Postgres timestamptz comparison — the caller passes a wall-clock time. --- name: DeleteOrphanedAttachments :many -DELETE FROM attachments WHERE message_id IS NULL AND uploaded_at < $1 RETURNING stored_as; - --- name: DeleteAttachment :exec -DELETE FROM attachments WHERE id = $1; diff --git a/Server/db/queries/postgres/blocks.sql b/Server/db/queries/postgres/blocks.sql deleted file mode 100644 index 23dda418..00000000 --- a/Server/db/queries/postgres/blocks.sql +++ /dev/null @@ -1,18 +0,0 @@ --- PostgreSQL variants of the sqlite user block queries. --- `INSERT OR IGNORE` becomes `INSERT ... ON CONFLICT DO NOTHING`. - --- name: BlockUser :exec -INSERT INTO user_blocks (blocker_id, blocked_id) VALUES ($1, $2) -ON CONFLICT (blocker_id, blocked_id) DO NOTHING; - --- name: UnblockUser :exec -DELETE FROM user_blocks WHERE blocker_id = $1 AND blocked_id = $2; - --- name: IsBlocked :one -SELECT 1 FROM user_blocks WHERE blocker_id = $1 AND blocked_id = $2 LIMIT 1; - --- name: IsEitherBlocked :one -SELECT 1 FROM user_blocks -WHERE (blocker_id = $1 AND blocked_id = $2) - OR (blocker_id = $3 AND blocked_id = $4) -LIMIT 1; diff --git a/Server/db/queries/postgres/channels.sql b/Server/db/queries/postgres/channels.sql deleted file mode 100644 index 0f3c2b6a..00000000 --- a/Server/db/queries/postgres/channels.sql +++ /dev/null @@ -1,69 +0,0 @@ --- PostgreSQL variants of the sqlite channels queries. - --- name: ListChannels :many -SELECT id, name, type, COALESCE(category, '') AS category, COALESCE(topic, '') AS topic, - position, slow_mode, archived, created_at, - COALESCE(voice_max_users, 0) AS voice_max_users, - voice_quality, - mixing_threshold, - COALESCE(voice_max_video, 0) AS voice_max_video -FROM channels ORDER BY position ASC, id ASC; - --- name: GetChannel :one -SELECT id, name, type, COALESCE(category, '') AS category, COALESCE(topic, '') AS topic, - position, slow_mode, archived, created_at, - COALESCE(voice_max_users, 0) AS voice_max_users, - voice_quality, - mixing_threshold, - COALESCE(voice_max_video, 0) AS voice_max_video -FROM channels WHERE id = $1; - --- name: CreateChannel :one -INSERT INTO channels (name, type, category, topic, position) -VALUES ($1, $2, $3, $4, $5) -RETURNING id; - --- name: UpdateChannel :exec -UPDATE channels SET name = $1, topic = $2, slow_mode = $3 WHERE id = $4; - --- name: SetChannelSlowMode :exec -UPDATE channels SET slow_mode = $1 WHERE id = $2; - --- name: SetChannelVoiceMaxUsers :exec -UPDATE channels SET voice_max_users = $1 WHERE id = $2; - --- name: SetChannelVoiceMaxVideo :exec -UPDATE channels SET voice_max_video = $1 WHERE id = $2; - --- name: SetChannelVoiceQuality :exec -UPDATE channels SET voice_quality = $1 WHERE id = $2; - --- name: SetChannelMixingThreshold :exec -UPDATE channels SET mixing_threshold = $1 WHERE id = $2; - --- name: ArchiveChannel :exec -UPDATE channels SET archived = $1 WHERE id = $2; - --- name: DeleteChannel :exec -DELETE FROM channels WHERE id = $1; - --- name: AdminUpdateChannel :exec -UPDATE channels -SET name = $1, topic = $2, slow_mode = $3, position = $4, archived = $5 -WHERE id = $6; - --- name: UpsertChannelPermission :exec -INSERT INTO channel_overrides (channel_id, role_id, allow, deny) -VALUES ($1, $2, $3, $4) -ON CONFLICT (channel_id, role_id) DO UPDATE SET - allow = EXCLUDED.allow, - deny = EXCLUDED.deny; - --- name: GetChannelPermission :one -SELECT allow, deny FROM channel_overrides WHERE channel_id = $1 AND role_id = $2; - --- name: GetRoleChannelPermissions :many -SELECT channel_id, allow, deny FROM channel_overrides WHERE role_id = $1; - --- name: DeleteChannelPermission :exec -DELETE FROM channel_overrides WHERE channel_id = $1 AND role_id = $2; diff --git a/Server/db/queries/postgres/dm.sql b/Server/db/queries/postgres/dm.sql deleted file mode 100644 index 93cfbedc..00000000 --- a/Server/db/queries/postgres/dm.sql +++ /dev/null @@ -1,64 +0,0 @@ --- PostgreSQL variants of the sqlite DM queries. --- InsertDMChannel: sqlite uses :execresult (LastInsertId); postgres uses --- :one with RETURNING id. --- `INSERT OR IGNORE` becomes `INSERT ... ON CONFLICT DO NOTHING`. - --- name: InsertDMChannel :one -INSERT INTO channels (name, type) VALUES ('', 'dm') RETURNING id; - --- name: InsertDMParticipants :exec -INSERT INTO dm_participants (channel_id, user_id) VALUES ($1, $2), ($3, $4); - --- name: InsertDMOpenState :exec -INSERT INTO dm_open_state (user_id, channel_id) VALUES ($1, $2), ($3, $4) -ON CONFLICT (user_id, channel_id) DO NOTHING; - --- name: FindExistingDMChannel :one -SELECT dp1.channel_id -FROM dm_participants dp1 -JOIN dm_participants dp2 ON dp1.channel_id = dp2.channel_id -JOIN channels c ON c.id = dp1.channel_id -WHERE dp1.user_id = $1 AND dp2.user_id = $2 AND c.type = 'dm' -LIMIT 1; - --- name: OpenDM :exec -INSERT INTO dm_open_state (user_id, channel_id) VALUES ($1, $2) -ON CONFLICT (user_id, channel_id) DO NOTHING; - --- name: CloseDM :exec -DELETE FROM dm_open_state WHERE user_id = $1 AND channel_id = $2; - --- name: IsDMParticipant :one -SELECT user_id FROM dm_participants WHERE user_id = $1 AND channel_id = $2; - --- name: GetDMParticipantIDs :many -SELECT user_id FROM dm_participants WHERE channel_id = $1; - --- For the "last message at" and "last message content" columns, sqlite --- COALESCEs to '' (empty string). Postgres TIMESTAMPTZ cannot COALESCE to an --- empty string, so we COALESCE to the dm_open_state.opened_at fallback and --- leave conversion to the store wrapper. --- name: GetUserDMChannels :many -SELECT - c.id AS channel_id, - u.id AS recipient_id, - u.username AS recipient_username, - COALESCE(u.avatar, '') AS recipient_avatar, - u.status AS recipient_status, - lm.id AS last_message_id, - COALESCE(lm.content, '') AS last_message, - COALESCE(lm.timestamp, dos.opened_at) AS last_message_at, - COUNT(CASE WHEN m_unread.id > COALESCE(rs.last_message_id, 0) - AND m_unread.deleted = FALSE THEN 1 END) AS unread_count -FROM dm_open_state dos -JOIN channels c ON c.id = dos.channel_id AND c.type = 'dm' -JOIN dm_participants dp ON dp.channel_id = c.id AND dp.user_id != $1 -JOIN users u ON u.id = dp.user_id -LEFT JOIN messages lm ON lm.id = ( - SELECT MAX(id) FROM messages WHERE channel_id = c.id AND deleted = FALSE -) -LEFT JOIN messages m_unread ON m_unread.channel_id = c.id -LEFT JOIN read_states rs ON rs.channel_id = c.id AND rs.user_id = $2 -WHERE dos.user_id = $3 -GROUP BY c.id, u.id, lm.id, lm.content, lm.timestamp, dos.opened_at -ORDER BY COALESCE(lm.timestamp, dos.opened_at) DESC; diff --git a/Server/db/queries/postgres/events.sql b/Server/db/queries/postgres/events.sql deleted file mode 100644 index ec332592..00000000 --- a/Server/db/queries/postgres/events.sql +++ /dev/null @@ -1,19 +0,0 @@ --- name: PersistEvent :exec --- seq is supplied by the hub so the row seq matches the wrapped-payload seq. --- The schema's BIGSERIAL still owns the id column for inserts that omit seq, --- but PersistEvent always supplies an explicit value. -INSERT INTO events (seq, event_type, channel_id, payload) -VALUES ($1, $2, $3, $4); - --- name: GetMaxEventSeq :one -SELECT COALESCE(MAX(seq), 0)::BIGINT FROM events; - --- name: GetEventsSince :many -SELECT seq, event_type, channel_id, payload, created_at -FROM events -WHERE seq > $1 -ORDER BY seq ASC -LIMIT $2; - --- name: PruneEventsOlderThan :execrows -DELETE FROM events WHERE created_at < $1; diff --git a/Server/db/queries/postgres/invites.sql b/Server/db/queries/postgres/invites.sql deleted file mode 100644 index cd01e81f..00000000 --- a/Server/db/queries/postgres/invites.sql +++ /dev/null @@ -1,23 +0,0 @@ --- PostgreSQL variants of the sqlite invites queries. --- The expiry check uses native timestamp comparison instead of sqlite's --- strftime('%s', …) trick. - --- name: CreateInvite :exec -INSERT INTO invites (code, created_by, max_uses, expires_at) VALUES ($1, $2, $3, $4); - --- name: GetInvite :one -SELECT id, code, created_by, max_uses, use_count, expires_at, revoked, created_at -FROM invites WHERE code = $1; - --- name: UseInviteAtomic :execrows -UPDATE invites SET use_count = use_count + 1 -WHERE code = $1 AND revoked = FALSE - AND (max_uses IS NULL OR use_count < max_uses) - AND (expires_at IS NULL OR expires_at > NOW()); - --- name: RevokeInvite :exec -UPDATE invites SET revoked = TRUE WHERE code = $1; - --- name: ListInvites :many -SELECT id, code, created_by, max_uses, use_count, expires_at, revoked, created_at -FROM invites ORDER BY created_at DESC LIMIT 200; diff --git a/Server/db/queries/postgres/lockouts.sql b/Server/db/queries/postgres/lockouts.sql deleted file mode 100644 index 3e607926..00000000 --- a/Server/db/queries/postgres/lockouts.sql +++ /dev/null @@ -1,15 +0,0 @@ --- PostgreSQL variants of the sqlite rate-lockout queries. --- `INSERT OR REPLACE` becomes `INSERT ... ON CONFLICT (key) DO UPDATE`. - --- name: UpsertLockout :exec -INSERT INTO rate_lockouts (key, expires_at) VALUES ($1, $2) -ON CONFLICT (key) DO UPDATE SET expires_at = EXCLUDED.expires_at; - --- name: LoadActiveLockouts :many -SELECT key, expires_at FROM rate_lockouts WHERE expires_at > $1; - --- name: CleanupExpiredLockouts :exec -DELETE FROM rate_lockouts WHERE expires_at <= $1; - --- name: DeleteLockout :exec -DELETE FROM rate_lockouts WHERE key = $1; diff --git a/Server/db/queries/postgres/messages.sql b/Server/db/queries/postgres/messages.sql deleted file mode 100644 index 6b73e0ee..00000000 --- a/Server/db/queries/postgres/messages.sql +++ /dev/null @@ -1,79 +0,0 @@ --- PostgreSQL variants of the sqlite messages queries. --- `deleted = 0/1` and `pinned = 0/1` become FALSE/TRUE (columns are BOOLEAN). --- The FTS search queries are NOT included here: on postgres, messages.fts is --- a tsvector column with a GIN index (see migrations/postgres/001_initial_schema.sql) --- and FTS queries are hand-written in the postgres-specific store dispatch, --- mirroring how sqlite's FTS5 queries live in message_queries.go. - --- name: CreateMessage :one -INSERT INTO messages (channel_id, user_id, content, reply_to) -VALUES ($1, $2, $3, $4) -RETURNING id; - --- name: GetMessage :one -SELECT id, channel_id, user_id, content, reply_to, edited_at, deleted, pinned, timestamp -FROM messages WHERE id = $1; - --- name: GetMessagesByChannelBeforeCursor :many -SELECT m.id, m.channel_id, m.user_id, m.content, m.reply_to, - m.edited_at, m.deleted, m.pinned, m.timestamp, - u.username, u.avatar -FROM messages m JOIN users u ON m.user_id = u.id -WHERE m.channel_id = $1 AND m.id < $2 AND m.deleted = FALSE -ORDER BY m.id DESC LIMIT $3; - --- name: GetMessagesByChannel :many -SELECT m.id, m.channel_id, m.user_id, m.content, m.reply_to, - m.edited_at, m.deleted, m.pinned, m.timestamp, - u.username, u.avatar -FROM messages m JOIN users u ON m.user_id = u.id -WHERE m.channel_id = $1 AND m.deleted = FALSE -ORDER BY m.id DESC LIMIT $2; - --- name: GetMessagesForAPIBeforeCursor :many -SELECT m.id, m.channel_id, m.user_id, u.username, u.avatar, - m.content, m.reply_to, m.edited_at, m.deleted, m.pinned, m.timestamp -FROM messages m JOIN users u ON m.user_id = u.id -WHERE m.channel_id = $1 AND m.id < $2 AND m.deleted = FALSE -ORDER BY m.id DESC LIMIT $3; - --- name: GetMessagesForAPI :many -SELECT m.id, m.channel_id, m.user_id, u.username, u.avatar, - m.content, m.reply_to, m.edited_at, m.deleted, m.pinned, m.timestamp -FROM messages m JOIN users u ON m.user_id = u.id -WHERE m.channel_id = $1 AND m.deleted = FALSE -ORDER BY m.id DESC LIMIT $2; - --- name: GetPinnedMessageRows :many -SELECT m.id, m.channel_id, m.user_id, u.username, u.avatar, - m.content, m.reply_to, m.edited_at, m.deleted, m.pinned, m.timestamp -FROM messages m JOIN users u ON m.user_id = u.id -WHERE m.channel_id = $1 AND m.pinned = TRUE AND m.deleted = FALSE -ORDER BY m.id DESC; - --- name: EditMessageContent :exec -UPDATE messages SET content = $1, edited_at = NOW() WHERE id = $2; - --- name: SoftDeleteMessage :exec -UPDATE messages SET deleted = TRUE WHERE id = $1; - --- name: SetMessagePinned :execrows -UPDATE messages SET pinned = $1 WHERE id = $2 AND deleted = FALSE; - --- name: GetLatestMessageID :one -SELECT COALESCE(MAX(id), 0)::BIGINT FROM messages WHERE channel_id = $1 AND deleted = FALSE; - --- name: UpdateReadState :exec -INSERT INTO read_states (user_id, channel_id, last_message_id) -VALUES ($1, $2, $3) -ON CONFLICT (user_id, channel_id) DO UPDATE SET last_message_id = EXCLUDED.last_message_id; - --- name: GetChannelUnreadCounts :many -SELECT c.id, - COALESCE(MAX(m.id), 0)::BIGINT AS last_msg_id, - COUNT(CASE WHEN m.id > COALESCE(rs.last_message_id, 0) AND m.deleted = FALSE THEN 1 END) AS unread -FROM channels c -LEFT JOIN messages m ON m.channel_id = c.id AND m.deleted = FALSE -LEFT JOIN read_states rs ON rs.channel_id = c.id AND rs.user_id = $1 -WHERE c.type = 'text' -GROUP BY c.id; diff --git a/Server/db/queries/postgres/plugins.sql b/Server/db/queries/postgres/plugins.sql deleted file mode 100644 index d0349d86..00000000 --- a/Server/db/queries/postgres/plugins.sql +++ /dev/null @@ -1,36 +0,0 @@ --- name: InstallPlugin :one -INSERT INTO plugins (name, version, manifest_json) -VALUES ($1, $2, $3) -ON CONFLICT (name) DO UPDATE - SET version = excluded.version, - manifest_json = excluded.manifest_json -RETURNING id; - --- name: EnablePlugin :exec -UPDATE plugins SET enabled = TRUE WHERE id = $1; - --- name: DisablePlugin :exec -UPDATE plugins SET enabled = FALSE WHERE id = $1; - --- name: UninstallPlugin :exec -DELETE FROM plugins WHERE id = $1; - --- name: GetPlugin :one -SELECT id, name, version, enabled, manifest_json, installed_at FROM plugins WHERE id = $1; - --- name: GetPluginByName :one -SELECT id, name, version, enabled, manifest_json, installed_at FROM plugins WHERE name = $1; - --- name: ListPlugins :many -SELECT id, name, version, enabled, manifest_json, installed_at FROM plugins ORDER BY name; - --- name: PluginKVGet :one -SELECT value FROM plugin_kv WHERE plugin_id = $1 AND key = $2; - --- name: PluginKVSet :exec -INSERT INTO plugin_kv (plugin_id, key, value) -VALUES ($1, $2, $3) -ON CONFLICT (plugin_id, key) DO UPDATE SET value = excluded.value; - --- name: PluginKVDelete :exec -DELETE FROM plugin_kv WHERE plugin_id = $1 AND key = $2; diff --git a/Server/db/queries/postgres/profile.sql b/Server/db/queries/postgres/profile.sql deleted file mode 100644 index d95a03a4..00000000 --- a/Server/db/queries/postgres/profile.sql +++ /dev/null @@ -1,9 +0,0 @@ --- PostgreSQL variants of the sqlite profile queries. --- UpdateUserProfile uses :execrows because postgres has no LastInsertId; --- the caller checks rows-affected for existence. - --- name: UpdateUserProfile :execrows -UPDATE users SET username = $1, avatar = $2 WHERE id = $3; - --- name: UpdateUserPassword :exec -UPDATE users SET password = $1 WHERE id = $2; diff --git a/Server/db/queries/postgres/reactions.sql b/Server/db/queries/postgres/reactions.sql deleted file mode 100644 index 4980c16f..00000000 --- a/Server/db/queries/postgres/reactions.sql +++ /dev/null @@ -1,12 +0,0 @@ --- PostgreSQL variants of the sqlite reactions queries. - --- name: AddReaction :exec -INSERT INTO reactions (message_id, user_id, emoji) VALUES ($1, $2, $3); - --- name: RemoveReaction :execrows -DELETE FROM reactions WHERE message_id = $1 AND user_id = $2 AND emoji = $3; - --- name: GetReactionCounts :many -SELECT emoji, COUNT(*) AS count -FROM reactions WHERE message_id = $1 -GROUP BY emoji; diff --git a/Server/db/queries/postgres/roles.sql b/Server/db/queries/postgres/roles.sql deleted file mode 100644 index a52474d9..00000000 --- a/Server/db/queries/postgres/roles.sql +++ /dev/null @@ -1,29 +0,0 @@ --- PostgreSQL variants of the sqlite roles queries. --- `is_default = 1` becomes `is_default = TRUE` since the column is BOOLEAN. - --- name: GetRoleByID :one -SELECT id, name, color, permissions, position, is_default -FROM roles WHERE id = $1; - --- name: ListRoles :many -SELECT id, name, color, permissions, position, is_default -FROM roles ORDER BY position DESC; - --- name: GetRoleForUser :one -SELECT r.id, r.name, r.color, r.permissions, r.position, r.is_default -FROM users u -JOIN roles r ON u.role_id = r.id -WHERE u.id = $1; - --- name: GetUserWithRole :one -SELECT u.id, u.username, u.password, u.avatar, u.role_id, - u.totp_secret, u.status, u.created_at, u.last_seen, - u.banned, u.ban_reason, u.ban_expires, - r.id, r.name, r.color, r.permissions, r.position, r.is_default -FROM users u -JOIN roles r ON r.id = u.role_id -WHERE u.id = $1; - --- name: GetDefaultRole :one -SELECT id, name, color, permissions, position, is_default -FROM roles WHERE is_default = TRUE LIMIT 1; diff --git a/Server/db/queries/postgres/sessions.sql b/Server/db/queries/postgres/sessions.sql deleted file mode 100644 index 33845cd7..00000000 --- a/Server/db/queries/postgres/sessions.sql +++ /dev/null @@ -1,49 +0,0 @@ --- PostgreSQL variants of the sqlite sessions queries. - --- name: InsertSession :one -INSERT INTO sessions (user_id, token, device, ip_address, expires_at) -VALUES ($1, $2, $3, $4, $5) -RETURNING id; - --- Delete all but the N most recent sessions for a user. Postgres replaces --- sqlite's `LIMIT -1 OFFSET ?` with `OFFSET $2`. --- name: EvictOldestSessions :exec -DELETE FROM sessions WHERE id IN ( - SELECT s2.id FROM sessions AS s2 WHERE s2.user_id = $1 - ORDER BY s2.created_at DESC - OFFSET $2 -); - --- name: GetSessionByTokenHash :one -SELECT id, user_id, token, device, ip_address, created_at, last_used, expires_at -FROM sessions WHERE token = $1; - --- name: GetSessionWithBanStatus :one -SELECT s.id, s.user_id, s.token, s.device, s.ip_address, - s.created_at, s.last_used, s.expires_at, - u.banned, u.ban_reason, u.ban_expires -FROM sessions s -JOIN users u ON s.user_id = u.id -WHERE s.token = $1; - --- name: DeleteSessionByToken :exec -DELETE FROM sessions WHERE token = $1; - --- name: DeleteSessionByID :exec -DELETE FROM sessions WHERE id = $1 AND user_id = $2; - --- name: DeleteOtherSessions :execrows -DELETE FROM sessions WHERE user_id = $1 AND id != $2; - --- Use native timestamp comparison instead of sqlite's strftime trick. --- name: DeleteExpiredSessions :exec -DELETE FROM sessions WHERE expires_at < NOW(); - --- name: TouchSession :exec -UPDATE sessions SET last_used = NOW() WHERE token = $1; - --- name: ListUserSessions :many -SELECT id, user_id, token, device, ip_address, created_at, last_used, expires_at -FROM sessions -WHERE user_id = $1 -ORDER BY created_at DESC; diff --git a/Server/db/queries/postgres/users.sql b/Server/db/queries/postgres/users.sql deleted file mode 100644 index 6a244152..00000000 --- a/Server/db/queries/postgres/users.sql +++ /dev/null @@ -1,51 +0,0 @@ --- PostgreSQL variants of the sqlite users queries. --- Differences from sqlite: --- - `?` -> `$1`, `$2`, … --- - `COLLATE NOCASE` -> removed; the `username` column is CITEXT. --- - `datetime('now')` -> `NOW()` --- - `banned = 0/1` -> `banned = FALSE/TRUE` (column is BOOLEAN) --- - `:execresult INSERT` -> `:one ... RETURNING id` (pgx has no LastInsertId) - --- name: GetUserByUsername :one -SELECT id, username, password, avatar, role_id, totp_secret, status, - created_at, last_seen, banned, ban_reason, ban_expires -FROM users WHERE username = $1; - --- name: GetUserByID :one -SELECT id, username, password, avatar, role_id, totp_secret, status, - created_at, last_seen, banned, ban_reason, ban_expires -FROM users WHERE id = $1; - --- name: CreateUser :one -INSERT INTO users (username, password, role_id) -VALUES ($1, $2, $3) -RETURNING id; - --- name: UpdateUserStatus :exec -UPDATE users SET status = $1, last_seen = NOW() WHERE id = $2; - --- name: UpdateUserTOTPSecret :exec -UPDATE users SET totp_secret = $1 WHERE id = $2; - --- name: ResetAllUserStatuses :exec -UPDATE users SET status = 'offline' WHERE status != 'offline'; - --- name: BanUser :exec -UPDATE users SET banned = TRUE, ban_reason = $1, ban_expires = $2 WHERE id = $3; - --- name: UnbanUser :exec -UPDATE users SET banned = FALSE, ban_reason = NULL, ban_expires = NULL WHERE id = $1; - --- name: ListMembers :many -SELECT u.id, u.username, u.avatar, u.status, LOWER(r.name) -FROM users u -JOIN roles r ON u.role_id = r.id -WHERE u.banned = FALSE -ORDER BY u.username ASC -LIMIT 1000; - --- name: CountUsers :one -SELECT COUNT(*) FROM users; - --- name: CountUsersWithoutTOTP :one -SELECT COUNT(*) FROM users WHERE banned = FALSE AND totp_secret IS NULL; diff --git a/Server/db/queries/postgres/voice.sql b/Server/db/queries/postgres/voice.sql deleted file mode 100644 index c9db052a..00000000 --- a/Server/db/queries/postgres/voice.sql +++ /dev/null @@ -1,88 +0,0 @@ --- PostgreSQL variants of the sqlite voice queries. --- voice_states boolean columns (muted, deafened, speaking, camera, --- screenshare) use FALSE/TRUE instead of 0/1. - --- name: JoinVoiceChannel :exec -INSERT INTO voice_states (user_id, channel_id, muted, deafened, speaking, camera, screenshare, joined_at) -VALUES ($1, $2, FALSE, FALSE, FALSE, FALSE, FALSE, $3) -ON CONFLICT (user_id) DO UPDATE SET - channel_id = EXCLUDED.channel_id, - muted = FALSE, - deafened = FALSE, - speaking = FALSE, - camera = FALSE, - screenshare = FALSE, - joined_at = EXCLUDED.joined_at; - --- name: JoinVoiceChannelIfCapacity :execrows -INSERT INTO voice_states (user_id, channel_id, muted, deafened, speaking, camera, screenshare, joined_at) -SELECT $1, $2, FALSE, FALSE, FALSE, FALSE, FALSE, $3 -WHERE (SELECT COUNT(*) FROM voice_states AS vs2 WHERE vs2.channel_id = $4) < $5 -ON CONFLICT (user_id) DO UPDATE SET - channel_id = EXCLUDED.channel_id, - muted = FALSE, - deafened = FALSE, - speaking = FALSE, - camera = FALSE, - screenshare = FALSE, - joined_at = EXCLUDED.joined_at; - --- name: LeaveVoiceChannel :exec -DELETE FROM voice_states WHERE user_id = $1; - --- name: LeaveVoiceChannelIfMatch :execrows -DELETE FROM voice_states WHERE user_id = $1 AND channel_id = $2 AND joined_at = $3; - --- name: GetUserVoiceState :one -SELECT vs.user_id, vs.channel_id, u.username, - vs.muted, vs.deafened, vs.speaking, - vs.camera, vs.screenshare, vs.joined_at -FROM voice_states vs -JOIN users u ON u.id = vs.user_id -WHERE vs.user_id = $1; - --- name: GetChannelVoiceStates :many -SELECT vs.user_id, vs.channel_id, u.username, - vs.muted, vs.deafened, vs.speaking, - vs.camera, vs.screenshare, vs.joined_at -FROM voice_states vs -JOIN users u ON u.id = vs.user_id -WHERE vs.channel_id = $1 -ORDER BY vs.joined_at ASC; - --- name: GetAllVoiceStates :many -SELECT vs.user_id, vs.channel_id, u.username, - vs.muted, vs.deafened, vs.speaking, - vs.camera, vs.screenshare, vs.joined_at -FROM voice_states vs -JOIN users u ON u.id = vs.user_id -ORDER BY vs.channel_id, vs.joined_at ASC; - --- name: UpdateVoiceMute :exec -UPDATE voice_states SET muted = $1 WHERE user_id = $2; - --- name: UpdateVoiceDeafen :exec -UPDATE voice_states SET deafened = $1 WHERE user_id = $2; - --- name: UpdateVoiceSpeaking :exec -UPDATE voice_states SET speaking = $1 WHERE user_id = $2; - --- name: UpdateVoiceCamera :exec -UPDATE voice_states SET camera = $1 WHERE user_id = $2; - --- name: UpdateVoiceScreenshare :exec -UPDATE voice_states SET screenshare = $1 WHERE user_id = $2; - --- name: EnableCameraIfUnderLimit :execrows -UPDATE voice_states SET camera = TRUE -WHERE voice_states.user_id = $1 AND voice_states.channel_id = $2 - AND (SELECT COUNT(*) FROM voice_states AS vs2 WHERE vs2.channel_id = $3 AND vs2.camera = TRUE) < $4; - --- name: ClearVoiceState :exec -DELETE FROM voice_states WHERE user_id = $1; - --- name: ClearAllVoiceStates :exec -DELETE FROM voice_states; - --- name: CountActiveCameras :one -SELECT COUNT(*) FROM voice_states WHERE channel_id = $1 AND camera = TRUE; diff --git a/Server/go.mod b/Server/go.mod index 237e64af..48165a87 100644 --- a/Server/go.mod +++ b/Server/go.mod @@ -8,7 +8,6 @@ require ( github.com/corazawaf/coraza/v3 v3.6.0 github.com/go-chi/chi/v5 v5.2.5 github.com/google/uuid v1.6.0 - github.com/jackc/pgx/v5 v5.9.1 github.com/knadh/koanf/parsers/yaml v1.1.0 github.com/knadh/koanf/providers/env v1.1.0 github.com/knadh/koanf/providers/file v1.2.1 @@ -71,9 +70,6 @@ require ( github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-retryablehttp v0.7.7 // indirect - github.com/jackc/pgpassfile v1.0.0 // indirect - github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect - github.com/jackc/puddle/v2 v2.2.2 // indirect github.com/jxskiss/base62 v1.1.0 // indirect github.com/kaptinlin/go-i18n v0.1.4 // indirect github.com/kaptinlin/jsonschema v0.4.6 // indirect diff --git a/Server/go.sum b/Server/go.sum index 4bd3b46a..f10fffaa 100644 --- a/Server/go.sum +++ b/Server/go.sum @@ -69,6 +69,8 @@ github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4 github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= +github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= @@ -121,18 +123,12 @@ github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 h1:HWRh5R2+9EifMyIHV7ZV+MIZqgz github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0/go.mod h1:JfhWUomR1baixubs02l85lZYYOm7LV6om4ceouMv45c= github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= +github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k= +github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= github.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU= github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk= github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= -github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= -github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= -github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= -github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= -github.com/jackc/pgx/v5 v5.9.1 h1:uwrxJXBnx76nyISkhr33kQLlUqjv7et7b9FjCen/tdc= -github.com/jackc/pgx/v5 v5.9.1/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4= -github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= -github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/jcchavezs/mergefs v0.1.0 h1:7oteO7Ocl/fnfFMkoVLJxTveCjrsd//UB0j89xmnpec= github.com/jcchavezs/mergefs v0.1.0/go.mod h1:eRLTrsA+vFwQZ48hj8p8gki/5v9C2bFtHH5Mnn4bcGk= github.com/jxskiss/base62 v1.1.0 h1:A5zbF8v8WXx2xixnAKD2w+abC+sIzYJX+nxmhA6HWFw= @@ -177,6 +173,8 @@ github.com/livekit/server-sdk-go/v2 v2.16.0 h1:xbr6PLprgasruzEk4Qv2sHVcK6r+cebUv github.com/livekit/server-sdk-go/v2 v2.16.0/go.mod h1:+HCKTpzV21b/jvBtu+OmWbquUxaL74kHLI9ZwKmdhKU= github.com/magefile/mage v1.15.1-0.20250615140142-78acbaf2e3ae h1:yyMUG1VUd6IjV5jonMKpLXgwm9AzkfRsYisdCXc5OVI= github.com/magefile/mage v1.15.1-0.20250615140142-78acbaf2e3ae/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk= @@ -287,7 +285,6 @@ github.com/shoenig/test v1.7.0/go.mod h1:UxJ6u/x2v/TNs/LoLxBNJRV9DiwBBKYxXSyczsB github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= diff --git a/Server/main.go b/Server/main.go index d58b62c3..e983d5c3 100644 --- a/Server/main.go +++ b/Server/main.go @@ -95,30 +95,11 @@ func run(log *slog.Logger, logBuf *admin.RingBuffer) error { printBanner(cfg, version, tlsCfg != nil) // ── 4. Open database + run migrations ───────────────────────────────── - // The Phase A plan calls for two backends (sqlite, postgres) selected via - // config. SQLite is the only backend currently wired through the *db.DB - // type. The PostgreSQL scaffolding is in place (schema under - // Server/migrations/postgres, sqlc query files under - // Server/db/queries/postgres, PostgresStore behind the `postgres` build - // tag in Server/store/postgres.go), but PostgresStore's query methods - // are still stubs and the handler boundary still passes *db.DB directly - // rather than store.Store. Until both of those land, selecting - // type: "postgres" refuses to start with a clear pointer at what's left. - switch dbType := cfg.Database.Type; dbType { - case "", "sqlite": - // fall through to the existing SQLite path - case "postgres": - return fmt.Errorf("database.type=postgres is configured, but the postgres " + - "backend is not yet wired into the runtime. PostgresStore exists at " + - "Server/store/postgres.go behind the `postgres` build tag, with connection " + - "lifecycle fully implemented but query methods stubbed. What's still " + - "pending: (1) run `make sqlc-generate` to produce Server/db/pgdbgen/, " + - "(2) replace the stub query methods in postgres.go with wrappers around " + - "pgdbgen, and (3) refactor api/router.go and this main.go to thread " + - "store.Store through the handler boundary instead of *db.DB. Until those " + - "land, set database.type to \"sqlite\" or omit it to start the server") - default: - return fmt.Errorf("database.type=%q is not recognised; expected \"sqlite\" or \"postgres\"", dbType) + // SQLite is the only supported backend; the unfinished Postgres + // scaffolding (stubbed query layer, never wired into the runtime) was + // removed rather than completed. + if t := cfg.Database.Type; t != "" && t != "sqlite" { + return fmt.Errorf("database.type=%q is not supported; set \"sqlite\" or omit it", t) } database, err := db.Open(cfg.Database.Path) diff --git a/Server/migrations/postgres/001_initial_schema.sql b/Server/migrations/postgres/001_initial_schema.sql deleted file mode 100644 index 07fc8ab5..00000000 --- a/Server/migrations/postgres/001_initial_schema.sql +++ /dev/null @@ -1,336 +0,0 @@ --- Migration 001 (PostgreSQL): Initial schema --- --- This is a consolidated PostgreSQL translation of the SQLite migrations --- 001-013 found in Server/migrations/. PostgreSQL is a fresh-start backend, --- so we collapse the SQLite migration history into a single canonical schema --- file. Future PostgreSQL schema changes should land as 002_*.sql, 003_*.sql, --- etc., mirroring the SQLite numbering convention. --- --- DIFFERENCES FROM SQLITE: --- - INTEGER PRIMARY KEY AUTOINCREMENT -> BIGSERIAL PRIMARY KEY --- - TEXT NOT NULL DEFAULT (datetime('now')) -> TIMESTAMPTZ NOT NULL DEFAULT NOW() --- - INTEGER (used as bool) -> BOOLEAN NOT NULL DEFAULT FALSE --- - INTEGER permission bitfield -> BIGINT NOT NULL DEFAULT 0 --- - FTS5 virtual table -> tsvector column on messages + GIN index + --- trigger to keep tsvector in sync (see "Full-text search" section). --- - SQLite triggers using RAISE(ABORT) -> native CHECK constraints. --- - COLLATE NOCASE -> CITEXT extension on the username column. --- --- The store.MessageStore.SearchMessages implementation must dispatch on --- backend type because the query syntax differs (MATCH vs @@). - -CREATE EXTENSION IF NOT EXISTS citext; - --- ── roles ─────────────────────────────────────────────────────────────────── -CREATE TABLE IF NOT EXISTS roles ( - id BIGSERIAL PRIMARY KEY, - name TEXT NOT NULL UNIQUE, - color TEXT, - permissions BIGINT NOT NULL DEFAULT 0, - position INTEGER NOT NULL DEFAULT 0, - is_default BOOLEAN NOT NULL DEFAULT FALSE -); - --- Default roles. Permission bitfields match the SQLite seed values. --- Member final value (7779) reflects SQLite migrations 005 and 007 combined. -INSERT INTO roles (id, name, color, permissions, position, is_default) VALUES - (1, 'Owner', '#E74C3C', 2147483647, 100, FALSE), - (2, 'Admin', '#F39C12', 1073741823, 80, FALSE), - (3, 'Moderator', '#3498DB', 1048575, 60, FALSE), - (4, 'Member', NULL, 7779, 40, TRUE) -ON CONFLICT (id) DO NOTHING; - --- Reset the sequence past the seeded rows so user-created roles get IDs >= 5. -SELECT setval('roles_id_seq', GREATEST((SELECT MAX(id) FROM roles), 1)); - --- ── users ─────────────────────────────────────────────────────────────────── -CREATE TABLE IF NOT EXISTS users ( - id BIGSERIAL PRIMARY KEY, - username CITEXT NOT NULL UNIQUE, - password TEXT NOT NULL, - avatar TEXT, - role_id BIGINT NOT NULL DEFAULT 4 REFERENCES roles(id), - totp_secret TEXT, - status TEXT NOT NULL DEFAULT 'offline', - created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - last_seen TIMESTAMPTZ, - banned BOOLEAN NOT NULL DEFAULT FALSE, - ban_reason TEXT, - ban_expires TIMESTAMPTZ -); - --- ── sessions ──────────────────────────────────────────────────────────────── -CREATE TABLE IF NOT EXISTS sessions ( - id BIGSERIAL PRIMARY KEY, - user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE, - token TEXT NOT NULL UNIQUE, - device TEXT, - ip_address TEXT, - created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - last_used TIMESTAMPTZ NOT NULL DEFAULT NOW(), - expires_at TIMESTAMPTZ NOT NULL -); - -CREATE INDEX IF NOT EXISTS idx_sessions_token ON sessions(token); -CREATE INDEX IF NOT EXISTS idx_sessions_user ON sessions(user_id); - --- ── channels ──────────────────────────────────────────────────────────────── --- Includes columns from migrations 001 + 004 (voice columns). --- The CHECK constraint replaces SQLite migration 013's trigger. -CREATE TABLE IF NOT EXISTS channels ( - id BIGSERIAL PRIMARY KEY, - name TEXT NOT NULL, - type TEXT NOT NULL DEFAULT 'text' - CHECK (type IN ('text', 'voice', 'dm')), - category TEXT, - topic TEXT, - position INTEGER NOT NULL DEFAULT 0, - slow_mode INTEGER NOT NULL DEFAULT 0, - archived BOOLEAN NOT NULL DEFAULT FALSE, - created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - voice_max_users INTEGER NOT NULL DEFAULT 0, - voice_quality TEXT, - mixing_threshold INTEGER, - voice_max_video INTEGER NOT NULL DEFAULT 25 -); - --- ── channel_overrides ─────────────────────────────────────────────────────── -CREATE TABLE IF NOT EXISTS channel_overrides ( - id BIGSERIAL PRIMARY KEY, - channel_id BIGINT NOT NULL REFERENCES channels(id) ON DELETE CASCADE, - role_id BIGINT NOT NULL REFERENCES roles(id) ON DELETE CASCADE, - allow BIGINT NOT NULL DEFAULT 0, - deny BIGINT NOT NULL DEFAULT 0, - UNIQUE (channel_id, role_id) -); - -CREATE INDEX IF NOT EXISTS idx_channel_overrides_channel_role - ON channel_overrides(channel_id, role_id); - --- ── messages + full-text search ───────────────────────────────────────────── --- PostgreSQL uses a tsvector column with a GIN index instead of the SQLite --- FTS5 virtual table. The fts column is maintained automatically by a --- trigger so application code doesn't need to set it. -CREATE TABLE IF NOT EXISTS messages ( - id BIGSERIAL PRIMARY KEY, - channel_id BIGINT NOT NULL REFERENCES channels(id) ON DELETE CASCADE, - user_id BIGINT NOT NULL REFERENCES users(id), - content TEXT NOT NULL, - reply_to BIGINT REFERENCES messages(id) ON DELETE SET NULL, - edited_at TIMESTAMPTZ, - deleted BOOLEAN NOT NULL DEFAULT FALSE, - pinned BOOLEAN NOT NULL DEFAULT FALSE, - timestamp TIMESTAMPTZ NOT NULL DEFAULT NOW(), - fts tsvector -); - -CREATE INDEX IF NOT EXISTS idx_messages_channel ON messages(channel_id, id DESC); -CREATE INDEX IF NOT EXISTS idx_messages_user ON messages(user_id); -CREATE INDEX IF NOT EXISTS idx_messages_fts ON messages USING GIN (fts); - -CREATE OR REPLACE FUNCTION messages_fts_update() RETURNS trigger AS $$ -BEGIN - NEW.fts := to_tsvector('simple', COALESCE(NEW.content, '')); - RETURN NEW; -END; -$$ LANGUAGE plpgsql; - -DROP TRIGGER IF EXISTS trg_messages_fts_update ON messages; -CREATE TRIGGER trg_messages_fts_update - BEFORE INSERT OR UPDATE OF content ON messages - FOR EACH ROW - EXECUTE FUNCTION messages_fts_update(); - --- ── attachments ───────────────────────────────────────────────────────────── --- Combines migrations 001 + 008 (width, height) + 010 (uploader_id). -CREATE TABLE IF NOT EXISTS attachments ( - id TEXT PRIMARY KEY, - message_id BIGINT REFERENCES messages(id) ON DELETE CASCADE, - filename TEXT NOT NULL, - stored_as TEXT NOT NULL, - mime_type TEXT NOT NULL, - size BIGINT NOT NULL, - uploaded_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - width INTEGER, - height INTEGER, - uploader_id BIGINT REFERENCES users(id) -); - -CREATE INDEX IF NOT EXISTS idx_attachments_uploader ON attachments(uploader_id); - --- ── reactions ─────────────────────────────────────────────────────────────── -CREATE TABLE IF NOT EXISTS reactions ( - id BIGSERIAL PRIMARY KEY, - message_id BIGINT NOT NULL REFERENCES messages(id) ON DELETE CASCADE, - user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE, - emoji TEXT NOT NULL, - UNIQUE (message_id, user_id, emoji) -); - --- ── invites ───────────────────────────────────────────────────────────────── -CREATE TABLE IF NOT EXISTS invites ( - id BIGSERIAL PRIMARY KEY, - code TEXT NOT NULL UNIQUE, - created_by BIGINT NOT NULL REFERENCES users(id), - redeemed_by BIGINT REFERENCES users(id), - max_uses INTEGER, - use_count INTEGER NOT NULL DEFAULT 0, - expires_at TIMESTAMPTZ, - created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - revoked BOOLEAN NOT NULL DEFAULT FALSE -); - -CREATE INDEX IF NOT EXISTS idx_invites_code ON invites(code); - --- ── read_states ───────────────────────────────────────────────────────────── -CREATE TABLE IF NOT EXISTS read_states ( - user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE, - channel_id BIGINT NOT NULL REFERENCES channels(id) ON DELETE CASCADE, - last_message_id BIGINT NOT NULL DEFAULT 0, - mention_count INTEGER NOT NULL DEFAULT 0, - PRIMARY KEY (user_id, channel_id) -); - --- ── audit_log ─────────────────────────────────────────────────────────────── --- Phase-6 canonical column names (matches SQLite migration 003). -CREATE TABLE IF NOT EXISTS audit_log ( - id BIGSERIAL PRIMARY KEY, - actor_id BIGINT NOT NULL DEFAULT 0, - action TEXT NOT NULL, - target_type TEXT NOT NULL DEFAULT '', - target_id BIGINT NOT NULL DEFAULT 0, - detail TEXT NOT NULL DEFAULT '', - created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() -); - -CREATE INDEX IF NOT EXISTS idx_audit_timestamp ON audit_log(created_at DESC); -CREATE INDEX IF NOT EXISTS idx_audit_log_actor ON audit_log(actor_id); - --- ── login_attempts ────────────────────────────────────────────────────────── -CREATE TABLE IF NOT EXISTS login_attempts ( - id BIGSERIAL PRIMARY KEY, - ip_address TEXT NOT NULL, - username TEXT, - success BOOLEAN NOT NULL DEFAULT FALSE, - timestamp TIMESTAMPTZ NOT NULL DEFAULT NOW() -); - -CREATE INDEX IF NOT EXISTS idx_login_ip ON login_attempts(ip_address, timestamp); - --- ── settings ──────────────────────────────────────────────────────────────── -CREATE TABLE IF NOT EXISTS settings ( - key TEXT PRIMARY KEY, - value TEXT NOT NULL -); - -INSERT INTO settings (key, value) VALUES - ('server_name', 'OwnCord Server'), - ('server_icon', ''), - ('motd', 'Welcome!'), - ('max_upload_bytes', '26214400'), - ('voice_quality', 'high'), - ('require_2fa', '0'), - ('registration_open', '0'), - ('backup_schedule', 'daily'), - ('backup_retention', '7'), - ('schema_version', '1') -ON CONFLICT (key) DO NOTHING; - --- ── emoji ─────────────────────────────────────────────────────────────────── -CREATE TABLE IF NOT EXISTS emoji ( - id BIGSERIAL PRIMARY KEY, - shortcode TEXT NOT NULL UNIQUE, - filename TEXT NOT NULL, - uploaded_by BIGINT NOT NULL REFERENCES users(id), - created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() -); - --- ── sounds ────────────────────────────────────────────────────────────────── -CREATE TABLE IF NOT EXISTS sounds ( - id BIGSERIAL PRIMARY KEY, - name TEXT NOT NULL, - filename TEXT NOT NULL, - duration_ms INTEGER NOT NULL, - uploaded_by BIGINT NOT NULL REFERENCES users(id), - created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() -); - --- ── voice_states ──────────────────────────────────────────────────────────── --- Combines migrations 002 + 004 (camera, screenshare). -CREATE TABLE IF NOT EXISTS voice_states ( - user_id BIGINT PRIMARY KEY REFERENCES users(id) ON DELETE CASCADE, - channel_id BIGINT NOT NULL REFERENCES channels(id) ON DELETE CASCADE, - muted BOOLEAN NOT NULL DEFAULT FALSE, - deafened BOOLEAN NOT NULL DEFAULT FALSE, - speaking BOOLEAN NOT NULL DEFAULT FALSE, - joined_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - camera BOOLEAN NOT NULL DEFAULT FALSE, - screenshare BOOLEAN NOT NULL DEFAULT FALSE -); - -CREATE INDEX IF NOT EXISTS idx_voice_states_channel ON voice_states(channel_id); - --- ── direct messages ───────────────────────────────────────────────────────── -CREATE TABLE IF NOT EXISTS dm_participants ( - channel_id BIGINT NOT NULL REFERENCES channels(id) ON DELETE CASCADE, - user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE, - PRIMARY KEY (channel_id, user_id) -); - -CREATE INDEX IF NOT EXISTS idx_dm_participants_user ON dm_participants(user_id); - -CREATE TABLE IF NOT EXISTS dm_open_state ( - user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE, - channel_id BIGINT NOT NULL REFERENCES channels(id) ON DELETE CASCADE, - opened_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - PRIMARY KEY (user_id, channel_id) -); - --- ── rate_lockouts ─────────────────────────────────────────────────────────── -CREATE TABLE IF NOT EXISTS rate_lockouts ( - key TEXT PRIMARY KEY, - expires_at TIMESTAMPTZ NOT NULL -); - --- ── user_blocks ───────────────────────────────────────────────────────────── -CREATE TABLE IF NOT EXISTS user_blocks ( - blocker_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE, - blocked_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE, - created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - PRIMARY KEY (blocker_id, blocked_id), - CHECK (blocker_id <> blocked_id) -); - -CREATE INDEX IF NOT EXISTS idx_user_blocks_blocked ON user_blocks(blocked_id, blocker_id); - --- ── events (Phase B Step 7: event persistence) ────────────────────────────── --- Cold-storage replay buffer for WebSocket reconnections that fall outside the --- in-memory ring window. Pruned by a background goroutine after the configured --- retention window (default 24h). -CREATE TABLE IF NOT EXISTS events ( - seq BIGSERIAL PRIMARY KEY, - event_type TEXT NOT NULL, - payload BYTEA NOT NULL, - channel_id BIGINT NOT NULL DEFAULT 0, - created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() -); - -CREATE INDEX IF NOT EXISTS idx_events_channel_seq ON events(channel_id, seq); -CREATE INDEX IF NOT EXISTS idx_events_created_at ON events(created_at); - --- ── plugins (Phase C Step 9: Wazero plugin runtime) ───────────────────────── -CREATE TABLE IF NOT EXISTS plugins ( - id BIGSERIAL PRIMARY KEY, - name TEXT NOT NULL UNIQUE, - version TEXT NOT NULL, - enabled BOOLEAN NOT NULL DEFAULT FALSE, - manifest_json TEXT NOT NULL, - installed_at TIMESTAMPTZ NOT NULL DEFAULT NOW() -); - -CREATE TABLE IF NOT EXISTS plugin_kv ( - plugin_id BIGINT NOT NULL REFERENCES plugins(id) ON DELETE CASCADE, - key TEXT NOT NULL, - value BYTEA NOT NULL, - PRIMARY KEY (plugin_id, key) -); diff --git a/Server/migrations/postgres/migrations.go b/Server/migrations/postgres/migrations.go deleted file mode 100644 index 824b8c73..00000000 --- a/Server/migrations/postgres/migrations.go +++ /dev/null @@ -1,17 +0,0 @@ -// Package postgres holds embedded SQL migration files for the PostgreSQL -// backend of the OwnCord server. PostgreSQL is opt-in via the -// `database.type = "postgres"` setting in owncord.yaml. -// -// PostgreSQL migrations are numbered independently from the SQLite migration -// set in Server/migrations/. The two are NOT interchangeable: PostgreSQL -// uses native types (BIGSERIAL, TIMESTAMPTZ, BOOLEAN, tsvector) and a -// consolidated initial schema rather than the historical SQLite migration -// chain. -package postgres - -import "embed" - -// FS holds all PostgreSQL migration SQL files embedded at compile time. -// -//go:embed *.sql -var FS embed.FS diff --git a/Server/plugin/manifest.go b/Server/plugin/manifest.go index 8e0afd62..8eae6faf 100644 --- a/Server/plugin/manifest.go +++ b/Server/plugin/manifest.go @@ -17,7 +17,7 @@ // go get github.com/tetratelabs/wazero // go build -tags wazero ./... // -// This mirrors the postgres / otel build-tag approach used elsewhere in the +// This mirrors the otel / wazero build-tag approach used elsewhere in the // repo so the default build stays self-contained. package plugin diff --git a/Server/plugin/sandbox_wazero.go b/Server/plugin/sandbox_wazero.go index 2be24278..c2abeb6f 100644 --- a/Server/plugin/sandbox_wazero.go +++ b/Server/plugin/sandbox_wazero.go @@ -1,7 +1,7 @@ //go:build wazero // Phase C Step 9 — Real Wazero-backed plugin runtime. Compiled only with -// `-tags wazero`; matches the postgres / otel build-tag pattern used +// `-tags wazero`; matches the otel build-tag pattern used // elsewhere in the repo so the default sqlite-only build does not pull // wazero into go.mod at runtime. // diff --git a/Server/sqlc.yaml b/Server/sqlc.yaml index 60042c41..4abde8d5 100644 --- a/Server/sqlc.yaml +++ b/Server/sqlc.yaml @@ -16,23 +16,3 @@ sql: emit_empty_slices: true emit_json_tags: true json_tags_case_style: "camel" - - # ── PostgreSQL backend (Phase A Step 3) ─────────────────────────────────── - # Postgres is opt-in via database.type = "postgres" in owncord.yaml. - # The generated querier lives in a separate package (pgdbgen) so sqlite - # and postgres code can coexist without import collisions. - - engine: "postgresql" - queries: "db/queries/postgres" - schema: "migrations/postgres" - gen: - go: - package: "pgdbgen" - out: "db/pgdbgen" - sql_package: "pgx/v5" - emit_interface: true - emit_pointers_for_null_types: true - emit_prepared_queries: false - emit_exact_table_names: false - emit_empty_slices: true - emit_json_tags: true - json_tags_case_style: "camel" diff --git a/Server/store/postgres.go b/Server/store/postgres.go deleted file mode 100644 index d99377cf..00000000 --- a/Server/store/postgres.go +++ /dev/null @@ -1,833 +0,0 @@ -//go:build postgres - -// Package store — PostgreSQL backend. -// -// This file is compiled only when the `postgres` build tag is set. The -// default `go build ./...` produces a sqlite-only binary with zero postgres -// dependencies. To enable postgres support: -// -// go get github.com/jackc/pgx/v5 -// go build -tags postgres ./... -// -// The connection lifecycle methods (Open, Close, SQLDb, WithTx) are fully -// implemented against the pgx stdlib driver. Query methods are stubbed — -// they satisfy the Store interface so the type-assertion check at the -// bottom of this file passes, but each returns ErrPostgresNotImplemented at -// runtime. The stubs will be replaced incrementally as Server/db/pgdbgen/ -// is generated from the query files in Server/db/queries/postgres/ via -// `make sqlc-generate`, and PostgresStore methods migrate to wrap the -// generated querier. -// -// Until the store-everywhere refactor lands in main.go / router.go, this -// type is not yet wired into the runtime — see phase-a-foundation.md's -// "Pending" section. Constructing a PostgresStore in isolation works, but -// main.go will still refuse to start with type: "postgres" until the -// boundary refactor lands. - -package store - -import ( - "context" - "database/sql" - "errors" - "fmt" - "strings" - "time" - - // pgx's stdlib driver exposes pgx as a database/sql driver, letting - // PostgresStore reuse the same *sql.DB patterns as SQLiteStore. Once the - // sqlc-generated pgdbgen package is wired in, this import shifts to the - // native pgxpool API for zero-overhead query execution. - _ "github.com/jackc/pgx/v5/stdlib" - - "github.com/owncord/server/config" - "github.com/owncord/server/db" -) - -// ErrPostgresNotImplemented is returned by every query method that is not -// yet backed by sqlc-generated code. It is a sentinel error so callers and -// tests can detect "postgres path reached but implementation pending". -var ErrPostgresNotImplemented = errors.New("postgres backend: query not yet implemented (awaiting sqlc-generated pgdbgen)") - -// PostgresStore implements store.Store against a PostgreSQL database. -// It wraps a *sql.DB opened with pgx's stdlib driver. The query methods are -// currently stubs; see the package-level comment for the migration path. -type PostgresStore struct { - sqlDB *sql.DB -} - -// NewPostgresStore creates a PostgresStore from an already-open *sql.DB. -// Callers that want a one-step constructor should use OpenPostgres. -func NewPostgresStore(sqlDB *sql.DB) *PostgresStore { - return &PostgresStore{sqlDB: sqlDB} -} - -// OpenPostgres dials a PostgreSQL server using the connection settings in -// cfg and returns a ready-to-use PostgresStore. The caller is responsible -// for calling Close when done. Connection pooling is handled by *sql.DB; -// cfg.MaxConns > 0 caps the pool at that size, otherwise the database/sql -// default is used. -func OpenPostgres(cfg *config.DatabaseConfig) (*PostgresStore, error) { - if cfg == nil { - return nil, errors.New("OpenPostgres: nil config") - } - dsn := fmt.Sprintf( - "host=%s port=%d user=%s password=%s dbname=%s sslmode=%s", - cfg.Host, cfg.Port, cfg.User, cfg.Password, cfg.Name, cfg.SSLMode, - ) - sqlDB, err := sql.Open("pgx", dsn) - if err != nil { - return nil, fmt.Errorf("OpenPostgres: open: %w", err) - } - if cfg.MaxConns > 0 { - sqlDB.SetMaxOpenConns(cfg.MaxConns) - sqlDB.SetMaxIdleConns(cfg.MaxConns) - } - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) - defer cancel() - if err := sqlDB.PingContext(ctx); err != nil { - _ = sqlDB.Close() - return nil, fmt.Errorf("OpenPostgres: ping: %w", err) - } - return &PostgresStore{sqlDB: sqlDB}, nil -} - -// Close releases the underlying database connection pool. -func (s *PostgresStore) Close() error { return s.sqlDB.Close() } - -// SQLDb returns the underlying *sql.DB for callers that need raw access -// (backup, migration runners, ad-hoc queries). -func (s *PostgresStore) SQLDb() *sql.DB { return s.sqlDB } - -// WithTx runs fn inside a transaction. The transaction is committed if fn -// returns nil, otherwise rolled back. Postgres supports full transactional -// semantics (unlike SQLite's single-writer model), so concurrent callers -// are safe. -func (s *PostgresStore) WithTx(ctx context.Context, fn func(Store) error) error { - tx, err := s.sqlDB.BeginTx(ctx, nil) - if err != nil { - return fmt.Errorf("PostgresStore.WithTx: begin: %w", err) - } - if txErr := fn(s); txErr != nil { - _ = tx.Rollback() - return txErr - } - return tx.Commit() -} - -// ── MessageStore (stubs) ──────────────────────────────────────────────────── - -func (s *PostgresStore) CreateMessage(channelID, userID int64, content string, replyTo *int64) (int64, error) { - return 0, ErrPostgresNotImplemented -} - -func (s *PostgresStore) GetMessage(id int64) (*db.Message, error) { - return nil, ErrPostgresNotImplemented -} - -func (s *PostgresStore) GetMessages(channelID, before int64, limit int) ([]db.MessageWithUser, error) { - return nil, ErrPostgresNotImplemented -} - -func (s *PostgresStore) GetMessagesForAPI(channelID, before int64, limit int, requestingUserID int64) ([]db.MessageAPIResponse, error) { - return nil, ErrPostgresNotImplemented -} - -func (s *PostgresStore) EditMessage(id, userID int64, content string) error { - return ErrPostgresNotImplemented -} - -func (s *PostgresStore) DeleteMessage(id, userID int64, isMod bool) error { - return ErrPostgresNotImplemented -} - -func (s *PostgresStore) SearchMessages(query string, channelID *int64, limit int) ([]db.MessageSearchResult, error) { - return nil, ErrPostgresNotImplemented -} - -func (s *PostgresStore) SearchMessagesInChannels(query string, channelIDs []int64, limit int) ([]db.MessageSearchResult, error) { - return nil, ErrPostgresNotImplemented -} - -func (s *PostgresStore) GetPinnedMessages(channelID int64, requestingUserID int64) ([]db.MessageAPIResponse, error) { - return nil, ErrPostgresNotImplemented -} - -func (s *PostgresStore) SetMessagePinned(id int64, pinned bool) error { - return ErrPostgresNotImplemented -} - -func (s *PostgresStore) AddReaction(messageID, userID int64, emoji string) error { - return ErrPostgresNotImplemented -} - -func (s *PostgresStore) RemoveReaction(messageID, userID int64, emoji string) error { - return ErrPostgresNotImplemented -} - -func (s *PostgresStore) GetReactions(messageID int64) ([]db.ReactionCount, error) { - return nil, ErrPostgresNotImplemented -} - -func (s *PostgresStore) UpdateReadState(userID, channelID, lastReadMessageID int64) error { - return ErrPostgresNotImplemented -} - -func (s *PostgresStore) GetChannelUnreadCounts(userID int64) (map[int64]db.ChannelUnread, error) { - return nil, ErrPostgresNotImplemented -} - -func (s *PostgresStore) GetLatestMessageID(channelID int64) (int64, error) { - return 0, ErrPostgresNotImplemented -} - -func (s *PostgresStore) LinkAttachmentsToMessage(messageID int64, attachmentIDs []string) (int64, error) { - return 0, ErrPostgresNotImplemented -} - -func (s *PostgresStore) GetAttachmentsByMessageIDs(msgIDs []int64) (map[int64][]db.AttachmentInfo, error) { - return nil, ErrPostgresNotImplemented -} - -// ── ChannelStore (stubs) ──────────────────────────────────────────────────── - -func (s *PostgresStore) ListChannels() ([]db.Channel, error) { - return nil, ErrPostgresNotImplemented -} - -func (s *PostgresStore) GetChannel(id int64) (*db.Channel, error) { - return nil, ErrPostgresNotImplemented -} - -func (s *PostgresStore) CreateChannel(name, chanType, category, topic string, position int) (int64, error) { - return 0, ErrPostgresNotImplemented -} - -func (s *PostgresStore) UpdateChannel(id int64, name, topic string, slowMode int) error { - return ErrPostgresNotImplemented -} - -func (s *PostgresStore) DeleteChannel(id int64) error { - return ErrPostgresNotImplemented -} - -func (s *PostgresStore) SetChannelSlowMode(id int64, slowMode int) error { - return ErrPostgresNotImplemented -} - -func (s *PostgresStore) SetChannelVoiceMaxUsers(id int64, maxUsers int) error { - return ErrPostgresNotImplemented -} - -func (s *PostgresStore) GetChannelPermissions(channelID, roleID int64) (int64, int64, error) { - return 0, 0, ErrPostgresNotImplemented -} - -func (s *PostgresStore) GetAllChannelPermissionsForRole(roleID int64) (map[int64]db.ChannelOverride, error) { - return nil, ErrPostgresNotImplemented -} - -func (s *PostgresStore) GetChannelTypes(ids []int64) (map[int64]string, error) { - return nil, ErrPostgresNotImplemented -} - -// ── UserStore (stubs) ─────────────────────────────────────────────────────── - -func (s *PostgresStore) GetUserByID(id int64) (*db.User, error) { - return nil, ErrPostgresNotImplemented -} - -func (s *PostgresStore) GetUserByUsername(username string) (*db.User, error) { - return nil, ErrPostgresNotImplemented -} - -func (s *PostgresStore) CreateUser(username, passwordHash string, roleID int) (int64, error) { - return 0, ErrPostgresNotImplemented -} - -func (s *PostgresStore) CreateOwnerIfEmpty(username, passwordHash string, roleID int) (int64, error) { - return 0, ErrPostgresNotImplemented -} - -func (s *PostgresStore) CreateUserWithInvite(username, passwordHash string, roleID int, inviteCode string) (int64, error) { - return 0, ErrPostgresNotImplemented -} - -func (s *PostgresStore) UpdateUserProfile(userID int64, username string, avatar *string) error { - return ErrPostgresNotImplemented -} - -func (s *PostgresStore) UpdateUserPassword(userID int64, newPasswordHash string) error { - return ErrPostgresNotImplemented -} - -func (s *PostgresStore) UpdateUserStatus(id int64, status string) error { - return ErrPostgresNotImplemented -} - -func (s *PostgresStore) UpdateUserTOTPSecret(id int64, secret *string) error { - return ErrPostgresNotImplemented -} - -func (s *PostgresStore) UpdateUserRole(userID, roleID int64) error { - return ErrPostgresNotImplemented -} - -func (s *PostgresStore) ResetAllUserStatuses() error { - return ErrPostgresNotImplemented -} - -func (s *PostgresStore) DeleteAccount(ctx context.Context, userID int64) error { - return ErrPostgresNotImplemented -} - -func (s *PostgresStore) ListMembers() ([]db.MemberSummary, error) { - return nil, ErrPostgresNotImplemented -} - -// ── SessionStore (stubs) ──────────────────────────────────────────────────── - -func (s *PostgresStore) CreateSession(userID int64, tokenHash, device, ip string) (int64, error) { - return 0, ErrPostgresNotImplemented -} - -func (s *PostgresStore) GetSessionByTokenHash(tokenHash string) (*db.Session, error) { - return nil, ErrPostgresNotImplemented -} - -func (s *PostgresStore) GetSessionWithBanStatus(tokenHash string) (*db.SessionWithBanStatus, error) { - return nil, ErrPostgresNotImplemented -} - -func (s *PostgresStore) DeleteSession(tokenHash string) error { - return ErrPostgresNotImplemented -} - -func (s *PostgresStore) DeleteOtherSessions(userID, keepSessionID int64) (int64, error) { - return 0, ErrPostgresNotImplemented -} - -func (s *PostgresStore) DeleteExpiredSessions() error { - return ErrPostgresNotImplemented -} - -func (s *PostgresStore) DeleteSessionByID(sessionID, userID int64) error { - return ErrPostgresNotImplemented -} - -func (s *PostgresStore) TouchSession(tokenHash string) error { - return ErrPostgresNotImplemented -} - -func (s *PostgresStore) ListUserSessions(userID int64) ([]db.Session, error) { - return nil, ErrPostgresNotImplemented -} - -func (s *PostgresStore) ForceLogoutUser(userID int64) error { - return ErrPostgresNotImplemented -} - -func (s *PostgresStore) GetUserSessions(userID int64) ([]db.Session, error) { - return nil, ErrPostgresNotImplemented -} - -// ── RoleStore (stubs) ─────────────────────────────────────────────────────── - -func (s *PostgresStore) GetRoleByID(id int64) (*db.Role, error) { - return nil, ErrPostgresNotImplemented -} - -func (s *PostgresStore) GetRoleForUser(userID int64) (*db.Role, error) { - return nil, ErrPostgresNotImplemented -} - -func (s *PostgresStore) GetUserWithRole(userID int64) (*db.User, *db.Role, error) { - return nil, nil, ErrPostgresNotImplemented -} - -func (s *PostgresStore) ListRoles() ([]*db.Role, error) { - return nil, ErrPostgresNotImplemented -} - -// ── InviteStore (stubs) ───────────────────────────────────────────────────── - -func (s *PostgresStore) CreateInvite(createdBy int64, maxUses int, expiresAt *time.Time) (string, error) { - return "", ErrPostgresNotImplemented -} - -func (s *PostgresStore) GetInvite(code string) (*db.Invite, error) { - return nil, ErrPostgresNotImplemented -} - -func (s *PostgresStore) ListInvites() ([]*db.Invite, error) { - return nil, ErrPostgresNotImplemented -} - -func (s *PostgresStore) UseInviteAtomic(code string) error { - return ErrPostgresNotImplemented -} - -func (s *PostgresStore) RevokeInvite(code string) error { - return ErrPostgresNotImplemented -} - -// ── VoiceStore (stubs) ────────────────────────────────────────────────────── - -func (s *PostgresStore) JoinVoiceChannel(userID, channelID int64) error { - return ErrPostgresNotImplemented -} - -func (s *PostgresStore) JoinVoiceChannelIfCapacity(userID, channelID int64, maxUsers int) error { - return ErrPostgresNotImplemented -} - -func (s *PostgresStore) LeaveVoiceChannel(userID int64) error { - return ErrPostgresNotImplemented -} - -func (s *PostgresStore) LeaveVoiceChannelIfMatch(userID, expectedChannelID int64, expectedJoinedAt string) (bool, error) { - return false, ErrPostgresNotImplemented -} - -func (s *PostgresStore) GetVoiceState(userID int64) (*db.VoiceState, error) { - return nil, ErrPostgresNotImplemented -} - -func (s *PostgresStore) GetChannelVoiceStates(channelID int64) ([]db.VoiceState, error) { - return nil, ErrPostgresNotImplemented -} - -func (s *PostgresStore) GetAllVoiceStates() ([]db.VoiceState, error) { - return nil, ErrPostgresNotImplemented -} - -func (s *PostgresStore) UpdateVoiceMute(userID int64, muted bool) error { - return ErrPostgresNotImplemented -} - -func (s *PostgresStore) UpdateVoiceDeafen(userID int64, deafened bool) error { - return ErrPostgresNotImplemented -} - -func (s *PostgresStore) ClearVoiceState(userID int64) error { - return ErrPostgresNotImplemented -} - -func (s *PostgresStore) ClearAllVoiceStates() error { - return ErrPostgresNotImplemented -} - -func (s *PostgresStore) CountActiveCameras(channelID int64) (int, error) { - return 0, ErrPostgresNotImplemented -} - -func (s *PostgresStore) UpdateVoiceCamera(userID int64, camera bool) error { - return ErrPostgresNotImplemented -} - -func (s *PostgresStore) EnableCameraIfUnderLimit(userID, channelID int64, maxVideo int) (bool, error) { - return false, ErrPostgresNotImplemented -} - -func (s *PostgresStore) UpdateVoiceScreenshare(userID int64, screenshare bool) error { - return ErrPostgresNotImplemented -} - -func (s *PostgresStore) CountChannelVoiceUsers(channelID int64) (int, error) { - return 0, ErrPostgresNotImplemented -} - -// ── DMStore (stubs) ───────────────────────────────────────────────────────── - -func (s *PostgresStore) GetOrCreateDMChannel(user1ID, user2ID int64) (*db.Channel, bool, error) { - return nil, false, ErrPostgresNotImplemented -} - -func (s *PostgresStore) GetUserDMChannels(userID int64) ([]db.DMChannelInfo, error) { - return nil, ErrPostgresNotImplemented -} - -func (s *PostgresStore) OpenDM(userID, channelID int64) error { - return ErrPostgresNotImplemented -} - -func (s *PostgresStore) CloseDM(userID, channelID int64) error { - return ErrPostgresNotImplemented -} - -func (s *PostgresStore) IsDMParticipant(userID, channelID int64) (bool, error) { - return false, ErrPostgresNotImplemented -} - -func (s *PostgresStore) GetDMParticipantIDs(channelID int64) ([]int64, error) { - return nil, ErrPostgresNotImplemented -} - -func (s *PostgresStore) GetDMRecipient(channelID, requestingUserID int64) (*db.User, error) { - return nil, ErrPostgresNotImplemented -} - -// ── BlockStore (stubs) ────────────────────────────────────────────────────── - -func (s *PostgresStore) BlockUser(blockerID, blockedID int64) error { - return ErrPostgresNotImplemented -} - -func (s *PostgresStore) UnblockUser(blockerID, blockedID int64) error { - return ErrPostgresNotImplemented -} - -func (s *PostgresStore) IsBlocked(blockerID, blockedID int64) (bool, error) { - return false, ErrPostgresNotImplemented -} - -func (s *PostgresStore) IsEitherBlocked(userA, userB int64) (bool, error) { - return false, ErrPostgresNotImplemented -} - -func (s *PostgresStore) ListBlockedUsers(blockerID int64) ([]int64, error) { - return nil, ErrPostgresNotImplemented -} - -// ── AttachmentStore (stubs) ───────────────────────────────────────────────── - -func (s *PostgresStore) CreateAttachment(id string, uploaderID int64, filename, storedAs, mimeType string, size int64, width, height *int) error { - return ErrPostgresNotImplemented -} - -func (s *PostgresStore) GetAttachmentByID(id string) (*db.Attachment, error) { - return nil, ErrPostgresNotImplemented -} - -func (s *PostgresStore) GetAttachmentWithChannel(id string) (*db.AttachmentAccess, error) { - return nil, ErrPostgresNotImplemented -} - -func (s *PostgresStore) DeleteOrphanedAttachments(cutoff string) ([]string, error) { - return nil, ErrPostgresNotImplemented -} - -// ── AdminStore (stubs) ────────────────────────────────────────────────────── - -func (s *PostgresStore) UserCount() (int64, error) { - return 0, ErrPostgresNotImplemented -} - -func (s *PostgresStore) GetServerStats() (*db.ServerStats, error) { - return nil, ErrPostgresNotImplemented -} - -func (s *PostgresStore) ListAllUsers(limit, offset int) ([]db.UserWithRole, error) { - return nil, ErrPostgresNotImplemented -} - -func (s *PostgresStore) BanUser(id int64, reason string, expires *time.Time) error { - return ErrPostgresNotImplemented -} - -func (s *PostgresStore) UnbanUser(id int64) error { - return ErrPostgresNotImplemented -} - -func (s *PostgresStore) LogAudit(actorID int64, action, targetType string, targetID int64, detail string) error { - return ErrPostgresNotImplemented -} - -func (s *PostgresStore) GetAuditLog(limit, offset int) ([]db.AuditEntry, error) { - return nil, ErrPostgresNotImplemented -} - -func (s *PostgresStore) AdminCreateChannel(name, chanType, category, topic string, position int) (int64, error) { - return 0, ErrPostgresNotImplemented -} - -func (s *PostgresStore) AdminUpdateChannel(id int64, name, topic string, slowMode, position int, archived bool) error { - return ErrPostgresNotImplemented -} - -func (s *PostgresStore) AdminDeleteChannel(id int64) error { - return ErrPostgresNotImplemented -} - -func (s *PostgresStore) BackupTo(path string) error { - return ErrPostgresNotImplemented -} - -func (s *PostgresStore) BackupToSafe(path, safeRoot string) error { - return ErrPostgresNotImplemented -} - -func (s *PostgresStore) CountUsersWithoutTOTP() (int, error) { - return 0, ErrPostgresNotImplemented -} - -// ── SettingsStore (stubs) ─────────────────────────────────────────────────── - -func (s *PostgresStore) GetSetting(key string) (string, error) { - return "", ErrPostgresNotImplemented -} - -func (s *PostgresStore) SetSetting(key, value string) error { - return ErrPostgresNotImplemented -} - -func (s *PostgresStore) GetAllSettings() (map[string]string, error) { - return nil, ErrPostgresNotImplemented -} - -// ── EventStore (Phase B Step 7) ────────────────────────────────────────────── - -func (s *PostgresStore) PersistEvent(ctx context.Context, seq int64, eventType string, channelID int64, payload []byte) error { - _, err := s.sqlDB.ExecContext(ctx, - `INSERT INTO events (seq, event_type, channel_id, payload) VALUES ($1, $2, $3, $4)`, - seq, eventType, channelID, payload, - ) - if err != nil { - return fmt.Errorf("PersistEvent: %w", err) - } - return nil -} - -func (s *PostgresStore) GetEventsSince(ctx context.Context, afterSeq int64, limit int) ([]db.PersistedEvent, error) { - rows, err := s.sqlDB.QueryContext(ctx, - `SELECT seq, event_type, channel_id, payload, created_at - FROM events - WHERE seq > $1 - ORDER BY seq ASC - LIMIT $2`, - afterSeq, limit, - ) - if err != nil { - return nil, fmt.Errorf("GetEventsSince: %w", err) - } - defer rows.Close() - return scanPgEventRows(rows) -} - -func (s *PostgresStore) GetEventsSinceForChannels(ctx context.Context, afterSeq int64, channelIDs []int64, limit int) ([]db.PersistedEvent, error) { - if len(channelIDs) == 0 { - rows, err := s.sqlDB.QueryContext(ctx, - `SELECT seq, event_type, channel_id, payload, created_at - FROM events - WHERE seq > $1 AND channel_id = 0 - ORDER BY seq ASC - LIMIT $2`, - afterSeq, limit, - ) - if err != nil { - return nil, fmt.Errorf("GetEventsSinceForChannels (global only): %w", err) - } - defer rows.Close() - return scanPgEventRows(rows) - } - - placeholders := make([]string, len(channelIDs)) - args := make([]any, 0, len(channelIDs)+2) - args = append(args, afterSeq) - for i, cid := range channelIDs { - placeholders[i] = fmt.Sprintf("$%d", i+2) - args = append(args, cid) - } - args = append(args, limit) - - query := fmt.Sprintf( - `SELECT seq, event_type, channel_id, payload, created_at - FROM events - WHERE seq > $1 - AND (channel_id = 0 OR channel_id IN (%s)) - ORDER BY seq ASC - LIMIT $%d`, - strings.Join(placeholders, ","), - len(channelIDs)+2, - ) - rows, err := s.sqlDB.QueryContext(ctx, query, args...) - if err != nil { - return nil, fmt.Errorf("GetEventsSinceForChannels: %w", err) - } - defer rows.Close() - return scanPgEventRows(rows) -} - -func (s *PostgresStore) PruneEventsOlderThan(ctx context.Context, cutoff time.Time) (int64, error) { - res, err := s.sqlDB.ExecContext(ctx, - `DELETE FROM events WHERE created_at < $1`, - cutoff.UTC(), - ) - if err != nil { - return 0, fmt.Errorf("PruneEventsOlderThan: %w", err) - } - n, err := res.RowsAffected() - if err != nil { - return 0, fmt.Errorf("PruneEventsOlderThan RowsAffected: %w", err) - } - return n, nil -} - -func (s *PostgresStore) GetMaxEventSeq(ctx context.Context) (int64, error) { - var maxSeq int64 - err := s.sqlDB.QueryRowContext(ctx, - `SELECT COALESCE(MAX(seq), 0)::BIGINT FROM events`, - ).Scan(&maxSeq) - if err != nil { - return 0, fmt.Errorf("GetMaxEventSeq: %w", err) - } - return maxSeq, nil -} - -// ── PluginStore (Phase C Step 9) ──────────────────────────────────────────── - -func (s *PostgresStore) InstallPlugin(ctx context.Context, name, version, manifestJSON string) (int64, error) { - var id int64 - err := s.sqlDB.QueryRowContext(ctx, - `INSERT INTO plugins (name, version, manifest_json) - VALUES ($1, $2, $3) - ON CONFLICT (name) DO UPDATE - SET version = excluded.version, - manifest_json = excluded.manifest_json - RETURNING id`, - name, version, manifestJSON, - ).Scan(&id) - if err != nil { - return 0, fmt.Errorf("InstallPlugin: %w", err) - } - return id, nil -} - -func (s *PostgresStore) EnablePlugin(ctx context.Context, id int64) error { - _, err := s.sqlDB.ExecContext(ctx, `UPDATE plugins SET enabled = TRUE WHERE id = $1`, id) - return err -} - -func (s *PostgresStore) DisablePlugin(ctx context.Context, id int64) error { - _, err := s.sqlDB.ExecContext(ctx, `UPDATE plugins SET enabled = FALSE WHERE id = $1`, id) - return err -} - -func (s *PostgresStore) UninstallPlugin(ctx context.Context, id int64) error { - _, err := s.sqlDB.ExecContext(ctx, `DELETE FROM plugins WHERE id = $1`, id) - return err -} - -func (s *PostgresStore) GetPlugin(ctx context.Context, id int64) (*db.PluginRow, error) { - row := s.sqlDB.QueryRowContext(ctx, - `SELECT id, name, version, enabled, manifest_json, installed_at FROM plugins WHERE id = $1`, - id, - ) - return scanPgPluginRow(row) -} - -func (s *PostgresStore) GetPluginByName(ctx context.Context, name string) (*db.PluginRow, error) { - row := s.sqlDB.QueryRowContext(ctx, - `SELECT id, name, version, enabled, manifest_json, installed_at FROM plugins WHERE name = $1`, - name, - ) - return scanPgPluginRow(row) -} - -func (s *PostgresStore) ListPlugins(ctx context.Context) ([]db.PluginRow, error) { - rows, err := s.sqlDB.QueryContext(ctx, - `SELECT id, name, version, enabled, manifest_json, installed_at FROM plugins ORDER BY name`, - ) - if err != nil { - return nil, fmt.Errorf("ListPlugins: %w", err) - } - defer rows.Close() - var out []db.PluginRow - for rows.Next() { - var p db.PluginRow - if err := rows.Scan(&p.ID, &p.Name, &p.Version, &p.Enabled, &p.ManifestJSON, &p.InstalledAt); err != nil { - return nil, fmt.Errorf("ListPlugins scan: %w", err) - } - out = append(out, p) - } - return out, rows.Err() -} - -func (s *PostgresStore) PluginKVGet(ctx context.Context, pluginID int64, key string) ([]byte, error) { - var v []byte - err := s.sqlDB.QueryRowContext(ctx, - `SELECT value FROM plugin_kv WHERE plugin_id = $1 AND key = $2`, - pluginID, key, - ).Scan(&v) - if err != nil { - return nil, err - } - return v, nil -} - -func (s *PostgresStore) PluginKVSet(ctx context.Context, pluginID int64, key string, value []byte) error { - _, err := s.sqlDB.ExecContext(ctx, - `INSERT INTO plugin_kv (plugin_id, key, value) VALUES ($1, $2, $3) - ON CONFLICT (plugin_id, key) DO UPDATE SET value = excluded.value`, - pluginID, key, value, - ) - return err -} - -func (s *PostgresStore) PluginKVDelete(ctx context.Context, pluginID int64, key string) error { - _, err := s.sqlDB.ExecContext(ctx, - `DELETE FROM plugin_kv WHERE plugin_id = $1 AND key = $2`, - pluginID, key, - ) - return err -} - -func (s *PostgresStore) PluginKVScan(ctx context.Context, pluginID int64, prefix string, limit int) (map[string][]byte, error) { - rows, err := s.sqlDB.QueryContext(ctx, - `SELECT key, value FROM plugin_kv WHERE plugin_id = $1 AND key LIKE $2 ORDER BY key LIMIT $3`, - pluginID, prefix+"%", limit, - ) - if err != nil { - return nil, fmt.Errorf("PluginKVScan: %w", err) - } - defer rows.Close() - out := make(map[string][]byte) - for rows.Next() { - var k string - var v []byte - if err := rows.Scan(&k, &v); err != nil { - return nil, err - } - out[k] = v - } - return out, rows.Err() -} - -// ── postgres scan helpers ──────────────────────────────────────────────────── - -type pgRowScanner interface { - Scan(dest ...any) error -} - -func scanPgPluginRow(row pgRowScanner) (*db.PluginRow, error) { - var p db.PluginRow - if err := row.Scan(&p.ID, &p.Name, &p.Version, &p.Enabled, &p.ManifestJSON, &p.InstalledAt); err != nil { - return nil, err - } - return &p, nil -} - -type pgRowsScanner interface { - Next() bool - Scan(dest ...any) error - Err() error -} - -func scanPgEventRows(rows pgRowsScanner) ([]db.PersistedEvent, error) { - var out []db.PersistedEvent - for rows.Next() { - var e db.PersistedEvent - if err := rows.Scan(&e.Seq, &e.EventType, &e.ChannelID, &e.Payload, &e.CreatedAt); err != nil { - return nil, fmt.Errorf("scanPgEventRows: %w", err) - } - out = append(out, e) - } - if err := rows.Err(); err != nil { - return nil, err - } - return out, nil -} - -// Compile-time interface check — fails to compile if any Store method is -// missing a PostgresStore receiver. -var _ Store = (*PostgresStore)(nil) diff --git a/Server/telemetry/telemetry_otel.go b/Server/telemetry/telemetry_otel.go index b4b78822..fd9c3506 100644 --- a/Server/telemetry/telemetry_otel.go +++ b/Server/telemetry/telemetry_otel.go @@ -1,7 +1,7 @@ //go:build otel // Phase B Step 8 — Real OpenTelemetry-backed implementation. Compiled only -// with `-tags otel`, matching the postgres / wazero build-tag pattern used +// with `-tags otel`, matching the wazero build-tag pattern used // elsewhere in the repo. The default build ships telemetry_default.go with a // no-op provider so sqlite-only binaries do not pull the OTel SDK in. //