feat: add fluxer upstream source and self-hosting documentation

- Clone of github.com/fluxerapp/fluxer (official upstream)
- SELF_HOSTING.md: full VM rebuild procedure, architecture overview,
  service reference, step-by-step setup, troubleshooting, seattle reference
- dev/.env.example: all env vars with secrets redacted and generation instructions
- dev/livekit.yaml: LiveKit config template with placeholder keys
- fluxer-seattle/: existing seattle deployment setup scripts
This commit is contained in:
Vish
2026-03-13 00:55:14 -07:00
parent 5ceda343b8
commit 3b9d759b4b
5859 changed files with 1923440 additions and 0 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,457 @@
/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import {mkdirSync} from 'node:fs';
import {dirname} from 'node:path';
import {DatabaseSync, type StatementSync} from 'node:sqlite';
import {Config} from '@fluxer/api/src/Config';
import {Logger} from '@fluxer/api/src/Logger';
export interface KvEntry<T = Record<string, unknown>> {
key: string;
table: string;
value: T;
expiresAt: number | null;
}
interface SqliteExecutor {
exec(sql: string): void;
}
export function executeSqliteTransaction<T>(db: SqliteExecutor, fn: () => T): T {
db.exec('BEGIN');
try {
const result = fn();
db.exec('COMMIT');
return result;
} catch (error) {
try {
db.exec('ROLLBACK');
} catch {}
throw error;
}
}
function upperBound(prefix: string): string {
return `${prefix}\u{10FFFF}`;
}
const FLUXER_TAG = '__fluxer__' as const;
const FLUXER_TAG_VERSION = 1 as const;
type FluxerTagType = 'bigint' | 'date' | 'set' | 'map' | 'buffer' | 'uint8array';
type FluxerTagged =
| {[FLUXER_TAG]: {v: typeof FLUXER_TAG_VERSION; t: 'bigint'; d: string}}
| {[FLUXER_TAG]: {v: typeof FLUXER_TAG_VERSION; t: 'date'; d: string}}
| {[FLUXER_TAG]: {v: typeof FLUXER_TAG_VERSION; t: 'set'; d: Array<unknown>}}
| {[FLUXER_TAG]: {v: typeof FLUXER_TAG_VERSION; t: 'map'; d: Array<[unknown, unknown]>}}
| {[FLUXER_TAG]: {v: typeof FLUXER_TAG_VERSION; t: 'buffer'; d: string}}
| {[FLUXER_TAG]: {v: typeof FLUXER_TAG_VERSION; t: 'uint8array'; d: string}};
class FluxerSerialisationError extends Error {
override name = 'FluxerSerialisationError';
}
class FluxerDeserialisationError extends Error {
override name = 'FluxerDeserialisationError';
}
function isPlainObject(value: unknown): value is Record<string, unknown> {
if (value === null || typeof value !== 'object') return false;
const proto = Object.getPrototypeOf(value);
return proto === Object.prototype || proto === null;
}
function tag<T extends FluxerTagged[typeof FLUXER_TAG]['t']>(
t: T,
d: Extract<FluxerTagged, {[FLUXER_TAG]: {t: T}}>['__fluxer__']['d'],
): Extract<FluxerTagged, {[FLUXER_TAG]: {t: T}}> {
return {[FLUXER_TAG]: {v: FLUXER_TAG_VERSION, t, d}} as Extract<FluxerTagged, {[FLUXER_TAG]: {t: T}}>;
}
function isKnownTagType(t: unknown): t is FluxerTagType {
return t === 'bigint' || t === 'date' || t === 'set' || t === 'map' || t === 'buffer' || t === 'uint8array';
}
function assertNever(_x: never, msg: string): never {
throw new FluxerDeserialisationError(msg);
}
function fluxerReplacer(this: unknown, key: string, value: unknown): unknown {
const holder = this as Record<string, unknown> | null;
const original = key === '' ? value : holder?.[key];
if (typeof original === 'bigint') {
return tag('bigint', original.toString());
}
if (original instanceof Date) {
return tag('date', original.toISOString());
}
if (original instanceof Set) {
return tag('set', Array.from(original));
}
if (original instanceof Map) {
return tag('map', Array.from(original.entries()));
}
if (Buffer.isBuffer(original)) {
return tag('buffer', original.toString('base64'));
}
if (original instanceof Uint8Array && !Buffer.isBuffer(original)) {
return tag('uint8array', Buffer.from(original).toString('base64'));
}
return value;
}
function decodeTagged(meta: unknown): unknown {
if (!isPlainObject(meta)) {
throw new FluxerDeserialisationError('Malformed __fluxer__ tag: meta is not an object');
}
const v = meta['v'];
const t = meta['t'];
const d = meta['d'];
if (v !== FLUXER_TAG_VERSION) {
throw new FluxerDeserialisationError(
`Unsupported __fluxer__ tag version: ${String(v)} (expected ${String(FLUXER_TAG_VERSION)})`,
);
}
if (!isKnownTagType(t)) {
throw new FluxerDeserialisationError(`Unknown __fluxer__ tag type: ${String(t)}`);
}
switch (t) {
case 'bigint': {
if (typeof d !== 'string') throw new FluxerDeserialisationError('Malformed bigint tag: d must be a string');
try {
return BigInt(d);
} catch (_e) {
throw new FluxerDeserialisationError(`Invalid bigint payload: ${String(d)}`);
}
}
case 'date': {
if (typeof d !== 'string') throw new FluxerDeserialisationError('Malformed date tag: d must be a string');
const dt = new Date(d);
if (Number.isNaN(dt.getTime())) {
throw new FluxerDeserialisationError(`Invalid date payload: ${String(d)}`);
}
return dt;
}
case 'set': {
if (!Array.isArray(d)) throw new FluxerDeserialisationError('Malformed set tag: d must be an array');
return new Set(d);
}
case 'map': {
if (!Array.isArray(d)) throw new FluxerDeserialisationError('Malformed map tag: d must be an array');
for (const entry of d) {
if (!Array.isArray(entry) || entry.length !== 2) {
throw new FluxerDeserialisationError('Malformed map tag: every entry must be a [k, v] tuple');
}
}
return new Map(d as Array<[unknown, unknown]>);
}
case 'buffer': {
if (typeof d !== 'string') throw new FluxerDeserialisationError('Malformed buffer tag: d must be a string');
try {
return Buffer.from(d, 'base64');
} catch {
throw new FluxerDeserialisationError('Invalid buffer payload: base64 decode failed');
}
}
case 'uint8array': {
if (typeof d !== 'string') throw new FluxerDeserialisationError('Malformed uint8array tag: d must be a string');
let buf: Buffer;
try {
buf = Buffer.from(d, 'base64');
} catch {
throw new FluxerDeserialisationError('Invalid uint8array payload: base64 decode failed');
}
return new Uint8Array(buf);
}
default:
return assertNever(t, `Unhandled __fluxer__ tag type: ${String(t)}`);
}
}
function fluxerReviver(_key: string, value: unknown): unknown {
if (isPlainObject(value)) {
const keys = Object.keys(value);
if (keys.length === 1 && keys[0] === FLUXER_TAG) {
const meta = (value as Record<string, unknown>)[FLUXER_TAG];
return decodeTagged(meta);
}
if (value['type'] === 'Buffer') {
const data = (value as Record<string, unknown>)['data'];
if (!Array.isArray(data)) throw new FluxerDeserialisationError('Malformed Buffer JSON: data must be an array');
for (const n of data) {
if (typeof n !== 'number' || !Number.isInteger(n) || n < 0 || n > 255) {
throw new FluxerDeserialisationError('Malformed Buffer JSON: data must be byte integers 0..255');
}
}
return Buffer.from(data as Array<number>);
}
}
return value;
}
function serialize(value: unknown): Buffer {
try {
const json = JSON.stringify(value, fluxerReplacer);
if (json === undefined) {
throw new FluxerSerialisationError('Value is not JSON serialisable');
}
return Buffer.from(json, 'utf8');
} catch (e) {
if (e instanceof FluxerSerialisationError) throw e;
throw new FluxerSerialisationError(`Failed to serialise value: ${e instanceof Error ? e.message : String(e)}`);
}
}
function deserialize<T>(blob: Buffer | Uint8Array | string): T {
const text =
typeof blob === 'string'
? blob
: Buffer.isBuffer(blob)
? blob.toString('utf8')
: Buffer.from(blob).toString('utf8');
try {
return JSON.parse(text, fluxerReviver) as T;
} catch (e) {
if (e instanceof FluxerDeserialisationError) throw e;
throw new FluxerDeserialisationError(`Failed to deserialise value: ${e instanceof Error ? e.message : String(e)}`);
}
}
type KvRow = {
key: string;
value: Uint8Array | Buffer | string;
expires_at: number | null;
};
class SqliteKvStore {
private db: DatabaseSync;
private purgeExpiredStmt: StatementSync;
private putStmt: StatementSync;
private getStmt: StatementSync;
private deleteStmt: StatementSync;
private deletePrefixStmt: StatementSync;
private scanPrefixStmt: StatementSync;
private scanAllStmt: StatementSync;
constructor(path: string) {
this.db = new DatabaseSync(path);
this.db.exec(`
PRAGMA journal_mode = WAL;
PRAGMA synchronous = NORMAL;
PRAGMA busy_timeout = 5000;
CREATE TABLE IF NOT EXISTS kv_store (
table_name TEXT NOT NULL,
key TEXT NOT NULL,
value BLOB NOT NULL,
expires_at INTEGER,
PRIMARY KEY(table_name, key)
) WITHOUT ROWID;
CREATE INDEX IF NOT EXISTS kv_store_expires_idx ON kv_store(expires_at);
`);
this.purgeExpiredStmt = this.db.prepare(`DELETE FROM kv_store WHERE expires_at IS NOT NULL AND expires_at <= ?;`);
this.putStmt = this.db.prepare(`
INSERT INTO kv_store (table_name, key, value, expires_at)
VALUES (?, ?, ?, ?)
ON CONFLICT(table_name, key) DO UPDATE SET
value = excluded.value,
expires_at = excluded.expires_at;
`);
this.getStmt = this.db.prepare(`SELECT value, expires_at FROM kv_store WHERE table_name = ? AND key = ?;`);
this.deleteStmt = this.db.prepare(`DELETE FROM kv_store WHERE table_name = ? AND key = ?;`);
this.deletePrefixStmt = this.db.prepare(`DELETE FROM kv_store WHERE table_name = ? AND key >= ? AND key < ?;`);
this.scanPrefixStmt = this.db.prepare(`
SELECT key, value, expires_at
FROM kv_store
WHERE table_name = ? AND key >= ? AND key < ?
ORDER BY key ASC;
`);
this.scanAllStmt = this.db.prepare(`
SELECT key, value, expires_at
FROM kv_store
WHERE table_name = ?
ORDER BY key ASC;
`);
}
private purgeExpired(now: number): void {
this.purgeExpiredStmt.run(now);
}
private runInTransaction<T>(fn: () => T): T {
return executeSqliteTransaction(this.db, fn);
}
put(table: string, key: string, value: unknown, ttlSeconds?: number): void {
let expiresAt: number | null = null;
if (ttlSeconds !== undefined) {
if (!Number.isFinite(ttlSeconds)) throw new TypeError('ttlSeconds must be a finite number');
if (ttlSeconds <= 0) {
expiresAt = Date.now();
} else {
const ms = ttlSeconds * 1000;
const now = Date.now();
const sum = now + ms;
expiresAt = Number.isSafeInteger(sum) ? sum : Number.MAX_SAFE_INTEGER;
}
}
this.putStmt.run(table, key, serialize(value), expiresAt);
}
get<T>(table: string, key: string): T | null {
const row = this.getStmt.get(table, key) as
| {value: Uint8Array | Buffer | string; expires_at: number | null}
| undefined;
if (!row) return null;
const now = Date.now();
if (row.expires_at !== null && row.expires_at <= now) {
this.delete(table, key);
return null;
}
return deserialize<T>(row.value);
}
delete(table: string, key: string): void {
this.deleteStmt.run(table, key);
}
deletePrefix(table: string, prefix: string): void {
this.deletePrefixStmt.run(table, prefix, upperBound(prefix));
}
scan<T>(table: string, prefix?: string): Array<KvEntry<T>> {
const now = Date.now();
return this.runInTransaction(() => {
this.purgeExpired(now);
const rows: Array<KvRow> = prefix
? (this.scanPrefixStmt.all(table, prefix, upperBound(prefix)) as Array<KvRow>)
: (this.scanAllStmt.all(table) as Array<KvRow>);
const out: Array<KvEntry<T>> = [];
for (const row of rows) {
if (row.expires_at !== null && row.expires_at <= now) continue;
out.push({
key: row.key,
table,
value: deserialize<T>(row.value),
expiresAt: row.expires_at,
});
}
return out;
});
}
clearAll(): void {
executeSqliteTransaction(this.db, () => {
this.db.exec('DELETE FROM kv_store;');
return undefined;
});
}
getDatabase(): DatabaseSync {
return this.db;
}
}
let kvStore: SqliteKvStore | null = null;
export function getKvStore(): SqliteKvStore {
if (!kvStore) {
const path = Config.database?.sqlitePath ?? ':memory:';
if (path !== ':memory:') {
mkdirSync(dirname(path), {recursive: true});
}
Logger.info({path}, 'Initialising SQLite KV backend');
kvStore = new SqliteKvStore(path);
}
return kvStore;
}
export function clearSqliteStore(): void {
const store = getKvStore();
store.clearAll();
}
function encodeKeyPart(part: unknown): string {
if (part === null) return encodeURIComponent('null');
if (part === undefined) return encodeURIComponent('undefined');
switch (typeof part) {
case 'string':
case 'number':
case 'boolean':
case 'bigint':
return encodeURIComponent(String(part));
default: {
const normalised = part instanceof Uint8Array && !(part instanceof Buffer) ? Buffer.from(part) : part;
return encodeURIComponent(serialize(normalised).toString('utf8'));
}
}
}
export function encodeKey(parts: ReadonlyArray<unknown>): string {
return parts.map(encodeKeyPart).join('|');
}

