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 <noreply@anthropic.com>
This commit is contained in:
J3vb
2026-07-19 08:15:58 +02:00
co-authored by Claude Fable 5
parent d0da33f1da
commit 0c093e8403
47 changed files with 22 additions and 5635 deletions
+2 -8
View File
@@ -1,8 +1,7 @@
# OwnCord Server — developer convenience targets # OwnCord Server — developer convenience targets
# #
# sqlc-generate Regenerate type-safe Go for both the sqlite and postgres # sqlc-generate Regenerate type-safe Go from sqlc.yaml (db/dbgen).
# engines defined in sqlc.yaml (db/dbgen + db/pgdbgen). # sqlc-verify Fail if the committed dbgen output is stale (used by CI).
# sqlc-verify Fail if either committed dbgen output is stale (used by CI).
# sqlc-install Install the pinned sqlc version into $GOBIN. # sqlc-install Install the pinned sqlc version into $GOBIN.
# otel-up Start Jaeger + Prometheus for local tracing development. # otel-up Start Jaeger + Prometheus for local tracing development.
# otel-down Stop and remove the OTel dev containers. # otel-down Stop and remove the OTel dev containers.
@@ -17,13 +16,8 @@ sqlc-install:
sqlc-generate: sqlc-generate:
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-verify:
sqlc generate sqlc generate
@git checkout -- db/pgdbgen
@git diff --exit-code db/dbgen || ( \ @git diff --exit-code db/dbgen || ( \
echo "ERROR: generated sqlc output is stale. Run 'make sqlc-generate' and commit the result." ; \ echo "ERROR: generated sqlc output is stale. Run 'make sqlc-generate' and commit the result." ; \
exit 1 ; \ exit 1 ; \
+6 -26
View File
@@ -123,20 +123,12 @@ type ServerConfig struct {
// cause the server to refuse to start with a clear error pointing at the // cause the server to refuse to start with a clear error pointing at the
// follow-up work — see Server/main.go. // follow-up work — see Server/main.go.
type DatabaseConfig struct { 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"` 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"` 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. // TLSConfig holds TLS/certificate settings.
@@ -173,12 +165,8 @@ func defaults() Config {
}, },
}, },
Database: DatabaseConfig{ Database: DatabaseConfig{
Type: "sqlite", Type: "sqlite",
Path: "data/chatserver.db", Path: "data/chatserver.db",
Host: "localhost",
Port: 5432,
Name: "owncord",
SSLMode: "disable",
}, },
TLS: TLSConfig{ TLS: TLSConfig{
Mode: "self_signed", Mode: "self_signed",
@@ -236,16 +224,8 @@ server:
# - "192.168.0.0/16" # - "192.168.0.0/16"
database: database:
type: "sqlite" # "sqlite" (default, zero-config) or "postgres" type: "sqlite" # "sqlite" is the only supported backend
path: "data/chatserver.db" 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: tls:
mode: "self_signed" # self_signed, acme, manual, off mode: "self_signed" # self_signed, acme, manual, off
-307
View File
@@ -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
}
-169
View File
@@ -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
}
-86
View File
@@ -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
}
-381
View File
@@ -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
}
-34
View File
@@ -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,
}
}
-241
View File
@@ -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
}
-109
View File
@@ -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
}
-140
View File
@@ -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
}
-74
View File
@@ -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
}
-479
View File
@@ -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
}
-218
View File
@@ -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"`
}
-175
View File
@@ -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
}
-48
View File
@@ -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
}
-197
View File
@@ -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)
-78
View File
@@ -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
}
-165
View File
@@ -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
}
-221
View File
@@ -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
}
-219
View File
@@ -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
}
-371
View File
@@ -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
}
-55
View File
@@ -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;
@@ -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;
-18
View File
@@ -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;
-69
View File
@@ -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;
-64
View File
@@ -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;
-19
View File
@@ -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;
-23
View File
@@ -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;
-15
View File
@@ -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;
-79
View File
@@ -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;
-36
View File
@@ -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;
-9
View File
@@ -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;
-12
View File
@@ -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;
-29
View File
@@ -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;
-49
View File
@@ -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;
-51
View File
@@ -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;
-88
View File
@@ -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;
-4
View File
@@ -8,7 +8,6 @@ require (
github.com/corazawaf/coraza/v3 v3.6.0 github.com/corazawaf/coraza/v3 v3.6.0
github.com/go-chi/chi/v5 v5.2.5 github.com/go-chi/chi/v5 v5.2.5
github.com/google/uuid v1.6.0 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/parsers/yaml v1.1.0
github.com/knadh/koanf/providers/env v1.1.0 github.com/knadh/koanf/providers/env v1.1.0
github.com/knadh/koanf/providers/file v1.2.1 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/grpc-ecosystem/grpc-gateway/v2 v2.28.0 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-retryablehttp v0.7.7 // 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/jxskiss/base62 v1.1.0 // indirect
github.com/kaptinlin/go-i18n v0.1.4 // indirect github.com/kaptinlin/go-i18n v0.1.4 // indirect
github.com/kaptinlin/jsonschema v0.4.6 // indirect github.com/kaptinlin/jsonschema v0.4.6 // indirect
+6 -9
View File
@@ -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/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 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= 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 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo=
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= 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/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 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= 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 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU=
github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk= 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 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= 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 h1:7oteO7Ocl/fnfFMkoVLJxTveCjrsd//UB0j89xmnpec=
github.com/jcchavezs/mergefs v0.1.0/go.mod h1:eRLTrsA+vFwQZ48hj8p8gki/5v9C2bFtHH5Mnn4bcGk= 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= 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/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 h1:yyMUG1VUd6IjV5jonMKpLXgwm9AzkfRsYisdCXc5OVI=
github.com/magefile/mage v1.15.1-0.20250615140142-78acbaf2e3ae/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A= 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 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 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= 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 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= 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/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.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 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
+5 -24
View File
@@ -95,30 +95,11 @@ func run(log *slog.Logger, logBuf *admin.RingBuffer) error {
printBanner(cfg, version, tlsCfg != nil) printBanner(cfg, version, tlsCfg != nil)
// ── 4. Open database + run migrations ───────────────────────────────── // ── 4. Open database + run migrations ─────────────────────────────────
// The Phase A plan calls for two backends (sqlite, postgres) selected via // SQLite is the only supported backend; the unfinished Postgres
// config. SQLite is the only backend currently wired through the *db.DB // scaffolding (stubbed query layer, never wired into the runtime) was
// type. The PostgreSQL scaffolding is in place (schema under // removed rather than completed.
// Server/migrations/postgres, sqlc query files under if t := cfg.Database.Type; t != "" && t != "sqlite" {
// Server/db/queries/postgres, PostgresStore behind the `postgres` build return fmt.Errorf("database.type=%q is not supported; set \"sqlite\" or omit it", t)
// 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)
} }
database, err := db.Open(cfg.Database.Path) database, err := db.Open(cfg.Database.Path)
@@ -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)
);
-17
View File
@@ -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
+1 -1
View File
@@ -17,7 +17,7 @@
// go get github.com/tetratelabs/wazero // go get github.com/tetratelabs/wazero
// go build -tags 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. // repo so the default build stays self-contained.
package plugin package plugin
+1 -1
View File
@@ -1,7 +1,7 @@
//go:build wazero //go:build wazero
// Phase C Step 9 — Real Wazero-backed plugin runtime. Compiled only with // 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 // elsewhere in the repo so the default sqlite-only build does not pull
// wazero into go.mod at runtime. // wazero into go.mod at runtime.
// //
-20
View File
@@ -16,23 +16,3 @@ sql:
emit_empty_slices: true emit_empty_slices: true
emit_json_tags: true emit_json_tags: true
json_tags_case_style: "camel" 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"
-833
View File
@@ -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)
+1 -1
View File
@@ -1,7 +1,7 @@
//go:build otel //go:build otel
// Phase B Step 8 — Real OpenTelemetry-backed implementation. Compiled only // 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 // 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. // no-op provider so sqlite-only binaries do not pull the OTel SDK in.
// //