feat: adopt sqlc for type-safe database access (Phase A Step 2)

- Add sqlc.yaml config (SQLite engine, db/queries/sqlite/, db/dbgen/ output)
- Pin sqlc v1.30.0 in sqlc.version
- Add Makefile with sqlc-install, sqlc-generate, sqlc-verify targets
- Write 14 SQL query files covering all DB domains (users, sessions,
  invites, channels, messages, reactions, voice, roles, attachments,
  admin, dm, blocks, lockouts, profile)
- Commit generated db/dbgen/ package (querier interface + typed fns)
- FTS5 search queries remain hand-written in message_queries.go;
  transactional multi-step operations unchanged in Go
This commit is contained in:
J3vb
2026-04-06 09:12:59 +02:00
parent f1a5b5188c
commit 2a46b95111
36 changed files with 4079 additions and 0 deletions
+22
View File
@@ -0,0 +1,22 @@
# OwnCord Server — developer convenience targets
#
# sqlc-generate Regenerate type-safe Go from db/queries/sqlite/*.sql
# sqlc-verify Fail if committed dbgen output is stale (used by CI)
# sqlc-install Install the pinned sqlc version into $GOBIN
SQLC_VERSION := $(shell cat sqlc.version)
.PHONY: sqlc-install sqlc-generate sqlc-verify
sqlc-install:
go install github.com/sqlc-dev/sqlc/cmd/sqlc@$(SQLC_VERSION)
sqlc-generate:
sqlc generate
sqlc-verify:
sqlc generate
@git diff --exit-code db/dbgen || ( \
echo "ERROR: db/dbgen is stale. Run 'make sqlc-generate' and commit the result." ; \
exit 1 ; \
)
+313
View File
@@ -0,0 +1,313 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.30.0
// source: admin.sql
package dbgen
import (
"context"
)
const countActiveInvites = `-- name: CountActiveInvites :one
SELECT COUNT(*) FROM invites WHERE revoked = 0
`
func (q *Queries) CountActiveInvites(ctx context.Context) (int64, error) {
row := q.db.QueryRowContext(ctx, countActiveInvites)
var count int64
err := row.Scan(&count)
return count, err
}
const countActiveMessages = `-- name: CountActiveMessages :one
SELECT COUNT(*) FROM messages WHERE deleted = 0
`
func (q *Queries) CountActiveMessages(ctx context.Context) (int64, error) {
row := q.db.QueryRowContext(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.QueryRowContext(ctx, countChannels)
var count int64
err := row.Scan(&count)
return count, err
}
const forceLogoutUser = `-- name: ForceLogoutUser :exec
DELETE FROM sessions WHERE user_id = ?
`
func (q *Queries) ForceLogoutUser(ctx context.Context, userID int64) error {
_, err := q.db.ExecContext(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.QueryContext(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.Close(); err != nil {
return nil, err
}
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 ? OFFSET ?
`
type GetAuditLogParams struct {
Limit int64 `json:"limit"`
Offset int64 `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 string `json:"createdAt"`
}
func (q *Queries) GetAuditLog(ctx context.Context, arg GetAuditLogParams) ([]GetAuditLogRow, error) {
rows, err := q.db.QueryContext(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.Close(); err != nil {
return nil, err
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const getSetting = `-- name: GetSetting :one
SELECT value FROM settings WHERE key = ?
`
func (q *Queries) GetSetting(ctx context.Context, key string) (string, error) {
row := q.db.QueryRowContext(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 = ?
ORDER BY created_at DESC
`
func (q *Queries) GetUserSessions(ctx context.Context, userID int64) ([]Session, error) {
rows, err := q.db.QueryContext(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.Close(); err != nil {
return nil, err
}
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 ? OFFSET ?
`
type ListAllUsersParams struct {
Limit int64 `json:"limit"`
Offset int64 `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 string `json:"createdAt"`
LastSeen *string `json:"lastSeen"`
Banned int64 `json:"banned"`
BanReason *string `json:"banReason"`
BanExpires *string `json:"banExpires"`
RoleName string `json:"roleName"`
}
func (q *Queries) ListAllUsers(ctx context.Context, arg ListAllUsersParams) ([]ListAllUsersRow, error) {
rows, err := q.db.QueryContext(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.Close(); err != nil {
return nil, err
}
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 (?, ?, ?, ?, ?)
`
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.ExecContext(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 (?, ?)
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.ExecContext(ctx, setSetting, arg.Key, arg.Value)
return err
}
const updateUserRole = `-- name: UpdateUserRole :exec
UPDATE users SET role_id = ? WHERE id = ?
`
type UpdateUserRoleParams struct {
RoleID int64 `json:"roleId"`
ID int64 `json:"id"`
}
func (q *Queries) UpdateUserRole(ctx context.Context, arg UpdateUserRoleParams) error {
_, err := q.db.ExecContext(ctx, updateUserRole, arg.RoleID, arg.ID)
return err
}
const userCount = `-- name: UserCount :one
SELECT COUNT(*) FROM users
`
func (q *Queries) UserCount(ctx context.Context) (int64, error) {
row := q.db.QueryRowContext(ctx, userCount)
var count int64
err := row.Scan(&count)
return count, err
}
+162
View File
@@ -0,0 +1,162 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.30.0
// source: attachments.sql
package dbgen
import (
"context"
"database/sql"
)
const createAttachment = `-- name: CreateAttachment :exec
INSERT INTO attachments (id, uploader_id, filename, stored_as, mime_type, size, width, height)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
`
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 *int64 `json:"width"`
Height *int64 `json:"height"`
}
func (q *Queries) CreateAttachment(ctx context.Context, arg CreateAttachmentParams) error {
_, err := q.db.ExecContext(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 = ?
`
func (q *Queries) DeleteAttachment(ctx context.Context, id string) error {
_, err := q.db.ExecContext(ctx, deleteAttachment, id)
return err
}
const deleteOrphanedAttachments = `-- name: DeleteOrphanedAttachments :many
DELETE FROM attachments WHERE message_id IS NULL AND uploaded_at < ? RETURNING stored_as
`
func (q *Queries) DeleteOrphanedAttachments(ctx context.Context, uploadedAt string) ([]string, error) {
rows, err := q.db.QueryContext(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.Close(); err != nil {
return nil, err
}
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 = ?
`
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 string `json:"uploadedAt"`
UploaderID *int64 `json:"uploaderId"`
}
func (q *Queries) GetAttachmentByID(ctx context.Context, id string) (GetAttachmentByIDRow, error) {
row := q.db.QueryRowContext(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 = ?
`
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 string `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.QueryRowContext(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 :execresult
UPDATE attachments SET message_id = ? WHERE id = ? 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) (sql.Result, error) {
return q.db.ExecContext(ctx, linkAttachmentToMessage, arg.MessageID, arg.ID)
}
+80
View File
@@ -0,0 +1,80 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.30.0
// source: blocks.sql
package dbgen
import (
"context"
)
const blockUser = `-- name: BlockUser :exec
INSERT OR IGNORE INTO user_blocks (blocker_id, blocked_id) VALUES (?, ?)
`
type BlockUserParams struct {
BlockerID int64 `json:"blockerId"`
BlockedID int64 `json:"blockedId"`
}
func (q *Queries) BlockUser(ctx context.Context, arg BlockUserParams) error {
_, err := q.db.ExecContext(ctx, blockUser, arg.BlockerID, arg.BlockedID)
return err
}
const isBlocked = `-- name: IsBlocked :one
SELECT 1 FROM user_blocks WHERE blocker_id = ? AND blocked_id = ? LIMIT 1
`
type IsBlockedParams struct {
BlockerID int64 `json:"blockerId"`
BlockedID int64 `json:"blockedId"`
}
func (q *Queries) IsBlocked(ctx context.Context, arg IsBlockedParams) (int64, error) {
row := q.db.QueryRowContext(ctx, isBlocked, arg.BlockerID, arg.BlockedID)
var column_1 int64
err := row.Scan(&column_1)
return column_1, err
}
const isEitherBlocked = `-- name: IsEitherBlocked :one
SELECT 1 FROM user_blocks
WHERE (blocker_id = ? AND blocked_id = ?)
OR (blocker_id = ? AND blocked_id = ?)
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) (int64, error) {
row := q.db.QueryRowContext(ctx, isEitherBlocked,
arg.BlockerID,
arg.BlockedID,
arg.BlockerID_2,
arg.BlockedID_2,
)
var column_1 int64
err := row.Scan(&column_1)
return column_1, err
}
const unblockUser = `-- name: UnblockUser :exec
DELETE FROM user_blocks WHERE blocker_id = ? AND blocked_id = ?
`
type UnblockUserParams struct {
BlockerID int64 `json:"blockerId"`
BlockedID int64 `json:"blockedId"`
}
func (q *Queries) UnblockUser(ctx context.Context, arg UnblockUserParams) error {
_, err := q.db.ExecContext(ctx, unblockUser, arg.BlockerID, arg.BlockedID)
return err
}
+377
View File
@@ -0,0 +1,377 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.30.0
// source: channels.sql
package dbgen
import (
"context"
"database/sql"
)
const adminUpdateChannel = `-- name: AdminUpdateChannel :exec
UPDATE channels
SET name = ?, topic = ?, slow_mode = ?, position = ?, archived = ?
WHERE id = ?
`
type AdminUpdateChannelParams struct {
Name string `json:"name"`
Topic *string `json:"topic"`
SlowMode int64 `json:"slowMode"`
Position int64 `json:"position"`
Archived int64 `json:"archived"`
ID int64 `json:"id"`
}
func (q *Queries) AdminUpdateChannel(ctx context.Context, arg AdminUpdateChannelParams) error {
_, err := q.db.ExecContext(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 = ? WHERE id = ?
`
type ArchiveChannelParams struct {
Archived int64 `json:"archived"`
ID int64 `json:"id"`
}
func (q *Queries) ArchiveChannel(ctx context.Context, arg ArchiveChannelParams) error {
_, err := q.db.ExecContext(ctx, archiveChannel, arg.Archived, arg.ID)
return err
}
const createChannel = `-- name: CreateChannel :execresult
INSERT INTO channels (name, type, category, topic, position) VALUES (?, ?, ?, ?, ?)
`
type CreateChannelParams struct {
Name string `json:"name"`
Type string `json:"type"`
Category *string `json:"category"`
Topic *string `json:"topic"`
Position int64 `json:"position"`
}
func (q *Queries) CreateChannel(ctx context.Context, arg CreateChannelParams) (sql.Result, error) {
return q.db.ExecContext(ctx, createChannel,
arg.Name,
arg.Type,
arg.Category,
arg.Topic,
arg.Position,
)
}
const deleteChannel = `-- name: DeleteChannel :exec
DELETE FROM channels WHERE id = ?
`
func (q *Queries) DeleteChannel(ctx context.Context, id int64) error {
_, err := q.db.ExecContext(ctx, deleteChannel, id)
return err
}
const deleteChannelPermission = `-- name: DeleteChannelPermission :exec
DELETE FROM channel_overrides WHERE channel_id = ? AND role_id = ?
`
type DeleteChannelPermissionParams struct {
ChannelID int64 `json:"channelId"`
RoleID int64 `json:"roleId"`
}
func (q *Queries) DeleteChannelPermission(ctx context.Context, arg DeleteChannelPermissionParams) error {
_, err := q.db.ExecContext(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 = ?
`
type GetChannelRow struct {
ID int64 `json:"id"`
Name string `json:"name"`
Type string `json:"type"`
Category string `json:"category"`
Topic string `json:"topic"`
Position int64 `json:"position"`
SlowMode int64 `json:"slowMode"`
Archived int64 `json:"archived"`
CreatedAt string `json:"createdAt"`
VoiceMaxUsers int64 `json:"voiceMaxUsers"`
VoiceQuality *string `json:"voiceQuality"`
MixingThreshold *int64 `json:"mixingThreshold"`
VoiceMaxVideo int64 `json:"voiceMaxVideo"`
}
func (q *Queries) GetChannel(ctx context.Context, id int64) (GetChannelRow, error) {
row := q.db.QueryRowContext(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 = ? AND role_id = ?
`
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.QueryRowContext(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 = ?
`
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.QueryContext(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.Close(); err != nil {
return nil, err
}
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 int64 `json:"position"`
SlowMode int64 `json:"slowMode"`
Archived int64 `json:"archived"`
CreatedAt string `json:"createdAt"`
VoiceMaxUsers int64 `json:"voiceMaxUsers"`
VoiceQuality *string `json:"voiceQuality"`
MixingThreshold *int64 `json:"mixingThreshold"`
VoiceMaxVideo int64 `json:"voiceMaxVideo"`
}
func (q *Queries) ListChannels(ctx context.Context) ([]ListChannelsRow, error) {
rows, err := q.db.QueryContext(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.Close(); err != nil {
return nil, err
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const setChannelMixingThreshold = `-- name: SetChannelMixingThreshold :exec
UPDATE channels SET mixing_threshold = ? WHERE id = ?
`
type SetChannelMixingThresholdParams struct {
MixingThreshold *int64 `json:"mixingThreshold"`
ID int64 `json:"id"`
}
func (q *Queries) SetChannelMixingThreshold(ctx context.Context, arg SetChannelMixingThresholdParams) error {
_, err := q.db.ExecContext(ctx, setChannelMixingThreshold, arg.MixingThreshold, arg.ID)
return err
}
const setChannelSlowMode = `-- name: SetChannelSlowMode :exec
UPDATE channels SET slow_mode = ? WHERE id = ?
`
type SetChannelSlowModeParams struct {
SlowMode int64 `json:"slowMode"`
ID int64 `json:"id"`
}
func (q *Queries) SetChannelSlowMode(ctx context.Context, arg SetChannelSlowModeParams) error {
_, err := q.db.ExecContext(ctx, setChannelSlowMode, arg.SlowMode, arg.ID)
return err
}
const setChannelVoiceMaxUsers = `-- name: SetChannelVoiceMaxUsers :exec
UPDATE channels SET voice_max_users = ? WHERE id = ?
`
type SetChannelVoiceMaxUsersParams struct {
VoiceMaxUsers int64 `json:"voiceMaxUsers"`
ID int64 `json:"id"`
}
func (q *Queries) SetChannelVoiceMaxUsers(ctx context.Context, arg SetChannelVoiceMaxUsersParams) error {
_, err := q.db.ExecContext(ctx, setChannelVoiceMaxUsers, arg.VoiceMaxUsers, arg.ID)
return err
}
const setChannelVoiceMaxVideo = `-- name: SetChannelVoiceMaxVideo :exec
UPDATE channels SET voice_max_video = ? WHERE id = ?
`
type SetChannelVoiceMaxVideoParams struct {
VoiceMaxVideo int64 `json:"voiceMaxVideo"`
ID int64 `json:"id"`
}
func (q *Queries) SetChannelVoiceMaxVideo(ctx context.Context, arg SetChannelVoiceMaxVideoParams) error {
_, err := q.db.ExecContext(ctx, setChannelVoiceMaxVideo, arg.VoiceMaxVideo, arg.ID)
return err
}
const setChannelVoiceQuality = `-- name: SetChannelVoiceQuality :exec
UPDATE channels SET voice_quality = ? WHERE id = ?
`
type SetChannelVoiceQualityParams struct {
VoiceQuality *string `json:"voiceQuality"`
ID int64 `json:"id"`
}
func (q *Queries) SetChannelVoiceQuality(ctx context.Context, arg SetChannelVoiceQualityParams) error {
_, err := q.db.ExecContext(ctx, setChannelVoiceQuality, arg.VoiceQuality, arg.ID)
return err
}
const updateChannel = `-- name: UpdateChannel :exec
UPDATE channels SET name = ?, topic = ?, slow_mode = ? WHERE id = ?
`
type UpdateChannelParams struct {
Name string `json:"name"`
Topic *string `json:"topic"`
SlowMode int64 `json:"slowMode"`
ID int64 `json:"id"`
}
func (q *Queries) UpdateChannel(ctx context.Context, arg UpdateChannelParams) error {
_, err := q.db.ExecContext(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 (?, ?, ?, ?)
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.ExecContext(ctx, upsertChannelPermission,
arg.ChannelID,
arg.RoleID,
arg.Allow,
arg.Deny,
)
return err
}
+31
View File
@@ -0,0 +1,31 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.30.0
package dbgen
import (
"context"
"database/sql"
)
type DBTX interface {
ExecContext(context.Context, string, ...interface{}) (sql.Result, error)
PrepareContext(context.Context, string) (*sql.Stmt, error)
QueryContext(context.Context, string, ...interface{}) (*sql.Rows, error)
QueryRowContext(context.Context, string, ...interface{}) *sql.Row
}
func New(db DBTX) *Queries {
return &Queries{db: db}
}
type Queries struct {
db DBTX
}
func (q *Queries) WithTx(tx *sql.Tx) *Queries {
return &Queries{
db: tx,
}
}
+230
View File
@@ -0,0 +1,230 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.30.0
// source: dm.sql
package dbgen
import (
"context"
"database/sql"
)
const closeDM = `-- name: CloseDM :exec
DELETE FROM dm_open_state WHERE user_id = ? AND channel_id = ?
`
type CloseDMParams struct {
UserID int64 `json:"userId"`
ChannelID int64 `json:"channelId"`
}
func (q *Queries) CloseDM(ctx context.Context, arg CloseDMParams) error {
_, err := q.db.ExecContext(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 = ? AND dp2.user_id = ? 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.QueryRowContext(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 = ?
`
func (q *Queries) GetDMParticipantIDs(ctx context.Context, channelID int64) ([]int64, error) {
rows, err := q.db.QueryContext(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.Close(); err != nil {
return nil, err
}
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, '') AS last_message_at,
COUNT(CASE WHEN m_unread.id > COALESCE(rs.last_message_id, 0)
AND m_unread.deleted = 0 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 != ?
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 = 0
)
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 = ?
WHERE dos.user_id = ?
GROUP BY c.id
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 string `json:"lastMessageAt"`
UnreadCount int64 `json:"unreadCount"`
}
func (q *Queries) GetUserDMChannels(ctx context.Context, arg GetUserDMChannelsParams) ([]GetUserDMChannelsRow, error) {
rows, err := q.db.QueryContext(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.Close(); err != nil {
return nil, err
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const insertDMChannel = `-- name: InsertDMChannel :execresult
INSERT INTO channels (name, type) VALUES ('', 'dm')
`
func (q *Queries) InsertDMChannel(ctx context.Context) (sql.Result, error) {
return q.db.ExecContext(ctx, insertDMChannel)
}
const insertDMOpenState = `-- name: InsertDMOpenState :exec
INSERT OR IGNORE INTO dm_open_state (user_id, channel_id) VALUES (?, ?), (?, ?)
`
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.ExecContext(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 (?, ?), (?, ?)
`
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.ExecContext(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 = ? AND channel_id = ?
`
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.QueryRowContext(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 OR IGNORE INTO dm_open_state (user_id, channel_id) VALUES (?, ?)
`
type OpenDMParams struct {
UserID int64 `json:"userId"`
ChannelID int64 `json:"channelId"`
}
func (q *Queries) OpenDM(ctx context.Context, arg OpenDMParams) error {
_, err := q.db.ExecContext(ctx, openDM, arg.UserID, arg.ChannelID)
return err
}
+132
View File
@@ -0,0 +1,132 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.30.0
// source: invites.sql
package dbgen
import (
"context"
"database/sql"
)
const createInvite = `-- name: CreateInvite :exec
INSERT INTO invites (code, created_by, max_uses, expires_at) VALUES (?, ?, ?, ?)
`
type CreateInviteParams struct {
Code string `json:"code"`
CreatedBy int64 `json:"createdBy"`
MaxUses *int64 `json:"maxUses"`
ExpiresAt *string `json:"expiresAt"`
}
func (q *Queries) CreateInvite(ctx context.Context, arg CreateInviteParams) error {
_, err := q.db.ExecContext(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 = ?
`
type GetInviteRow struct {
ID int64 `json:"id"`
Code string `json:"code"`
CreatedBy int64 `json:"createdBy"`
MaxUses *int64 `json:"maxUses"`
UseCount int64 `json:"useCount"`
ExpiresAt *string `json:"expiresAt"`
Revoked int64 `json:"revoked"`
CreatedAt string `json:"createdAt"`
}
func (q *Queries) GetInvite(ctx context.Context, code string) (GetInviteRow, error) {
row := q.db.QueryRowContext(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 *int64 `json:"maxUses"`
UseCount int64 `json:"useCount"`
ExpiresAt *string `json:"expiresAt"`
Revoked int64 `json:"revoked"`
CreatedAt string `json:"createdAt"`
}
func (q *Queries) ListInvites(ctx context.Context) ([]ListInvitesRow, error) {
rows, err := q.db.QueryContext(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.Close(); err != nil {
return nil, err
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const revokeInvite = `-- name: RevokeInvite :exec
UPDATE invites SET revoked = 1 WHERE code = ?
`
func (q *Queries) RevokeInvite(ctx context.Context, code string) error {
_, err := q.db.ExecContext(ctx, revokeInvite, code)
return err
}
const useInviteAtomic = `-- name: UseInviteAtomic :execresult
UPDATE invites SET use_count = use_count + 1
WHERE code = ? AND revoked = 0
AND (max_uses IS NULL OR use_count < max_uses)
AND (expires_at IS NULL OR strftime('%s', expires_at) > strftime('%s', 'now'))
`
func (q *Queries) UseInviteAtomic(ctx context.Context, code string) (sql.Result, error) {
return q.db.ExecContext(ctx, useInviteAtomic, code)
}
+69
View File
@@ -0,0 +1,69 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.30.0
// source: lockouts.sql
package dbgen
import (
"context"
)
const cleanupExpiredLockouts = `-- name: CleanupExpiredLockouts :exec
DELETE FROM rate_lockouts WHERE expires_at <= ?
`
func (q *Queries) CleanupExpiredLockouts(ctx context.Context, expiresAt string) error {
_, err := q.db.ExecContext(ctx, cleanupExpiredLockouts, expiresAt)
return err
}
const deleteLockout = `-- name: DeleteLockout :exec
DELETE FROM rate_lockouts WHERE key = ?
`
func (q *Queries) DeleteLockout(ctx context.Context, key string) error {
_, err := q.db.ExecContext(ctx, deleteLockout, key)
return err
}
const loadActiveLockouts = `-- name: LoadActiveLockouts :many
SELECT key, expires_at FROM rate_lockouts WHERE expires_at > ?
`
func (q *Queries) LoadActiveLockouts(ctx context.Context, expiresAt string) ([]RateLockout, error) {
rows, err := q.db.QueryContext(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.Close(); err != nil {
return nil, err
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const upsertLockout = `-- name: UpsertLockout :exec
INSERT OR REPLACE INTO rate_lockouts (key, expires_at) VALUES (?, ?)
`
type UpsertLockoutParams struct {
Key string `json:"key"`
ExpiresAt string `json:"expiresAt"`
}
func (q *Queries) UpsertLockout(ctx context.Context, arg UpsertLockoutParams) error {
_, err := q.db.ExecContext(ctx, upsertLockout, arg.Key, arg.ExpiresAt)
return err
}
+466
View File
@@ -0,0 +1,466 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.30.0
// source: messages.sql
package dbgen
import (
"context"
"database/sql"
)
const createMessage = `-- name: CreateMessage :execresult
INSERT INTO messages (channel_id, user_id, content, reply_to) VALUES (?, ?, ?, ?)
`
type CreateMessageParams struct {
ChannelID int64 `json:"channelId"`
UserID int64 `json:"userId"`
Content string `json:"content"`
ReplyTo *int64 `json:"replyTo"`
}
func (q *Queries) CreateMessage(ctx context.Context, arg CreateMessageParams) (sql.Result, error) {
return q.db.ExecContext(ctx, createMessage,
arg.ChannelID,
arg.UserID,
arg.Content,
arg.ReplyTo,
)
}
const editMessageContent = `-- name: EditMessageContent :exec
UPDATE messages SET content = ?, edited_at = datetime('now') WHERE id = ?
`
type EditMessageContentParams struct {
Content string `json:"content"`
ID int64 `json:"id"`
}
func (q *Queries) EditMessageContent(ctx context.Context, arg EditMessageContentParams) error {
_, err := q.db.ExecContext(ctx, editMessageContent, arg.Content, arg.ID)
return err
}
const getChannelUnreadCounts = `-- name: GetChannelUnreadCounts :many
SELECT c.id,
COALESCE(MAX(m.id), 0) AS last_msg_id,
COUNT(CASE WHEN m.id > COALESCE(rs.last_message_id, 0) AND m.deleted = 0 THEN 1 END) AS unread
FROM channels c
LEFT JOIN messages m ON m.channel_id = c.id AND m.deleted = 0
LEFT JOIN read_states rs ON rs.channel_id = c.id AND rs.user_id = ?
WHERE c.type = 'text'
GROUP BY c.id
`
type GetChannelUnreadCountsRow struct {
ID int64 `json:"id"`
LastMsgID interface{} `json:"lastMsgId"`
Unread int64 `json:"unread"`
}
func (q *Queries) GetChannelUnreadCounts(ctx context.Context, userID int64) ([]GetChannelUnreadCountsRow, error) {
rows, err := q.db.QueryContext(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.Close(); err != nil {
return nil, err
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const getLatestMessageID = `-- name: GetLatestMessageID :one
SELECT COALESCE(MAX(id), 0) FROM messages WHERE channel_id = ? AND deleted = 0
`
func (q *Queries) GetLatestMessageID(ctx context.Context, channelID int64) (interface{}, error) {
row := q.db.QueryRowContext(ctx, getLatestMessageID, channelID)
var coalesce interface{}
err := row.Scan(&coalesce)
return coalesce, err
}
const getMessage = `-- name: GetMessage :one
SELECT id, channel_id, user_id, content, reply_to, edited_at, deleted, pinned, timestamp
FROM messages WHERE id = ?
`
func (q *Queries) GetMessage(ctx context.Context, id int64) (Message, error) {
row := q.db.QueryRowContext(ctx, getMessage, id)
var i Message
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 = ? AND m.deleted = 0
ORDER BY m.id DESC LIMIT ?
`
type GetMessagesByChannelParams struct {
ChannelID int64 `json:"channelId"`
Limit int64 `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 *string `json:"editedAt"`
Deleted int64 `json:"deleted"`
Pinned int64 `json:"pinned"`
Timestamp string `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.QueryContext(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.Close(); err != nil {
return nil, err
}
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 = ? AND m.id < ? AND m.deleted = 0
ORDER BY m.id DESC LIMIT ?
`
type GetMessagesByChannelBeforeCursorParams struct {
ChannelID int64 `json:"channelId"`
ID int64 `json:"id"`
Limit int64 `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 *string `json:"editedAt"`
Deleted int64 `json:"deleted"`
Pinned int64 `json:"pinned"`
Timestamp string `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.QueryContext(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.Close(); err != nil {
return nil, err
}
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 = ? AND m.deleted = 0
ORDER BY m.id DESC LIMIT ?
`
type GetMessagesForAPIParams struct {
ChannelID int64 `json:"channelId"`
Limit int64 `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 *string `json:"editedAt"`
Deleted int64 `json:"deleted"`
Pinned int64 `json:"pinned"`
Timestamp string `json:"timestamp"`
}
func (q *Queries) GetMessagesForAPI(ctx context.Context, arg GetMessagesForAPIParams) ([]GetMessagesForAPIRow, error) {
rows, err := q.db.QueryContext(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.Close(); err != nil {
return nil, err
}
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 = ? AND m.id < ? AND m.deleted = 0
ORDER BY m.id DESC LIMIT ?
`
type GetMessagesForAPIBeforeCursorParams struct {
ChannelID int64 `json:"channelId"`
ID int64 `json:"id"`
Limit int64 `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 *string `json:"editedAt"`
Deleted int64 `json:"deleted"`
Pinned int64 `json:"pinned"`
Timestamp string `json:"timestamp"`
}
func (q *Queries) GetMessagesForAPIBeforeCursor(ctx context.Context, arg GetMessagesForAPIBeforeCursorParams) ([]GetMessagesForAPIBeforeCursorRow, error) {
rows, err := q.db.QueryContext(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.Close(); err != nil {
return nil, err
}
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 = ? AND m.pinned = 1 AND m.deleted = 0
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 *string `json:"editedAt"`
Deleted int64 `json:"deleted"`
Pinned int64 `json:"pinned"`
Timestamp string `json:"timestamp"`
}
func (q *Queries) GetPinnedMessageRows(ctx context.Context, channelID int64) ([]GetPinnedMessageRowsRow, error) {
rows, err := q.db.QueryContext(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.Close(); err != nil {
return nil, err
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const setMessagePinned = `-- name: SetMessagePinned :execresult
UPDATE messages SET pinned = ? WHERE id = ? AND deleted = 0
`
type SetMessagePinnedParams struct {
Pinned int64 `json:"pinned"`
ID int64 `json:"id"`
}
func (q *Queries) SetMessagePinned(ctx context.Context, arg SetMessagePinnedParams) (sql.Result, error) {
return q.db.ExecContext(ctx, setMessagePinned, arg.Pinned, arg.ID)
}
const softDeleteMessage = `-- name: SoftDeleteMessage :exec
UPDATE messages SET deleted = 1 WHERE id = ?
`
func (q *Queries) SoftDeleteMessage(ctx context.Context, id int64) error {
_, err := q.db.ExecContext(ctx, softDeleteMessage, id)
return err
}
const updateReadState = `-- name: UpdateReadState :exec
INSERT INTO read_states (user_id, channel_id, last_message_id)
VALUES (?, ?, ?)
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.ExecContext(ctx, updateReadState, arg.UserID, arg.ChannelID, arg.LastMessageID)
return err
}
+192
View File
@@ -0,0 +1,192 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.30.0
package dbgen
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 string `json:"uploadedAt"`
Width *int64 `json:"width"`
Height *int64 `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 string `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 int64 `json:"position"`
SlowMode int64 `json:"slowMode"`
Archived int64 `json:"archived"`
CreatedAt string `json:"createdAt"`
VoiceMaxUsers int64 `json:"voiceMaxUsers"`
VoiceQuality *string `json:"voiceQuality"`
MixingThreshold *int64 `json:"mixingThreshold"`
VoiceMaxVideo int64 `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 string `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 string `json:"createdAt"`
}
type Invite struct {
ID int64 `json:"id"`
Code string `json:"code"`
CreatedBy int64 `json:"createdBy"`
RedeemedBy *int64 `json:"redeemedBy"`
MaxUses *int64 `json:"maxUses"`
UseCount int64 `json:"useCount"`
ExpiresAt *string `json:"expiresAt"`
CreatedAt string `json:"createdAt"`
Revoked int64 `json:"revoked"`
}
type LoginAttempt struct {
ID int64 `json:"id"`
IpAddress string `json:"ipAddress"`
Username *string `json:"username"`
Success int64 `json:"success"`
Timestamp string `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 *string `json:"editedAt"`
Deleted int64 `json:"deleted"`
Pinned int64 `json:"pinned"`
Timestamp string `json:"timestamp"`
}
type MessagesFt struct {
Content string `json:"content"`
}
type RateLockout struct {
Key string `json:"key"`
ExpiresAt string `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 int64 `json:"mentionCount"`
}
type Role struct {
ID int64 `json:"id"`
Name string `json:"name"`
Color *string `json:"color"`
Permissions int64 `json:"permissions"`
Position int64 `json:"position"`
IsDefault int64 `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 string `json:"createdAt"`
LastUsed string `json:"lastUsed"`
ExpiresAt string `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 int64 `json:"durationMs"`
UploadedBy int64 `json:"uploadedBy"`
CreatedAt string `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 string `json:"createdAt"`
LastSeen *string `json:"lastSeen"`
Banned int64 `json:"banned"`
BanReason *string `json:"banReason"`
BanExpires *string `json:"banExpires"`
}
type UserBlock struct {
BlockerID int64 `json:"blockerId"`
BlockedID int64 `json:"blockedId"`
CreatedAt string `json:"createdAt"`
}
type VoiceState struct {
UserID int64 `json:"userId"`
ChannelID int64 `json:"channelId"`
Muted int64 `json:"muted"`
Deafened int64 `json:"deafened"`
Speaking int64 `json:"speaking"`
JoinedAt string `json:"joinedAt"`
Camera int64 `json:"camera"`
Screenshare int64 `json:"screenshare"`
}
+39
View File
@@ -0,0 +1,39 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.30.0
// source: profile.sql
package dbgen
import (
"context"
"database/sql"
)
const updateUserPassword = `-- name: UpdateUserPassword :exec
UPDATE users SET password = ? WHERE id = ?
`
type UpdateUserPasswordParams struct {
Password string `json:"password"`
ID int64 `json:"id"`
}
func (q *Queries) UpdateUserPassword(ctx context.Context, arg UpdateUserPasswordParams) error {
_, err := q.db.ExecContext(ctx, updateUserPassword, arg.Password, arg.ID)
return err
}
const updateUserProfile = `-- name: UpdateUserProfile :execresult
UPDATE users SET username = ?, avatar = ? WHERE id = ?
`
type UpdateUserProfileParams struct {
Username string `json:"username"`
Avatar *string `json:"avatar"`
ID int64 `json:"id"`
}
func (q *Queries) UpdateUserProfile(ctx context.Context, arg UpdateUserProfileParams) (sql.Result, error) {
return q.db.ExecContext(ctx, updateUserProfile, arg.Username, arg.Avatar, arg.ID)
}
+132
View File
@@ -0,0 +1,132 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.30.0
package dbgen
import (
"context"
"database/sql"
)
type Querier interface {
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
BlockUser(ctx context.Context, arg BlockUserParams) error
CleanupExpiredLockouts(ctx context.Context, expiresAt string) 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)
CreateAttachment(ctx context.Context, arg CreateAttachmentParams) error
CreateChannel(ctx context.Context, arg CreateChannelParams) (sql.Result, error)
CreateInvite(ctx context.Context, arg CreateInviteParams) error
CreateMessage(ctx context.Context, arg CreateMessageParams) (sql.Result, error)
CreateUser(ctx context.Context, arg CreateUserParams) (sql.Result, error)
DeleteAttachment(ctx context.Context, id string) error
DeleteChannel(ctx context.Context, id int64) error
DeleteChannelPermission(ctx context.Context, arg DeleteChannelPermissionParams) error
DeleteExpiredSessions(ctx context.Context) error
DeleteLockout(ctx context.Context, key string) error
DeleteOrphanedAttachments(ctx context.Context, uploadedAt string) ([]string, error)
DeleteOtherSessions(ctx context.Context, arg DeleteOtherSessionsParams) (sql.Result, error)
DeleteSessionByID(ctx context.Context, arg DeleteSessionByIDParams) error
DeleteSessionByToken(ctx context.Context, token string) error
EditMessageContent(ctx context.Context, arg EditMessageContentParams) error
EnableCameraIfUnderLimit(ctx context.Context, arg EnableCameraIfUnderLimitParams) (sql.Result, error)
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)
GetInvite(ctx context.Context, code string) (GetInviteRow, error)
GetLatestMessageID(ctx context.Context, channelID int64) (interface{}, error)
GetMessage(ctx context.Context, id int64) (Message, 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)
GetReactionCounts(ctx context.Context, messageID int64) ([]GetReactionCountsRow, error)
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)
GetUserByUsername(ctx context.Context, username string) (User, error)
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)
InsertDMChannel(ctx context.Context) (sql.Result, error)
InsertDMOpenState(ctx context.Context, arg InsertDMOpenStateParams) error
InsertDMParticipants(ctx context.Context, arg InsertDMParticipantsParams) error
InsertSession(ctx context.Context, arg InsertSessionParams) (sql.Result, error)
IsBlocked(ctx context.Context, arg IsBlockedParams) (int64, error)
IsDMParticipant(ctx context.Context, arg IsDMParticipantParams) (int64, error)
IsEitherBlocked(ctx context.Context, arg IsEitherBlockedParams) (int64, error)
JoinVoiceChannel(ctx context.Context, arg JoinVoiceChannelParams) error
JoinVoiceChannelIfCapacity(ctx context.Context, arg JoinVoiceChannelIfCapacityParams) (sql.Result, error)
LeaveVoiceChannel(ctx context.Context, userID int64) error
LeaveVoiceChannelIfMatch(ctx context.Context, arg LeaveVoiceChannelIfMatchParams) (sql.Result, error)
LinkAttachmentToMessage(ctx context.Context, arg LinkAttachmentToMessageParams) (sql.Result, error)
ListAllUsers(ctx context.Context, arg ListAllUsersParams) ([]ListAllUsersRow, error)
ListChannels(ctx context.Context) ([]ListChannelsRow, error)
ListInvites(ctx context.Context) ([]ListInvitesRow, error)
ListMembers(ctx context.Context) ([]ListMembersRow, error)
ListRoles(ctx context.Context) ([]Role, error)
ListUserSessions(ctx context.Context, userID int64) ([]Session, error)
LoadActiveLockouts(ctx context.Context, expiresAt string) ([]RateLockout, error)
LogAudit(ctx context.Context, arg LogAuditParams) error
OpenDM(ctx context.Context, arg OpenDMParams) error
RemoveReaction(ctx context.Context, arg RemoveReactionParams) (sql.Result, 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) (sql.Result, 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
UpdateChannel(ctx context.Context, arg UpdateChannelParams) error
UpdateReadState(ctx context.Context, arg UpdateReadStateParams) error
UpdateUserPassword(ctx context.Context, arg UpdateUserPasswordParams) error
UpdateUserProfile(ctx context.Context, arg UpdateUserProfileParams) (sql.Result, 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
UpsertLockout(ctx context.Context, arg UpsertLockoutParams) error
UseInviteAtomic(ctx context.Context, code string) (sql.Result, error)
UserCount(ctx context.Context) (int64, error)
}
var _ Querier = (*Queries)(nil)
+74
View File
@@ -0,0 +1,74 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.30.0
// source: reactions.sql
package dbgen
import (
"context"
"database/sql"
)
const addReaction = `-- name: AddReaction :exec
INSERT INTO reactions (message_id, user_id, emoji) VALUES (?, ?, ?)
`
type AddReactionParams struct {
MessageID int64 `json:"messageId"`
UserID int64 `json:"userId"`
Emoji string `json:"emoji"`
}
func (q *Queries) AddReaction(ctx context.Context, arg AddReactionParams) error {
_, err := q.db.ExecContext(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 = ?
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.QueryContext(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.Close(); err != nil {
return nil, err
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const removeReaction = `-- name: RemoveReaction :execresult
DELETE FROM reactions WHERE message_id = ? AND user_id = ? AND emoji = ?
`
type RemoveReactionParams struct {
MessageID int64 `json:"messageId"`
UserID int64 `json:"userId"`
Emoji string `json:"emoji"`
}
func (q *Queries) RemoveReaction(ctx context.Context, arg RemoveReactionParams) (sql.Result, error) {
return q.db.ExecContext(ctx, removeReaction, arg.MessageID, arg.UserID, arg.Emoji)
}
+161
View File
@@ -0,0 +1,161 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.30.0
// source: roles.sql
package dbgen
import (
"context"
)
const getDefaultRole = `-- name: GetDefaultRole :one
SELECT id, name, color, permissions, position, is_default
FROM roles WHERE is_default = 1 LIMIT 1
`
func (q *Queries) GetDefaultRole(ctx context.Context) (Role, error) {
row := q.db.QueryRowContext(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 = ?
`
func (q *Queries) GetRoleByID(ctx context.Context, id int64) (Role, error) {
row := q.db.QueryRowContext(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 = ?
`
func (q *Queries) GetRoleForUser(ctx context.Context, id int64) (Role, error) {
row := q.db.QueryRowContext(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 = ?
`
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 string `json:"createdAt"`
LastSeen *string `json:"lastSeen"`
Banned int64 `json:"banned"`
BanReason *string `json:"banReason"`
BanExpires *string `json:"banExpires"`
ID_2 int64 `json:"id2"`
Name string `json:"name"`
Color *string `json:"color"`
Permissions int64 `json:"permissions"`
Position int64 `json:"position"`
IsDefault int64 `json:"isDefault"`
}
func (q *Queries) GetUserWithRole(ctx context.Context, id int64) (GetUserWithRoleRow, error) {
row := q.db.QueryRowContext(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.QueryContext(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.Close(); err != nil {
return nil, err
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
+208
View File
@@ -0,0 +1,208 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.30.0
// source: sessions.sql
package dbgen
import (
"context"
"database/sql"
)
const deleteExpiredSessions = `-- name: DeleteExpiredSessions :exec
DELETE FROM sessions WHERE strftime('%s', expires_at) < strftime('%s', 'now')
`
func (q *Queries) DeleteExpiredSessions(ctx context.Context) error {
_, err := q.db.ExecContext(ctx, deleteExpiredSessions)
return err
}
const deleteOtherSessions = `-- name: DeleteOtherSessions :execresult
DELETE FROM sessions WHERE user_id = ? AND id != ?
`
type DeleteOtherSessionsParams struct {
UserID int64 `json:"userId"`
ID int64 `json:"id"`
}
func (q *Queries) DeleteOtherSessions(ctx context.Context, arg DeleteOtherSessionsParams) (sql.Result, error) {
return q.db.ExecContext(ctx, deleteOtherSessions, arg.UserID, arg.ID)
}
const deleteSessionByID = `-- name: DeleteSessionByID :exec
DELETE FROM sessions WHERE id = ? AND user_id = ?
`
type DeleteSessionByIDParams struct {
ID int64 `json:"id"`
UserID int64 `json:"userId"`
}
func (q *Queries) DeleteSessionByID(ctx context.Context, arg DeleteSessionByIDParams) error {
_, err := q.db.ExecContext(ctx, deleteSessionByID, arg.ID, arg.UserID)
return err
}
const deleteSessionByToken = `-- name: DeleteSessionByToken :exec
DELETE FROM sessions WHERE token = ?
`
func (q *Queries) DeleteSessionByToken(ctx context.Context, token string) error {
_, err := q.db.ExecContext(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 = ?
ORDER BY s2.created_at DESC
LIMIT -1 OFFSET ?
)
`
type EvictOldestSessionsParams struct {
UserID int64 `json:"userId"`
Offset int64 `json:"offset"`
}
func (q *Queries) EvictOldestSessions(ctx context.Context, arg EvictOldestSessionsParams) error {
_, err := q.db.ExecContext(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 = ?
`
func (q *Queries) GetSessionByTokenHash(ctx context.Context, token string) (Session, error) {
row := q.db.QueryRowContext(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 = ?
`
type GetSessionWithBanStatusRow struct {
ID int64 `json:"id"`
UserID int64 `json:"userId"`
Token string `json:"token"`
Device *string `json:"device"`
IpAddress *string `json:"ipAddress"`
CreatedAt string `json:"createdAt"`
LastUsed string `json:"lastUsed"`
ExpiresAt string `json:"expiresAt"`
Banned int64 `json:"banned"`
BanReason *string `json:"banReason"`
BanExpires *string `json:"banExpires"`
}
func (q *Queries) GetSessionWithBanStatus(ctx context.Context, token string) (GetSessionWithBanStatusRow, error) {
row := q.db.QueryRowContext(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 :execresult
INSERT INTO sessions (user_id, token, device, ip_address, expires_at)
VALUES (?, ?, ?, ?, ?)
`
type InsertSessionParams struct {
UserID int64 `json:"userId"`
Token string `json:"token"`
Device *string `json:"device"`
IpAddress *string `json:"ipAddress"`
ExpiresAt string `json:"expiresAt"`
}
func (q *Queries) InsertSession(ctx context.Context, arg InsertSessionParams) (sql.Result, error) {
return q.db.ExecContext(ctx, insertSession,
arg.UserID,
arg.Token,
arg.Device,
arg.IpAddress,
arg.ExpiresAt,
)
}
const listUserSessions = `-- name: ListUserSessions :many
SELECT id, user_id, token, device, ip_address, created_at, last_used, expires_at
FROM sessions
WHERE user_id = ?
ORDER BY created_at DESC
`
func (q *Queries) ListUserSessions(ctx context.Context, userID int64) ([]Session, error) {
rows, err := q.db.QueryContext(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.Close(); err != nil {
return nil, err
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const touchSession = `-- name: TouchSession :exec
UPDATE sessions SET last_used = datetime('now') WHERE token = ?
`
func (q *Queries) TouchSession(ctx context.Context, token string) error {
_, err := q.db.ExecContext(ctx, touchSession, token)
return err
}
+206
View File
@@ -0,0 +1,206 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.30.0
// source: users.sql
package dbgen
import (
"context"
"database/sql"
)
const banUser = `-- name: BanUser :exec
UPDATE users SET banned = 1, ban_reason = ?, ban_expires = ? WHERE id = ?
`
type BanUserParams struct {
BanReason *string `json:"banReason"`
BanExpires *string `json:"banExpires"`
ID int64 `json:"id"`
}
func (q *Queries) BanUser(ctx context.Context, arg BanUserParams) error {
_, err := q.db.ExecContext(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.QueryRowContext(ctx, countUsers)
var count int64
err := row.Scan(&count)
return count, err
}
const countUsersWithoutTOTP = `-- name: CountUsersWithoutTOTP :one
SELECT COUNT(*) FROM users WHERE banned = 0 AND totp_secret IS NULL
`
func (q *Queries) CountUsersWithoutTOTP(ctx context.Context) (int64, error) {
row := q.db.QueryRowContext(ctx, countUsersWithoutTOTP)
var count int64
err := row.Scan(&count)
return count, err
}
const createUser = `-- name: CreateUser :execresult
INSERT INTO users (username, password, role_id) VALUES (?, ?, ?)
`
type CreateUserParams struct {
Username string `json:"username"`
Password string `json:"password"`
RoleID int64 `json:"roleId"`
}
func (q *Queries) CreateUser(ctx context.Context, arg CreateUserParams) (sql.Result, error) {
return q.db.ExecContext(ctx, createUser, arg.Username, arg.Password, arg.RoleID)
}
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 = ?
`
func (q *Queries) GetUserByID(ctx context.Context, id int64) (User, error) {
row := q.db.QueryRowContext(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 = ? COLLATE NOCASE
`
func (q *Queries) GetUserByUsername(ctx context.Context, username string) (User, error) {
row := q.db.QueryRowContext(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 = 0
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.QueryContext(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.Close(); err != nil {
return nil, err
}
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.ExecContext(ctx, resetAllUserStatuses)
return err
}
const unbanUser = `-- name: UnbanUser :exec
UPDATE users SET banned = 0, ban_reason = NULL, ban_expires = NULL WHERE id = ?
`
func (q *Queries) UnbanUser(ctx context.Context, id int64) error {
_, err := q.db.ExecContext(ctx, unbanUser, id)
return err
}
const updateUserStatus = `-- name: UpdateUserStatus :exec
UPDATE users SET status = ?, last_seen = datetime('now') WHERE id = ?
`
type UpdateUserStatusParams struct {
Status string `json:"status"`
ID int64 `json:"id"`
}
func (q *Queries) UpdateUserStatus(ctx context.Context, arg UpdateUserStatusParams) error {
_, err := q.db.ExecContext(ctx, updateUserStatus, arg.Status, arg.ID)
return err
}
const updateUserTOTPSecret = `-- name: UpdateUserTOTPSecret :exec
UPDATE users SET totp_secret = ? WHERE id = ?
`
type UpdateUserTOTPSecretParams struct {
TotpSecret *string `json:"totpSecret"`
ID int64 `json:"id"`
}
func (q *Queries) UpdateUserTOTPSecret(ctx context.Context, arg UpdateUserTOTPSecretParams) error {
_, err := q.db.ExecContext(ctx, updateUserTOTPSecret, arg.TotpSecret, arg.ID)
return err
}
+358
View File
@@ -0,0 +1,358 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.30.0
// source: voice.sql
package dbgen
import (
"context"
"database/sql"
)
const clearAllVoiceStates = `-- name: ClearAllVoiceStates :exec
DELETE FROM voice_states
`
func (q *Queries) ClearAllVoiceStates(ctx context.Context) error {
_, err := q.db.ExecContext(ctx, clearAllVoiceStates)
return err
}
const clearVoiceState = `-- name: ClearVoiceState :exec
DELETE FROM voice_states WHERE user_id = ?
`
func (q *Queries) ClearVoiceState(ctx context.Context, userID int64) error {
_, err := q.db.ExecContext(ctx, clearVoiceState, userID)
return err
}
const countActiveCameras = `-- name: CountActiveCameras :one
SELECT COUNT(*) FROM voice_states WHERE channel_id = ? AND camera = 1
`
func (q *Queries) CountActiveCameras(ctx context.Context, channelID int64) (int64, error) {
row := q.db.QueryRowContext(ctx, countActiveCameras, channelID)
var count int64
err := row.Scan(&count)
return count, err
}
const enableCameraIfUnderLimit = `-- name: EnableCameraIfUnderLimit :execresult
UPDATE voice_states SET camera = 1
WHERE voice_states.user_id = ? AND voice_states.channel_id = ?
AND (SELECT COUNT(*) FROM voice_states AS vs2 WHERE vs2.channel_id = ? AND vs2.camera = 1) < ?
`
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) (sql.Result, error) {
return q.db.ExecContext(ctx, enableCameraIfUnderLimit,
arg.UserID,
arg.ChannelID,
arg.ChannelID_2,
arg.ChannelID_3,
)
}
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 int64 `json:"muted"`
Deafened int64 `json:"deafened"`
Speaking int64 `json:"speaking"`
Camera int64 `json:"camera"`
Screenshare int64 `json:"screenshare"`
JoinedAt string `json:"joinedAt"`
}
func (q *Queries) GetAllVoiceStates(ctx context.Context) ([]GetAllVoiceStatesRow, error) {
rows, err := q.db.QueryContext(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.Close(); err != nil {
return nil, err
}
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 = ?
ORDER BY vs.joined_at ASC
`
type GetChannelVoiceStatesRow struct {
UserID int64 `json:"userId"`
ChannelID int64 `json:"channelId"`
Username string `json:"username"`
Muted int64 `json:"muted"`
Deafened int64 `json:"deafened"`
Speaking int64 `json:"speaking"`
Camera int64 `json:"camera"`
Screenshare int64 `json:"screenshare"`
JoinedAt string `json:"joinedAt"`
}
func (q *Queries) GetChannelVoiceStates(ctx context.Context, channelID int64) ([]GetChannelVoiceStatesRow, error) {
rows, err := q.db.QueryContext(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.Close(); err != nil {
return nil, err
}
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 = ?
`
type GetUserVoiceStateRow struct {
UserID int64 `json:"userId"`
ChannelID int64 `json:"channelId"`
Username string `json:"username"`
Muted int64 `json:"muted"`
Deafened int64 `json:"deafened"`
Speaking int64 `json:"speaking"`
Camera int64 `json:"camera"`
Screenshare int64 `json:"screenshare"`
JoinedAt string `json:"joinedAt"`
}
func (q *Queries) GetUserVoiceState(ctx context.Context, userID int64) (GetUserVoiceStateRow, error) {
row := q.db.QueryRowContext(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 (?, ?, 0, 0, 0, 0, 0, ?)
ON CONFLICT(user_id) DO UPDATE SET
channel_id = excluded.channel_id,
muted = 0,
deafened = 0,
speaking = 0,
camera = 0,
screenshare = 0,
joined_at = excluded.joined_at
`
type JoinVoiceChannelParams struct {
UserID int64 `json:"userId"`
ChannelID int64 `json:"channelId"`
JoinedAt string `json:"joinedAt"`
}
func (q *Queries) JoinVoiceChannel(ctx context.Context, arg JoinVoiceChannelParams) error {
_, err := q.db.ExecContext(ctx, joinVoiceChannel, arg.UserID, arg.ChannelID, arg.JoinedAt)
return err
}
const joinVoiceChannelIfCapacity = `-- name: JoinVoiceChannelIfCapacity :execresult
INSERT INTO voice_states (user_id, channel_id, muted, deafened, speaking, camera, screenshare, joined_at)
SELECT ?, ?, 0, 0, 0, 0, 0, ?
WHERE (SELECT COUNT(*) FROM voice_states AS vs2 WHERE vs2.channel_id = ?) < ?
ON CONFLICT(user_id) DO UPDATE SET
channel_id = excluded.channel_id,
muted = 0,
deafened = 0,
speaking = 0,
camera = 0,
screenshare = 0,
joined_at = excluded.joined_at
`
type JoinVoiceChannelIfCapacityParams struct {
UserID int64 `json:"userId"`
ChannelID int64 `json:"channelId"`
JoinedAt string `json:"joinedAt"`
ChannelID_2 int64 `json:"channelId2"`
ChannelID_3 int64 `json:"channelId3"`
}
func (q *Queries) JoinVoiceChannelIfCapacity(ctx context.Context, arg JoinVoiceChannelIfCapacityParams) (sql.Result, error) {
return q.db.ExecContext(ctx, joinVoiceChannelIfCapacity,
arg.UserID,
arg.ChannelID,
arg.JoinedAt,
arg.ChannelID_2,
arg.ChannelID_3,
)
}
const leaveVoiceChannel = `-- name: LeaveVoiceChannel :exec
DELETE FROM voice_states WHERE user_id = ?
`
func (q *Queries) LeaveVoiceChannel(ctx context.Context, userID int64) error {
_, err := q.db.ExecContext(ctx, leaveVoiceChannel, userID)
return err
}
const leaveVoiceChannelIfMatch = `-- name: LeaveVoiceChannelIfMatch :execresult
DELETE FROM voice_states WHERE user_id = ? AND channel_id = ? AND joined_at = ?
`
type LeaveVoiceChannelIfMatchParams struct {
UserID int64 `json:"userId"`
ChannelID int64 `json:"channelId"`
JoinedAt string `json:"joinedAt"`
}
func (q *Queries) LeaveVoiceChannelIfMatch(ctx context.Context, arg LeaveVoiceChannelIfMatchParams) (sql.Result, error) {
return q.db.ExecContext(ctx, leaveVoiceChannelIfMatch, arg.UserID, arg.ChannelID, arg.JoinedAt)
}
const updateVoiceCamera = `-- name: UpdateVoiceCamera :exec
UPDATE voice_states SET camera = ? WHERE user_id = ?
`
type UpdateVoiceCameraParams struct {
Camera int64 `json:"camera"`
UserID int64 `json:"userId"`
}
func (q *Queries) UpdateVoiceCamera(ctx context.Context, arg UpdateVoiceCameraParams) error {
_, err := q.db.ExecContext(ctx, updateVoiceCamera, arg.Camera, arg.UserID)
return err
}
const updateVoiceDeafen = `-- name: UpdateVoiceDeafen :exec
UPDATE voice_states SET deafened = ? WHERE user_id = ?
`
type UpdateVoiceDeafenParams struct {
Deafened int64 `json:"deafened"`
UserID int64 `json:"userId"`
}
func (q *Queries) UpdateVoiceDeafen(ctx context.Context, arg UpdateVoiceDeafenParams) error {
_, err := q.db.ExecContext(ctx, updateVoiceDeafen, arg.Deafened, arg.UserID)
return err
}
const updateVoiceMute = `-- name: UpdateVoiceMute :exec
UPDATE voice_states SET muted = ? WHERE user_id = ?
`
type UpdateVoiceMuteParams struct {
Muted int64 `json:"muted"`
UserID int64 `json:"userId"`
}
func (q *Queries) UpdateVoiceMute(ctx context.Context, arg UpdateVoiceMuteParams) error {
_, err := q.db.ExecContext(ctx, updateVoiceMute, arg.Muted, arg.UserID)
return err
}
const updateVoiceScreenshare = `-- name: UpdateVoiceScreenshare :exec
UPDATE voice_states SET screenshare = ? WHERE user_id = ?
`
type UpdateVoiceScreenshareParams struct {
Screenshare int64 `json:"screenshare"`
UserID int64 `json:"userId"`
}
func (q *Queries) UpdateVoiceScreenshare(ctx context.Context, arg UpdateVoiceScreenshareParams) error {
_, err := q.db.ExecContext(ctx, updateVoiceScreenshare, arg.Screenshare, arg.UserID)
return err
}
const updateVoiceSpeaking = `-- name: UpdateVoiceSpeaking :exec
UPDATE voice_states SET speaking = ? WHERE user_id = ?
`
type UpdateVoiceSpeakingParams struct {
Speaking int64 `json:"speaking"`
UserID int64 `json:"userId"`
}
func (q *Queries) UpdateVoiceSpeaking(ctx context.Context, arg UpdateVoiceSpeakingParams) error {
_, err := q.db.ExecContext(ctx, updateVoiceSpeaking, arg.Speaking, arg.UserID)
return err
}
+53
View File
@@ -0,0 +1,53 @@
-- name: UserCount :one
SELECT COUNT(*) FROM users;
-- name: CountActiveMessages :one
SELECT COUNT(*) FROM messages WHERE deleted = 0;
-- name: CountChannels :one
SELECT COUNT(*) FROM channels;
-- name: CountActiveInvites :one
SELECT COUNT(*) FROM invites WHERE revoked = 0;
-- 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 ? OFFSET ?;
-- name: UpdateUserRole :exec
UPDATE users SET role_id = ? WHERE id = ?;
-- name: ForceLogoutUser :exec
DELETE FROM sessions WHERE user_id = ?;
-- name: GetUserSessions :many
SELECT id, user_id, token, device, ip_address, created_at, last_used, expires_at
FROM sessions WHERE user_id = ?
ORDER BY created_at DESC;
-- name: LogAudit :exec
INSERT INTO audit_log (actor_id, action, target_type, target_id, detail)
VALUES (?, ?, ?, ?, ?);
-- 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 ? OFFSET ?;
-- name: GetSetting :one
SELECT value FROM settings WHERE key = ?;
-- name: SetSetting :exec
INSERT INTO settings (key, value) VALUES (?, ?)
ON CONFLICT(key) DO UPDATE SET value = excluded.value;
-- name: GetAllSettings :many
SELECT key, value FROM settings;
+24
View File
@@ -0,0 +1,24 @@
-- name: CreateAttachment :exec
INSERT INTO attachments (id, uploader_id, filename, stored_as, mime_type, size, width, height)
VALUES (?, ?, ?, ?, ?, ?, ?, ?);
-- name: GetAttachmentByID :one
SELECT id, message_id, filename, stored_as, mime_type, size, uploaded_at, uploader_id
FROM attachments WHERE id = ?;
-- 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 = ?;
-- name: LinkAttachmentToMessage :execresult
UPDATE attachments SET message_id = ? WHERE id = ? AND message_id IS NULL;
-- name: DeleteOrphanedAttachments :many
DELETE FROM attachments WHERE message_id IS NULL AND uploaded_at < ? RETURNING stored_as;
-- name: DeleteAttachment :exec
DELETE FROM attachments WHERE id = ?;
+14
View File
@@ -0,0 +1,14 @@
-- name: BlockUser :exec
INSERT OR IGNORE INTO user_blocks (blocker_id, blocked_id) VALUES (?, ?);
-- name: UnblockUser :exec
DELETE FROM user_blocks WHERE blocker_id = ? AND blocked_id = ?;
-- name: IsBlocked :one
SELECT 1 FROM user_blocks WHERE blocker_id = ? AND blocked_id = ? LIMIT 1;
-- name: IsEitherBlocked :one
SELECT 1 FROM user_blocks
WHERE (blocker_id = ? AND blocked_id = ?)
OR (blocker_id = ? AND blocked_id = ?)
LIMIT 1;
+65
View File
@@ -0,0 +1,65 @@
-- 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 = ?;
-- name: CreateChannel :execresult
INSERT INTO channels (name, type, category, topic, position) VALUES (?, ?, ?, ?, ?);
-- name: UpdateChannel :exec
UPDATE channels SET name = ?, topic = ?, slow_mode = ? WHERE id = ?;
-- name: SetChannelSlowMode :exec
UPDATE channels SET slow_mode = ? WHERE id = ?;
-- name: SetChannelVoiceMaxUsers :exec
UPDATE channels SET voice_max_users = ? WHERE id = ?;
-- name: SetChannelVoiceMaxVideo :exec
UPDATE channels SET voice_max_video = ? WHERE id = ?;
-- name: SetChannelVoiceQuality :exec
UPDATE channels SET voice_quality = ? WHERE id = ?;
-- name: SetChannelMixingThreshold :exec
UPDATE channels SET mixing_threshold = ? WHERE id = ?;
-- name: ArchiveChannel :exec
UPDATE channels SET archived = ? WHERE id = ?;
-- name: DeleteChannel :exec
DELETE FROM channels WHERE id = ?;
-- name: AdminUpdateChannel :exec
UPDATE channels
SET name = ?, topic = ?, slow_mode = ?, position = ?, archived = ?
WHERE id = ?;
-- name: UpsertChannelPermission :exec
INSERT INTO channel_overrides (channel_id, role_id, allow, deny)
VALUES (?, ?, ?, ?)
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 = ? AND role_id = ?;
-- name: GetRoleChannelPermissions :many
SELECT channel_id, allow, deny FROM channel_overrides WHERE role_id = ?;
-- name: DeleteChannelPermission :exec
DELETE FROM channel_overrides WHERE channel_id = ? AND role_id = ?;
+53
View File
@@ -0,0 +1,53 @@
-- name: InsertDMChannel :execresult
INSERT INTO channels (name, type) VALUES ('', 'dm');
-- name: InsertDMParticipants :exec
INSERT INTO dm_participants (channel_id, user_id) VALUES (?, ?), (?, ?);
-- name: InsertDMOpenState :exec
INSERT OR IGNORE INTO dm_open_state (user_id, channel_id) VALUES (?, ?), (?, ?);
-- 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 = ? AND dp2.user_id = ? AND c.type = 'dm'
LIMIT 1;
-- name: OpenDM :exec
INSERT OR IGNORE INTO dm_open_state (user_id, channel_id) VALUES (?, ?);
-- name: CloseDM :exec
DELETE FROM dm_open_state WHERE user_id = ? AND channel_id = ?;
-- name: IsDMParticipant :one
SELECT user_id FROM dm_participants WHERE user_id = ? AND channel_id = ?;
-- name: GetDMParticipantIDs :many
SELECT user_id FROM dm_participants WHERE channel_id = ?;
-- 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, '') AS last_message_at,
COUNT(CASE WHEN m_unread.id > COALESCE(rs.last_message_id, 0)
AND m_unread.deleted = 0 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 != ?
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 = 0
)
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 = ?
WHERE dos.user_id = ?
GROUP BY c.id
ORDER BY COALESCE(lm.timestamp, dos.opened_at) DESC;
+19
View File
@@ -0,0 +1,19 @@
-- name: CreateInvite :exec
INSERT INTO invites (code, created_by, max_uses, expires_at) VALUES (?, ?, ?, ?);
-- name: GetInvite :one
SELECT id, code, created_by, max_uses, use_count, expires_at, revoked, created_at
FROM invites WHERE code = ?;
-- name: UseInviteAtomic :execresult
UPDATE invites SET use_count = use_count + 1
WHERE code = ? AND revoked = 0
AND (max_uses IS NULL OR use_count < max_uses)
AND (expires_at IS NULL OR strftime('%s', expires_at) > strftime('%s', 'now'));
-- name: RevokeInvite :exec
UPDATE invites SET revoked = 1 WHERE code = ?;
-- 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;
+11
View File
@@ -0,0 +1,11 @@
-- name: UpsertLockout :exec
INSERT OR REPLACE INTO rate_lockouts (key, expires_at) VALUES (?, ?);
-- name: LoadActiveLockouts :many
SELECT key, expires_at FROM rate_lockouts WHERE expires_at > ?;
-- name: CleanupExpiredLockouts :exec
DELETE FROM rate_lockouts WHERE expires_at <= ?;
-- name: DeleteLockout :exec
DELETE FROM rate_lockouts WHERE key = ?;
+74
View File
@@ -0,0 +1,74 @@
-- name: CreateMessage :execresult
INSERT INTO messages (channel_id, user_id, content, reply_to) VALUES (?, ?, ?, ?);
-- name: GetMessage :one
SELECT id, channel_id, user_id, content, reply_to, edited_at, deleted, pinned, timestamp
FROM messages WHERE id = ?;
-- 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 = ? AND m.id < ? AND m.deleted = 0
ORDER BY m.id DESC LIMIT ?;
-- 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 = ? AND m.deleted = 0
ORDER BY m.id DESC LIMIT ?;
-- 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 = ? AND m.id < ? AND m.deleted = 0
ORDER BY m.id DESC LIMIT ?;
-- 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 = ? AND m.deleted = 0
ORDER BY m.id DESC LIMIT ?;
-- 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 = ? AND m.pinned = 1 AND m.deleted = 0
ORDER BY m.id DESC;
-- name: EditMessageContent :exec
UPDATE messages SET content = ?, edited_at = datetime('now') WHERE id = ?;
-- name: SoftDeleteMessage :exec
UPDATE messages SET deleted = 1 WHERE id = ?;
-- name: SetMessagePinned :execresult
UPDATE messages SET pinned = ? WHERE id = ? AND deleted = 0;
-- name: GetLatestMessageID :one
SELECT COALESCE(MAX(id), 0) FROM messages WHERE channel_id = ? AND deleted = 0;
-- name: UpdateReadState :exec
INSERT INTO read_states (user_id, channel_id, last_message_id)
VALUES (?, ?, ?)
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) AS last_msg_id,
COUNT(CASE WHEN m.id > COALESCE(rs.last_message_id, 0) AND m.deleted = 0 THEN 1 END) AS unread
FROM channels c
LEFT JOIN messages m ON m.channel_id = c.id AND m.deleted = 0
LEFT JOIN read_states rs ON rs.channel_id = c.id AND rs.user_id = ?
WHERE c.type = 'text'
GROUP BY c.id;
-- SearchMessages and SearchMessagesInChannel use the messages_fts FTS5 virtual
-- table which sqlc cannot introspect. Those queries remain as hand-written Go
-- in message_queries.go.
+5
View File
@@ -0,0 +1,5 @@
-- name: UpdateUserProfile :execresult
UPDATE users SET username = ?, avatar = ? WHERE id = ?;
-- name: UpdateUserPassword :exec
UPDATE users SET password = ? WHERE id = ?;
+10
View File
@@ -0,0 +1,10 @@
-- name: AddReaction :exec
INSERT INTO reactions (message_id, user_id, emoji) VALUES (?, ?, ?);
-- name: RemoveReaction :execresult
DELETE FROM reactions WHERE message_id = ? AND user_id = ? AND emoji = ?;
-- name: GetReactionCounts :many
SELECT emoji, COUNT(*) AS count
FROM reactions WHERE message_id = ?
GROUP BY emoji;
+26
View File
@@ -0,0 +1,26 @@
-- name: GetRoleByID :one
SELECT id, name, color, permissions, position, is_default
FROM roles WHERE id = ?;
-- 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 = ?;
-- 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 = ?;
-- name: GetDefaultRole :one
SELECT id, name, color, permissions, position, is_default
FROM roles WHERE is_default = 1 LIMIT 1;
+43
View File
@@ -0,0 +1,43 @@
-- name: InsertSession :execresult
INSERT INTO sessions (user_id, token, device, ip_address, expires_at)
VALUES (?, ?, ?, ?, ?);
-- name: EvictOldestSessions :exec
DELETE FROM sessions WHERE id IN (
SELECT s2.id FROM sessions AS s2 WHERE s2.user_id = ?
ORDER BY s2.created_at DESC
LIMIT -1 OFFSET ?
);
-- name: GetSessionByTokenHash :one
SELECT id, user_id, token, device, ip_address, created_at, last_used, expires_at
FROM sessions WHERE token = ?;
-- 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 = ?;
-- name: DeleteSessionByToken :exec
DELETE FROM sessions WHERE token = ?;
-- name: DeleteSessionByID :exec
DELETE FROM sessions WHERE id = ? AND user_id = ?;
-- name: DeleteOtherSessions :execresult
DELETE FROM sessions WHERE user_id = ? AND id != ?;
-- name: DeleteExpiredSessions :exec
DELETE FROM sessions WHERE strftime('%s', expires_at) < strftime('%s', 'now');
-- name: TouchSession :exec
UPDATE sessions SET last_used = datetime('now') WHERE token = ?;
-- name: ListUserSessions :many
SELECT id, user_id, token, device, ip_address, created_at, last_used, expires_at
FROM sessions
WHERE user_id = ?
ORDER BY created_at DESC;
+41
View File
@@ -0,0 +1,41 @@
-- 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 = ? COLLATE NOCASE;
-- 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 = ?;
-- name: CreateUser :execresult
INSERT INTO users (username, password, role_id) VALUES (?, ?, ?);
-- name: UpdateUserStatus :exec
UPDATE users SET status = ?, last_seen = datetime('now') WHERE id = ?;
-- name: UpdateUserTOTPSecret :exec
UPDATE users SET totp_secret = ? WHERE id = ?;
-- name: ResetAllUserStatuses :exec
UPDATE users SET status = 'offline' WHERE status != 'offline';
-- name: BanUser :exec
UPDATE users SET banned = 1, ban_reason = ?, ban_expires = ? WHERE id = ?;
-- name: UnbanUser :exec
UPDATE users SET banned = 0, ban_reason = NULL, ban_expires = NULL WHERE id = ?;
-- 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 = 0
ORDER BY u.username ASC
LIMIT 1000;
-- name: CountUsers :one
SELECT COUNT(*) FROM users;
-- name: CountUsersWithoutTOTP :one
SELECT COUNT(*) FROM users WHERE banned = 0 AND totp_secret IS NULL;
+84
View File
@@ -0,0 +1,84 @@
-- name: JoinVoiceChannel :exec
INSERT INTO voice_states (user_id, channel_id, muted, deafened, speaking, camera, screenshare, joined_at)
VALUES (?, ?, 0, 0, 0, 0, 0, ?)
ON CONFLICT(user_id) DO UPDATE SET
channel_id = excluded.channel_id,
muted = 0,
deafened = 0,
speaking = 0,
camera = 0,
screenshare = 0,
joined_at = excluded.joined_at;
-- name: JoinVoiceChannelIfCapacity :execresult
INSERT INTO voice_states (user_id, channel_id, muted, deafened, speaking, camera, screenshare, joined_at)
SELECT ?, ?, 0, 0, 0, 0, 0, ?
WHERE (SELECT COUNT(*) FROM voice_states AS vs2 WHERE vs2.channel_id = ?) < ?
ON CONFLICT(user_id) DO UPDATE SET
channel_id = excluded.channel_id,
muted = 0,
deafened = 0,
speaking = 0,
camera = 0,
screenshare = 0,
joined_at = excluded.joined_at;
-- name: LeaveVoiceChannel :exec
DELETE FROM voice_states WHERE user_id = ?;
-- name: LeaveVoiceChannelIfMatch :execresult
DELETE FROM voice_states WHERE user_id = ? AND channel_id = ? AND joined_at = ?;
-- 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 = ?;
-- 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 = ?
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 = ? WHERE user_id = ?;
-- name: UpdateVoiceDeafen :exec
UPDATE voice_states SET deafened = ? WHERE user_id = ?;
-- name: UpdateVoiceSpeaking :exec
UPDATE voice_states SET speaking = ? WHERE user_id = ?;
-- name: UpdateVoiceCamera :exec
UPDATE voice_states SET camera = ? WHERE user_id = ?;
-- name: UpdateVoiceScreenshare :exec
UPDATE voice_states SET screenshare = ? WHERE user_id = ?;
-- name: EnableCameraIfUnderLimit :execresult
UPDATE voice_states SET camera = 1
WHERE voice_states.user_id = ? AND voice_states.channel_id = ?
AND (SELECT COUNT(*) FROM voice_states AS vs2 WHERE vs2.channel_id = ? AND vs2.camera = 1) < ?;
-- name: ClearVoiceState :exec
DELETE FROM voice_states WHERE user_id = ?;
-- name: ClearAllVoiceStates :exec
DELETE FROM voice_states;
-- name: CountActiveCameras :one
SELECT COUNT(*) FROM voice_states WHERE channel_id = ? AND camera = 1;
+1
View File
@@ -0,0 +1 @@
v1.30.0
+17
View File
@@ -0,0 +1,17 @@
version: "2"
sql:
- engine: "sqlite"
queries: "db/queries/sqlite"
schema: "migrations"
gen:
go:
package: "dbgen"
out: "db/dbgen"
sql_package: "database/sql"
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"
+148
View File
@@ -0,0 +1,148 @@
# OwnCord — Phase B: Acceleration
**Steps 68 | Weeks 614 | Community Scale Edition**
*Solid.js Frontend • Event Persistence • OpenTelemetry*
---
## Phase Overview
Phase B runs after the server foundation is in place. It tackles the three remaining infrastructure gaps: a proper frontend framework to accelerate UI development, event persistence for reliable reconnection at scale, and pluggable observability for community hub operators. These steps overlap with the tail end of Phase A since Step 6 is entirely client-side work.
The Solid.js migration (Step 6) is the highest-effort, highest-payoff change in the entire plan. Event persistence (Step 7) and OpenTelemetry (Step 8) are shorter server-side tasks that run in parallel. All three are required before the community hub milestone can ship.
### Timeline
| Step | Task | Duration | Depends On | Parallel? |
|------|------|----------|------------|-----------|
| 6 | Adopt Solid.js (incremental) | 46 weeks | — | Yes (client-side) |
| 7 | Event persistence layer | 12 weeks | Steps 1+3 | Yes (with 6, 8) |
| 8 | Add OpenTelemetry | 12 weeks | Step 1 | Yes (with 6, 7) |
### Milestone Gate
**After Phase B:** The client has a proper framework, reconnection is reliable at scale, and operators have pluggable monitoring. The community hub milestone (Milestone 2 from the parity plan) is shippable.
---
## Step 6: Adopt Solid.js Frontend Framework
**Effort:** High (46 weeks incremental) | **Impact:** Critical | **Phase:** B — Acceleration
### Problem
The vanilla TypeScript frontend has effectively become a custom framework: reactive stores, a component model, a dispatcher, virtual scrolling, manual DOM lifecycle management. Every new UI feature requires hand-wiring DOM creation, updates, and teardown. Community-scale features (role permission editors with tri-state per-channel overrides, audit log viewers with filtering, moderation dashboards, member lists grouped by role with presence indicators) are complex stateful UIs that are painful to build and maintain without a framework.
### Options
| Framework | Reactivity Model | Migration Path | Tauri Ecosystem | Verdict |
|-----------|-----------------|----------------|-----------------|---------|
| **Solid.js** | Signals, effects, memos — maps directly to existing store pattern | Incremental: wrap stores as signals, convert components one by one | Growing. Solid + Tauri examples available. Vite integration native. | **RECOMMENDED** |
| Svelte 5 | Runes (compile-time reactivity). Simple component model, no virtual DOM. | Requires rewriting components in .svelte files. Larger upfront cost. | Good. Tauri + Svelte is well-documented. | VIABLE |
| React | Virtual DOM reconciliation. useState/useEffect hooks. Largest ecosystem. | Full rewrite into JSX components. Hooks model differs from current stores. | Excellent. Most Tauri projects use React. Huge community. | VIABLE |
| Vue 3 | Composition API with ref/reactive. Template-based. Good DX. | Moderate. Composition API maps reasonably to stores. | Good. Tauri + Vue works well. | VIABLE |
| Stay vanilla | Custom reactive stores + manual DOM. Full control, full burden. | No migration. But every new feature carries the full cost. | N/A | AVOID |
### Why Solid.js
Solid is the recommended choice because it has the lowest migration cost and the closest philosophical match to what OwnCord already has. Your existing reactive stores are conceptually identical to Solid signals. Solid compiles to direct DOM operations with no virtual DOM overhead, so performance stays where it is now. The migration is incremental: wrap existing stores as Solid signals, convert one component at a time, keep the WebSocket dispatcher and LiveKit facade untouched since they are framework-agnostic.
### Pros & Cons (Solid.js)
| Pros | Cons |
|------|------|
| Signals map 1:1 to existing store pattern | Smaller community than React or Vue |
| No virtual DOM — same performance as vanilla | Fewer off-the-shelf UI component libraries |
| Incremental migration (component by component) | Team must learn Solid's reactivity rules |
| Proper component lifecycle and error boundaries | Some vanilla patterns don't translate directly |
| Strong TypeScript support (first-class) | Migration is still weeks of work even incrementally |
| Small bundle size (~7KB gzipped) | |
| Vite integration is native (no config changes) | |
### Migration Strategy
1. Install Solid.js and configure Vite for JSX/TSX. Keep all existing code working — Solid and vanilla coexist.
2. Wrap existing stores as Solid signals using a thin adapter. This lets new Solid components read from existing state immediately.
3. Convert one leaf component (a simple, self-contained UI element) to Solid as a proof of concept. Validate that it works in the Tauri WebView.
4. Migrate components bottom-up: leaf components first, then containers. Each converted component is a self-contained PR.
5. Leave framework-agnostic code (dispatcher, LiveKit facade, API client, crypto) untouched. These don't need to change.
6. Once all components are migrated, remove the old DOM manipulation utilities and the custom component model.
---
## Step 7: Event Persistence Layer
> **NEW IN V2** — This step was a deferred TODO in v1. At community scale the 1000-event ring buffer is insufficient — 100 active users can burn through it in minutes.
**Effort:** Medium (12 weeks) | **Impact:** High | **Phase:** B — Acceleration
### Problem
The current reconnection protocol uses a 1000-event in-memory ring buffer. When a client reconnects, the server replays missed events from the buffer. At 100+ active users, a busy server generates 1000 events in minutes. Any user who goes offline for 1015 minutes will find their last_seq is no longer in the buffer, forcing a full ready re-sync. That re-sync serializes all channels, permissions, member lists, and presence state — an expensive operation that gets more expensive with more users and channels.
### Solution
Implement a tiered event persistence model. Keep the in-memory ring buffer for hot events (last ~60 seconds). Write events to the database (via the Store interface from Step 3) for cold replay. On reconnect, the server first checks the ring buffer. If last_seq is too old, it queries the database for events since last_seq up to a reasonable limit. Only if both are exhausted does it fall back to a full ready re-sync. Events in the database are pruned after a configurable retention period (default: 24 hours).
### Options
| Option | Details | Verdict |
|--------|---------|---------|
| **Tiered (buffer + DB)** | Ring buffer for hot path, database for cold replay. Best balance of performance and reliability. Uses existing Store interface. | **RECOMMENDED** |
| Larger ring buffer | Increase from 1000 to 50000 events in memory. Simple but uses significant RAM and is still lossy. | VIABLE |
| External event log | Use NATS JetStream or Redis Streams. Robust but adds an external dependency to self-hosted deployments. | AVOID |
| Status quo | Keep 1000-event buffer. Works for friend groups, fails at community scale. | AVOID |
### Pros & Cons (Tiered)
| Pros | Cons |
|------|------|
| Hot path stays fast (in-memory ring buffer) | Event writes add DB load (mitigated by batching) |
| Cold replay from DB handles longer disconnections | Must handle event serialization/deserialization |
| Full re-sync becomes rare instead of common | Retention pruning adds a background job |
| Uses existing Store interface — no new dependencies | Query performance matters — needs indexed seq column |
| Configurable retention (24h default) keeps DB size bounded | Two code paths for replay (buffer vs DB) add complexity |
| Architecture was already designed to be persistence-ready | |
### Implementation Plan
1. Add an events table to the schema (both SQLite and PostgreSQL migrations): seq INTEGER PRIMARY KEY, event_type TEXT, payload BLOB, channel_id TEXT, created_at TIMESTAMP. Index on (channel_id, seq).
2. Add PersistEvent and GetEventsSince methods to the Store interface. The SQLite implementation uses INSERT with write batching (flush every 100ms or 50 events, whichever comes first).
3. Modify the reconnection handler: check ring buffer first, then query DB, then fall back to full re-sync. Each tier returns the events it can plus a "complete" flag.
4. Add a background goroutine that prunes events older than the retention period (configurable, default 24h). Runs every hour.
5. Add metrics for reconnection tier hits (buffer/db/full) to feed into OpenTelemetry (Step 8).
---
## Step 8: Add OpenTelemetry for Observability
> **MOVED UP FROM PHASE C** — This step was Phase C in v1. Community hub operators expect pluggable monitoring from day one.
**Effort:** Medium (12 weeks) | **Impact:** High | **Phase:** B — Acceleration
### Problem
The current observability is custom: a /metrics endpoint with hand-picked counters, structured logging across two libraries, and client-side JSONL logs. Community hub operators running servers for 100+ people need plug-and-play compatibility with their existing monitoring stack (Prometheus, Grafana, Datadog). They also need distributed tracing to diagnose slow requests when users report latency.
### Solution
Integrate the OpenTelemetry Go SDK. Instrument the Chi router middleware for automatic HTTP tracing, add spans to service layer methods, and export metrics in Prometheus format via OTLP. Self-hosters get plug-and-play compatibility with whatever monitoring they already run. This replaces the custom /metrics endpoint with an industry-standard exporter.
### What to Instrument
- **HTTP layer:** Chi middleware for automatic request tracing — method, path, status code, duration. This is a single middleware addition.
- **Service layer:** Spans around key service methods (CreateMessage, EditMessage, CheckPermission, etc.). This shows where time is spent in business logic.
- **Database layer:** Query timing and connection pool metrics. Shows when the DB is the bottleneck.
- **WebSocket layer:** Custom metrics for messages/second, active connections, broadcast latency, reconnection rates (by tier from Step 7).
- **LiveKit:** Voice session metrics — active sessions, participant count, connection quality distribution.
### Pros & Cons
| Pros | Cons |
|------|------|
| Self-hosters plug into existing monitoring (Grafana, Datadog, etc.) | OTel SDK adds ~5MB to binary size |
| Distributed tracing across REST → Service → DB | Tracing overhead (small but nonzero) on every request |
| Replaces custom /metrics with industry-standard export | Configuration complexity for exporters |
| Chi and database drivers have OTel middleware available | Additional Docker Compose services (collector) for full setup |
| Essential for community hub operators to diagnose issues | |
+139
View File
@@ -0,0 +1,139 @@
# OwnCord — Phase C: Differentiation
**Step 9 | When Phase 6 Features Start | Community Scale Edition**
*Wazero Plugin Runtime • Game Detection • Server Browser • Stats*
---
## Phase Overview
Phase C is about what makes OwnCord different from every other Discord alternative. The plugin system is the core differentiator — game detection, server browser, stats, leaderboards, screenshots. All of these are implemented as plugins, not core features, which keeps the server lean while enabling an ecosystem.
This phase has a single step: implementing the plugin runtime using Wazero. It waits until the plugin features from the product roadmap (Phase 6) are actively being built. There is no urgency to implement the runtime before the features that use it are in scope. The server foundation from Phase A and the client framework from Phase B must be in place first.
### Timeline
| Step | Task | Duration | Depends On | Parallel? |
|------|------|----------|------------|-----------|
| 9 | Wazero plugin runtime | 46 weeks | Step 1 | When Phase 6 starts |
### Milestone Gate
**After Phase C:** The plugin runtime enables the gaming features that differentiate OwnCord from every other Discord alternative. Game detection, rich presence, playtime tracking, server browser, stats, and leaderboards all run as sandboxed WASM plugins. Third-party developers can build and distribute plugins through the plugin marketplace.
### Prerequisites
- **Phase A complete:** The service layer (Step 1) provides the host API surface that plugins call into. The Store interface (Step 3) provides plugin-scoped storage. The pub/sub hub (Step 5) enables plugins to emit events to subscribed clients.
- **Phase B complete:** The Solid.js frontend (Step 6) enables plugin UI tabs and widgets. OpenTelemetry (Step 8) provides plugin performance monitoring.
---
## Step 9: Use Wazero for Plugin Runtime
**Effort:** High (46 weeks) | **Impact:** High | **Phase:** C — Differentiation
### Problem
The plugin system is OwnCord's core differentiator — game detection, server browser, stats, leaderboards — but the runtime is only designed, not implemented. The design mentions WASM or shared library loading. Shared libraries (.so/.dll) offer no sandboxing, crash the host process on failure, and are platform-specific. At community scale, a misbehaving plugin taking down a 100-user server is unacceptable.
### Options
| Option | Details | Verdict |
|--------|---------|---------|
| **Wazero** | Pure-Go WebAssembly runtime. No CGO. Memory-sandboxed, resource-limited, language-agnostic plugin authoring (Rust, Go, C, AssemblyScript). | **RECOMMENDED** |
| Extism | Plugin framework built on Wazero. Higher-level API, easier plugin development. Adds a dependency layer on top of Wazero. | VIABLE |
| Shared libs | Native .so/.dll loading via Go plugin package. No sandboxing, platform-specific, crashes take down the server. | AVOID |
| gRPC sidecar | Plugins as separate processes communicating via gRPC. Strong isolation but heavy: each plugin is a full process with its own lifecycle. | VIABLE |
### Why Wazero
Wazero is the recommended choice because it is pure Go (no CGO, matching the existing constraint), provides real memory sandboxing, supports resource limits per plugin (CPU and memory caps), and enables language-agnostic plugin development. Plugins compile to WASM from Rust, Go, C, or AssemblyScript. A crashing plugin cannot take down the server. At community scale this isolation is non-negotiable — you cannot allow a third-party plugin to crash a server serving 100+ users.
Extism is a viable alternative if you want a higher-level abstraction over Wazero. It simplifies plugin authoring with PDKs (Plugin Development Kits) for multiple languages and handles memory management between host and guest. The tradeoff is an additional dependency and less control over the low-level WASM runtime. For OwnCord's plugin use cases (game detection, server queries, stats computation), the raw Wazero API is sufficient and more transparent.
### Pros & Cons (Wazero)
| Pros | Cons |
|------|------|
| Pure Go — no CGO, matches existing constraint | WASM has limited I/O capabilities by design |
| Real memory sandboxing (plugins can't crash server) | Plugin performance is slower than native (~25x) |
| Resource limits per plugin (CPU, memory) | Debugging WASM plugins is harder than native code |
| Language-agnostic: Rust, Go, C, AssemblyScript → WASM | Plugin authors must learn WASM toolchain |
| Well-defined host API for plugin ↔ server communication | Complex host API design for game detection, UI tabs, etc. |
| Critical for community scale — isolation is non-negotiable | |
---
## Architecture
The plugin runtime has four components: the loader, the host API, the sandbox, and the client bridge.
### Plugin Loader
Reads plugin.toml manifests, validates declared permissions, loads the .wasm binary into a Wazero runtime instance. Each plugin gets its own isolated module with a dedicated memory space. The loader handles plugin lifecycle: install, enable, disable, uninstall, update.
### Host API
The server exposes functions that plugins can call through WASM imports. These are the capabilities a plugin requests in its manifest:
- **commands:** Register slash commands (/playtime, /serverstatus, /stats). The command dispatcher routes user input to the owning plugin.
- **events:** Subscribe to server events (message_send, user_join, voice_join). The pub/sub hub (Step 5) delivers subscribed events to the plugin.
- **storage:** Plugin-scoped key-value storage via the Store interface (Step 3). Each plugin gets its own namespace. No cross-plugin data access.
- **http:** Outbound HTTP requests (for querying game servers, APIs). Proxied through the server with configurable allowlists per plugin.
- **ui:** Register UI tabs and widgets that render in the Solid.js client (Step 6). Plugin declares HTML/JS assets, client renders them in an iframe sandbox.
### Sandbox
Each plugin instance runs with enforced limits: maximum memory allocation (default 64MB), CPU time budget per invocation (default 100ms), and no direct filesystem or network access. The server monitors resource usage and kills plugins that exceed their budget. A crashed or killed plugin is automatically disabled and the admin is notified via the mod log channel.
### Client Bridge
Plugins that declare UI capabilities get a rendering surface in the Solid.js client. Plugin UI runs in a sandboxed iframe with postMessage communication to the host client. The host provides a theme-aware CSS injection so plugin UIs match OwnCord's look and feel. The client bridge also handles plugin-specific settings panels.
---
## Implementation Plan
1. Define the plugin.toml manifest format: name, version, author, permissions (commands, events, storage, http, ui), resource limits. Validate against a JSON schema.
2. Implement the Wazero runtime wrapper: module loading, memory allocation, function imports/exports, lifecycle management (start, stop, restart).
3. Implement the host API functions one capability at a time. Start with commands (simplest — input/output only), then events (requires pub/sub integration), then storage, then HTTP.
4. Build the first plugin: game detection. This exercises commands (user queries playtime), events (presence updates), storage (playtime database), and HTTP (Steam API queries). If game detection works, the architecture is validated.
5. Add the UI capability: client-side iframe sandbox, postMessage bridge, theme injection. Build the server browser plugin to validate the UI integration.
6. Implement plugin marketplace: browse available plugins, install/update/remove from within the admin panel. Plugin packages are .wasm + assets in a zip archive hosted on a registry (GitHub Releases initially).
---
## Complete Timeline — All Phases
For reference, here is the complete execution timeline across all three phases.
### Phase A: Foundation (Weeks 17)
| Step | Task | Duration | Depends On | Parallel? |
|------|------|----------|------------|-----------|
| 1 | Extract service/domain layer + permission cache | 23 weeks | — | No |
| 2 | Adopt sqlc | 1 week | — | Yes (with 1) |
| 3 | Abstract DB + PostgreSQL target | 23 weeks | Steps 1+2 | No |
| 4 | Consolidate logging | 12 days | — | Yes (anytime) |
| 5 | Refactor hub to pub/sub + global rate limits | 23 weeks | Step 1 | After Step 1 |
### Phase B: Acceleration (Weeks 614)
| Step | Task | Duration | Depends On | Parallel? |
|------|------|----------|------------|-----------|
| 6 | Adopt Solid.js (incremental) | 46 weeks | — | Yes (client-side) |
| 7 | Event persistence layer | 12 weeks | Steps 1+3 | Yes (with 6, 8) |
| 8 | Add OpenTelemetry | 12 weeks | Step 1 | Yes (with 6, 7) |
### Phase C: Differentiation (When Phase 6 Features Start)
| Step | Task | Duration | Depends On | Parallel? |
|------|------|----------|------------|-----------|
| 9 | Wazero plugin runtime | 46 weeks | Step 1 | When ready |
### Total
**1418 weeks** for Steps 18. Step 9 is deferred until plugin features are in scope. Phases overlap, so calendar time is shorter than the sum of estimates.
The central principle: stop building infrastructure, start using infrastructure. Every hour spent maintaining a custom query layer, a custom component model, a custom broadcast loop, or a custom event buffer is an hour not spent on the 146 features that make OwnCord compete with Discord.