View File

@@ -0,0 +1,94 @@
/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
export interface AdminArchiveRow {
subject_type: 'user' | 'guild';
subject_id: bigint;
archive_id: bigint;
requested_by: bigint;
requested_at: Date;
started_at: Date | null;
completed_at: Date | null;
failed_at: Date | null;
storage_key: string | null;
file_size: bigint | null;
progress_percent: number;
progress_step: string | null;
error_message: string | null;
download_url_expires_at: Date | null;
expires_at: Date | null;
}
export const ADMIN_ARCHIVE_COLUMNS = [
'subject_type',
'subject_id',
'archive_id',
'requested_by',
'requested_at',
'started_at',
'completed_at',
'failed_at',
'storage_key',
'file_size',
'progress_percent',
'progress_step',
'error_message',
'download_url_expires_at',
'expires_at',
] as const satisfies ReadonlyArray<keyof AdminArchiveRow>;
export interface AdminAuditLogRow {
log_id: bigint;
admin_user_id: bigint;
target_type: string;
target_id: bigint;
action: string;
audit_log_reason: string | null;
metadata: Map<string, string>;
created_at: Date;
}
export const ADMIN_AUDIT_LOG_COLUMNS = [
'log_id',
'admin_user_id',
'target_type',
'target_id',
'action',
'audit_log_reason',
'metadata',
'created_at',
] as const satisfies ReadonlyArray<keyof AdminAuditLogRow>;
export interface BannedIpRow {
ip: string;
}
export const BANNED_IP_COLUMNS = ['ip'] as const satisfies ReadonlyArray<keyof BannedIpRow>;
export interface BannedEmailRow {
email_lower: string;
}
export const BANNED_EMAIL_COLUMNS = ['email_lower'] as const satisfies ReadonlyArray<keyof BannedEmailRow>;
export interface BannedPhoneRow {
phone: string;
}
export const BANNED_PHONE_COLUMNS = ['phone'] as const satisfies ReadonlyArray<keyof BannedPhoneRow>;

View File

@@ -0,0 +1,68 @@
/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import type {UserID} from '@fluxer/api/src/BrandedTypes';
type Nullish<T> = T | null;
export interface AdminApiKeyRow {
key_id: bigint;
key_hash: string;
name: string;
created_by_user_id: UserID;
created_at: Date;
last_used_at: Nullish<Date>;
expires_at: Nullish<Date>;
version: number;
acls: Nullish<Set<string>>;
}
export interface AdminApiKeyByCreatorRow {
created_by_user_id: UserID;
key_id: bigint;
created_at: Date;
name: string;
expires_at: Nullish<Date>;
last_used_at: Nullish<Date>;
version: number;
acls: Nullish<Set<string>>;
}
export const ADMIN_API_KEY_COLUMNS = [
'key_id',
'key_hash',
'name',
'created_by_user_id',
'created_at',
'last_used_at',
'expires_at',
'version',
'acls',
] as const satisfies ReadonlyArray<keyof AdminApiKeyRow>;
export const ADMIN_API_KEY_BY_CREATOR_COLUMNS = [
'created_by_user_id',
'key_id',
'created_at',
'name',
'expires_at',
'last_used_at',
'version',
'acls',
] as const satisfies ReadonlyArray<keyof AdminApiKeyByCreatorRow>;

View File

