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 {
|
||||
ArrowLeftRight,
|
||||
Badge,
|
||||
BookUser,
|
||||
Columns3Cog,
|
||||
@@ -59,8 +60,12 @@ export default async function AdminLayout({
|
||||
.select()
|
||||
.from(versions)
|
||||
.catch(() => [{ id: "..", name: "Error Fetching List." }]);
|
||||
const health: { database: boolean; enka: boolean; amber: boolean } =
|
||||
await fetch("http://localhost:3000/api/health").then((r) => r.json());
|
||||
const health: {
|
||||
database: boolean;
|
||||
enka: boolean;
|
||||
amber: boolean;
|
||||
red: boolean;
|
||||
} = await fetch("http://localhost:3000/api/health").then((r) => r.json());
|
||||
|
||||
return (
|
||||
<SidebarProvider>
|
||||
@@ -116,10 +121,18 @@ export default async function AdminLayout({
|
||||
}
|
||||
/>
|
||||
</SimpleTooltip>
|
||||
{/* Cache */}
|
||||
<SimpleTooltip text="Redis Cache/SSE">
|
||||
<ArrowLeftRight
|
||||
className={
|
||||
health.red ? "text-emerald-400" : "text-red-400"
|
||||
}
|
||||
/>
|
||||
</SimpleTooltip>
|
||||
</div>
|
||||
<span>
|
||||
{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>
|
||||
</div>
|
||||
|
||||
@@ -18,6 +18,7 @@ export default function AuditLogViewer({
|
||||
}) {
|
||||
const [logs, updateLogs] = useState(initialLogs);
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const entryRefs = useRef(new Map<string, HTMLDivElement>());
|
||||
|
||||
useEffect(() => {
|
||||
const es = new EventSource("/api/ev/log");
|
||||
@@ -26,18 +27,9 @@ export default function AuditLogViewer({
|
||||
return () => es.close();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (containerRef.current)
|
||||
containerRef.current.scrollTo({
|
||||
top: 999999,
|
||||
left: 0,
|
||||
behavior: "smooth",
|
||||
});
|
||||
});
|
||||
|
||||
const filters: Filters = {
|
||||
user: () => users.map((u) => u.email),
|
||||
has: () => ["artifact", "tierlist", "file"],
|
||||
has: () => ["artifact", "tierlist", "rubgram", "file"],
|
||||
};
|
||||
|
||||
const [query, setQuery] = useState<ParsedQuery>();
|
||||
@@ -58,6 +50,36 @@ export default function AuditLogViewer({
|
||||
}, [query, logs, users]);
|
||||
|
||||
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) => {
|
||||
setExpandedIds((prev) => {
|
||||
@@ -117,12 +139,16 @@ export default function AuditLogViewer({
|
||||
return (
|
||||
<div
|
||||
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"
|
||||
>
|
||||
<div
|
||||
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
|
||||
className={`text-xs whitespace-nowrap invisible ${isGrouped ? "group-hover:visible" : ""}`}
|
||||
>
|
||||
@@ -154,7 +180,7 @@ export default function AuditLogViewer({
|
||||
<Button
|
||||
variant="ghost"
|
||||
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 ? (
|
||||
<ChevronDown className="h-4 w-4" />
|
||||
|
||||
@@ -85,7 +85,7 @@ export function CallButton({ user }: { user: string }) {
|
||||
) : (
|
||||
<MessageSquareWarning />
|
||||
)}
|
||||
กดเรียกผ่านดิสคอร์ด
|
||||
เรียกในดิสคอร์ด
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { eq, isNotNull, or, type SQL } from "drizzle-orm";
|
||||
import { Dice3, Trash2 } from "lucide-react";
|
||||
import { redirect } from "next/navigation";
|
||||
import {
|
||||
@@ -42,10 +41,7 @@ export default async function AdminLayout({
|
||||
name: endgameSubmissions.name,
|
||||
checked: endgameSubmissions.checked,
|
||||
queue: endgameSubmissions.queue,
|
||||
paid: or(
|
||||
eq(endgameSubmissions.price, 0),
|
||||
isNotNull(endgameSubmissions.slip),
|
||||
) as SQL<boolean>,
|
||||
paid: endgameSubmissions.paid,
|
||||
})
|
||||
.from(endgameSubmissions)
|
||||
.orderBy(endgameSubmissions.id);
|
||||
|
||||
+5
-22
@@ -1,17 +1,6 @@
|
||||
"use server";
|
||||
|
||||
import {
|
||||
and,
|
||||
desc,
|
||||
eq,
|
||||
gt,
|
||||
isNotNull,
|
||||
isNull,
|
||||
lt,
|
||||
not,
|
||||
or,
|
||||
sql,
|
||||
} from "drizzle-orm";
|
||||
import { and, desc, eq, gt, lt, not, sql } from "drizzle-orm";
|
||||
import { revalidatePath } from "next/cache";
|
||||
import { cookies } from "next/headers";
|
||||
import image from "next/image";
|
||||
@@ -57,13 +46,8 @@ export async function random() {
|
||||
const [sub] = await db
|
||||
.select()
|
||||
.from(endgameSubmissions)
|
||||
.where(
|
||||
and(
|
||||
not(endgameSubmissions.checked),
|
||||
or(eq(endgameSubmissions.price, 0), isNotNull(endgameSubmissions.slip)),
|
||||
),
|
||||
)
|
||||
.orderBy(sql`RANDOM()`)
|
||||
.where(endgameSubmissions.paid.getSQL())
|
||||
.orderBy(sql`${endgameSubmissions.checked} ASC, RANDOM()`)
|
||||
.limit(1);
|
||||
if (sub) redirect(`/rubgram/admin/${sub.id}`);
|
||||
else throw "ไม่พบผู้ลงทะเบียนที่ยังไม่ตรวจสอบ";
|
||||
@@ -348,8 +332,7 @@ export async function calcPrice(service: string[]) {
|
||||
async function removeExpiredSubmissions() {
|
||||
// First, get count of items to be removed
|
||||
const expiredCond = and(
|
||||
not(eq(endgameSubmissions.price, 0)),
|
||||
isNull(endgameSubmissions.slip),
|
||||
not(endgameSubmissions.paid),
|
||||
lt(endgameSubmissions.expires, new Date()),
|
||||
);
|
||||
const toRemove = await db
|
||||
@@ -420,7 +403,7 @@ export async function discordCall(id: string) {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
}).then(async (r) => {
|
||||
console.log(await r.text());
|
||||
console.log(`called`, await r.text());
|
||||
return r.ok;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -214,7 +214,7 @@ export function CancelButton({ sid }: { sid: string }) {
|
||||
}}
|
||||
disabled={loading}
|
||||
>
|
||||
{loading && <Spinner />}ยกเลิก
|
||||
{loading && <Spinner />}ใช่
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
+14
-17
@@ -1,13 +1,9 @@
|
||||
import {
|
||||
and,
|
||||
eq,
|
||||
gt,
|
||||
isNotNull,
|
||||
isNull,
|
||||
lt,
|
||||
gt, lt,
|
||||
not,
|
||||
or,
|
||||
type SQL,
|
||||
or
|
||||
} from "drizzle-orm";
|
||||
import {
|
||||
AlertCircle,
|
||||
@@ -89,10 +85,7 @@ export default async function EndgamePage() {
|
||||
? await db
|
||||
.select({
|
||||
queue: endgameSubmissions.queue,
|
||||
paid: or(
|
||||
eq(endgameSubmissions.price, 0),
|
||||
isNotNull(endgameSubmissions.slip),
|
||||
) as SQL<boolean>,
|
||||
paid: endgameSubmissions.paid,
|
||||
price: endgameSubmissions.price,
|
||||
expires: endgameSubmissions.expires,
|
||||
})
|
||||
@@ -114,8 +107,7 @@ export default async function EndgamePage() {
|
||||
.from(endgameSubmissions)
|
||||
.where(
|
||||
and(
|
||||
not(eq(endgameSubmissions.price, 0)),
|
||||
isNull(endgameSubmissions.slip),
|
||||
not(endgameSubmissions.paid),
|
||||
lt(endgameSubmissions.queue, q.queue),
|
||||
),
|
||||
)
|
||||
@@ -293,13 +285,13 @@ export default async function EndgamePage() {
|
||||
</SimpleTooltip>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>แน่ใจเหรอ</AlertDialogTitle>
|
||||
<AlertDialogTitle>แน่ใจหรอ</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
ต้องการยกเลิกคิวของคุณหรือไม่
|
||||
ต้องการยกเลิกการลงคิวของคุณหรือไม่
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel>ไม่ใช่</AlertDialogCancel>
|
||||
<AlertDialogCancel>ไม่ยกเลิก</AlertDialogCancel>
|
||||
<CancelButton sid={sid!.value} />
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
@@ -330,9 +322,14 @@ export default async function EndgamePage() {
|
||||
</div>
|
||||
</CardFooter>
|
||||
</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>
|
||||
{sid && !q && <ClearCookie />}
|
||||
</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 { db } from "@/lib/db";
|
||||
import {
|
||||
@@ -94,7 +94,10 @@ export default async function TierlistPage({
|
||||
const badgesList = await tx
|
||||
.select()
|
||||
.from(tierlistBadges)
|
||||
.orderBy(tierlistBadges.order);
|
||||
.orderBy(tierlistBadges.order)
|
||||
.where(
|
||||
or(isNull(tierlistBadges.type), eq(tierlistBadges.type, version.type)),
|
||||
);
|
||||
const badges = badgesList.map((b) => ({
|
||||
...b,
|
||||
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 { redis } from "@/lib/db/redis";
|
||||
|
||||
export const revalidate = 900;
|
||||
|
||||
export async function GET() {
|
||||
const database = await db
|
||||
.execute(sql<boolean>`select true`)
|
||||
.then(() => true)
|
||||
.catch(() => false);
|
||||
const red = await redis
|
||||
.ping()
|
||||
.then(() => true)
|
||||
.catch(() => false);
|
||||
const enka = await fetch("https://enka.network/api/uid/888888888/?info", {
|
||||
cache: "force-cache",
|
||||
headers: { "User-Agent": "Buzz Event Platform(Health Checker)" },
|
||||
signal: AbortSignal.timeout(5000),
|
||||
})
|
||||
.then((r) => r.ok)
|
||||
.catch(() => false);
|
||||
const amber = await fetch("https://gi.yatta.moe/api/v2/en/avatar", {
|
||||
cache: "force-cache",
|
||||
signal: AbortSignal.timeout(5000),
|
||||
})
|
||||
.then((r) => r.ok)
|
||||
.catch(() => false);
|
||||
return Response.json({ database, red, enka, amber });
|
||||
const res = Object.fromEntries(
|
||||
await Promise.all(
|
||||
Object.entries(checks).map(
|
||||
async ([k, v]) =>
|
||||
[
|
||||
k,
|
||||
red
|
||||
? (await redis.exists(`health:${k}`))
|
||||
? (await redis.get(`health:${k}`)) === "ok"
|
||||
: await v().then(async (v) => {
|
||||
if (v) await redis.setex(`health:${k}`, 900, "ok");
|
||||
return v;
|
||||
})
|
||||
: 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.slip) notFound();
|
||||
|
||||
console.log(sub.slip.length);
|
||||
|
||||
return new Response(new Uint8Array(sub.slip), {
|
||||
headers: {
|
||||
"Content-Type": "image/jpeg",
|
||||
|
||||
@@ -52,7 +52,7 @@
|
||||
"framer-motion": "^12.23.26",
|
||||
"input-otp": "^1.4.2",
|
||||
"lucide-react": "^0.544.0",
|
||||
"next": "16.0.7",
|
||||
"next": "16.0.10",
|
||||
"next-themes": "^0.4.6",
|
||||
"pg": "^8.16.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/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=="],
|
||||
|
||||
@@ -849,7 +849,7 @@
|
||||
|
||||
"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=="],
|
||||
|
||||
|
||||
+7
-4
@@ -285,7 +285,9 @@ export async function cdnify(
|
||||
export async function actionLog(text: string, details?: unknown) {
|
||||
const session = await adminCheck();
|
||||
|
||||
const res = await db
|
||||
console.log(` LOG ${session?.name || ""} ${text}`);
|
||||
|
||||
const [res] = await db
|
||||
.insert(auditLog)
|
||||
.values({
|
||||
author: session?.name,
|
||||
@@ -293,11 +295,12 @@ export async function actionLog(text: string, details?: unknown) {
|
||||
details,
|
||||
})
|
||||
.returning()
|
||||
.catch(() =>
|
||||
.catch(() => {
|
||||
console.error(
|
||||
`Error logging audit log, printing it here:\n${session?.name || "[Unknown User]"} - ${text}`,
|
||||
),
|
||||
);
|
||||
);
|
||||
return [];
|
||||
});
|
||||
|
||||
revalidatePath("/admin/log");
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { isNotNull, lte, or, type SQL } from "drizzle-orm";
|
||||
import {
|
||||
type AnyPgColumn,
|
||||
boolean,
|
||||
@@ -128,6 +129,15 @@ export const endgameSubmissions = endgame.table("submissions", {
|
||||
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, 0),
|
||||
isNotNull(endgameSubmissions.slip),
|
||||
)!,
|
||||
),
|
||||
});
|
||||
|
||||
// deleted submissions
|
||||
@@ -226,6 +236,10 @@ export const tierlistBadges = tierlist.table("badges", {
|
||||
onUpdate: "cascade",
|
||||
}),
|
||||
order: integer().notNull(),
|
||||
type: text().references(() => tierlistTypes.id, {
|
||||
onDelete: "set null",
|
||||
onUpdate: "cascade",
|
||||
}),
|
||||
});
|
||||
|
||||
export const tierlistVersions = tierlist.table("versions", {
|
||||
|
||||
+26
-16
@@ -1,29 +1,38 @@
|
||||
events {}
|
||||
|
||||
http {
|
||||
map $uri $loggable {
|
||||
default 1;
|
||||
~^/_next 0;
|
||||
# 1 = log, 0 = don't log (based on path)
|
||||
map $uri $loggable_uri {
|
||||
default 1;
|
||||
~^/_next 0;
|
||||
=/favicon.webp 0;
|
||||
=/robots.txt 0;
|
||||
~^/api/.*/count$ 0;
|
||||
}
|
||||
|
||||
=/favicon.webp 0;
|
||||
=/robots.txt 0;
|
||||
|
||||
~^/api/.*/count$ 0;
|
||||
}
|
||||
# 1 = log, 0 = don't log (based on header)
|
||||
map $http_rsc $loggable_prefetch {
|
||||
default 1;
|
||||
"" 1; # header not present
|
||||
~.+ 0; # header exists (any value)
|
||||
}
|
||||
|
||||
log_format clean ' $request_method $uri $status';
|
||||
access_log /var/log/nginx/access.log clean if=$loggable;
|
||||
# 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';
|
||||
access_log /var/log/nginx/access.log clean if=$loggable;
|
||||
|
||||
upstream backend {
|
||||
# Use the service name defined in docker-compose.yml
|
||||
server app:3000;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
|
||||
# Docker's internal DNS resolver
|
||||
resolver 127.0.0.11 valid=1s ipv6=off;
|
||||
resolver 127.0.0.11 valid=5s ipv6=off;
|
||||
|
||||
location / {
|
||||
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-Proto $scheme;
|
||||
|
||||
# SSE support if you want it
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Connection "";
|
||||
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",
|
||||
"dr": "drizzle-kit",
|
||||
"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"
|
||||
},
|
||||
"dependencies": {
|
||||
@@ -62,7 +64,7 @@
|
||||
"framer-motion": "^12.23.26",
|
||||
"input-otp": "^1.4.2",
|
||||
"lucide-react": "^0.544.0",
|
||||
"next": "16.0.7",
|
||||
"next": "16.0.10",
|
||||
"next-themes": "^0.4.6",
|
||||
"pg": "^8.16.3",
|
||||
"radix-ui": "^1.4.3",
|
||||
|
||||
Reference in New Issue
Block a user