Files
buzz/lib/db/schema.ts
T
dmgnr b2c0849a54
Build and Push Image / build-backend (push) Successful in 1m5s
Build and Push Image / build-frontend (push) Successful in 2m42s
Build and Push Image / push (push) Successful in 1m23s
feat(don): purge expired mobile upload sessions
2026-08-29 21:45:45 +07:00

454 lines
13 KiB
TypeScript
Executable File

import { isNotNull, lte, or, type SQL, sql } from "drizzle-orm";
import {
type AnyPgColumn,
boolean,
integer,
jsonb,
numeric,
pgEnum,
pgSchema,
pgTable,
serial,
text,
timestamp,
} from "drizzle-orm/pg-core";
import { uuidv7 } from "uuidv7";
import type { SlipokResponse } from "@/app/(ui)/rubgram/api";
import { bytea } from "./custom";
export const element = pgEnum("character_element", [
"anemo",
"geo",
"dendro",
"hydro",
"pyro",
"cryo",
"electro",
]);
// #region Shared
export const cdn = pgTable("cdn", {
id: text().primaryKey().$defaultFn(uuidv7),
data: bytea().notNull(),
name: text(),
type: text().notNull(),
size: text().notNull(),
});
export const auditLog = pgTable("auditLog", {
id: text().primaryKey().$defaultFn(uuidv7),
author: text(), // Buzz
text: text().notNull(), // deleted Raiden Shogun from Base 1.0
details: jsonb(),
time: timestamp().notNull().defaultNow(),
});
export const characters = pgTable("characters", {
id: text().primaryKey().$defaultFn(uuidv7),
name: text().unique().notNull(), // Qiqi
version: text()
.notNull()
.references(() => versions.id, {
onDelete: "cascade",
onUpdate: "cascade",
}), // Li-A
stars: integer().notNull().$type<4 | 5>(),
vision: element().notNull(),
image: text()
.notNull()
.references(() => cdn.id, { onUpdate: "cascade" }),
weapon: text().notNull(),
amber: text().notNull(), // Amber character ID
order: integer().notNull(),
});
export const versions = pgTable("versions", {
id: text().primaryKey(), // LI
name: text().notNull(), // LunaI
from: text().references((): AnyPgColumn => versions.id, {
onDelete: "set null",
onUpdate: "cascade",
}),
// deprecate: date().notNull() // 21/03/2077
});
export const settings = pgTable("settings", {
id: boolean().primaryKey().default(true),
enka: boolean().notNull().default(true), // Turn this off when Enka is on maintenance
donatePromptpay: boolean().notNull().default(true),
donateTruemoney: boolean().notNull().default(true),
donateGoal: numeric({ mode: "number" }),
donateGoalStarting: timestamp(),
});
//#endregion
//#region Artifact
export const artifact = pgSchema("artifact");
const nullableSerial = serial();
(nullableSerial as any).config.notNull = false;
export const submissions = artifact.table("submissions", {
id: text().primaryKey().$defaultFn(uuidv7),
uid: text().notNull().unique(),
name: text().notNull(),
comment: text().notNull(),
char: text()
// .notNull() // For Special Queue
.references(() => characters.name, {
onDelete: "cascade",
onUpdate: "cascade",
}),
queue: nullableSerial, // NULL for Spacial Queue
editToken: text().notNull().$defaultFn(uuidv7),
edits: integer().notNull().default(0),
checked: boolean().notNull().default(false),
promoted: boolean().notNull().default(false),
});
export const cards = artifact.table("cards", {
id: text().primaryKey().$defaultFn(uuidv7),
submission: text()
.references(() => submissions.id, {
onUpdate: "cascade",
onDelete: "cascade",
})
.notNull()
.unique(),
tries: integer().notNull().default(0),
error: text(),
image: bytea(),
});
export const artifactSettings = artifact.table("settings", {
id: boolean().primaryKey().default(true),
locked: boolean().notNull().default(false),
limit: integer().notNull().default(-1),
});
//#endregion
//#region Endgame
export const endgame = pgSchema("endgame");
export const endgameServer = endgame.enum("server", ["as", "eu", "us", "tw"]);
export const endgameSubmissions = endgame.table("submissions", {
id: text().primaryKey().$defaultFn(uuidv7),
name: text().notNull(),
user: text()
.notNull()
.references(() => endgameDiscord.uid, {
onDelete: "cascade",
onUpdate: "cascade",
}),
expires: timestamp().$defaultFn(() => new Date(Date.now() + 20 * 60 * 1000)), // 20 minutes to pay
queue: serial(),
server: endgameServer().notNull(),
service: text()
.references(() => endgameTypes.id)
.array()
.notNull(),
price: integer().notNull(), // calculated on submission
slip: text().references(() => endgameSlips.id), // not paid = null
checked: boolean().notNull().default(false),
paid: boolean()
.notNull()
.generatedAlwaysAs(
(): SQL =>
or(
lte(endgameSubmissions.price, sql.raw("0")),
isNotNull(endgameSubmissions.slip),
)!,
),
deleted: boolean().notNull().default(false),
submit_day: timestamp().notNull().defaultNow(),
notes: jsonb().$type<Note[]>().notNull().default([]),
});
export type Note = {
id: string;
text: string;
createdAt: string;
};
// expired submission
export const endgameExpired = endgame.table("expired", {
id: text().primaryKey().$defaultFn(uuidv7),
name: text().notNull(),
user: text()
.notNull()
.references(() => endgameDiscord.uid, {
onDelete: "cascade",
onUpdate: "cascade",
}),
server: endgameServer().notNull(),
service: text()
.references(() => endgameTypes.id)
.array()
.notNull(),
});
export const endgameSlips = endgame.table("slips", {
id: text().primaryKey().$defaultFn(uuidv7),
slip: bytea().notNull(),
ref: text().unique().notNull(),
amount: numeric().notNull(),
data: jsonb().$type<SlipokResponse>().notNull(),
});
export const endgameSettings = endgame.table("settings", {
id: boolean().primaryKey().default(true),
locked: boolean().notNull().default(false),
limit: integer().notNull().default(-1),
free: integer().notNull().default(0),
allDiscount: integer().notNull().default(10),
monthly: jsonb().$type<Record<string, boolean>>().notNull().default({}),
});
export const endgameDiscord = endgame.table("discord", {
uid: text().primaryKey(), // user snowflake
display: text().notNull(),
username: text().notNull(),
token: text().notNull().$defaultFn(uuidv7), // for accessing existing user with cookie.
});
export const endgameTypes = endgame.table("types", {
id: text().primaryKey(), // theater
display: text().notNull(), // โรงละครในจินตนาการ
price: integer().notNull(), // 100
order: serial().notNull(),
});
//#endregion
//#region Tierlist
export const tierlist = pgSchema("tierlist");
export const tierlistTypes = tierlist.table("types", {
id: text().primaryKey().$defaultFn(uuidv7),
name: text().notNull(),
image: text().references(() => cdn.id, {
onDelete: "set null",
onUpdate: "cascade",
}),
order: integer().notNull(),
mode: text().notNull(),
});
export const tierlistTiers = tierlist.table("tiers", {
id: text().primaryKey().$defaultFn(uuidv7),
name: text().notNull(),
badges: text()
.references(() => tierlistBadges.id, {
onDelete: "set null",
onUpdate: "cascade",
})
.array(),
image: text().references(() => cdn.id, {
onDelete: "set null",
onUpdate: "cascade",
}),
order: integer().notNull(),
});
export const tierlistColumns = tierlist.table("columns", {
id: text().primaryKey().$defaultFn(uuidv7),
name: text().notNull(),
image: text().references(() => cdn.id, {
onDelete: "set null",
onUpdate: "cascade",
}),
order: integer().notNull(),
});
export const tierlistBadges = tierlist.table("badges", {
id: text().primaryKey().$defaultFn(uuidv7),
name: text().notNull(),
image: text().references(() => cdn.id, {
onDelete: "set null",
onUpdate: "cascade",
}),
order: integer().notNull(),
type: text().references(() => tierlistTypes.id, {
onDelete: "set null",
onUpdate: "cascade",
}),
});
export const tierlistVersions = tierlist.table("versions", {
id: text().primaryKey(),
name: text().notNull(),
hidden: boolean().notNull().default(false),
type: text()
.notNull()
.references(() => tierlistTypes.id, {
onDelete: "cascade",
onUpdate: "cascade",
}),
image: text().references(() => cdn.id, {
onDelete: "set null",
onUpdate: "cascade",
}),
disclaimer: text().references(() => cdn.id, {
onDelete: "set null",
onUpdate: "cascade",
}),
deprecates: text().notNull(),
from: text()
.notNull()
.references(() => versions.id, {
onDelete: "cascade",
onUpdate: "cascade",
}),
order: integer().notNull(),
placements: jsonb().notNull().$type<{ [x: string]: string[] }>().default({}),
snapshot: jsonb().$type<Record<string, unknown> | null>().default(null),
});
export const tierlistStates = tierlist.table("states", {
uuid: text().primaryKey().$defaultFn(uuidv7),
char: text()
.notNull()
.references(() => characters.id, {
onDelete: "cascade",
onUpdate: "cascade",
}),
ref: text().notNull(), // for manually created tiles, the id would be char#Date.now() instead
list: text()
.notNull()
.references(() => tierlistVersions.id, {
onDelete: "cascade",
onUpdate: "cascade",
}),
comment: text().notNull().default(""),
badges: text()
.references(() => tierlistBadges.id, {
onDelete: "cascade",
onUpdate: "cascade",
})
.array(4)
.default([]),
});
//#endregion
//#region Guide
export const guides = pgTable("guides", {
id: text().primaryKey().$defaultFn(uuidv7),
image: text().references(() => cdn.id, {
onDelete: "set null",
onUpdate: "cascade",
}),
order: integer().notNull(),
link: text().notNull(),
hidden: boolean().notNull().default(false),
name: text().notNull(),
});
//#endregion
//#region Donate
export const schDonate = pgSchema("donate");
export const paymentMethod = schDonate.enum("payment_method", ["tmn", "pp"]);
export const donations = schDonate.table("donations", {
id: text().primaryKey().$defaultFn(uuidv7),
name: text().notNull(),
amount: numeric({ mode: "number", precision: 7, scale: 2 }).notNull(),
image: bytea(),
message: text(),
created: timestamp("created_at").defaultNow().notNull(),
// The retry system
lastPing: timestamp("last_ping").default(new Date("01-01-2000")).notNull(),
sent: boolean().default(false).notNull(),
uid: text(),
method: paymentMethod().notNull().default("pp"),
});
export type TTrackingKey = string & { __brand: "tracking_key" };
export type TAccessKey = string & { __brand: "access_key" };
export type TUploadKey = string & { __brand: "upload_key" };
export const slipSync = schDonate.table("sync", {
trackingKey: text().primaryKey().$defaultFn(uuidv7).$type<TTrackingKey>(),
accessKey: text().notNull().$defaultFn(uuidv7).$type<TAccessKey>(),
uploadKey: text().notNull().$defaultFn(uuidv7).$type<TUploadKey>(),
created: timestamp("created_at").defaultNow().notNull(),
data: bytea(),
name: text(),
type: text(),
size: text(),
});
//#endregion
//#region Better Auth
export const user = pgTable("user", {
id: text("id").primaryKey(),
name: text("name").notNull(),
email: text("email").notNull().unique(),
emailVerified: boolean("email_verified").default(false).notNull(),
image: text("image"),
createdAt: timestamp("created_at").defaultNow().notNull(),
updatedAt: timestamp("updated_at")
.defaultNow()
.$onUpdate(() => /* @__PURE__ */ new Date())
.notNull(),
role: text("role"),
banned: boolean("banned").default(false),
banReason: text("ban_reason"),
banExpires: timestamp("ban_expires"),
});
export const session = pgTable("session", {
id: text("id").primaryKey(),
expiresAt: timestamp("expires_at").notNull(),
token: text("token").notNull().unique(),
createdAt: timestamp("created_at").defaultNow().notNull(),
updatedAt: timestamp("updated_at")
.$onUpdate(() => /* @__PURE__ */ new Date())
.notNull(),
ipAddress: text("ip_address"),
userAgent: text("user_agent"),
userId: text("user_id")
.notNull()
.references(() => user.id, { onDelete: "cascade" }),
impersonatedBy: text("impersonated_by"),
});
export const account = pgTable("account", {
id: text("id").primaryKey(),
accountId: text("account_id").notNull(),
providerId: text("provider_id").notNull(),
userId: text("user_id")
.notNull()
.references(() => user.id, { onDelete: "cascade" }),
accessToken: text("access_token"),
refreshToken: text("refresh_token"),
idToken: text("id_token"),
accessTokenExpiresAt: timestamp("access_token_expires_at"),
refreshTokenExpiresAt: timestamp("refresh_token_expires_at"),
scope: text("scope"),
password: text("password"),
createdAt: timestamp("created_at").defaultNow().notNull(),
updatedAt: timestamp("updated_at")
.$onUpdate(() => /* @__PURE__ */ new Date())
.notNull(),
});
export const verification = pgTable("verification", {
id: text("id").primaryKey(),
identifier: text("identifier").notNull(),
value: text("value").notNull(),
expiresAt: timestamp("expires_at").notNull(),
createdAt: timestamp("created_at").defaultNow().notNull(),
updatedAt: timestamp("updated_at")
.defaultNow()
.$onUpdate(() => /* @__PURE__ */ new Date())
.notNull(),
});
//#endregion