@@ -0,0 +1,224 @@
/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import type {
EmailRevertToken,
EmailVerificationToken,
IpAuthorizationToken,
MfaBackupCode,
PasswordResetToken,
UserID,
} from '@fluxer/api/src/BrandedTypes';
type Nullish<T> = T | null;
export interface AuthSessionRow {
user_id: UserID;
session_id_hash: Buffer;
created_at: Date;
approx_last_used_at: Date;
client_ip: string;
client_user_agent: Nullish<string>;
client_is_desktop: Nullish<boolean>;
client_os?: Nullish<string>;
client_platform?: Nullish<string>;
version: number;
}
export interface MfaBackupCodeRow {
user_id: UserID;
code: MfaBackupCode;
consumed: boolean;
}
export interface EmailVerificationTokenRow {
token_: EmailVerificationToken;
user_id: UserID;
email: string;
}
export interface PasswordResetTokenRow {
token_: PasswordResetToken;
user_id: UserID;
email: string;
}
export interface EmailRevertTokenRow {
token_: EmailRevertToken;
user_id: UserID;
email: string;
}
export interface IpAuthorizationTokenRow {
token_: IpAuthorizationToken;
user_id: UserID;
email: string;
}
export interface AuthorizedIpRow {
user_id: UserID;
ip: string;
}
export interface WebAuthnCredentialRow {
user_id: UserID;
credential_id: string;
public_key: Buffer;
counter: bigint;
transports: Nullish<Set<string>>;
name: string;
created_at: Date;
last_used_at: Nullish<Date>;
version: number;
}
export interface PhoneTokenRow {
token_: string;
phone: string;
user_id: Nullish<UserID>;
}
export interface EmailChangeTicketRow {
ticket: string;
user_id: UserID;
require_original: boolean;
original_email: Nullish<string>;
original_verified: boolean;
original_proof: Nullish<string>;
original_code: Nullish<string>;
original_code_sent_at: Nullish<Date>;
original_code_expires_at: Nullish<Date>;
new_email: Nullish<string>;
new_code: Nullish<string>;
new_code_sent_at: Nullish<Date>;
new_code_expires_at: Nullish<Date>;
status: string;
created_at: Date;
updated_at: Date;
}
export interface EmailChangeTokenRow {
token_: string;
user_id: UserID;
new_email: string;
expires_at: Date;
created_at: Date;
}
export const AUTH_SESSION_COLUMNS = [
'user_id',
'session_id_hash',
'created_at',
'approx_last_used_at',
'client_ip',
'client_user_agent',
'client_is_desktop',
'client_os',
'client_platform',
'version',
] as const satisfies ReadonlyArray<keyof AuthSessionRow>;
export const MFA_BACKUP_CODE_COLUMNS = ['user_id', 'code', 'consumed'] as const satisfies ReadonlyArray<
keyof MfaBackupCodeRow
>;
export const EMAIL_VERIFICATION_TOKEN_COLUMNS = ['token_', 'user_id', 'email'] as const satisfies ReadonlyArray<
keyof EmailVerificationTokenRow
>;
export const PASSWORD_RESET_TOKEN_COLUMNS = ['token_', 'user_id', 'email'] as const satisfies ReadonlyArray<
keyof PasswordResetTokenRow
>;
export const EMAIL_REVERT_TOKEN_COLUMNS = ['token_', 'user_id', 'email'] as const satisfies ReadonlyArray<
keyof EmailRevertTokenRow
>;
export const IP_AUTHORIZATION_TOKEN_COLUMNS = ['token_', 'user_id', 'email'] as const satisfies ReadonlyArray<
keyof IpAuthorizationTokenRow
>;
export const AUTHORIZED_IP_COLUMNS = ['user_id', 'ip'] as const satisfies ReadonlyArray<keyof AuthorizedIpRow>;
export const WEBAUTHN_CREDENTIAL_COLUMNS = [
'user_id',
'credential_id',
'public_key',
'counter',
'transports',
'name',
'created_at',
'last_used_at',
'version',
] as const satisfies ReadonlyArray<keyof WebAuthnCredentialRow>;
export const PHONE_TOKEN_COLUMNS = ['token_', 'phone', 'user_id'] as const satisfies ReadonlyArray<keyof PhoneTokenRow>;
export interface PasswordChangeTicketRow {
ticket: string;
user_id: UserID;
code: Nullish<string>;
code_sent_at: Nullish<Date>;
code_expires_at: Nullish<Date>;
verified: boolean;
verification_proof: Nullish<string>;
status: string;
created_at: Date;
updated_at: Date;
}
export const PASSWORD_CHANGE_TICKET_COLUMNS = [
'ticket',
'user_id',
'code',
'code_sent_at',
'code_expires_at',
'verified',
'verification_proof',
'status',
'created_at',
'updated_at',
] as const satisfies ReadonlyArray<keyof PasswordChangeTicketRow>;
export const EMAIL_CHANGE_TICKET_COLUMNS = [
'ticket',
'user_id',
'require_original',
'original_email',
'original_verified',
'original_proof',
'original_code',
'original_code_sent_at',
'original_code_expires_at',
'new_email',
'new_code',
'new_code_sent_at',
'new_code_expires_at',
'status',
'created_at',
'updated_at',
] as const satisfies ReadonlyArray<keyof EmailChangeTicketRow>;
export const EMAIL_CHANGE_TOKEN_COLUMNS = [
'token_',
'user_id',
'new_email',
'expires_at',
'created_at',
] as const satisfies ReadonlyArray<keyof EmailChangeTokenRow>;

View File

@@ -0,0 +1,186 @@
/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import type {
ChannelID,
GuildID,
InviteCode,
MessageID,
RoleID,
UserID,
WebhookID,
WebhookToken,
} from '@fluxer/api/src/BrandedTypes';
type Nullish<T> = T | null;
export interface PermissionOverwrite {
type: number;
allow_: Nullish<bigint>;
deny_: Nullish<bigint>;
}
export interface ChannelRow {
channel_id: ChannelID;
guild_id: Nullish<GuildID>;
type: number;
name: Nullish<string>;
topic: Nullish<string>;
icon_hash: Nullish<string>;
url: Nullish<string>;
parent_id: Nullish<ChannelID>;
position: Nullish<number>;
owner_id: Nullish<UserID>;
recipient_ids: Nullish<Set<UserID>>;
nsfw: Nullish<boolean>;
rate_limit_per_user: Nullish<number>;
bitrate: Nullish<number>;
user_limit: Nullish<number>;
rtc_region: Nullish<string>;
last_message_id: Nullish<MessageID>;
last_pin_timestamp: Nullish<Date>;
permission_overwrites: Nullish<Map<RoleID | UserID, PermissionOverwrite>>;
nicks: Nullish<Map<string, string>>;
soft_deleted: boolean;
indexed_at: Nullish<Date>;
version: number;
}
export interface InviteRow {
code: InviteCode;
type: number;
guild_id: Nullish<GuildID>;
channel_id: Nullish<ChannelID>;
inviter_id: Nullish<UserID>;
created_at: Date;
uses: number;
max_uses: number;
max_age: number;
temporary: Nullish<boolean>;
version: number;
}
export interface WebhookRow {
webhook_id: WebhookID;
webhook_token: WebhookToken;
type: number;
guild_id: Nullish<GuildID>;
channel_id: Nullish<ChannelID>;
creator_id: Nullish<UserID>;
name: string;
avatar_hash: Nullish<string>;
version: number;
}
export interface PrivateChannelRow {
user_id: UserID;
channel_id: ChannelID;
is_gdm: boolean;
}
export interface DmStateRow {
hi_user_id: UserID;
lo_user_id: UserID;
channel_id: ChannelID;
}
export interface ReadStateRow {
user_id: UserID;
channel_id: ChannelID;
message_id: Nullish<MessageID>;
mention_count: number;
last_pin_timestamp: Nullish<Date>;
}
export const CHANNEL_COLUMNS = [
'channel_id',
'guild_id',
'type',
'name',
'topic',
'icon_hash',
'url',
'parent_id',
'position',
'owner_id',
'recipient_ids',
'nsfw',
'rate_limit_per_user',
'bitrate',
'user_limit',
'rtc_region',
'last_message_id',
'last_pin_timestamp',
'permission_overwrites',
'nicks',
'soft_deleted',
'indexed_at',
'version',
] as const satisfies ReadonlyArray<keyof ChannelRow>;
export interface ChannelsByGuildRow {
guild_id: GuildID;
channel_id: ChannelID;
}
export const CHANNELS_BY_GUILD_COLUMNS = ['guild_id', 'channel_id'] as const satisfies ReadonlyArray<
keyof ChannelsByGuildRow
>;
export const INVITE_COLUMNS = [
'code',
'type',
'guild_id',
'channel_id',
'inviter_id',
'created_at',
'uses',
'max_uses',
'max_age',
'temporary',
'version',
] as const satisfies ReadonlyArray<keyof InviteRow>;
export const WEBHOOK_COLUMNS = [
'webhook_id',
'webhook_token',
'type',
'guild_id',
'channel_id',
'creator_id',
'name',
'avatar_hash',
'version',
] as const satisfies ReadonlyArray<keyof WebhookRow>;
export const READ_STATE_COLUMNS = [
'user_id',
'channel_id',
'message_id',
'mention_count',
'last_pin_timestamp',
] as const satisfies ReadonlyArray<keyof ReadStateRow>;
export const PRIVATE_CHANNEL_COLUMNS = ['user_id', 'channel_id', 'is_gdm'] as const satisfies ReadonlyArray<
keyof PrivateChannelRow
>;
export const DM_STATE_COLUMNS = ['hi_user_id', 'lo_user_id', 'channel_id'] as const satisfies ReadonlyArray<
keyof DmStateRow
>;

View File

@@ -0,0 +1,55 @@
/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import type {UserID} from '@fluxer/api/src/BrandedTypes';
import type {ConnectionType} from '@fluxer/constants/src/ConnectionConstants';
type Nullish<T> = T | null;
export interface UserConnectionRow {
user_id: UserID;
connection_id: string;
connection_type: ConnectionType;
identifier: string;
name: string;
verified: boolean;
visibility_flags: number;
sort_order: number;
verification_token: string;
verified_at: Nullish<Date>;
last_verified_at: Nullish<Date>;
created_at: Date;
version: number;
}
export const USER_CONNECTION_COLUMNS = [
'user_id',
'connection_id',
'connection_type',
'identifier',
'name',
'verified',
'visibility_flags',
'sort_order',
'verification_token',
'verified_at',
'last_verified_at',
'created_at',
'version',
] as const satisfies ReadonlyArray<keyof UserConnectionRow>;

View File

