feat(artifact): make card generation error more clear
This commit is contained in:
@@ -7,26 +7,34 @@ import {
|
|||||||
ImageIcon,
|
ImageIcon,
|
||||||
OctagonAlert,
|
OctagonAlert,
|
||||||
ScanSearch,
|
ScanSearch,
|
||||||
|
SquircleDashed,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { SimpleTooltip } from "@/components/tooltip";
|
import { SimpleTooltip } from "@/components/tooltip";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { getCardStatus } from "@/lib/api";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
export function EnkaBrowser({ sub, uid }: { sub: string; uid: string }) {
|
export function EnkaBrowser({ sub, uid }: { sub: string; uid: string }) {
|
||||||
const [ready, setReady] = useState(false);
|
const [ready, setReady] = useState(false);
|
||||||
const [error, setError] = useState(false);
|
const [error, setError] = useState<string | null>(null);
|
||||||
const [useWeb, setUseWeb] = useState(false);
|
const [useWeb, setUseWeb] = useState(false);
|
||||||
|
const [isCached, setIsCached] = useState(true);
|
||||||
|
|
||||||
// biome-ignore lint/correctness/useExhaustiveDependencies: for update
|
// biome-ignore lint/correctness/useExhaustiveDependencies: for update
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setReady(false);
|
setReady(false);
|
||||||
setError(false);
|
setError(null);
|
||||||
|
setIsCached(true);
|
||||||
|
getCardStatus(sub).then((s) => {
|
||||||
|
setIsCached(s.cached);
|
||||||
|
if (!s.cached) setError(s.error);
|
||||||
|
});
|
||||||
}, [sub, uid]);
|
}, [sub, uid]);
|
||||||
return (
|
return (
|
||||||
<div className="h-full w-full relative">
|
<div className="h-full w-full relative">
|
||||||
{!ready && !error && (
|
{!ready && !error && (isCached || useWeb) && (
|
||||||
<ScanSearch
|
<ScanSearch
|
||||||
className={cn(
|
className={cn(
|
||||||
"animate-pulse absolute left-1/2 -translate-x-1/2 -translate-y-1/2 size-8 text-muted-foreground z-45",
|
"animate-pulse absolute left-1/2 -translate-x-1/2 -translate-y-1/2 size-8 text-muted-foreground z-45",
|
||||||
@@ -36,29 +44,44 @@ export function EnkaBrowser({ sub, uid }: { sub: string; uid: string }) {
|
|||||||
)}
|
)}
|
||||||
{error && (
|
{error && (
|
||||||
<div className="flex flex-col items-center gap-2 absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 z-40">
|
<div className="flex flex-col items-center gap-2 absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 z-40">
|
||||||
<OctagonAlert className="size-8 text-red-500" />
|
{error.includes("ใหม่") ? (
|
||||||
<span>ไม่สามารถโหลดข้อมูลตัวละคร</span>
|
<SquircleDashed className="size-8 text-orange-500" />
|
||||||
|
) : (
|
||||||
|
<OctagonAlert className="size-8 text-red-500" />
|
||||||
|
)}
|
||||||
|
<span>{error}</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{(ready || error) && (
|
|
||||||
<SimpleTooltip text={useWeb ? "สลับเป็นรูปภาพ" : "สลับเป็น Enka"}>
|
{!error && !ready && !isCached && !useWeb && (
|
||||||
<Button
|
<div
|
||||||
variant="outline"
|
className={cn(
|
||||||
className={cn(
|
"flex flex-col items-center gap-2 absolute left-1/2 -translate-x-1/2 -translate-y-1/2 z-40",
|
||||||
"absolute left-1 z-45 bg-[#222a]! backdrop-blur-sm opacity-50 md:opacity-20 hover:opacity-100",
|
useWeb ? "top-[calc(50%+80px)]" : "top-1/2",
|
||||||
useWeb ? "-bottom-19" : "bottom-1",
|
)}
|
||||||
)}
|
>
|
||||||
size="icon"
|
<ScanSearch className="size-8 animate-pulse" />
|
||||||
onClick={() => {
|
<span>ยังไม่ได้เตรียมการ์ดไว้ล่วงหน้า จะใช้เวลาสักพัก</span>
|
||||||
setReady(false);
|
</div>
|
||||||
setError(false);
|
|
||||||
setUseWeb((x) => !x);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{useWeb ? <ImageIcon /> : <Dock />}
|
|
||||||
</Button>
|
|
||||||
</SimpleTooltip>
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
<SimpleTooltip text={useWeb ? "สลับเป็นรูปภาพ" : "สลับเป็น Enka"}>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
className={cn(
|
||||||
|
"absolute left-1 z-45 bg-[#222a]! backdrop-blur-sm opacity-50 md:opacity-20 hover:opacity-100",
|
||||||
|
useWeb ? "-bottom-19" : "bottom-1",
|
||||||
|
)}
|
||||||
|
size="icon"
|
||||||
|
onClick={() => {
|
||||||
|
setReady(false);
|
||||||
|
setError(null);
|
||||||
|
setUseWeb((x) => !x);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{useWeb ? <ImageIcon /> : <Dock />}
|
||||||
|
</Button>
|
||||||
|
</SimpleTooltip>
|
||||||
{useWeb ? (
|
{useWeb ? (
|
||||||
<iframe
|
<iframe
|
||||||
src={`https://enka.network/u/${uid}`}
|
src={`https://enka.network/u/${uid}`}
|
||||||
@@ -85,9 +108,9 @@ export function EnkaBrowser({ sub, uid }: { sub: string; uid: string }) {
|
|||||||
title="Enka Network"
|
title="Enka Network"
|
||||||
onLoad={() => {
|
onLoad={() => {
|
||||||
setReady(true);
|
setReady(true);
|
||||||
setError(false);
|
setError(null);
|
||||||
}}
|
}}
|
||||||
onError={() => setError(true)}
|
onError={() => error || setError("ไม่สามารถโหลดข้อมูลตัวละคร")}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
+28
-4
@@ -1,7 +1,7 @@
|
|||||||
import { env } from "node:process";
|
import { env } from "node:process";
|
||||||
import { redis } from "bun";
|
import { redis } from "bun";
|
||||||
import { formatDistanceToNow } from "date-fns";
|
import { formatDistanceToNow } from "date-fns";
|
||||||
import { and, asc, eq, isNull, lt, not, sql } from "drizzle-orm";
|
import { and, asc, eq, gt, isNull, lt, not, sql } from "drizzle-orm";
|
||||||
import { removeExpiredSubmissions } from "@/app/(ui)/rubgram/api";
|
import { removeExpiredSubmissions } from "@/app/(ui)/rubgram/api";
|
||||||
import { auth } from "@/lib/auth";
|
import { auth } from "@/lib/auth";
|
||||||
import { db } from "@/lib/db";
|
import { db } from "@/lib/db";
|
||||||
@@ -203,7 +203,7 @@ async function seedDatabase() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function cacheCards() {
|
async function cacheCards() {
|
||||||
const { log, schedule } = logger("cacheCards");
|
const { log, error, schedule } = logger("cacheCards");
|
||||||
const [notCached] = await db
|
const [notCached] = await db
|
||||||
.select({
|
.select({
|
||||||
id: submissions.id,
|
id: submissions.id,
|
||||||
@@ -228,19 +228,43 @@ async function cacheCards() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
log(`Generating card for ${notCached.id}`);
|
log(`Generating card for ${notCached.id}`);
|
||||||
const res = await fetch(`http://app:3000/api/card/${notCached.id}`);
|
const res = await fetch(`http://app:3000/api/card/${notCached.id}`).catch(
|
||||||
|
() => ({
|
||||||
|
ok: false,
|
||||||
|
status: 502,
|
||||||
|
text: () => "เกิดข้อผิดพลาดภายในระบบ กำลังพยายามลองใหม่",
|
||||||
|
}),
|
||||||
|
);
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
log(`Wasn't okay, queueing for retry.`);
|
log(`Wasn't okay, queueing for retry.`);
|
||||||
|
let text = await res.text();
|
||||||
|
error(text.includes("502") ? "502" : text);
|
||||||
|
if (text.includes("findIndex")) text = "ผู้เล่นนี้ไม่มีโชว์เคส มองไม่เห็นตัวละครใดๆ";
|
||||||
|
else if (text === "Invalid character provided")
|
||||||
|
text = "ตัวละครที่ผู้เล่นเลือก ไม่ได้อยู่ในโชว์เคส";
|
||||||
|
else if (text === "Failed to find card." || text === "Invalid UID Provided")
|
||||||
|
text = "ผู้เล่นนี้ไม่มีอยู่จริง โดนแบนไปแล้วรีเปล่า";
|
||||||
|
else if (text.length > 2000 || res.status === 502)
|
||||||
|
text = "ไม่สามารถสร้างการ์ดได้ กำลังพยายามลองใหม่";
|
||||||
|
text = text.split("\n")[0];
|
||||||
await db
|
await db
|
||||||
.insert(cards)
|
.insert(cards)
|
||||||
.values({
|
.values({
|
||||||
submission: notCached.id,
|
submission: notCached.id,
|
||||||
tries: 1,
|
tries: 1,
|
||||||
|
error: text,
|
||||||
})
|
})
|
||||||
.onConflictDoUpdate({
|
.onConflictDoUpdate({
|
||||||
target: cards.submission,
|
target: cards.submission,
|
||||||
set: {
|
set: {
|
||||||
tries: res.status === 400 ? 20 : sql<number>`${cards.tries} + 1`,
|
tries:
|
||||||
|
res.status === 400 || text === "ผู้เล่นนี้ไม่มีโชว์เคส มองไม่เห็นตัวละครใดๆ"
|
||||||
|
? 21
|
||||||
|
: sql<number>`${cards.tries} + 1`,
|
||||||
|
error: sql<string>`case when ${gt(cards.tries, 15)}
|
||||||
|
then ${text.replace(" กำลังพยายามลองใหม่", "")}
|
||||||
|
else ${text}
|
||||||
|
end`,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
schedule(60, cacheCards);
|
schedule(60, cacheCards);
|
||||||
|
|||||||
@@ -104,6 +104,7 @@ export const cards = artifact.table("cards", {
|
|||||||
.notNull()
|
.notNull()
|
||||||
.unique(),
|
.unique(),
|
||||||
tries: integer().notNull().default(0),
|
tries: integer().notNull().default(0),
|
||||||
|
error: text(),
|
||||||
image: bytea(),
|
image: bytea(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user