rubgram public side

Co-authored-by: Gunshin <[email protected]>
This commit is contained in:
Dreamgineer
2025-11-27 12:12:21 -05:00
co-authored by Gunshin
Unverified
parent 45f6d06cc9
commit 8a96ea8573
44 changed files with 2140 additions and 412 deletions
+28 -31
View File
@@ -4,8 +4,7 @@ import { and, eq, inArray, not, or, sql } from "drizzle-orm";
import type { PgDatabase } from "drizzle-orm/pg-core";
import { revalidatePath } from "next/cache";
import { redirect } from "next/navigation";
import { after } from "next/server";
import type { TypedFormData } from "@/app/(ui)/endgame/type";
import type { TypedFormData } from "@/app/(ui)/rubgram/type";
import { adminCheck } from "./auth";
import { uidRegex } from "./const";
import { db } from "./db";
@@ -93,7 +92,7 @@ export async function toggleCheck(submissionId: string) {
revalidatePath("/artifact/admin");
revalidatePath("/artifact");
actionLog(`Toggled an artifact submission check mark`);
await actionLog(`Toggled an artifact submission check mark`);
}
export async function toggleLock() {
@@ -109,7 +108,7 @@ export async function toggleLock() {
revalidatePath("/artifact/admin");
revalidatePath("/artifact");
actionLog(
await actionLog(
`${(existing.length ? existing[0].locked : true) ? "Locked" : "Unlocked"} artifact submission`,
);
}
@@ -131,7 +130,7 @@ export async function setLimit(limit: number) {
revalidatePath("/artifact/admin");
revalidatePath("/artifact");
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() {
@@ -143,7 +142,7 @@ export async function wipe() {
revalidatePath("/artifact/admin");
revalidatePath("/artifact");
actionLog(`Deleted artifact submissions`);
await actionLog(`Deleted artifact submissions`);
redirect("/artifact/admin");
}
@@ -192,7 +191,7 @@ export async function tlState(
revalidatePath(`/api/tl/${list}/states`);
actionLog(`Updated a state in tierlist ${list}`, data);
await actionLog(`Updated a state in tierlist ${list}`, data);
sse.publish(states, { topic: `tl-${list}`, event: "update_states" });
}
@@ -214,7 +213,7 @@ export async function tlPlacements(
revalidatePath(`/api/tl/${list}`);
actionLog(`Updated a placement in tierlist ${list}`);
await actionLog(`Updated a placement in tierlist ${list}`);
sse.publish(placements, { topic: `tl-${list}`, event: "update_placements" });
}
@@ -227,7 +226,7 @@ export async function cdnDelete(ids: string[], force = false) {
const refs = await checkCdnRefs(id);
if (refs.length) {
revalidatePath("/cdn/admin");
actionLog(
await actionLog(
`Deleted ${deleted}/${ids.length}(Incomplete) files`,
ids.slice(0, deleted),
);
@@ -238,7 +237,7 @@ export async function cdnDelete(ids: string[], force = false) {
}
revalidatePath("/cdn/admin");
actionLog(`Deleted ${deleted} files`, ids);
await actionLog(`Deleted ${deleted} files`, ids);
}
export async function checkCdnRefs(
@@ -272,7 +271,7 @@ export async function cdnify(
})
.returning({ id: cdn.id });
actionLog(`File uploaded: ${name || `[${id}]`} (${b2s(data.size)})`);
await actionLog(`File uploaded: ${name || `[${id}]`} (${b2s(data.size)})`);
try {
revalidatePath("/admin/cdn");
@@ -281,27 +280,25 @@ export async function cdnify(
}
export async function actionLog(text: string, details?: unknown) {
after(async () => {
// If not running in Next.js, skip.
if (typeof process.versions.bun !== "undefined") return;
const session = await adminCheck();
// If not running in Next.js, skip.
if (typeof process.versions.bun !== "undefined") return;
const session = await adminCheck();
const res = await db
.insert(auditLog)
.values({
author: session?.name,
text,
details,
})
.returning()
.catch(() =>
console.error(
`Error logging audit log, printing it here:\n${session?.name || "[Unknown User]"} - ${text}`,
),
);
const res = await db
.insert(auditLog)
.values({
author: session?.name,
text,
details,
})
.returning()
.catch(() =>
console.error(
`Error logging audit log, printing it here:\n${session?.name || "[Unknown User]"} - ${text}`,
),
);
revalidatePath("/admin/log");
revalidatePath("/admin/log");
if (res) sse.publish(res, { topic: "log" });
});
if (res) sse.publish(res, { topic: "log" });
}
-6
View File
@@ -10,12 +10,6 @@ export const auth = betterAuth({
enabled: true,
disableSignUp: true,
},
socialProviders: {
discord: {
enabled: true,
clientId: "1397024670355554336",
},
},
plugins: [admin()],
trustedOrigins: [
"http://nyx:3000",
+9 -1
View File
@@ -14,6 +14,7 @@ type WebsiteComms = Partial<{
debug: boolean;
connected: boolean;
updated: boolean;
"rubgram.services": ("abyss" | "theater" | "stygian")[];
_raw: Record<string, unknown>;
}>;
@@ -91,7 +92,14 @@ export default function CommsProvider({
}
export const comms = {
var(name: keyof WebsiteComms) {
var<K extends keyof WebsiteComms>(
name: K,
): [
WebsiteComms[K],
(
value: WebsiteComms[K] | ((prev: WebsiteComms[K]) => WebsiteComms[K]),
) => void,
] {
const comms = use(CommsContext);
if (!comms.active) {
console.warn("ICC used outside of provider. Using local state instead.");
+51 -3
View File
@@ -3,6 +3,7 @@ import {
boolean,
integer,
jsonb,
numeric,
pgEnum,
pgSchema,
pgTable,
@@ -11,6 +12,7 @@ import {
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", [
@@ -102,22 +104,68 @@ export const artifactSettings = artifact.table("settings", {
//#endregion
//#region Artifact
//#region Endgame
export const endgame = pgSchema("endgame");
export const endgameServer = endgame.enum("server", ["as", "eu", "us", "tw"]);
export const endgameService = endgame.enum("service", [
"abyss",
"theater",
"stygian",
]);
export const endgameSubmissions = endgame.table("submissions", {
id: text().primaryKey().$defaultFn(uuidv7),
name: text().notNull(),
user: text().notNull(),
expires: timestamp().$defaultFn(() => new Date(Date.now() + 15 * 60 * 1000)), // 15 minutes to pay
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: endgameService().array().notNull(),
price: integer().notNull(), // calculated on submission
slip: text().references(() => endgameSlips.id), // not paid = null
checked: boolean().notNull().default(false),
});
// deleted submissions
export const endgameArchive = endgame.table("archive", {
id: text().primaryKey().$defaultFn(uuidv7),
name: text().notNull(),
user: text()
.notNull()
.references(() => endgameDiscord.uid, {
onDelete: "cascade",
onUpdate: "cascade",
}),
server: endgameServer().notNull(),
service: endgameService().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),
});
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.
});
//#endregion
+3 -1
View File
@@ -117,7 +117,9 @@ export class EventSourceManager {
if (s.topic !== topic) continue;
try {
s.send(data, event);
} catch {}
} catch (error) {
console.error(` ERR #${topic}#${s.id}, ${error}`);
}
}
});
}