@@ -0,0 +1,152 @@
/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
export interface CsamEvidencePackageRow {
report_id: bigint;
resource_type: string;
bucket: string;
key: string;
cdn_url: string | null;
filename: string;
content_type: string | null;
channel_id: bigint | null;
message_id: bigint | null;
guild_id: bigint | null;
user_id: bigint | null;
match_tracking_id: string | null;
match_details: string | null;
frames: string | null;
hashes: string | null;
context_snapshot: string | null;
created_at: Date;
expires_at: Date;
integrity_sha256: string;
evidence_zip_key: string;
}
export interface CsamEvidenceLegalHoldRow {
report_id: bigint;
held_until: Date | null;
created_at: Date;
}
export interface CsamScanJobRow {
job_id: string;
resource_type: string;
bucket: string | null;
key: string | null;
cdn_url: string | null;
filename: string | null;
content_type: string | null;
channel_id: bigint | null;
message_id: bigint | null;
guild_id: bigint | null;
user_id: bigint | null;
status: string;
enqueue_time: Date;
last_updated: Date;
match_tracking_id: string | null;
match_details: string | null;
hashes: string | null;
error_message: string | null;
expires_at: Date;
}
export const CSAM_EVIDENCE_PACKAGE_COLUMNS = [
'report_id',
'resource_type',
'bucket',
'key',
'cdn_url',
'filename',
'content_type',
'channel_id',
'message_id',
'guild_id',
'user_id',
'match_tracking_id',
'match_details',
'frames',
'hashes',
'context_snapshot',
'created_at',
'expires_at',
'integrity_sha256',
'evidence_zip_key',
] as const satisfies ReadonlyArray<keyof CsamEvidencePackageRow>;
export const CSAM_EVIDENCE_LEGAL_HOLD_COLUMNS = [
'report_id',
'held_until',
'created_at',
] as const satisfies ReadonlyArray<keyof CsamEvidenceLegalHoldRow>;
export const CSAM_SCAN_JOB_COLUMNS = [
'job_id',
'resource_type',
'bucket',
'key',
'cdn_url',
'filename',
'content_type',
'channel_id',
'message_id',
'guild_id',
'user_id',
'status',
'enqueue_time',
'last_updated',
'match_tracking_id',
'match_details',
'hashes',
'error_message',
'expires_at',
] as const satisfies ReadonlyArray<keyof CsamScanJobRow>;
export interface CsamEvidenceExpirationRow {
bucket: string;
expires_at: Date;
report_id: bigint;
}
export const CSAM_EVIDENCE_EXPIRATION_COLUMNS = ['bucket', 'expires_at', 'report_id'] as const satisfies ReadonlyArray<
keyof CsamEvidenceExpirationRow
>;
export interface NcmecSubmissionRow {
report_id: bigint;
status: string;
ncmec_report_id: string | null;
submitted_at: Date | null;
submitted_by_admin_id: bigint | null;
failure_reason: string | null;
created_at: Date;
updated_at: Date;
}
export const NCMEC_SUBMISSION_COLUMNS = [
'report_id',
'status',
'ncmec_report_id',
'submitted_at',
'submitted_by_admin_id',
'failure_reason',
'created_at',
'updated_at',
] as const satisfies ReadonlyArray<keyof NcmecSubmissionRow>;

View File

@@ -0,0 +1,20 @@
/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
export type ExactRow<T> = T;

View File

@@ -0,0 +1,96 @@
/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
type Nullish<T> = T | null;
export interface DonorRow {
email: string;
stripe_customer_id: Nullish<string>;
business_name: Nullish<string>;
tax_id: Nullish<string>;
tax_id_type: Nullish<string>;
stripe_subscription_id: Nullish<string>;
subscription_amount_cents: Nullish<number>;
subscription_currency: Nullish<string>;
subscription_interval: Nullish<string>;
subscription_current_period_end: Nullish<Date>;
subscription_cancel_at: Nullish<Date>;
created_at: Date;
updated_at: Date;
version: number;
}
export interface DonorByStripeCustomerIdRow {
stripe_customer_id: string;
email: string;
}
export interface DonorByStripeSubscriptionIdRow {
stripe_subscription_id: string;
email: string;
}
export interface DonorMagicLinkTokenRow {
token_: string;
donor_email: string;
expires_at: Date;
used_at: Nullish<Date>;
}
export interface DonorMagicLinkTokenByEmailRow {
donor_email: string;
token_: string;
}
export const DONOR_COLUMNS = [
'email',
'stripe_customer_id',
'business_name',
'tax_id',
'tax_id_type',
'stripe_subscription_id',
'subscription_amount_cents',
'subscription_currency',
'subscription_interval',
'subscription_current_period_end',
'subscription_cancel_at',
'created_at',
'updated_at',
'version',
] as const satisfies ReadonlyArray<keyof DonorRow>;
export const DONOR_BY_STRIPE_CUSTOMER_ID_COLUMNS = ['stripe_customer_id', 'email'] as const satisfies ReadonlyArray<
keyof DonorByStripeCustomerIdRow
>;
export const DONOR_BY_STRIPE_SUBSCRIPTION_ID_COLUMNS = [
'stripe_subscription_id',
'email',
] as const satisfies ReadonlyArray<keyof DonorByStripeSubscriptionIdRow>;
export const DONOR_MAGIC_LINK_TOKEN_COLUMNS = [
'token_',
'donor_email',
'expires_at',
'used_at',
] as const satisfies ReadonlyArray<keyof DonorMagicLinkTokenRow>;
export const DONOR_MAGIC_LINK_TOKEN_BY_EMAIL_COLUMNS = ['donor_email', 'token_'] as const satisfies ReadonlyArray<
keyof DonorMagicLinkTokenByEmailRow
>;

View File

@@ -0,0 +1,60 @@
/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import type {GuildID, UserID} from '@fluxer/api/src/BrandedTypes';
type Nullish<T> = T | null;
export interface GuildDiscoveryRow {
guild_id: GuildID;
status: string;
category_type: number;
description: string;
applied_at: Date;
reviewed_at: Nullish<Date>;
reviewed_by: Nullish<UserID>;
review_reason: Nullish<string>;
removed_at: Nullish<Date>;
removed_by: Nullish<UserID>;
removal_reason: Nullish<string>;
}
export const GUILD_DISCOVERY_COLUMNS = [
'guild_id',
'status',
'category_type',
'description',
'applied_at',
'reviewed_at',
'reviewed_by',
'review_reason',
'removed_at',
'removed_by',
'removal_reason',
] as const satisfies ReadonlyArray<keyof GuildDiscoveryRow>;
export interface GuildDiscoveryByStatusRow {
status: string;
applied_at: Date;
guild_id: GuildID;
}
export const GUILD_DISCOVERY_BY_STATUS_COLUMNS = ['status', 'applied_at', 'guild_id'] as const satisfies ReadonlyArray<
keyof GuildDiscoveryByStatusRow
>;

View File

@@ -0,0 +1,297 @@
/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import type {
ChannelID,
EmojiID,
GuildID,
InviteCode,
RoleID,
StickerID,
UserID,
VanityURLCode,
} from '@fluxer/api/src/BrandedTypes';
import type {AuditLogActionType} from '@fluxer/constants/src/AuditLogActionType';
import type {GuildSplashCardAlignmentValue} from '@fluxer/constants/src/GuildConstants';
type Nullish<T> = T | null;
export interface GuildRow {
guild_id: GuildID;
owner_id: UserID;
name: string;
vanity_url_code: Nullish<VanityURLCode>;
icon_hash: Nullish<string>;
banner_hash: Nullish<string>;
banner_width: Nullish<number>;
banner_height: Nullish<number>;
splash_hash: Nullish<string>;
splash_width: Nullish<number>;
splash_height: Nullish<number>;
splash_card_alignment: Nullish<GuildSplashCardAlignmentValue>;
embed_splash_hash: Nullish<string>;
embed_splash_width: Nullish<number>;
embed_splash_height: Nullish<number>;
features: Nullish<Set<string>>;
verification_level: number;
mfa_level: number;
nsfw_level: number;
explicit_content_filter: number;
default_message_notifications: number;
system_channel_id: Nullish<ChannelID>;
system_channel_flags: number;
rules_channel_id: Nullish<ChannelID>;
afk_channel_id: Nullish<ChannelID>;
afk_timeout: number;
disabled_operations: number;
member_count: number;
audit_logs_indexed_at: Nullish<Date>;
members_indexed_at: Nullish<Date>;
message_history_cutoff: Nullish<Date>;
version: number;
}
export const GUILD_COLUMNS = [
'guild_id',
'owner_id',
'name',
'vanity_url_code',
'icon_hash',
'banner_hash',
'banner_width',
'banner_height',
'splash_hash',
'splash_width',
'splash_height',
'splash_card_alignment',
'embed_splash_hash',
'embed_splash_width',
'embed_splash_height',
'features',
'verification_level',
'mfa_level',
'nsfw_level',
'explicit_content_filter',
'default_message_notifications',
'system_channel_id',
'system_channel_flags',
'rules_channel_id',
'afk_channel_id',
'afk_timeout',
'disabled_operations',
'member_count',
'audit_logs_indexed_at',
'members_indexed_at',
'message_history_cutoff',
'version',
] as const satisfies ReadonlyArray<keyof GuildRow>;
export interface GuildMemberRow {
guild_id: GuildID;
user_id: UserID;
joined_at: Date;
nick: Nullish<string>;
avatar_hash: Nullish<string>;
banner_hash: Nullish<string>;
bio: Nullish<string>;
pronouns: Nullish<string>;
accent_color: Nullish<number>;
join_source_type: Nullish<number>;
source_invite_code: Nullish<InviteCode>;
inviter_id: Nullish<UserID>;
deaf: boolean;
mute: boolean;
communication_disabled_until: Nullish<Date>;
role_ids: Nullish<Set<RoleID>>;
is_premium_sanitized: Nullish<boolean>;
temporary: Nullish<boolean>;
profile_flags: Nullish<number>;
version: number;
}
export const GUILD_MEMBER_COLUMNS = [
'guild_id',
'user_id',
'joined_at',
'nick',
'avatar_hash',
'banner_hash',
'bio',
'pronouns',
'accent_color',
'join_source_type',
'source_invite_code',
'inviter_id',
'deaf',
'mute',
'communication_disabled_until',
'role_ids',
'is_premium_sanitized',
'temporary',
'profile_flags',
'version',
] as const satisfies ReadonlyArray<keyof GuildMemberRow>;
export interface GuildAuditLogRow {
guild_id: GuildID;
log_id: bigint;
user_id: UserID;
target_id: Nullish<string>;
action_type: AuditLogActionType;
reason: Nullish<string>;
options: Nullish<Map<string, string>>;
changes: Nullish<string>;
}
export const GUILD_AUDIT_LOG_COLUMNS = [
'guild_id',
'log_id',
'user_id',
'target_id',
'action_type',
'reason',
'options',
'changes',
] as const satisfies ReadonlyArray<keyof GuildAuditLogRow>;
export interface GuildRoleRow {
guild_id: GuildID;
role_id: RoleID;
name: string;
permissions: bigint;
position: number;
hoist_position: Nullish<number>;
color: number;
icon_hash: Nullish<string>;
unicode_emoji: Nullish<string>;
hoist: boolean;
mentionable: boolean;
version: number;
}
export const GUILD_ROLE_COLUMNS = [
'guild_id',
'role_id',
'name',
'permissions',
'position',
'hoist_position',
'color',
'icon_hash',
'unicode_emoji',
'hoist',
'mentionable',
'version',
] as const satisfies ReadonlyArray<keyof GuildRoleRow>;
export interface GuildBanRow {
guild_id: GuildID;
user_id: UserID;
moderator_id: UserID;
banned_at: Date;
expires_at: Nullish<Date>;
reason: Nullish<string>;
ip: Nullish<string>;
}
export const GUILD_BAN_COLUMNS = [
'guild_id',
'user_id',
'moderator_id',
'banned_at',
'expires_at',
'reason',
'ip',
] as const satisfies ReadonlyArray<keyof GuildBanRow>;
export interface GuildEmojiRow {
guild_id: GuildID;
emoji_id: EmojiID;
name: string;
creator_id: UserID;
animated: boolean;
version: number;
}
export const GUILD_EMOJI_COLUMNS = [
'guild_id',
'emoji_id',
'name',
'creator_id',
'animated',
'version',
] as const satisfies ReadonlyArray<keyof GuildEmojiRow>;
export const GUILD_EMOJI_BY_EMOJI_ID_COLUMNS = [
'guild_id',
'emoji_id',
'name',
'creator_id',
'animated',
] as const satisfies ReadonlyArray<keyof GuildEmojiRow>;
export interface GuildStickerRow {
guild_id: GuildID;
sticker_id: StickerID;
name: string;
description: Nullish<string>;
animated: boolean;
tags: Nullish<Array<string>>;
creator_id: UserID;
version: number;
}
export const GUILD_STICKER_COLUMNS = [
'guild_id',
'sticker_id',
'name',
'description',
'animated',
'tags',
'creator_id',
'version',
] as const satisfies ReadonlyArray<keyof GuildStickerRow>;
export const GUILD_STICKER_BY_STICKER_ID_COLUMNS = [
'guild_id',
'sticker_id',
'name',
'description',
'animated',
'tags',
'creator_id',
] as const satisfies ReadonlyArray<keyof GuildStickerRow>;
export interface GuildMemberByUserIdRow {
user_id: UserID;
guild_id: GuildID;
}
export const GUILD_MEMBER_BY_USER_ID_COLUMNS = ['user_id', 'guild_id'] as const satisfies ReadonlyArray<
keyof GuildMemberByUserIdRow
>;
export interface GuildByOwnerIdRow {
owner_id: UserID;
guild_id: GuildID;
}
export const GUILD_BY_OWNER_ID_COLUMNS = ['owner_id', 'guild_id'] as const satisfies ReadonlyArray<
keyof GuildByOwnerIdRow
>;

