feat: rubgram manual submission creation
Build and Push Image / docker (push) Successful in 5m49s

This commit is contained in:
2026-06-02 23:43:00 +07:00 Unverified
parent 809bb8d09c
commit 5d9b75aa9b
43 changed files with 1354 additions and 381 deletions
+57 -19
View File
@@ -27,7 +27,10 @@ import { redis } from "./db/redis";
import { fetch } from "bun";
export async function getCharacters(chars: string[]) {
return await db.select().from(characters).where(inArray(characters.amber, chars));
return await db
.select()
.from(characters)
.where(inArray(characters.amber, chars));
}
export async function getArtifactConfig() {
@@ -52,7 +55,9 @@ const ArtifactSubmission = (editToken?: string) =>
.where(
and(
eq(submissions.uid, uid),
editToken ? not(eq(submissions.editToken, editToken)) : undefined,
editToken
? not(eq(submissions.editToken, editToken))
: undefined,
),
)
.limit(1)
@@ -69,19 +74,25 @@ const ArtifactSubmission = (editToken?: string) =>
.then((r) => !!r.length),
"ไม่พบตัวละครที่เลือก",
),
comment: z.string().max(1024, "ข้อความเพิ่มเติมยาวเกินไป ต้องไม่เกิน 1024 ตัวอักษร"),
comment: z
.string()
.max(1024, "ข้อความเพิ่มเติมยาวเกินไป ต้องไม่เกิน 1024 ตัวอักษร"),
},
"กรุณากรอกข้อมูลให้ครบถ้วน",
);
export async function submitArtifact(formData: FormData, edit?: { sub: string; token: string }) {
export async function submitArtifact(
formData: FormData,
edit?: { sub: string; token: string },
) {
const config = await getArtifactConfig();
if (config.locked) return "ปิดรับลงทะเบียนชั่วคราว เนื่องจากมีผู้ลงจำนวนมาก";
const count = await db.$count(submissions);
if (config.limit !== -1 && count >= config.limit) return `คิวลงทะเบียนเต็มแล้ว (${config.limit} ครั้ง)`;
const { success, data, error } = await ArtifactSubmission(edit?.token).safeParseAsync(
Object.fromEntries(formData.entries()),
);
if (config.limit !== -1 && count >= config.limit)
return `คิวลงทะเบียนเต็มแล้ว (${config.limit} ครั้ง)`;
const { success, data, error } = await ArtifactSubmission(
edit?.token,
).safeParseAsync(Object.fromEntries(formData.entries()));
if (!success) return z.prettifyError(error);
if (edit) {
const [existing] = await db
@@ -179,7 +190,8 @@ export async function toggleLock() {
locked: not(artifactSettings.locked),
})
.returning({ locked: artifactSettings.locked });
if (existing.length === 0) await db.insert(artifactSettings).values({ locked: true });
if (existing.length === 0)
await db.insert(artifactSettings).values({ locked: true });
revalidatePath("/artifact/admin");
revalidatePath("/artifact");
@@ -207,13 +219,17 @@ export async function setLimit(limit: number) {
revalidatePath("/artifact");
sse.artifact.pub("update", { type: "setLimit" });
await actionLog(`Set artifact submit limit to ${limit < 0 ? "unlimited" : limit}`);
await actionLog(
`Set artifact submit limit to ${limit < 0 ? "unlimited" : limit}`,
);
}
export async function wipe() {
if (!(await adminCheck())) throw "Unauthorized";
await db.delete(submissions);
await db.execute(sql`ALTER SEQUENCE artifact.submissions_queue_seq RESTART WITH 1`);
await db.execute(
sql`ALTER SEQUENCE artifact.submissions_queue_seq RESTART WITH 1`,
);
revalidatePath("/artifact");
sse.artifact.pub("update", { type: "wipe" });
@@ -239,7 +255,9 @@ export async function revalidateCard(sub: string) {
revalidatePath(`/api/card/${sub}`);
}
export async function tlState(data: Partial<typeof tierlistStates.$inferInsert>) {
export async function tlState(
data: Partial<typeof tierlistStates.$inferInsert>,
) {
if (!(await adminCheck())) throw "Unauthorized";
const [existing] = await db
.select()
@@ -247,14 +265,26 @@ export async function tlState(data: Partial<typeof tierlistStates.$inferInsert>)
.where(
or(
eq(tierlistStates.uuid, `${data.uuid}`),
and(eq(tierlistStates.ref, `${data.ref}`), eq(tierlistStates.list, `${data.list}`)),
and(
eq(tierlistStates.ref, `${data.ref}`),
eq(tierlistStates.list, `${data.list}`),
),
),
);
if (existing)
await db.update(tierlistStates).set(data).where(eq(tierlistStates.uuid, existing.uuid));
else await db.insert(tierlistStates).values(data as typeof tierlistStates.$inferInsert);
await db
.update(tierlistStates)
.set(data)
.where(eq(tierlistStates.uuid, existing.uuid));
else
await db
.insert(tierlistStates)
.values(data as typeof tierlistStates.$inferInsert);
const list = data.list || existing?.list;
const states = await db.select().from(tierlistStates).where(eq(tierlistStates.list, list));
const states = await db
.select()
.from(tierlistStates)
.where(eq(tierlistStates.list, list));
revalidatePath(`/api/tl/${list}/states`);
@@ -262,7 +292,10 @@ export async function tlState(data: Partial<typeof tierlistStates.$inferInsert>)
tlSse(list).pub("update_states", states);
}
export async function tlPlacements(list: string, placements: Record<string, string[]>) {
export async function tlPlacements(
list: string,
placements: Record<string, string[]>,
) {
if (!(await adminCheck())) throw "Unauthorized";
const { untiered: _, ...placementObj } = placements;
@@ -325,7 +358,8 @@ export async function cdnify(
// biome-ignore lint/suspicious/noExplicitAny: type parameters
config: { tx?: PgDatabase<any, any, any>; name?: string } = {},
) {
const { tx = db, name = data instanceof File ? (data as File).name : null } = config;
const { tx = db, name = data instanceof File ? (data as File).name : null } =
config;
const [{ id }] = await tx
.insert(cdn)
.values({
@@ -382,7 +416,11 @@ export async function getAmberVh() {
});
const {
data: { vh },
} = Schema.parse(await fetch("https://gi.yatta.moe/api/v2/static/version").then((e) => e.json()));
} = Schema.parse(
await fetch("https://gi.yatta.moe/api/v2/static/version").then((e) =>
e.json(),
),
);
queueMicrotask(() => redis!.setex("amber:vh", 86400, vh));
return vh;
}
+1 -1
View File
@@ -185,7 +185,7 @@ export const shared = {
// unmount handler
useEffect(
() => () => {
const { [k]: removed, ...rest } = comms.get("inspect") || {};
const { [k]: _, ...rest } = comms.get("inspect") || {};
comms.set("inspect", rest);
},
[],
+1 -1
View File
@@ -1,4 +1,4 @@
import { drizzle } from "drizzle-orm/node-postgres";
import { drizzle } from "drizzle-orm/bun-sql";
import * as schema from "./schema";
export const db = drizzle(process.env.DATABASE_URL!, { schema });
+4 -1
View File
@@ -146,7 +146,10 @@ export const endgameSubmissions = endgame.table("submissions", {
.notNull()
.generatedAlwaysAs(
(): SQL =>
or(lte(endgameSubmissions.price, sql.raw("0")), isNotNull(endgameSubmissions.slip))!,
or(
lte(endgameSubmissions.price, sql.raw("0")),
isNotNull(endgameSubmissions.slip),
)!,
),
archived: boolean().notNull().default(false),
});