CVE Patch + Post stream rubgram + tl improvements
- Switch to using generated columns for rubgram's paid - Few text changes - Isolate badges to their type - Use Redis to cache health checks - Remove confusing logging and add actionLog stdout logging - Blacklist RSC/prefetch related requests from nginx logging - Fix nginx upload size limit
This commit is contained in:
@@ -1,4 +1,5 @@
|
|||||||
import {
|
import {
|
||||||
|
ArrowLeftRight,
|
||||||
Badge,
|
Badge,
|
||||||
BookUser,
|
BookUser,
|
||||||
Columns3Cog,
|
Columns3Cog,
|
||||||
@@ -59,8 +60,12 @@ export default async function AdminLayout({
|
|||||||
.select()
|
.select()
|
||||||
.from(versions)
|
.from(versions)
|
||||||
.catch(() => [{ id: "..", name: "Error Fetching List." }]);
|
.catch(() => [{ id: "..", name: "Error Fetching List." }]);
|
||||||
const health: { database: boolean; enka: boolean; amber: boolean } =
|
const health: {
|
||||||
await fetch("http://localhost:3000/api/health").then((r) => r.json());
|
database: boolean;
|
||||||
|
enka: boolean;
|
||||||
|
amber: boolean;
|
||||||
|
red: boolean;
|
||||||
|
} = await fetch("http://localhost:3000/api/health").then((r) => r.json());
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SidebarProvider>
|
<SidebarProvider>
|
||||||
@@ -116,10 +121,18 @@ export default async function AdminLayout({
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</SimpleTooltip>
|
</SimpleTooltip>
|
||||||
|
{/* Cache */}
|
||||||
|
<SimpleTooltip text="Redis Cache/SSE">
|
||||||
|
<ArrowLeftRight
|
||||||
|
className={
|
||||||
|
health.red ? "text-emerald-400" : "text-red-400"
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</SimpleTooltip>
|
||||||
</div>
|
</div>
|
||||||
<span>
|
<span>
|
||||||
{Object.values(health).some((x) => !x)
|
{Object.values(health).some((x) => !x)
|
||||||
? `${{ database: "ฐานข้อมูล", enka: "Enka ", amber: "Amber " }[Object.entries(health).find(([, v]) => !v)![0]]}มีปัญหา`
|
? `${{ database: "ฐานข้อมูล", enka: "Enka ", amber: "Amber ", red: "SSE " }[Object.entries(health).find(([, v]) => !v)![0]]}มีปัญหา`
|
||||||
: "ปกติทุกอย่าง"}
|
: "ปกติทุกอย่าง"}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ export default function AuditLogViewer({
|
|||||||
}) {
|
}) {
|
||||||
const [logs, updateLogs] = useState(initialLogs);
|
const [logs, updateLogs] = useState(initialLogs);
|
||||||
const containerRef = useRef<HTMLDivElement>(null);
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
|
const entryRefs = useRef(new Map<string, HTMLDivElement>());
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const es = new EventSource("/api/ev/log");
|
const es = new EventSource("/api/ev/log");
|
||||||
@@ -26,18 +27,9 @@ export default function AuditLogViewer({
|
|||||||
return () => es.close();
|
return () => es.close();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (containerRef.current)
|
|
||||||
containerRef.current.scrollTo({
|
|
||||||
top: 999999,
|
|
||||||
left: 0,
|
|
||||||
behavior: "smooth",
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
const filters: Filters = {
|
const filters: Filters = {
|
||||||
user: () => users.map((u) => u.email),
|
user: () => users.map((u) => u.email),
|
||||||
has: () => ["artifact", "tierlist", "file"],
|
has: () => ["artifact", "tierlist", "rubgram", "file"],
|
||||||
};
|
};
|
||||||
|
|
||||||
const [query, setQuery] = useState<ParsedQuery>();
|
const [query, setQuery] = useState<ParsedQuery>();
|
||||||
@@ -58,6 +50,36 @@ export default function AuditLogViewer({
|
|||||||
}, [query, logs, users]);
|
}, [query, logs, users]);
|
||||||
|
|
||||||
const [expandedIds, setExpandedIds] = useState<Set<string>>(new Set());
|
const [expandedIds, setExpandedIds] = useState<Set<string>>(new Set());
|
||||||
|
const focusedIds = useMemo(() => Array.from(expandedIds), [expandedIds]);
|
||||||
|
|
||||||
|
// biome-ignore lint/correctness/useExhaustiveDependencies: need to update
|
||||||
|
useEffect(() => {
|
||||||
|
const container = containerRef.current;
|
||||||
|
if (!container) return;
|
||||||
|
|
||||||
|
// exactly one focused → scroll to that entry
|
||||||
|
if (focusedIds.length === 1) {
|
||||||
|
const el = entryRefs.current.get(focusedIds[0]);
|
||||||
|
if (el) {
|
||||||
|
el.scrollIntoView({
|
||||||
|
behavior: "smooth",
|
||||||
|
block: "center",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// more than one focused → do nothing
|
||||||
|
if (focusedIds.length > 1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// none focused → always follow bottom
|
||||||
|
container.scrollTo({
|
||||||
|
top: container.scrollHeight,
|
||||||
|
behavior: "smooth",
|
||||||
|
});
|
||||||
|
}, [logs, focusedIds, query]);
|
||||||
|
|
||||||
const toggleExpand = (id: string) => {
|
const toggleExpand = (id: string) => {
|
||||||
setExpandedIds((prev) => {
|
setExpandedIds((prev) => {
|
||||||
@@ -117,12 +139,16 @@ export default function AuditLogViewer({
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={log.id}
|
key={log.id}
|
||||||
|
ref={(el) => {
|
||||||
|
if (el) entryRefs.current.set(log.id, el);
|
||||||
|
else entryRefs.current.delete(log.id);
|
||||||
|
}}
|
||||||
className="group hover:bg-[#2225] transition-colors"
|
className="group hover:bg-[#2225] transition-colors"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className={`flex gap-4 px-4 ${isGrouped ? "py-0.5" : "py-3.5"} pb-0.5`}
|
className={`flex gap-4 px-4 ${isGrouped ? "py-0.5" : "py-3.5"} pb-0.5`}
|
||||||
>
|
>
|
||||||
<div className="w-min flex-shrink-0 text-right pt-0.5">
|
<div className="w-min shrink-0 text-right pt-0.5">
|
||||||
<span
|
<span
|
||||||
className={`text-xs whitespace-nowrap invisible ${isGrouped ? "group-hover:visible" : ""}`}
|
className={`text-xs whitespace-nowrap invisible ${isGrouped ? "group-hover:visible" : ""}`}
|
||||||
>
|
>
|
||||||
@@ -154,7 +180,7 @@ export default function AuditLogViewer({
|
|||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="icon"
|
size="icon"
|
||||||
className="flex-shrink-0 h-6 w-6 text-gray-400 hover:text-gray-200 hover:bg-[#404249]"
|
className="shrink-0 h-6 w-6 text-gray-400 hover:text-gray-200 hover:bg-[#404249]"
|
||||||
>
|
>
|
||||||
{isExpanded ? (
|
{isExpanded ? (
|
||||||
<ChevronDown className="h-4 w-4" />
|
<ChevronDown className="h-4 w-4" />
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ export function CallButton({ user }: { user: string }) {
|
|||||||
) : (
|
) : (
|
||||||
<MessageSquareWarning />
|
<MessageSquareWarning />
|
||||||
)}
|
)}
|
||||||
กดเรียกผ่านดิสคอร์ด
|
เรียกในดิสคอร์ด
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { eq, isNotNull, or, type SQL } from "drizzle-orm";
|
|
||||||
import { Dice3, Trash2 } from "lucide-react";
|
import { Dice3, Trash2 } from "lucide-react";
|
||||||
import { redirect } from "next/navigation";
|
import { redirect } from "next/navigation";
|
||||||
import {
|
import {
|
||||||
@@ -42,10 +41,7 @@ export default async function AdminLayout({
|
|||||||
name: endgameSubmissions.name,
|
name: endgameSubmissions.name,
|
||||||
checked: endgameSubmissions.checked,
|
checked: endgameSubmissions.checked,
|
||||||
queue: endgameSubmissions.queue,
|
queue: endgameSubmissions.queue,
|
||||||
paid: or(
|
paid: endgameSubmissions.paid,
|
||||||
eq(endgameSubmissions.price, 0),
|
|
||||||
isNotNull(endgameSubmissions.slip),
|
|
||||||
) as SQL<boolean>,
|
|
||||||
})
|
})
|
||||||
.from(endgameSubmissions)
|
.from(endgameSubmissions)
|
||||||
.orderBy(endgameSubmissions.id);
|
.orderBy(endgameSubmissions.id);
|
||||||
|
|||||||
+5
-22
@@ -1,17 +1,6 @@
|
|||||||
"use server";
|
"use server";
|
||||||
|
|
||||||
import {
|
import { and, desc, eq, gt, lt, not, sql } from "drizzle-orm";
|
||||||
and,
|
|
||||||
desc,
|
|
||||||
eq,
|
|
||||||
gt,
|
|
||||||
isNotNull,
|
|
||||||
isNull,
|
|
||||||
lt,
|
|
||||||
not,
|
|
||||||
or,
|
|
||||||
sql,
|
|
||||||
} from "drizzle-orm";
|
|
||||||
import { revalidatePath } from "next/cache";
|
import { revalidatePath } from "next/cache";
|
||||||
import { cookies } from "next/headers";
|
import { cookies } from "next/headers";
|
||||||
import image from "next/image";
|
import image from "next/image";
|
||||||
@@ -57,13 +46,8 @@ export async function random() {
|
|||||||
const [sub] = await db
|
const [sub] = await db
|
||||||
.select()
|
.select()
|
||||||
.from(endgameSubmissions)
|
.from(endgameSubmissions)
|
||||||
.where(
|
.where(endgameSubmissions.paid.getSQL())
|
||||||
and(
|
.orderBy(sql`${endgameSubmissions.checked} ASC, RANDOM()`)
|
||||||
not(endgameSubmissions.checked),
|
|
||||||
or(eq(endgameSubmissions.price, 0), isNotNull(endgameSubmissions.slip)),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.orderBy(sql`RANDOM()`)
|
|
||||||
.limit(1);
|
.limit(1);
|
||||||
if (sub) redirect(`/rubgram/admin/${sub.id}`);
|
if (sub) redirect(`/rubgram/admin/${sub.id}`);
|
||||||
else throw "ไม่พบผู้ลงทะเบียนที่ยังไม่ตรวจสอบ";
|
else throw "ไม่พบผู้ลงทะเบียนที่ยังไม่ตรวจสอบ";
|
||||||
@@ -348,8 +332,7 @@ export async function calcPrice(service: string[]) {
|
|||||||
async function removeExpiredSubmissions() {
|
async function removeExpiredSubmissions() {
|
||||||
// First, get count of items to be removed
|
// First, get count of items to be removed
|
||||||
const expiredCond = and(
|
const expiredCond = and(
|
||||||
not(eq(endgameSubmissions.price, 0)),
|
not(endgameSubmissions.paid),
|
||||||
isNull(endgameSubmissions.slip),
|
|
||||||
lt(endgameSubmissions.expires, new Date()),
|
lt(endgameSubmissions.expires, new Date()),
|
||||||
);
|
);
|
||||||
const toRemove = await db
|
const toRemove = await db
|
||||||
@@ -420,7 +403,7 @@ export async function discordCall(id: string) {
|
|||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
}).then(async (r) => {
|
}).then(async (r) => {
|
||||||
console.log(await r.text());
|
console.log(`called`, await r.text());
|
||||||
return r.ok;
|
return r.ok;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -214,7 +214,7 @@ export function CancelButton({ sid }: { sid: string }) {
|
|||||||
}}
|
}}
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
>
|
>
|
||||||
{loading && <Spinner />}ยกเลิก
|
{loading && <Spinner />}ใช่
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
+14
-17
@@ -1,13 +1,9 @@
|
|||||||
import {
|
import {
|
||||||
and,
|
and,
|
||||||
eq,
|
eq,
|
||||||
gt,
|
gt, lt,
|
||||||
isNotNull,
|
|
||||||
isNull,
|
|
||||||
lt,
|
|
||||||
not,
|
not,
|
||||||
or,
|
or
|
||||||
type SQL,
|
|
||||||
} from "drizzle-orm";
|
} from "drizzle-orm";
|
||||||
import {
|
import {
|
||||||
AlertCircle,
|
AlertCircle,
|
||||||
@@ -89,10 +85,7 @@ export default async function EndgamePage() {
|
|||||||
? await db
|
? await db
|
||||||
.select({
|
.select({
|
||||||
queue: endgameSubmissions.queue,
|
queue: endgameSubmissions.queue,
|
||||||
paid: or(
|
paid: endgameSubmissions.paid,
|
||||||
eq(endgameSubmissions.price, 0),
|
|
||||||
isNotNull(endgameSubmissions.slip),
|
|
||||||
) as SQL<boolean>,
|
|
||||||
price: endgameSubmissions.price,
|
price: endgameSubmissions.price,
|
||||||
expires: endgameSubmissions.expires,
|
expires: endgameSubmissions.expires,
|
||||||
})
|
})
|
||||||
@@ -114,8 +107,7 @@ export default async function EndgamePage() {
|
|||||||
.from(endgameSubmissions)
|
.from(endgameSubmissions)
|
||||||
.where(
|
.where(
|
||||||
and(
|
and(
|
||||||
not(eq(endgameSubmissions.price, 0)),
|
not(endgameSubmissions.paid),
|
||||||
isNull(endgameSubmissions.slip),
|
|
||||||
lt(endgameSubmissions.queue, q.queue),
|
lt(endgameSubmissions.queue, q.queue),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@@ -293,13 +285,13 @@ export default async function EndgamePage() {
|
|||||||
</SimpleTooltip>
|
</SimpleTooltip>
|
||||||
<AlertDialogContent>
|
<AlertDialogContent>
|
||||||
<AlertDialogHeader>
|
<AlertDialogHeader>
|
||||||
<AlertDialogTitle>แน่ใจเหรอ</AlertDialogTitle>
|
<AlertDialogTitle>แน่ใจหรอ</AlertDialogTitle>
|
||||||
<AlertDialogDescription>
|
<AlertDialogDescription>
|
||||||
ต้องการยกเลิกคิวของคุณหรือไม่
|
ต้องการยกเลิกการลงคิวของคุณหรือไม่
|
||||||
</AlertDialogDescription>
|
</AlertDialogDescription>
|
||||||
</AlertDialogHeader>
|
</AlertDialogHeader>
|
||||||
<AlertDialogFooter>
|
<AlertDialogFooter>
|
||||||
<AlertDialogCancel>ไม่ใช่</AlertDialogCancel>
|
<AlertDialogCancel>ไม่ยกเลิก</AlertDialogCancel>
|
||||||
<CancelButton sid={sid!.value} />
|
<CancelButton sid={sid!.value} />
|
||||||
</AlertDialogFooter>
|
</AlertDialogFooter>
|
||||||
</AlertDialogContent>
|
</AlertDialogContent>
|
||||||
@@ -330,9 +322,14 @@ export default async function EndgamePage() {
|
|||||||
</div>
|
</div>
|
||||||
</CardFooter>
|
</CardFooter>
|
||||||
</Card>
|
</Card>
|
||||||
<span className="backdrop-blur-md p-1 text-xs m-1 border border-dashed rounded-sm">
|
<span className="backdrop-blur-md p-1 text-xs m-1 border rounded-sm">
|
||||||
หากติดปัญหา โปรดแจ้งผ่านทาง
|
หากติดปัญหา โปรดแจ้งผ่านทาง
|
||||||
<a href="https://discord.gg/HQwDXNhxuK" className="underline">ช่องดิสคอร์ด</a>
|
<a
|
||||||
|
href="https://discord.gg/HQwDXNhxuK"
|
||||||
|
className="underline text-green-200"
|
||||||
|
>
|
||||||
|
ช่องดิสคอร์ด
|
||||||
|
</a>
|
||||||
</span>
|
</span>
|
||||||
{sid && !q && <ClearCookie />}
|
{sid && !q && <ClearCookie />}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { and, eq, inArray } from "drizzle-orm";
|
import { and, eq, or, inArray, isNull } from "drizzle-orm";
|
||||||
import { notFound } from "next/navigation";
|
import { notFound } from "next/navigation";
|
||||||
import { db } from "@/lib/db";
|
import { db } from "@/lib/db";
|
||||||
import {
|
import {
|
||||||
@@ -94,7 +94,10 @@ export default async function TierlistPage({
|
|||||||
const badgesList = await tx
|
const badgesList = await tx
|
||||||
.select()
|
.select()
|
||||||
.from(tierlistBadges)
|
.from(tierlistBadges)
|
||||||
.orderBy(tierlistBadges.order);
|
.orderBy(tierlistBadges.order)
|
||||||
|
.where(
|
||||||
|
or(isNull(tierlistBadges.type), eq(tierlistBadges.type, version.type)),
|
||||||
|
);
|
||||||
const badges = badgesList.map((b) => ({
|
const badges = badgesList.map((b) => ({
|
||||||
...b,
|
...b,
|
||||||
tier: tiers.filter((t) => t.badges?.includes(b.id)).map((t) => t.id),
|
tier: tiers.filter((t) => t.badges?.includes(b.id)).map((t) => t.id),
|
||||||
|
|||||||
+44
-20
@@ -2,29 +2,53 @@ import { sql } from "drizzle-orm";
|
|||||||
import { db } from "@/lib/db";
|
import { db } from "@/lib/db";
|
||||||
import { redis } from "@/lib/db/redis";
|
import { redis } from "@/lib/db/redis";
|
||||||
|
|
||||||
export const revalidate = 900;
|
|
||||||
|
|
||||||
export async function GET() {
|
export async function GET() {
|
||||||
const database = await db
|
|
||||||
.execute(sql<boolean>`select true`)
|
|
||||||
.then(() => true)
|
|
||||||
.catch(() => false);
|
|
||||||
const red = await redis
|
const red = await redis
|
||||||
.ping()
|
.ping()
|
||||||
.then(() => true)
|
.then(() => true)
|
||||||
.catch(() => false);
|
.catch(() => false);
|
||||||
const enka = await fetch("https://enka.network/api/uid/888888888/?info", {
|
const res = Object.fromEntries(
|
||||||
cache: "force-cache",
|
await Promise.all(
|
||||||
headers: { "User-Agent": "Buzz Event Platform(Health Checker)" },
|
Object.entries(checks).map(
|
||||||
signal: AbortSignal.timeout(5000),
|
async ([k, v]) =>
|
||||||
})
|
[
|
||||||
.then((r) => r.ok)
|
k,
|
||||||
.catch(() => false);
|
red
|
||||||
const amber = await fetch("https://gi.yatta.moe/api/v2/en/avatar", {
|
? (await redis.exists(`health:${k}`))
|
||||||
cache: "force-cache",
|
? (await redis.get(`health:${k}`)) === "ok"
|
||||||
signal: AbortSignal.timeout(5000),
|
: await v().then(async (v) => {
|
||||||
})
|
if (v) await redis.setex(`health:${k}`, 900, "ok");
|
||||||
.then((r) => r.ok)
|
return v;
|
||||||
.catch(() => false);
|
})
|
||||||
return Response.json({ database, red, enka, amber });
|
: await v(),
|
||||||
|
] as const,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return Response.json({ red, ...res });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const checks = {
|
||||||
|
async database() {
|
||||||
|
return await db
|
||||||
|
.execute(sql<boolean>`select true`)
|
||||||
|
.then(() => true)
|
||||||
|
.catch(() => false);
|
||||||
|
},
|
||||||
|
async enka() {
|
||||||
|
return await fetch("https://enka.network/api/uid/888888888/?info", {
|
||||||
|
headers: { "User-Agent": "Buzz, https://buzz.sudloh.com/api/health" },
|
||||||
|
signal: AbortSignal.timeout(5000),
|
||||||
|
})
|
||||||
|
.then((r) => r.ok)
|
||||||
|
.catch(() => false);
|
||||||
|
},
|
||||||
|
async amber() {
|
||||||
|
return await fetch("https://gi.yatta.moe/api/v2/en/avatar", {
|
||||||
|
headers: { "User-Agent": "Buzz, https://buzz.sudloh.com/api/health" },
|
||||||
|
signal: AbortSignal.timeout(5000),
|
||||||
|
})
|
||||||
|
.then((r) => r.ok)
|
||||||
|
.catch(() => false);
|
||||||
|
},
|
||||||
|
} as const;
|
||||||
|
|||||||
@@ -18,8 +18,6 @@ export async function GET(
|
|||||||
if (!sub) notFound();
|
if (!sub) notFound();
|
||||||
if (!sub.slip) notFound();
|
if (!sub.slip) notFound();
|
||||||
|
|
||||||
console.log(sub.slip.length);
|
|
||||||
|
|
||||||
return new Response(new Uint8Array(sub.slip), {
|
return new Response(new Uint8Array(sub.slip), {
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "image/jpeg",
|
"Content-Type": "image/jpeg",
|
||||||
|
|||||||
@@ -52,7 +52,7 @@
|
|||||||
"framer-motion": "^12.23.26",
|
"framer-motion": "^12.23.26",
|
||||||
"input-otp": "^1.4.2",
|
"input-otp": "^1.4.2",
|
||||||
"lucide-react": "^0.544.0",
|
"lucide-react": "^0.544.0",
|
||||||
"next": "16.0.7",
|
"next": "16.0.10",
|
||||||
"next-themes": "^0.4.6",
|
"next-themes": "^0.4.6",
|
||||||
"pg": "^8.16.3",
|
"pg": "^8.16.3",
|
||||||
"radix-ui": "^1.4.3",
|
"radix-ui": "^1.4.3",
|
||||||
@@ -363,23 +363,23 @@
|
|||||||
|
|
||||||
"@next/bundle-analyzer": ["@next/[email protected]", "", { "dependencies": { "webpack-bundle-analyzer": "4.10.1" } }, "sha512-zoa/w3TYl6kBcJesvj7JM96fHu1WiHRLTRMgEdLEn2kh1cGJMkIIlOGyekjnccaozd+oIyQ925lKrkCMfgt1xg=="],
|
"@next/bundle-analyzer": ["@next/[email protected]", "", { "dependencies": { "webpack-bundle-analyzer": "4.10.1" } }, "sha512-zoa/w3TYl6kBcJesvj7JM96fHu1WiHRLTRMgEdLEn2kh1cGJMkIIlOGyekjnccaozd+oIyQ925lKrkCMfgt1xg=="],
|
||||||
|
|
||||||
"@next/env": ["@next/[email protected].7", "", {}, "sha512-gpaNgUh5nftFKRkRQGnVi5dpcYSKGcZZkQffZ172OrG/XkrnS7UBTQ648YY+8ME92cC4IojpI2LqTC8sTDhAaw=="],
|
"@next/env": ["@next/[email protected].10", "", {}, "sha512-8tuaQkyDVgeONQ1MeT9Mkk8pQmZapMKFh5B+OrFUlG3rVmYTXcXlBetBgTurKXGaIZvkoqRT9JL5K3phXcgang=="],
|
||||||
|
|
||||||
"@next/swc-darwin-arm64": ["@next/[email protected].7", "", { "os": "darwin", "cpu": "arm64" }, "sha512-LlDtCYOEj/rfSnEn/Idi+j1QKHxY9BJFmxx7108A6D8K0SB+bNgfYQATPk/4LqOl4C0Wo3LACg2ie6s7xqMpJg=="],
|
"@next/swc-darwin-arm64": ["@next/[email protected].10", "", { "os": "darwin", "cpu": "arm64" }, "sha512-4XgdKtdVsaflErz+B5XeG0T5PeXKDdruDf3CRpnhN+8UebNa5N2H58+3GDgpn/9GBurrQ1uWW768FfscwYkJRg=="],
|
||||||
|
|
||||||
"@next/swc-darwin-x64": ["@next/[email protected].7", "", { "os": "darwin", "cpu": "x64" }, "sha512-rtZ7BhnVvO1ICf3QzfW9H3aPz7GhBrnSIMZyr4Qy6boXF0b5E3QLs+cvJmg3PsTCG2M1PBoC+DANUi4wCOKXpA=="],
|
"@next/swc-darwin-x64": ["@next/[email protected].10", "", { "os": "darwin", "cpu": "x64" }, "sha512-spbEObMvRKkQ3CkYVOME+ocPDFo5UqHb8EMTS78/0mQ+O1nqE8toHJVioZo4TvebATxgA8XMTHHrScPrn68OGw=="],
|
||||||
|
|
||||||
"@next/swc-linux-arm64-gnu": ["@next/[email protected].7", "", { "os": "linux", "cpu": "arm64" }, "sha512-mloD5WcPIeIeeZqAIP5c2kdaTa6StwP4/2EGy1mUw8HiexSHGK/jcM7lFuS3u3i2zn+xH9+wXJs6njO7VrAqww=="],
|
"@next/swc-linux-arm64-gnu": ["@next/[email protected].10", "", { "os": "linux", "cpu": "arm64" }, "sha512-uQtWE3X0iGB8apTIskOMi2w/MKONrPOUCi5yLO+v3O8Mb5c7K4Q5KD1jvTpTF5gJKa3VH/ijKjKUq9O9UhwOYw=="],
|
||||||
|
|
||||||
"@next/swc-linux-arm64-musl": ["@next/[email protected].7", "", { "os": "linux", "cpu": "arm64" }, "sha512-+ksWNrZrthisXuo9gd1XnjHRowCbMtl/YgMpbRvFeDEqEBd523YHPWpBuDjomod88U8Xliw5DHhekBC3EOOd9g=="],
|
"@next/swc-linux-arm64-musl": ["@next/[email protected].10", "", { "os": "linux", "cpu": "arm64" }, "sha512-llA+hiDTrYvyWI21Z0L1GiXwjQaanPVQQwru5peOgtooeJ8qx3tlqRV2P7uH2pKQaUfHxI/WVarvI5oYgGxaTw=="],
|
||||||
|
|
||||||
"@next/swc-linux-x64-gnu": ["@next/[email protected].7", "", { "os": "linux", "cpu": "x64" }, "sha512-4WtJU5cRDxpEE44Ana2Xro1284hnyVpBb62lIpU5k85D8xXxatT+rXxBgPkc7C1XwkZMWpK5rXLXTh9PFipWsA=="],
|
"@next/swc-linux-x64-gnu": ["@next/[email protected].10", "", { "os": "linux", "cpu": "x64" }, "sha512-AK2q5H0+a9nsXbeZ3FZdMtbtu9jxW4R/NgzZ6+lrTm3d6Zb7jYrWcgjcpM1k8uuqlSy4xIyPR2YiuUr+wXsavA=="],
|
||||||
|
|
||||||
"@next/swc-linux-x64-musl": ["@next/[email protected].7", "", { "os": "linux", "cpu": "x64" }, "sha512-HYlhqIP6kBPXalW2dbMTSuB4+8fe+j9juyxwfMwCe9kQPPeiyFn7NMjNfoFOfJ2eXkeQsoUGXg+O2SE3m4Qg2w=="],
|
"@next/swc-linux-x64-musl": ["@next/[email protected].10", "", { "os": "linux", "cpu": "x64" }, "sha512-1TDG9PDKivNw5550S111gsO4RGennLVl9cipPhtkXIFVwo31YZ73nEbLjNC8qG3SgTz/QZyYyaFYMeY4BKZR/g=="],
|
||||||
|
|
||||||
"@next/swc-win32-arm64-msvc": ["@next/[email protected].7", "", { "os": "win32", "cpu": "arm64" }, "sha512-EviG+43iOoBRZg9deGauXExjRphhuYmIOJ12b9sAPy0eQ6iwcPxfED2asb/s2/yiLYOdm37kPaiZu8uXSYPs0Q=="],
|
"@next/swc-win32-arm64-msvc": ["@next/[email protected].10", "", { "os": "win32", "cpu": "arm64" }, "sha512-aEZIS4Hh32xdJQbHz121pyuVZniSNoqDVx1yIr2hy+ZwJGipeqnMZBJHyMxv2tiuAXGx6/xpTcQJ6btIiBjgmg=="],
|
||||||
|
|
||||||
"@next/swc-win32-x64-msvc": ["@next/[email protected].7", "", { "os": "win32", "cpu": "x64" }, "sha512-gniPjy55zp5Eg0896qSrf3yB1dw4F/3s8VK1ephdsZZ129j2n6e1WqCbE2YgcKhW9hPB9TVZENugquWJD5x0ug=="],
|
"@next/swc-win32-x64-msvc": ["@next/[email protected].10", "", { "os": "win32", "cpu": "x64" }, "sha512-E+njfCoFLb01RAFEnGZn6ERoOqhK1Gl3Lfz1Kjnj0Ulfu7oJbuMyvBKNj/bw8XZnenHDASlygTjZICQW+rYW1Q=="],
|
||||||
|
|
||||||
"@noble/ciphers": ["@noble/[email protected]", "", {}, "sha512-xHK3XHPUW8DTAobU+G0XT+/w+JLM7/8k1UFdB5xg/zTFPnFCobhftzw8wl4Lw2aq/Rvir5pxfZV5fEazmeCJ2g=="],
|
"@noble/ciphers": ["@noble/[email protected]", "", {}, "sha512-xHK3XHPUW8DTAobU+G0XT+/w+JLM7/8k1UFdB5xg/zTFPnFCobhftzw8wl4Lw2aq/Rvir5pxfZV5fEazmeCJ2g=="],
|
||||||
|
|
||||||
@@ -849,7 +849,7 @@
|
|||||||
|
|
||||||
"nanostores": ["[email protected]", "", {}, "sha512-kNZ9xnoJYKg/AfxjrVL4SS0fKX++4awQReGqWnwTRHxeHGZ1FJFVgTqr/eMrNQdp0Tz7M7tG/TDaX8QfHDwVCw=="],
|
"nanostores": ["[email protected]", "", {}, "sha512-kNZ9xnoJYKg/AfxjrVL4SS0fKX++4awQReGqWnwTRHxeHGZ1FJFVgTqr/eMrNQdp0Tz7M7tG/TDaX8QfHDwVCw=="],
|
||||||
|
|
||||||
"next": ["[email protected].7", "", { "dependencies": { "@next/env": "16.0.7", "@swc/helpers": "0.5.15", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", "styled-jsx": "5.1.6" }, "optionalDependencies": { "@next/swc-darwin-arm64": "16.0.7", "@next/swc-darwin-x64": "16.0.7", "@next/swc-linux-arm64-gnu": "16.0.7", "@next/swc-linux-arm64-musl": "16.0.7", "@next/swc-linux-x64-gnu": "16.0.7", "@next/swc-linux-x64-musl": "16.0.7", "@next/swc-win32-arm64-msvc": "16.0.7", "@next/swc-win32-x64-msvc": "16.0.7", "sharp": "^0.34.4" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", "@playwright/test": "^1.51.1", "babel-plugin-react-compiler": "*", "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "sass": "^1.3.0" }, "optionalPeers": ["@opentelemetry/api", "@playwright/test", "babel-plugin-react-compiler", "sass"], "bin": { "next": "dist/bin/next" } }, "sha512-3mBRJyPxT4LOxAJI6IsXeFtKfiJUbjCLgvXO02fV8Wy/lIhPvP94Fe7dGhUgHXcQy4sSuYwQNcOLhIfOm0rL0A=="],
|
"next": ["[email protected].10", "", { "dependencies": { "@next/env": "16.0.10", "@swc/helpers": "0.5.15", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", "styled-jsx": "5.1.6" }, "optionalDependencies": { "@next/swc-darwin-arm64": "16.0.10", "@next/swc-darwin-x64": "16.0.10", "@next/swc-linux-arm64-gnu": "16.0.10", "@next/swc-linux-arm64-musl": "16.0.10", "@next/swc-linux-x64-gnu": "16.0.10", "@next/swc-linux-x64-musl": "16.0.10", "@next/swc-win32-arm64-msvc": "16.0.10", "@next/swc-win32-x64-msvc": "16.0.10", "sharp": "^0.34.4" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", "@playwright/test": "^1.51.1", "babel-plugin-react-compiler": "*", "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "sass": "^1.3.0" }, "optionalPeers": ["@opentelemetry/api", "@playwright/test", "babel-plugin-react-compiler", "sass"], "bin": { "next": "dist/bin/next" } }, "sha512-RtWh5PUgI+vxlV3HdR+IfWA1UUHu0+Ram/JBO4vWB54cVPentCD0e+lxyAYEsDTqGGMg7qpjhKh6dc6aW7W/sA=="],
|
||||||
|
|
||||||
"next-themes": ["[email protected]", "", { "peerDependencies": { "react": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc", "react-dom": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc" } }, "sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA=="],
|
"next-themes": ["[email protected]", "", { "peerDependencies": { "react": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc", "react-dom": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc" } }, "sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA=="],
|
||||||
|
|
||||||
|
|||||||
+7
-4
@@ -285,7 +285,9 @@ export async function cdnify(
|
|||||||
export async function actionLog(text: string, details?: unknown) {
|
export async function actionLog(text: string, details?: unknown) {
|
||||||
const session = await adminCheck();
|
const session = await adminCheck();
|
||||||
|
|
||||||
const res = await db
|
console.log(` LOG ${session?.name || ""} ${text}`);
|
||||||
|
|
||||||
|
const [res] = await db
|
||||||
.insert(auditLog)
|
.insert(auditLog)
|
||||||
.values({
|
.values({
|
||||||
author: session?.name,
|
author: session?.name,
|
||||||
@@ -293,11 +295,12 @@ export async function actionLog(text: string, details?: unknown) {
|
|||||||
details,
|
details,
|
||||||
})
|
})
|
||||||
.returning()
|
.returning()
|
||||||
.catch(() =>
|
.catch(() => {
|
||||||
console.error(
|
console.error(
|
||||||
`Error logging audit log, printing it here:\n${session?.name || "[Unknown User]"} - ${text}`,
|
`Error logging audit log, printing it here:\n${session?.name || "[Unknown User]"} - ${text}`,
|
||||||
),
|
);
|
||||||
);
|
return [];
|
||||||
|
});
|
||||||
|
|
||||||
revalidatePath("/admin/log");
|
revalidatePath("/admin/log");
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { isNotNull, lte, or, type SQL } from "drizzle-orm";
|
||||||
import {
|
import {
|
||||||
type AnyPgColumn,
|
type AnyPgColumn,
|
||||||
boolean,
|
boolean,
|
||||||
@@ -128,6 +129,15 @@ export const endgameSubmissions = endgame.table("submissions", {
|
|||||||
price: integer().notNull(), // calculated on submission
|
price: integer().notNull(), // calculated on submission
|
||||||
slip: text().references(() => endgameSlips.id), // not paid = null
|
slip: text().references(() => endgameSlips.id), // not paid = null
|
||||||
checked: boolean().notNull().default(false),
|
checked: boolean().notNull().default(false),
|
||||||
|
paid: boolean()
|
||||||
|
.notNull()
|
||||||
|
.generatedAlwaysAs(
|
||||||
|
(): SQL =>
|
||||||
|
or(
|
||||||
|
lte(endgameSubmissions.price, 0),
|
||||||
|
isNotNull(endgameSubmissions.slip),
|
||||||
|
)!,
|
||||||
|
),
|
||||||
});
|
});
|
||||||
|
|
||||||
// deleted submissions
|
// deleted submissions
|
||||||
@@ -226,6 +236,10 @@ export const tierlistBadges = tierlist.table("badges", {
|
|||||||
onUpdate: "cascade",
|
onUpdate: "cascade",
|
||||||
}),
|
}),
|
||||||
order: integer().notNull(),
|
order: integer().notNull(),
|
||||||
|
type: text().references(() => tierlistTypes.id, {
|
||||||
|
onDelete: "set null",
|
||||||
|
onUpdate: "cascade",
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const tierlistVersions = tierlist.table("versions", {
|
export const tierlistVersions = tierlist.table("versions", {
|
||||||
|
|||||||
+25
-15
@@ -1,29 +1,38 @@
|
|||||||
events {}
|
events {}
|
||||||
|
|
||||||
http {
|
http {
|
||||||
map $uri $loggable {
|
# 1 = log, 0 = don't log (based on path)
|
||||||
default 1;
|
map $uri $loggable_uri {
|
||||||
~^/_next 0;
|
default 1;
|
||||||
|
~^/_next 0;
|
||||||
|
=/favicon.webp 0;
|
||||||
|
=/robots.txt 0;
|
||||||
|
~^/api/.*/count$ 0;
|
||||||
|
}
|
||||||
|
|
||||||
=/favicon.webp 0;
|
# 1 = log, 0 = don't log (based on header)
|
||||||
=/robots.txt 0;
|
map $http_rsc $loggable_prefetch {
|
||||||
|
default 1;
|
||||||
|
"" 1; # header not present
|
||||||
|
~.+ 0; # header exists (any value)
|
||||||
|
}
|
||||||
|
|
||||||
~^/api/.*/count$ 0;
|
# final decision: log only if BOTH say yes
|
||||||
}
|
map "$loggable_uri:$loggable_prefetch" $loggable {
|
||||||
|
default 0;
|
||||||
|
"1:1" 1;
|
||||||
|
}
|
||||||
|
|
||||||
log_format clean ' $request_method $uri $status';
|
log_format clean ' $request_method $uri $status';
|
||||||
access_log /var/log/nginx/access.log clean if=$loggable;
|
access_log /var/log/nginx/access.log clean if=$loggable;
|
||||||
|
|
||||||
upstream backend {
|
upstream backend {
|
||||||
# Use the service name defined in docker-compose.yml
|
|
||||||
server app:3000;
|
server app:3000;
|
||||||
}
|
}
|
||||||
|
|
||||||
server {
|
server {
|
||||||
listen 80;
|
listen 80;
|
||||||
|
resolver 127.0.0.11 valid=5s ipv6=off;
|
||||||
# Docker's internal DNS resolver
|
|
||||||
resolver 127.0.0.11 valid=1s ipv6=off;
|
|
||||||
|
|
||||||
location / {
|
location / {
|
||||||
proxy_pass http://backend;
|
proxy_pass http://backend;
|
||||||
@@ -32,11 +41,12 @@ http {
|
|||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
proxy_set_header X-Forwarded-Proto $scheme;
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
|
||||||
# SSE support if you want it
|
|
||||||
proxy_http_version 1.1;
|
proxy_http_version 1.1;
|
||||||
proxy_set_header Connection "";
|
proxy_set_header Connection "";
|
||||||
proxy_buffering off;
|
proxy_buffering off;
|
||||||
proxy_read_timeout 100s;
|
proxy_read_timeout 200s;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
client_max_body_size 100M;
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-2
@@ -11,7 +11,9 @@
|
|||||||
"format": "biome format --write",
|
"format": "biome format --write",
|
||||||
"dr": "drizzle-kit",
|
"dr": "drizzle-kit",
|
||||||
"ba": "bunx @better-auth/cli",
|
"ba": "bunx @better-auth/cli",
|
||||||
"up": "docker compose build && docker rollout app && docker compose up -d && docker compose restart -t 0 nginx",
|
"up": "docker compose build && docker rollout app && docker compose up -d backup && bun ngreload",
|
||||||
|
"logs": "docker compose logs -fn50",
|
||||||
|
"ngreload": "docker compose restart -t 0 nginx",
|
||||||
"test": "playwright test"
|
"test": "playwright test"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -62,7 +64,7 @@
|
|||||||
"framer-motion": "^12.23.26",
|
"framer-motion": "^12.23.26",
|
||||||
"input-otp": "^1.4.2",
|
"input-otp": "^1.4.2",
|
||||||
"lucide-react": "^0.544.0",
|
"lucide-react": "^0.544.0",
|
||||||
"next": "16.0.7",
|
"next": "16.0.10",
|
||||||
"next-themes": "^0.4.6",
|
"next-themes": "^0.4.6",
|
||||||
"pg": "^8.16.3",
|
"pg": "^8.16.3",
|
||||||
"radix-ui": "^1.4.3",
|
"radix-ui": "^1.4.3",
|
||||||
|
|||||||
Reference in New Issue
Block a user