View File

@@ -0,0 +1,28 @@
/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
export interface InstanceConfigurationRow {
key: string;
value: string | null;
updated_at: Date | null;
}
export const INSTANCE_CONFIGURATION_COLUMNS = ['key', 'value', 'updated_at'] as const satisfies ReadonlyArray<
keyof InstanceConfigurationRow
>;

View File

@@ -0,0 +1,284 @@
/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import type {
AttachmentID,
ChannelID,
EmojiID,
GuildID,
MessageID,
RoleID,
StickerID,
UserID,
WebhookID,
} from '@fluxer/api/src/BrandedTypes';
type Nullish<T> = T | null;
export interface MessageAttachment {
attachment_id: AttachmentID;
filename: string;
size: bigint;
title: Nullish<string>;
description: Nullish<string>;
width: Nullish<number>;
height: Nullish<number>;
content_type: string;
content_hash: Nullish<string>;
placeholder: Nullish<string>;
flags: number;
duration: Nullish<number>;
nsfw: Nullish<boolean>;
waveform: Nullish<string>;
}
export interface MessageEmbedAuthor {
name: Nullish<string>;
url: Nullish<string>;
icon_url: Nullish<string>;
}
export interface MessageEmbedProvider {
name: Nullish<string>;
url: Nullish<string>;
}
export interface MessageEmbedFooter {
text: Nullish<string>;
icon_url: Nullish<string>;
}
export interface MessageEmbedMedia {
url: Nullish<string>;
width: Nullish<number>;
height: Nullish<number>;
description: Nullish<string>;
content_type: Nullish<string>;
content_hash: Nullish<string>;
placeholder: Nullish<string>;
flags: number;
duration: Nullish<number>;
}
export interface MessageEmbedField {
name: Nullish<string>;
value: Nullish<string>;
inline: boolean;
}
interface MessageEmbedBase {
type: Nullish<string>;
title: Nullish<string>;
description: Nullish<string>;
url: Nullish<string>;
timestamp: Nullish<Date>;
color: Nullish<number>;
author: Nullish<MessageEmbedAuthor>;
provider: Nullish<MessageEmbedProvider>;
thumbnail: Nullish<MessageEmbedMedia>;
image: Nullish<MessageEmbedMedia>;
video: Nullish<MessageEmbedMedia>;
footer: Nullish<MessageEmbedFooter>;
fields: Nullish<Array<MessageEmbedField>>;
nsfw: Nullish<boolean>;
}
export interface MessageEmbedChild extends MessageEmbedBase {}
export interface MessageEmbed extends MessageEmbedBase {
children?: Nullish<Array<MessageEmbedChild>>;
}
export interface MessageStickerItem {
sticker_id: StickerID;
name: string;
animated?: boolean;
}
export interface MessageReference {
channel_id: ChannelID;
message_id: MessageID;
guild_id: Nullish<GuildID>;
type: number;
}
export interface MessageSnapshot {
content: Nullish<string>;
timestamp: Date;
edited_timestamp: Nullish<Date>;
mention_users: Nullish<Set<UserID>>;
mention_roles: Nullish<Set<RoleID>>;
mention_channels: Nullish<Set<ChannelID>>;
attachments: Nullish<Array<MessageAttachment>>;
embeds: Nullish<Array<MessageEmbed>>;
sticker_items: Nullish<Array<MessageStickerItem>>;
type: number;
flags: number;
}
export interface MessageCall {
participant_ids: Nullish<Set<UserID>>;
ended_timestamp: Nullish<Date>;
}
export interface MessageRow {
channel_id: ChannelID;
bucket: number;
message_id: MessageID;
author_id: Nullish<UserID>;
type: number;
webhook_id: Nullish<WebhookID>;
webhook_name: Nullish<string>;
webhook_avatar_hash: Nullish<string>;
content: Nullish<string>;
edited_timestamp: Nullish<Date>;
pinned_timestamp: Nullish<Date>;
flags: number;
mention_everyone: boolean;
mention_users: Nullish<Set<UserID>>;
mention_roles: Nullish<Set<RoleID>>;
mention_channels: Nullish<Set<ChannelID>>;
attachments: Nullish<Array<MessageAttachment>>;
embeds: Nullish<Array<MessageEmbed>>;
sticker_items: Nullish<Array<MessageStickerItem>>;
message_reference: Nullish<MessageReference>;
message_snapshots: Nullish<Array<MessageSnapshot>>;
call: Nullish<MessageCall>;
has_reaction: Nullish<boolean>;
version: number;
}
export const MESSAGE_COLUMNS = [
'channel_id',
'bucket',
'message_id',
'author_id',
'type',
'webhook_id',
'webhook_name',
'webhook_avatar_hash',
'content',
'edited_timestamp',
'pinned_timestamp',
'flags',
'mention_everyone',
'mention_users',
'mention_roles',
'mention_channels',
'attachments',
'embeds',
'sticker_items',
'message_reference',
'message_snapshots',
'call',
'has_reaction',
'version',
] as const satisfies ReadonlyArray<keyof MessageRow>;
export interface ChannelPinRow {
channel_id: ChannelID;
message_id: MessageID;
pinned_timestamp: Date;
}
export interface MessageReactionRow {
channel_id: ChannelID;
bucket: number;
message_id: MessageID;
user_id: UserID;
emoji_id: EmojiID;
emoji_name: string;
emoji_animated: boolean;
}
export interface AttachmentLookupRow {
channel_id: ChannelID;
attachment_id: AttachmentID;
filename: string;
message_id: MessageID;
}
export const ATTACHMENT_LOOKUP_COLUMNS = [
'channel_id',
'attachment_id',
'filename',
'message_id',
] as const satisfies ReadonlyArray<keyof AttachmentLookupRow>;
export const CHANNEL_PIN_COLUMNS = ['channel_id', 'message_id', 'pinned_timestamp'] as const satisfies ReadonlyArray<
keyof ChannelPinRow
>;
export const MESSAGE_REACTION_COLUMNS = [
'channel_id',
'bucket',
'message_id',
'user_id',
'emoji_id',
'emoji_name',
'emoji_animated',
] as const satisfies ReadonlyArray<keyof MessageReactionRow>;
export interface MessageByAuthorRow {
author_id: UserID;
channel_id: ChannelID;
message_id: MessageID;
}
export const MESSAGE_BY_AUTHOR_COLUMNS = ['author_id', 'channel_id', 'message_id'] as const satisfies ReadonlyArray<
keyof MessageByAuthorRow
>;
export interface ChannelStateRow {
channel_id: ChannelID;
created_bucket: number;
has_messages: boolean;
last_message_id: Nullish<MessageID>;
last_message_bucket: Nullish<number>;
updated_at: Date;
}
export const CHANNEL_STATE_COLUMNS = [
'channel_id',
'created_bucket',
'has_messages',
'last_message_id',
'last_message_bucket',
'updated_at',
] as const satisfies ReadonlyArray<keyof ChannelStateRow>;
export interface ChannelMessageBucketRow {
channel_id: ChannelID;
bucket: number;
updated_at: Date;
}
export const CHANNEL_MESSAGE_BUCKET_COLUMNS = ['channel_id', 'bucket', 'updated_at'] as const satisfies ReadonlyArray<
keyof ChannelMessageBucketRow
>;
export interface ChannelEmptyBucketRow {
channel_id: ChannelID;
bucket: number;
updated_at: Date;
}
export const CHANNEL_EMPTY_BUCKET_COLUMNS = ['channel_id', 'bucket', 'updated_at'] as const satisfies ReadonlyArray<
keyof ChannelEmptyBucketRow
>;

