mirror of
https://github.com/J3vb/OwnCord.git
synced 2026-09-03 03:50:00 +03:00
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:
+2
-8
@@ -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
@@ -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
|
||||||
|
|||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -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,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -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"`
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -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)
|
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
@@ -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;
|
|
||||||
@@ -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;
|
|
||||||
@@ -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;
|
|
||||||
@@ -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;
|
|
||||||
@@ -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;
|
|
||||||
@@ -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;
|
|
||||||
@@ -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;
|
|
||||||
@@ -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;
|
|
||||||
@@ -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;
|
|
||||||
@@ -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;
|
|
||||||
@@ -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;
|
|
||||||
@@ -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;
|
|
||||||
@@ -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;
|
|
||||||
@@ -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;
|
|
||||||
@@ -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
@@ -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
@@ -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)
|
|
||||||
);
|
|
||||||
@@ -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
|
|
||||||
@@ -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,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.
|
||||||
//
|
//
|
||||||
|
|||||||
@@ -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"
|
|
||||||
|
|||||||
@@ -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,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.
|
||||||
//
|
//
|
||||||
|
|||||||
Reference in New Issue
Block a user