mirror of
https://github.com/J3vb/OwnCord.git
synced 2026-09-02 19:43:10 +03:00
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:
@@ -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 ; \
|
||||
)
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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"`
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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 = ?;
|
||||
@@ -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;
|
||||
@@ -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 = ?;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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 = ?;
|
||||
@@ -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.
|
||||
@@ -0,0 +1,5 @@
|
||||
-- name: UpdateUserProfile :execresult
|
||||
UPDATE users SET username = ?, avatar = ? WHERE id = ?;
|
||||
|
||||
-- name: UpdateUserPassword :exec
|
||||
UPDATE users SET password = ? WHERE id = ?;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -0,0 +1 @@
|
||||
v1.30.0
|
||||
@@ -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"
|
||||
@@ -0,0 +1,148 @@
|
||||
# OwnCord — Phase B: Acceleration
|
||||
|
||||
**Steps 6–8 | Weeks 6–14 | 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) | 4–6 weeks | — | Yes (client-side) |
|
||||
| 7 | Event persistence layer | 1–2 weeks | Steps 1+3 | Yes (with 6, 8) |
|
||||
| 8 | Add OpenTelemetry | 1–2 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 (4–6 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 (1–2 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 10–15 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 (1–2 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 | |
|
||||
@@ -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 | 4–6 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 (4–6 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 (~2–5x) |
|
||||
| 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 1–7)
|
||||
|
||||
| Step | Task | Duration | Depends On | Parallel? |
|
||||
|------|------|----------|------------|-----------|
|
||||
| 1 | Extract service/domain layer + permission cache | 2–3 weeks | — | No |
|
||||
| 2 | Adopt sqlc | 1 week | — | Yes (with 1) |
|
||||
| 3 | Abstract DB + PostgreSQL target | 2–3 weeks | Steps 1+2 | No |
|
||||
| 4 | Consolidate logging | 1–2 days | — | Yes (anytime) |
|
||||
| 5 | Refactor hub to pub/sub + global rate limits | 2–3 weeks | Step 1 | After Step 1 |
|
||||
|
||||
### Phase B: Acceleration (Weeks 6–14)
|
||||
|
||||
| Step | Task | Duration | Depends On | Parallel? |
|
||||
|------|------|----------|------------|-----------|
|
||||
| 6 | Adopt Solid.js (incremental) | 4–6 weeks | — | Yes (client-side) |
|
||||
| 7 | Event persistence layer | 1–2 weeks | Steps 1+3 | Yes (with 6, 8) |
|
||||
| 8 | Add OpenTelemetry | 1–2 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 | 4–6 weeks | Step 1 | When ready |
|
||||
|
||||
### Total
|
||||
|
||||
**14–18 weeks** for Steps 1–8. 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.
|
||||
Reference in New Issue
Block a user