View File

@@ -0,0 +1,119 @@
/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import type {ApplicationID, UserID} from '@fluxer/api/src/BrandedTypes';
export interface ApplicationRow {
application_id: ApplicationID;
owner_user_id: UserID;
name: string;
bot_user_id: UserID | null;
bot_is_public: boolean | null;
bot_require_code_grant?: boolean | null;
oauth2_redirect_uris: Set<string>;
client_secret_hash: string | null;
bot_token_hash: string | null;
bot_token_preview: string | null;
bot_token_created_at: Date | null;
client_secret_created_at: Date | null;
version?: number | null;
}
export interface ApplicationByOwnerRow {
owner_user_id: UserID;
application_id: ApplicationID;
}
export interface OAuth2AuthorizationCodeRow {
code: string;
application_id: ApplicationID;
user_id: UserID;
redirect_uri: string;
scope: Set<string>;
nonce: string | null;
created_at: Date;
}
export interface OAuth2AccessTokenRow {
token_: string;
application_id: ApplicationID;
user_id: UserID | null;
scope: Set<string>;
created_at: Date;
}
export interface OAuth2AccessTokenByUserRow {
user_id: UserID;
token_: string;
}
export interface OAuth2RefreshTokenRow {
token_: string;
application_id: ApplicationID;
user_id: UserID;
scope: Set<string>;
created_at: Date;
}
export interface OAuth2RefreshTokenByUserRow {
user_id: UserID;
token_: string;
}
export const APPLICATION_COLUMNS = [
'application_id',
'owner_user_id',
'name',
'bot_user_id',
'bot_is_public',
'bot_require_code_grant',
'oauth2_redirect_uris',
'client_secret_hash',
'bot_token_hash',
'bot_token_preview',
'bot_token_created_at',
'client_secret_created_at',
'version',
] as const satisfies ReadonlyArray<keyof ApplicationRow>;
export const OAUTH2_AUTHORIZATION_CODE_COLUMNS = [
'code',
'application_id',
'user_id',
'redirect_uri',
'scope',
'nonce',
'created_at',
] as const satisfies ReadonlyArray<keyof OAuth2AuthorizationCodeRow>;
export const OAUTH2_ACCESS_TOKEN_COLUMNS = [
'token_',
'application_id',
'user_id',
'scope',
'created_at',
] as const satisfies ReadonlyArray<keyof OAuth2AccessTokenRow>;
export const OAUTH2_REFRESH_TOKEN_COLUMNS = [
'token_',
'application_id',
'user_id',
'scope',
'created_at',
] as const satisfies ReadonlyArray<keyof OAuth2RefreshTokenRow>;

View File

@@ -0,0 +1,145 @@
/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import type {UserID} from '@fluxer/api/src/BrandedTypes';
type Nullish<T> = T | null;
export interface GiftCodeRow {
code: string;
duration_months: number;
created_at: Date;
created_by_user_id: UserID;
redeemed_at: Nullish<Date>;
redeemed_by_user_id: Nullish<UserID>;
stripe_payment_intent_id: Nullish<string>;
visionary_sequence_number: Nullish<number>;
checkout_session_id: Nullish<string>;
version: number;
}
export interface PaymentRow {
checkout_session_id: string;
user_id: UserID;
stripe_customer_id: Nullish<string>;
payment_intent_id: Nullish<string>;
subscription_id: Nullish<string>;
invoice_id: Nullish<string>;
price_id: Nullish<string>;
product_type: Nullish<string>;
amount_cents: number;
currency: string;
status: string;
is_gift: boolean;
gift_code: Nullish<string>;
created_at: Date;
completed_at: Nullish<Date>;
version: number;
}
export interface PaymentBySubscriptionRow {
subscription_id: string;
checkout_session_id: string;
user_id: UserID;
price_id: string;
product_type: string;
}
export interface VisionarySlotRow {
slot_index: number;
user_id: UserID | null;
}
export const PAYMENT_COLUMNS = [
'checkout_session_id',
'user_id',
'stripe_customer_id',
'payment_intent_id',
'subscription_id',
'invoice_id',
'price_id',
'product_type',
'amount_cents',
'currency',
'status',
'is_gift',
'gift_code',
'created_at',
'completed_at',
'version',
] as const;
export const PAYMENT_BY_SUBSCRIPTION_COLUMNS = [
'subscription_id',
'checkout_session_id',
'user_id',
'price_id',
'product_type',
] as const;
export const PAYMENT_BY_PAYMENT_INTENT_COLUMNS = ['payment_intent_id', 'checkout_session_id'] as const;
export const PAYMENT_BY_USER_COLUMNS = ['user_id', 'created_at', 'checkout_session_id'] as const;
export const GIFT_CODE_COLUMNS = [
'code',
'duration_months',
'created_at',
'created_by_user_id',
'redeemed_at',
'redeemed_by_user_id',
'stripe_payment_intent_id',
'visionary_sequence_number',
'checkout_session_id',
'version',
] as const;
export const GIFT_CODE_BY_CREATOR_COLUMNS = ['created_by_user_id', 'code'] as const;
export const GIFT_CODE_BY_PAYMENT_INTENT_COLUMNS = ['stripe_payment_intent_id', 'code'] as const;
export const GIFT_CODE_BY_REDEEMER_COLUMNS = ['redeemed_by_user_id', 'code'] as const;
export const VISIONARY_SLOT_COLUMNS = ['slot_index', 'user_id'] as const;
export interface PaymentByPaymentIntentRow {
payment_intent_id: string;
checkout_session_id: string;
}
export interface PaymentByUserRow {
user_id: UserID;
created_at: Date;
checkout_session_id: string;
}
export interface GiftCodeByCreatorRow {
created_by_user_id: UserID;
code: string;
}
export interface GiftCodeByPaymentIntentRow {
stripe_payment_intent_id: string;
code: string;
}
export interface GiftCodeByRedeemerRow {
redeemed_by_user_id: UserID;
code: string;
}

View File

@@ -0,0 +1,127 @@
/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import type {MessageAttachment, MessageEmbed, MessageStickerItem} from '@fluxer/api/src/database/types/MessageTypes';
type MentionCollection = ReadonlyArray<bigint> | Set<bigint> | null | undefined;
export interface IARMessageContextRow {
message_id: bigint;
channel_id: bigint | null;
author_id: bigint;
author_username: string;
author_discriminator: number;
author_avatar_hash: string | null;
content: string | null;
timestamp: Date;
edited_timestamp: Date | null;
type: number;
flags: number;
mention_everyone: boolean;
mention_users: MentionCollection;
mention_roles: MentionCollection;
mention_channels: MentionCollection;
attachments: Array<MessageAttachment> | null;
embeds: Array<MessageEmbed> | null;
sticker_items: Array<MessageStickerItem> | null;
}
export interface IARSubmissionRow {
report_id: bigint;
reporter_id: bigint | null;
reporter_email: string | null;
reporter_full_legal_name: string | null;
reporter_country_of_residence: string | null;
reported_at: Date;
status: number;
report_type: number;
category: string;
additional_info: string | null;
reported_user_id: bigint | null;
reported_user_avatar_hash: string | null;
reported_guild_id: bigint | null;
reported_guild_name: string | null;
reported_guild_icon_hash: string | null;
reported_message_id: bigint | null;
reported_channel_id: bigint | null;
reported_channel_name: string | null;
message_context: Array<IARMessageContextRow> | null;
guild_context_id: bigint | null;
resolved_at: Date | null;
resolved_by_admin_id: bigint | null;
public_comment: string | null;
audit_log_reason: string | null;
reported_guild_invite_code: string | null;
}
export interface DSAReportEmailVerificationRow {
email_lower: string;
code_hash: string;
expires_at: Date;
last_sent_at: Date;
}
export interface DSAReportTicketRow {
ticket: string;
email_lower: string;
expires_at: Date;
created_at: Date;
}
export const IAR_SUBMISSION_COLUMNS = [
'report_id',
'reporter_id',
'reporter_email',
'reporter_full_legal_name',
'reporter_country_of_residence',
'reported_at',
'status',
'report_type',
'category',
'additional_info',
'reported_user_id',
'reported_user_avatar_hash',
'reported_guild_id',
'reported_guild_name',
'reported_guild_icon_hash',
'reported_message_id',
'reported_channel_id',
'reported_channel_name',
'message_context',
'guild_context_id',
'resolved_at',
'resolved_by_admin_id',
'public_comment',
'audit_log_reason',
'reported_guild_invite_code',
] as const satisfies ReadonlyArray<keyof IARSubmissionRow>;
export const DSA_REPORT_EMAIL_VERIFICATION_COLUMNS = [
'email_lower',
'code_hash',
'expires_at',
'last_sent_at',
] as const satisfies ReadonlyArray<keyof DSAReportEmailVerificationRow>;
export const DSA_REPORT_TICKET_COLUMNS = [
'ticket',
'email_lower',
'expires_at',
'created_at',
] as const satisfies ReadonlyArray<keyof DSAReportTicketRow>;

View File

@@ -0,0 +1,61 @@
/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import type {UserID} from '@fluxer/api/src/BrandedTypes';
export type SystemDmJobStatus = 'pending' | 'approved' | 'running' | 'completed' | 'failed';
export interface SystemDmJobRow {
job_type: string;
job_id: bigint;
admin_user_id: UserID;
status: SystemDmJobStatus;
content: string;
registration_start: Date | null;
registration_end: Date | null;
excluded_guild_ids: ReadonlySet<string>;
target_count: number;
sent_count: number;
failed_count: number;
last_error: string | null;
worker_job_key: string | null;
created_at: Date;
updated_at: Date;
approved_by: UserID | null;
approved_at: Date | null;
}
export const SYSTEM_DM_JOB_COLUMNS = [
'job_type',
'job_id',
'admin_user_id',
'status',
'content',
'registration_start',
'registration_end',
'excluded_guild_ids',
'target_count',
'sent_count',
'failed_count',
'last_error',
'worker_job_key',
'created_at',
'updated_at',
'approved_by',
'approved_at',
] as const satisfies ReadonlyArray<keyof SystemDmJobRow>;

View File

@@ -0,0 +1,614 @@
/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import type {AttachmentID, ChannelID, EmojiID, GuildID, MemeID, MessageID, UserID} from '@fluxer/api/src/BrandedTypes';
import type {LocaleCode} from '@fluxer/constants/src/Locales';
import type {GuildFolderIcon} from '@fluxer/constants/src/UserConstants';
import type {types} from 'cassandra-driver';
type Nullish<T> = T | null;
export interface UserRow {
user_id: UserID;
username: string;
discriminator: number;
global_name: Nullish<string>;
bot: Nullish<boolean>;
system: Nullish<boolean>;
email: Nullish<string>;
email_verified: Nullish<boolean>;
email_bounced: Nullish<boolean>;
phone: Nullish<string>;
password_hash: Nullish<string>;
password_last_changed_at: Nullish<Date>;
totp_secret: Nullish<string>;
authenticator_types: Nullish<Set<number>>;
avatar_hash: Nullish<string>;
avatar_color: Nullish<number>;
banner_hash: Nullish<string>;
banner_color: Nullish<number>;
bio: Nullish<string>;
pronouns: Nullish<string>;
accent_color: Nullish<number>;
date_of_birth: Nullish<types.LocalDate>;
locale: Nullish<string>;
flags: Nullish<bigint>;
premium_type: Nullish<number>;
premium_since: Nullish<Date>;
premium_until: Nullish<Date>;
premium_will_cancel: Nullish<boolean>;
premium_billing_cycle: Nullish<string>;
premium_lifetime_sequence: Nullish<number>;
stripe_subscription_id: Nullish<string>;
stripe_customer_id: Nullish<string>;
has_ever_purchased: Nullish<boolean>;
suspicious_activity_flags: Nullish<number>;
terms_agreed_at: Nullish<Date>;
privacy_agreed_at: Nullish<Date>;
last_active_at: Nullish<Date>;
last_active_ip: Nullish<string>;
temp_banned_until: Nullish<Date>;
pending_bulk_message_deletion_at: Nullish<Date>;
pending_bulk_message_deletion_channel_count: Nullish<number>;
pending_bulk_message_deletion_message_count: Nullish<number>;
pending_deletion_at: Nullish<Date>;
deletion_reason_code: Nullish<number>;
deletion_public_reason: Nullish<string>;
deletion_audit_log_reason: Nullish<string>;
acls: Nullish<Set<string>>;
traits: Nullish<Set<string>>;
first_refund_at: Nullish<Date>;
gift_inventory_server_seq: Nullish<number>;
gift_inventory_client_seq: Nullish<number>;
premium_onboarding_dismissed_at: Nullish<Date>;
version: number;
}
export const USER_COLUMNS = [
'user_id',
'username',
'discriminator',
'global_name',
'bot',
'system',
'email',
'email_verified',
'email_bounced',
'phone',
'password_hash',
'password_last_changed_at',
'totp_secret',
'authenticator_types',
'avatar_hash',
'avatar_color',
'banner_hash',
'banner_color',
'bio',
'pronouns',
'accent_color',
'date_of_birth',
'locale',
'flags',
'premium_type',
'premium_since',
'premium_until',
'premium_will_cancel',
'premium_billing_cycle',
'premium_lifetime_sequence',
'stripe_subscription_id',
'stripe_customer_id',
'has_ever_purchased',
'suspicious_activity_flags',
'terms_agreed_at',
'privacy_agreed_at',
'last_active_at',
'last_active_ip',
'temp_banned_until',
'pending_bulk_message_deletion_at',
'pending_bulk_message_deletion_channel_count',
'pending_bulk_message_deletion_message_count',
'pending_deletion_at',
'deletion_reason_code',
'deletion_public_reason',
'deletion_audit_log_reason',
'acls',
'traits',
'first_refund_at',
'gift_inventory_server_seq',
'gift_inventory_client_seq',
'premium_onboarding_dismissed_at',
'version',
] as const satisfies ReadonlyArray<keyof UserRow>;
export const EMPTY_USER_ROW: UserRow = {
user_id: -1n as UserID,
username: '',
discriminator: 0,
global_name: null,
bot: null,
system: null,
email: null,
email_verified: null,
email_bounced: null,
phone: null,
password_hash: null,
password_last_changed_at: null,
totp_secret: null,
authenticator_types: null,
avatar_hash: null,
avatar_color: null,
banner_hash: null,
banner_color: null,
bio: null,
pronouns: null,
accent_color: null,
date_of_birth: null,
locale: null,
flags: null,
premium_type: null,
premium_since: null,
premium_until: null,
premium_will_cancel: null,
premium_billing_cycle: null,
premium_lifetime_sequence: null,
stripe_subscription_id: null,
stripe_customer_id: null,
has_ever_purchased: null,
suspicious_activity_flags: null,
terms_agreed_at: null,
privacy_agreed_at: null,
last_active_at: null,
last_active_ip: null,
temp_banned_until: null,
pending_bulk_message_deletion_at: null,
pending_bulk_message_deletion_channel_count: null,
pending_bulk_message_deletion_message_count: null,
pending_deletion_at: null,
deletion_reason_code: null,
deletion_public_reason: null,
deletion_audit_log_reason: null,
acls: null,
traits: null,
first_refund_at: null,
gift_inventory_server_seq: null,
gift_inventory_client_seq: null,
premium_onboarding_dismissed_at: null,
version: 1,
};
export interface CustomStatus {
text: Nullish<string>;
emoji_id: Nullish<EmojiID>;
emoji_name: Nullish<string>;
emoji_animated: boolean;
expires_at: Nullish<Date>;
}
export interface GuildFolder {
folder_id: number;
name: Nullish<string>;
color: Nullish<number>;
flags: Nullish<number>;
icon: Nullish<GuildFolderIcon>;
guild_ids: Nullish<Array<GuildID>>;
}
export interface UserSettingsRow {
user_id: UserID;
locale: LocaleCode;
theme: string;
status: string;
status_resets_at: Nullish<Date>;
status_resets_to: Nullish<string>;
custom_status: Nullish<CustomStatus>;
developer_mode: boolean;
message_display_compact: boolean;
animate_emoji: boolean;
animate_stickers: number;
gif_auto_play: boolean;
render_embeds: boolean;
render_reactions: boolean;
render_spoilers: number;
inline_attachment_media: boolean;
inline_embed_media: boolean;
explicit_content_filter: number;
friend_source_flags: number;
incoming_call_flags: number;
group_dm_add_permission_flags: number;
default_guilds_restricted: boolean;
bot_default_guilds_restricted: boolean;
restricted_guilds: Nullish<Set<GuildID>>;
bot_restricted_guilds: Nullish<Set<GuildID>>;
guild_positions: Nullish<Array<GuildID>>;
guild_folders: Nullish<Array<GuildFolder>>;
afk_timeout: Nullish<number>;
time_format: Nullish<number>;
trusted_domains: Nullish<Set<string>>;
default_hide_muted_channels: Nullish<boolean>;
version: number;
}
export interface RelationshipRow {
source_user_id: UserID;
target_user_id: UserID;
type: number;
nickname: Nullish<string>;
since: Nullish<Date>;
version: number;
}
export interface NoteRow {
source_user_id: UserID;
target_user_id: UserID;
note: string;
version: number;
}
export interface MuteConfig {
end_time: Nullish<Date>;
selected_time_window: Nullish<number>;
}
export interface ChannelOverride {
collapsed: boolean;
message_notifications: Nullish<number>;
muted: boolean;
mute_config: Nullish<MuteConfig>;
}
export interface UserGuildSettingsRow {
user_id: UserID;
guild_id: GuildID;
message_notifications: Nullish<number>;
muted: boolean;
mute_config: Nullish<MuteConfig>;
mobile_push: boolean;
suppress_everyone: boolean;
suppress_roles: boolean;
hide_muted_channels: boolean;
channel_overrides: Nullish<Map<ChannelID, ChannelOverride>>;
version: number;
}
export interface ExpressionPackRow {
pack_id: GuildID;
pack_type: string;
creator_id: UserID;
name: string;
description: Nullish<string>;
created_at: Date;
updated_at: Date;
version: number;
}
export interface PackInstallationRow {
user_id: UserID;
pack_id: GuildID;
pack_type: string;
installed_at: Date;
}
export interface SavedMessageRow {
user_id: UserID;
channel_id: ChannelID;
message_id: MessageID;
saved_at: Date;
}
export const SAVED_MESSAGE_COLUMNS = [
'user_id',
'channel_id',
'message_id',
'saved_at',
] as const satisfies ReadonlyArray<keyof SavedMessageRow>;
export interface ScheduledMessageRow {
user_id: UserID;
scheduled_message_id: MessageID;
channel_id: ChannelID;
payload: string;
scheduled_at: Date;
scheduled_local_at: string;
timezone: string;
status: string;
status_reason: string | null;
created_at: Date;
invalidated_at: Date | null;
}
export const SCHEDULED_MESSAGE_COLUMNS = [
'user_id',
'scheduled_message_id',
'channel_id',
'payload',
'scheduled_at',
'scheduled_local_at',
'timezone',
'status',
'status_reason',
'created_at',
'invalidated_at',
] as const satisfies ReadonlyArray<keyof ScheduledMessageRow>;
export interface FavoriteMemeRow {
user_id: UserID;
meme_id: MemeID;
name: string;
alt_text: Nullish<string>;
tags: Nullish<Array<string>>;
attachment_id: AttachmentID;
filename: string;
content_type: string;
content_hash: Nullish<string>;
size: bigint;
width: Nullish<number>;
height: Nullish<number>;
duration: Nullish<number>;
is_gifv: boolean;
klipy_slug: Nullish<string>;
tenor_id_str: Nullish<string>;
version: number;
}
export const FAVORITE_MEME_COLUMNS = [
'user_id',
'meme_id',
'name',
'alt_text',
'tags',
'attachment_id',
'filename',
'content_type',
'content_hash',
'size',
'width',
'height',
'duration',
'is_gifv',
'klipy_slug',
'tenor_id_str',
'version',
] as const satisfies ReadonlyArray<keyof FavoriteMemeRow>;
export interface RecentMentionRow {
user_id: UserID;
channel_id: ChannelID;
message_id: MessageID;
guild_id: GuildID;
is_everyone: boolean;
is_role: boolean;
}
export const RECENT_MENTION_COLUMNS = [
'user_id',
'channel_id',
'message_id',
'guild_id',
'is_everyone',
'is_role',
] as const satisfies ReadonlyArray<keyof RecentMentionRow>;
export interface UserHarvestRow {
user_id: UserID;
harvest_id: bigint;
requested_at: Date;
started_at: Nullish<Date>;
completed_at: Nullish<Date>;
failed_at: Nullish<Date>;
storage_key: Nullish<string>;
file_size: Nullish<bigint>;
progress_percent: number;
progress_step: Nullish<string>;
error_message: Nullish<string>;
download_url_expires_at: Nullish<Date>;
}
export const USER_HARVEST_COLUMNS = [
'user_id',
'harvest_id',
'requested_at',
'started_at',
'completed_at',
'failed_at',
'storage_key',
'file_size',
'progress_percent',
'progress_step',
'error_message',
'download_url_expires_at',
] as const satisfies ReadonlyArray<keyof UserHarvestRow>;
export interface PushSubscriptionRow {
user_id: UserID;
subscription_id: string;
endpoint: string;
p256dh_key: string;
auth_key: string;
user_agent: Nullish<string>;
}
export const PUSH_SUBSCRIPTION_COLUMNS = [
'user_id',
'subscription_id',
'endpoint',
'p256dh_key',
'auth_key',
'user_agent',
] as const satisfies ReadonlyArray<keyof PushSubscriptionRow>;
export interface UserContactChangeLogRow {
user_id: UserID;
event_id: types.TimeUuid;
field: string;
old_value: Nullish<string>;
new_value: Nullish<string>;
reason: string;
actor_user_id: Nullish<UserID>;
event_at: Date;
}
export const USER_SETTINGS_COLUMNS = [
'user_id',
'locale',
'theme',
'status',
'status_resets_at',
'status_resets_to',
'custom_status',
'developer_mode',
'message_display_compact',
'animate_emoji',
'animate_stickers',
'gif_auto_play',
'render_embeds',
'render_reactions',
'render_spoilers',
'inline_attachment_media',
'inline_embed_media',
'explicit_content_filter',
'friend_source_flags',
'incoming_call_flags',
'group_dm_add_permission_flags',
'default_guilds_restricted',
'bot_default_guilds_restricted',
'restricted_guilds',
'bot_restricted_guilds',
'guild_positions',
'guild_folders',
'afk_timeout',
'time_format',
'trusted_domains',
'default_hide_muted_channels',
'version',
] as const satisfies ReadonlyArray<keyof UserSettingsRow>;
export const EXPRESSION_PACK_COLUMNS = [
'pack_id',
'pack_type',
'creator_id',
'name',
'description',
'created_at',
'updated_at',
'version',
] as const satisfies ReadonlyArray<keyof ExpressionPackRow>;
export const USER_GUILD_SETTINGS_COLUMNS = [
'user_id',
'guild_id',
'message_notifications',
'muted',
'mute_config',
'mobile_push',
'suppress_everyone',
'suppress_roles',
'hide_muted_channels',
'channel_overrides',
'version',
] as const satisfies ReadonlyArray<keyof UserGuildSettingsRow>;
export const RELATIONSHIP_COLUMNS = [
'source_user_id',
'target_user_id',
'type',
'nickname',
'since',
'version',
] as const satisfies ReadonlyArray<keyof RelationshipRow>;
export const NOTE_COLUMNS = ['source_user_id', 'target_user_id', 'note', 'version'] as const satisfies ReadonlyArray<
keyof NoteRow
>;
export const USER_CONTACT_CHANGE_LOG_COLUMNS = [
'user_id',
'event_id',
'field',
'old_value',
'new_value',
'reason',
'actor_user_id',
'event_at',
] as const satisfies ReadonlyArray<keyof UserContactChangeLogRow>;
export interface UserByUsernameRow {
username: string;
discriminator: number;
user_id: UserID;
}
export interface UserByEmailRow {
email_lower: string;
user_id: UserID;
}
export interface UserByPhoneRow {
phone: string;
user_id: UserID;
}
export interface UserByStripeCustomerIdRow {
stripe_customer_id: string;
user_id: UserID;
}
export interface UserByStripeSubscriptionIdRow {
stripe_subscription_id: string;
user_id: UserID;
}
export const USER_BY_USERNAME_COLUMNS = ['username', 'discriminator', 'user_id'] as const satisfies ReadonlyArray<
keyof UserByUsernameRow
>;
export const USER_BY_EMAIL_COLUMNS = ['email_lower', 'user_id'] as const satisfies ReadonlyArray<keyof UserByEmailRow>;
export const USER_BY_PHONE_COLUMNS = ['phone', 'user_id'] as const satisfies ReadonlyArray<keyof UserByPhoneRow>;
export const USER_BY_STRIPE_CUSTOMER_ID_COLUMNS = ['stripe_customer_id', 'user_id'] as const satisfies ReadonlyArray<
keyof UserByStripeCustomerIdRow
>;
export const USER_BY_STRIPE_SUBSCRIPTION_ID_COLUMNS = [
'stripe_subscription_id',
'user_id',
] as const satisfies ReadonlyArray<keyof UserByStripeSubscriptionIdRow>;
export interface UsersPendingDeletionRow {
deletion_date: string;
pending_deletion_at: Date;
user_id: UserID;
deletion_reason_code: number;
}
export const USERS_PENDING_DELETION_COLUMNS = [
'deletion_date',
'pending_deletion_at',
'user_id',
'deletion_reason_code',
] as const satisfies ReadonlyArray<keyof UsersPendingDeletionRow>;
export interface UserDmHistoryRow {
user_id: UserID;
channel_id: ChannelID;
}
export const USER_DM_HISTORY_COLUMNS = ['user_id', 'channel_id'] as const satisfies ReadonlyArray<
keyof UserDmHistoryRow
>;

View File

@@ -0,0 +1,78 @@
/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
export interface VoiceRegionRow {
id: string;
name: string;
emoji: string;
latitude: number;
longitude: number;
is_default: boolean | null;
vip_only: boolean | null;
required_guild_features: Set<string> | null;
allowed_guild_ids: Set<bigint> | null;
allowed_user_ids: Set<bigint> | null;
created_at: Date | null;
updated_at: Date | null;
}
export const VOICE_REGION_COLUMNS = [
'id',
'name',
'emoji',
'latitude',
'longitude',
'is_default',
'vip_only',
'required_guild_features',
'allowed_guild_ids',
'allowed_user_ids',
'created_at',
'updated_at',
] as const satisfies ReadonlyArray<keyof VoiceRegionRow>;
export interface VoiceServerRow {
region_id: string;
server_id: string;
endpoint: string;
api_key: string;
api_secret: string;
is_active: boolean | null;
vip_only: boolean | null;
required_guild_features: Set<string> | null;
allowed_guild_ids: Set<bigint> | null;
allowed_user_ids: Set<bigint> | null;
created_at: Date | null;
updated_at: Date | null;
}
export const VOICE_SERVER_COLUMNS = [
'region_id',
'server_id',
'endpoint',
'api_key',
'api_secret',
'is_active',
'vip_only',
'required_guild_features',
'allowed_guild_ids',
'allowed_user_ids',
'created_at',
'updated_at',
] as const satisfies ReadonlyArray<keyof VoiceServerRow>;