diff --git a/.env.example b/.env.example
index 396dc1e..0b07d0e 100644
--- a/.env.example
+++ b/.env.example
@@ -8,13 +8,13 @@ NEXT_PUBLIC_GOOGLE_ADSENSE_CLIENT_ID=ca-pub-9687404323559597
DATABASE_URL=postgresql://buzz_sheet:replace-me@postgres.example.internal:5432/buzz_sheet?sslmode=require
DATABASE_POOL_SIZE=10
-# Better Auth email/password administrator login
+# Better Auth email/password administrator and customer login
BETTER_AUTH_URL=http://localhost:3000
BETTER_AUTH_SECRET=replace-with-at-least-32-random-bytes
# Separate trusted browser origins with commas for local development or proxies.
BETTER_AUTH_TRUSTED_ORIGINS=http://localhost:3000
-# Cloudflare Turnstile administrator login protection
+# Cloudflare Turnstile login and public registration protection
TURNSTILE_SITE_KEY=replace-with-turnstile-site-key
TURNSTILE_SECRET_KEY=replace-with-turnstile-secret-key
@@ -34,6 +34,7 @@ CATALOG_SYNC_CONCURRENCY=12
DISCORD_BOT_TOKEN=replace-with-discord-bot-token
DISCORD_CHANNEL_ID=replace-with-private-channel-id
DISCORD_LOG_CHANNEL_ID=1547193755772125184
+COMMISSION_DISCORD_WEBHOOK_URL=replace-with-commission-discord-webhook-url
# S3-compatible media storage; direct guide uploads use public CDN objects.
S3_ENDPOINT=https://s3.example.internal
@@ -44,6 +45,14 @@ S3_ACCESS_KEY_ID=replace-with-access-key
S3_SECRET_ACCESS_KEY=replace-with-secret-key
S3_VIRTUAL_HOSTED_STYLE=false
+# Commission payment. Use the recipient number format accepted by Slip2Go.
+COMMISSION_PROMPTPAY_TYPE=mobile
+COMMISSION_PROMPTPAY_VALUE=0812345678
+COMMISSION_RECEIVER_ACCOUNT_NUMBER=0812345678
+# Use the complete verification URL supplied by your Slip2Go API Connect account.
+SLIP2GO_VERIFY_URL=https://your-slip2go-api-host/api/verify-slip/qr-image/info
+SLIP2GO_API_SECRET=replace-with-slip2go-secret
+
# Stable across replicas. Generate once and store only in the external secret.
NEXT_SERVER_ACTIONS_ENCRYPTION_KEY=replace-with-a-stable-32-byte-base64-key
diff --git a/README.md b/README.md
index fcd0043..c69dff3 100644
--- a/README.md
+++ b/README.md
@@ -31,6 +31,8 @@ formula fixtures. Their guide text and media are not imported or published.
| `/` | Searchable public character directory |
| `/[character]` | Redirect to the first visible page |
| `/[character]/[page]` | Render a public guide page |
+| `/login` | Public account sign-in |
+| `/register` | Public account registration |
| `/admin/login` | Administrator email/password sign-in |
| `/admin/register` | Create and remove administrator accounts |
| `/admin` | Character and page overview |
@@ -63,7 +65,7 @@ cp .env.example .env
ignored by Git and must never be committed. `BETTER_AUTH_URL` must exactly
match the application origin. Set `BASE_URL` to the public site origin for SEO
metadata, sitemap, and robots.txt. Existing administrators can create additional
-credential accounts from `/admin/register`; public registration is disabled.
+credential accounts from `/admin/register`. Visitors can create public accounts at `/register`.
Prepare the database and start the application:
@@ -207,6 +209,11 @@ S3_SECRET_ACCESS_KEY
NEXT_SERVER_ACTIONS_ENCRYPTION_KEY
TURNSTILE_SITE_KEY
TURNSTILE_SECRET_KEY
+COMMISSION_PROMPTPAY_TYPE
+COMMISSION_PROMPTPAY_VALUE
+COMMISSION_RECEIVER_ACCOUNT_NUMBER
+SLIP2GO_VERIFY_URL
+SLIP2GO_API_SECRET
DISCORD_BOT_TOKEN
DISCORD_CHANNEL_ID
```
@@ -215,6 +222,20 @@ DISCORD_CHANNEL_ID
across replicas and rolling deployments. Do not place secret values in the
ConfigMap or commit them to this repository.
+The commission page accepts PromptPay mobile, national ID, or e-wallet identifiers.
+For `COMMISSION_PROMPTPAY_TYPE=mobile`, use a real 10-digit Thai mobile number
+registered with PromptPay for receiving transfers in `COMMISSION_PROMPTPAY_VALUE`.
+For `nationalId` use the registered 13-digit ID; for `ewallet` use its 15-digit ID.
+An ordinary bank account number cannot be used as the mobile identifier.
+Set `COMMISSION_RECEIVER_ACCOUNT_NUMBER` to the recipient value in the format
+Slip2Go expects for its receiver check. Set `SLIP2GO_VERIFY_URL` to the full
+image-verification endpoint from your Slip2Go API Connect account. Set
+`SLIP2GO_API_SECRET` to the raw secret; the server adds the `Bearer` authorization
+prefix when calling Slip2Go. Private
+payment slips and ticket images use the configured S3 bucket with private ACL;
+the bucket/CDN must honor private object access. Apply the commission database
+migration before enabling checkout.
+
Traefik must overwrite `X-Real-Ip` for every request, and the web pods must be
reachable only through Traefik before enabling the configured client IP based
rate limits. The NetworkPolicy assumes Traefik runs in `kube-system` with the
diff --git a/app/admin/commission/[id]/page.tsx b/app/admin/commission/[id]/page.tsx
new file mode 100644
index 0000000..e5aae54
--- /dev/null
+++ b/app/admin/commission/[id]/page.tsx
@@ -0,0 +1,17 @@
+import Link from "next/link";
+import { notFound } from "next/navigation";
+import { connection } from "next/server";
+import { AdminHeader } from "@/components/admin/admin-header";
+import { CommissionTicketDetail } from "@/components/commission/ticket-detail";
+import { getAdminSession } from "@/lib/auth/server";
+
+export const instant = false;
+export default async function AdminCommissionTicketPage({ params }: PageProps<"/admin/commission/[id]">) {
+ await connection();
+ if (!(await getAdminSession())) notFound();
+ const { id } = await params;
+ return
+ กลับไปหน้า Ticket ทั้งหมด
+
+ ;
+}
diff --git a/app/admin/commission/page.tsx b/app/admin/commission/page.tsx
new file mode 100644
index 0000000..a2f556d
--- /dev/null
+++ b/app/admin/commission/page.tsx
@@ -0,0 +1,44 @@
+import Link from "next/link";
+import { desc, eq } from "drizzle-orm";
+import { redirect } from "next/navigation";
+import { connection } from "next/server";
+import { getDb } from "@/db";
+import { commissionCheckouts, commissionTickets, users } from "@/db/schema";
+import { AdminHeader } from "@/components/admin/admin-header";
+import { CommissionLiveRefresh } from "@/components/commission/live-refresh";
+import { Badge } from "@/components/ui/badge";
+import { Card, CardAction, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
+import { getAdminSession } from "@/lib/auth/server";
+
+export const instant = false;
+export default async function AdminCommissionPage({ searchParams }: PageProps<"/admin/commission">) {
+ await connection();
+ if (!(await getAdminSession())) redirect("/admin/login");
+ const query = await searchParams;
+ const rawPage = Number(query.page ?? 1);
+ const page = Number.isInteger(rawPage) && rawPage > 0 ? Math.min(rawPage, 10000) : 1;
+ const tickets = await getDb().select({ ticket: commissionTickets, amount: commissionCheckouts.amountBaht,
+ customer: users.name }).from(commissionTickets)
+ .innerJoin(commissionCheckouts, eq(commissionTickets.checkoutId, commissionCheckouts.id))
+ .innerJoin(users, eq(commissionTickets.userId, users.id))
+ .orderBy(desc(commissionTickets.status), desc(commissionTickets.updatedAt), desc(commissionTickets.id))
+ .limit(51).offset((page - 1) * 50);
+ return
+
+ Commission tickets
+ {tickets.length === 0 && ยังไม่มี Ticket
}
+ {tickets.slice(0, 50).map(({ ticket, amount, customer }) =>
+ {ticket.title || `#${ticket.id.slice(0, 8)}`} - {customer}
+
+ {ticket.status === "open" ? "เปิดอยู่" : "ปิดแล้ว"}
+
+ ฿{amount} - {ticket.updatedAt.toLocaleString("th-TH")}
+ )}
+
+ {page > 1 && ก่อนหน้า}
+ {tickets.length > 50 && ถัดไป}
+
+
+
;
+}
diff --git a/app/api/commission/checkouts/[id]/verify/route.ts b/app/api/commission/checkouts/[id]/verify/route.ts
new file mode 100644
index 0000000..09d95ab
--- /dev/null
+++ b/app/api/commission/checkouts/[id]/verify/route.ts
@@ -0,0 +1,66 @@
+import { and, eq } from "drizzle-orm";
+import { getDb } from "@/db";
+import { commissionCheckouts, commissionPayments, commissionTickets } from "@/db/schema";
+import { notifyCommission, requireCommissionUser } from "@/lib/commission/server";
+import { notifyPaidTicketDiscord } from "@/lib/commission/discord";
+import { verifyCommissionSlip } from "@/lib/commission/payment";
+import { getMediaStorage } from "@/lib/media/storage";
+import { inspectImage } from "@/lib/media/inspect";
+import { boundedBody, errorResponse, HttpError, requireSameOrigin, withUploadSlot } from "@/lib/security/http";
+import { limitRequest } from "@/lib/security/rate-limit";
+
+const MAX_SLIP_BYTES = 6 * 1024 * 1024;
+
+export async function POST(request: Request, context: RouteContext<"/api/commission/checkouts/[id]/verify">) {
+ try {
+ requireSameOrigin(request);
+ const user = await requireCommissionUser();
+ await limitRequest("commission-slip", user.id, 10);
+ const { id } = await context.params;
+ const [checkout] = await getDb().select().from(commissionCheckouts)
+ .where(and(eq(commissionCheckouts.id, id), eq(commissionCheckouts.userId, user.id))).limit(1);
+ if (!checkout) throw new HttpError(404, "checkout-not-found");
+ const [existing] = await getDb().select({ id: commissionTickets.id }).from(commissionTickets)
+ .where(eq(commissionTickets.checkoutId, id)).limit(1);
+ if (existing) return Response.json({ ticketId: existing.id });
+ if (!request.headers.get("content-type")?.startsWith("multipart/form-data;"))
+ throw new HttpError(415, "expected-multipart");
+ return await withUploadSlot(async () => {
+ const form = await boundedBody(request, MAX_SLIP_BYTES + 64 * 1024).formData();
+ const file = form.get("file");
+ if (!(file instanceof File) || file.size === 0 || file.size > MAX_SLIP_BYTES ||
+ !["image/png", "image/jpeg", "image/webp"].includes(file.type))
+ throw new HttpError(415, "invalid-slip-image");
+ const bytes = new Uint8Array(await file.arrayBuffer());
+ await inspectImage(bytes, file.type as "image/png" | "image/jpeg" | "image/webp");
+ const verified = await verifyCommissionSlip(file, checkout.amountBaht, checkout.createdAt);
+ const objectKey = `commission/slips/${crypto.randomUUID()}`;
+ const storage = await getMediaStorage();
+ await storage.write(objectKey, bytes, { type: file.type, acl: "private" });
+ let ticketId: string;
+ try {
+ ticketId = await getDb().transaction(async (tx) => {
+ const [ticket] = await tx.insert(commissionTickets).values({
+ checkoutId: checkout.id, userId: user.id,
+ }).returning({ id: commissionTickets.id });
+ await tx.insert(commissionPayments).values({
+ checkoutId: checkout.id, ticketId: ticket.id, slipObjectKey: objectKey,
+ slipMimeType: file.type, ...verified,
+ });
+ return ticket.id;
+ });
+ } catch (cause) {
+ await storage.delete(objectKey).catch(() => undefined);
+ const [paid] = await getDb().select({ id: commissionTickets.id }).from(commissionTickets)
+ .where(eq(commissionTickets.checkoutId, checkout.id)).limit(1);
+ if (paid) return Response.json({ ticketId: paid.id });
+ if (cause && typeof cause === "object" && "code" in cause && cause.code === "23505")
+ throw new HttpError(409, "payment-already-used");
+ throw cause;
+ }
+ await notifyCommission(ticketId, user.id);
+ await notifyPaidTicketDiscord(ticketId, checkout.amountBaht);
+ return Response.json({ ticketId }, { status: 201, headers: { "Cache-Control": "no-store" } });
+ });
+ } catch (cause) { return errorResponse(cause); }
+}
diff --git a/app/api/commission/checkouts/route.ts b/app/api/commission/checkouts/route.ts
new file mode 100644
index 0000000..a61e9e1
--- /dev/null
+++ b/app/api/commission/checkouts/route.ts
@@ -0,0 +1,24 @@
+import { getDb } from "@/db";
+import { commissionCheckouts } from "@/db/schema";
+import { validateCommissionCatalog } from "@/lib/commission/catalog";
+import { promptPayConfig } from "@/lib/commission/payment";
+import { commissionPrice, commissionRequestSchema } from "@/lib/commission/request";
+import { requireCommissionUser } from "@/lib/commission/server";
+import { errorResponse, HttpError, readJson, requireSameOrigin } from "@/lib/security/http";
+import { limitRequest } from "@/lib/security/rate-limit";
+
+export async function POST(request: Request) {
+ try {
+ requireSameOrigin(request);
+ const user = await requireCommissionUser();
+ await limitRequest("commission-checkout", user.id, 15);
+ const parsed = commissionRequestSchema.safeParse(await readJson(request, 32 * 1024));
+ if (!parsed.success || !(await validateCommissionCatalog(parsed.data)))
+ throw new HttpError(400, "invalid-commission-request");
+ promptPayConfig();
+ const [checkout] = await getDb().insert(commissionCheckouts).values({
+ userId: user.id, request: parsed.data, amountBaht: commissionPrice(parsed.data),
+ }).returning({ id: commissionCheckouts.id });
+ return Response.json({ checkoutId: checkout.id }, { status: 201, headers: { "Cache-Control": "no-store" } });
+ } catch (cause) { return errorResponse(cause); }
+}
diff --git a/app/api/commission/events/route.ts b/app/api/commission/events/route.ts
new file mode 100644
index 0000000..cd316d3
--- /dev/null
+++ b/app/api/commission/events/route.ts
@@ -0,0 +1,25 @@
+import { createRedisNamedEventResponse } from "@/lib/events/redis-stream";
+import { authorizeTicket } from "@/lib/commission/tickets";
+import { requireCommissionUser } from "@/lib/commission/server";
+import { errorResponse, HttpError } from "@/lib/security/http";
+import { limitRequest, trustedClientAddress } from "@/lib/security/rate-limit";
+
+export async function GET(request: Request) {
+ try {
+ const user = await requireCommissionUser();
+ await limitRequest("stream-open", trustedClientAddress(request.headers), 60);
+ const url = new URL(request.url);
+ const ticketId = url.searchParams.get("ticketId");
+ const scope = url.searchParams.get("scope");
+ let topic: string;
+ if (ticketId) {
+ if (!/^[a-f0-9-]{36}$/i.test(ticketId)) throw new HttpError(400, "invalid-ticket-id");
+ await authorizeTicket(ticketId);
+ topic = `commission:ticket:${ticketId}`;
+ } else if (scope === "admin") {
+ if (user.role !== "admin" || !user.emailVerified) throw new HttpError(403, "forbidden");
+ topic = "commission:admin";
+ } else topic = `commission:user:${user.id}`;
+ return await createRedisNamedEventResponse(topic, "changed", request.signal);
+ } catch (cause) { return errorResponse(cause); }
+}
diff --git a/app/api/commission/tickets/[id]/images/[messageId]/route.ts b/app/api/commission/tickets/[id]/images/[messageId]/route.ts
new file mode 100644
index 0000000..9268fe3
--- /dev/null
+++ b/app/api/commission/tickets/[id]/images/[messageId]/route.ts
@@ -0,0 +1,21 @@
+import { and, eq } from "drizzle-orm";
+import { getDb } from "@/db";
+import { commissionMessages } from "@/db/schema";
+import { authorizeTicket } from "@/lib/commission/tickets";
+import { getMediaStorage } from "@/lib/media/storage";
+import { errorResponse, HttpError } from "@/lib/security/http";
+
+export async function GET(_request: Request,
+ context: RouteContext<"/api/commission/tickets/[id]/images/[messageId]">) {
+ try {
+ const { id, messageId } = await context.params;
+ await authorizeTicket(id);
+ const [message] = await getDb().select({ key: commissionMessages.imageObjectKey,
+ type: commissionMessages.imageMimeType }).from(commissionMessages)
+ .where(and(eq(commissionMessages.id, messageId), eq(commissionMessages.ticketId, id))).limit(1);
+ if (!message?.key || !message.type) throw new HttpError(404, "image-not-found");
+ const file = (await getMediaStorage()).file(message.key);
+ return new Response(await file.bytes(), { headers: { "Content-Type": message.type,
+ "Cache-Control": "private, no-store", "X-Content-Type-Options": "nosniff" } });
+ } catch (cause) { return errorResponse(cause); }
+}
diff --git a/app/api/commission/tickets/[id]/messages/[messageId]/route.test.ts b/app/api/commission/tickets/[id]/messages/[messageId]/route.test.ts
new file mode 100644
index 0000000..0863776
--- /dev/null
+++ b/app/api/commission/tickets/[id]/messages/[messageId]/route.test.ts
@@ -0,0 +1,76 @@
+import { PgDialect } from "drizzle-orm/pg-core";
+import { beforeEach, describe, expect, it, vi } from "vitest";
+
+const authorizeTicket = vi.fn();
+const notifyCommission = vi.fn();
+const deleteObject = vi.fn();
+const deleteReturning = vi.fn();
+const deleteWhere = vi.fn();
+const updateWhere = vi.fn();
+
+const tx = {
+ delete: vi.fn(() => ({ where: deleteWhere })),
+ update: vi.fn(() => ({ set: () => ({ where: updateWhere }) })),
+};
+
+vi.mock("@/db", () => ({ getDb: () => ({ transaction: async (run: (value: typeof tx) => Promise) => run(tx) }) }));
+vi.mock("@/lib/commission/tickets", () => ({ authorizeTicket }));
+vi.mock("@/lib/commission/server", () => ({ notifyCommission }));
+vi.mock("@/lib/media/storage", () => ({ getMediaStorage: async () => ({ delete: deleteObject }) }));
+vi.mock("@/lib/security/rate-limit", () => ({ limitRequest: async () => undefined }));
+
+const { DELETE } = await import("./route");
+const ticketId = "11111111-1111-4111-8111-111111111111";
+const messageId = "22222222-2222-4222-8222-222222222222";
+const ownerId = "owner-1";
+
+function deletionRequest(origin = "https://guide.sudloh.com") {
+ return new Request(`https://guide.sudloh.com/api/commission/tickets/${ticketId}/messages/${messageId}`,
+ { method: "DELETE", headers: { Origin: origin } });
+}
+
+function context() {
+ return { params: Promise.resolve({ id: ticketId, messageId }) } as RouteContext<
+ "/api/commission/tickets/[id]/messages/[messageId]">;
+}
+
+describe("commission message deletion", () => {
+ beforeEach(() => {
+ process.env.BETTER_AUTH_URL = "https://guide.sudloh.com";
+ vi.clearAllMocks();
+ authorizeTicket.mockResolvedValue({ ticket: { userId: ownerId }, user: { id: ownerId } });
+ deleteWhere.mockImplementation(() => ({ returning: deleteReturning }));
+ deleteReturning.mockResolvedValue([{ imageObjectKey: "commission/messages/image-1" }]);
+ updateWhere.mockResolvedValue(undefined);
+ deleteObject.mockResolvedValue(undefined);
+ notifyCommission.mockResolvedValue(undefined);
+ });
+
+ it("rejects cross-origin requests before ticket authorization", async () => {
+ const response = await DELETE(deletionRequest("https://example.com"), context());
+ expect(response.status).toBe(403);
+ expect(authorizeTicket).not.toHaveBeenCalled();
+ });
+
+ it("deletes only a message owned by the current user in this ticket", async () => {
+ const response = await DELETE(deletionRequest(), context());
+ expect(response.status).toBe(204);
+ const condition = deleteWhere.mock.calls[0][0];
+ const query = new PgDialect().sqlToQuery(condition);
+ expect(query.sql).toContain('"ticket_id"');
+ expect(query.sql).toContain('"author_id"');
+ expect(query.params).toContain(ticketId);
+ expect(query.params).toContain(messageId);
+ expect(query.params).toContain(ownerId);
+ expect(deleteObject).toHaveBeenCalledWith("commission/messages/image-1");
+ expect(notifyCommission).toHaveBeenCalledWith(ticketId, ownerId);
+ });
+
+ it("leaves storage and notifications untouched when no owned message exists", async () => {
+ deleteReturning.mockResolvedValue([]);
+ const response = await DELETE(deletionRequest(), context());
+ expect(response.status).toBe(404);
+ expect(deleteObject).not.toHaveBeenCalled();
+ expect(notifyCommission).not.toHaveBeenCalled();
+ });
+});
diff --git a/app/api/commission/tickets/[id]/messages/[messageId]/route.ts b/app/api/commission/tickets/[id]/messages/[messageId]/route.ts
new file mode 100644
index 0000000..b36bfd3
--- /dev/null
+++ b/app/api/commission/tickets/[id]/messages/[messageId]/route.ts
@@ -0,0 +1,32 @@
+import { and, eq } from "drizzle-orm";
+import * as z from "zod";
+import { getDb } from "@/db";
+import { commissionMessages, commissionTickets } from "@/db/schema";
+import { notifyCommission } from "@/lib/commission/server";
+import { authorizeTicket } from "@/lib/commission/tickets";
+import { getMediaStorage } from "@/lib/media/storage";
+import { errorResponse, HttpError, requireSameOrigin } from "@/lib/security/http";
+import { limitRequest } from "@/lib/security/rate-limit";
+
+export async function DELETE(request: Request,
+ context: RouteContext<"/api/commission/tickets/[id]/messages/[messageId]">) {
+ try {
+ requireSameOrigin(request);
+ const { id, messageId } = await context.params;
+ if (!z.uuid().safeParse(messageId).success) throw new HttpError(404, "message-not-found");
+ const { ticket, user } = await authorizeTicket(id);
+ await limitRequest("commission-message-delete", user.id, 30);
+ const imageObjectKey = await getDb().transaction(async (tx) => {
+ const [message] = await tx.delete(commissionMessages)
+ .where(and(eq(commissionMessages.id, messageId), eq(commissionMessages.ticketId, id),
+ eq(commissionMessages.authorId, user.id)))
+ .returning({ imageObjectKey: commissionMessages.imageObjectKey });
+ if (!message) throw new HttpError(404, "message-not-found");
+ await tx.update(commissionTickets).set({ updatedAt: new Date() }).where(eq(commissionTickets.id, id));
+ return message.imageObjectKey;
+ });
+ if (imageObjectKey) await (await getMediaStorage()).delete(imageObjectKey).catch(() => undefined);
+ await notifyCommission(id, ticket.userId);
+ return new Response(null, { status: 204 });
+ } catch (cause) { return errorResponse(cause); }
+}
diff --git a/app/api/commission/tickets/[id]/messages/route.ts b/app/api/commission/tickets/[id]/messages/route.ts
new file mode 100644
index 0000000..c24e8b7
--- /dev/null
+++ b/app/api/commission/tickets/[id]/messages/route.ts
@@ -0,0 +1,55 @@
+import { eq } from "drizzle-orm";
+import { getDb } from "@/db";
+import { commissionMessages, commissionTickets } from "@/db/schema";
+import { notifyCommission } from "@/lib/commission/server";
+import { authorizeTicket } from "@/lib/commission/tickets";
+import { getMediaStorage } from "@/lib/media/storage";
+import { inspectImage } from "@/lib/media/inspect";
+import { boundedBody, errorResponse, HttpError, requireSameOrigin, withUploadSlot } from "@/lib/security/http";
+import { limitRequest } from "@/lib/security/rate-limit";
+
+export async function POST(request: Request, context: RouteContext<"/api/commission/tickets/[id]/messages">) {
+ try {
+ requireSameOrigin(request);
+ const { id } = await context.params;
+ const { ticket, user } = await authorizeTicket(id);
+ if (ticket.status !== "open") throw new HttpError(409, "ticket-closed");
+ await limitRequest("commission-message", user.id, 60);
+ if (!request.headers.get("content-type")?.startsWith("multipart/form-data;"))
+ throw new HttpError(415, "expected-multipart");
+ return await withUploadSlot(async () => {
+ const form = await boundedBody(request, 6 * 1024 * 1024 + 64 * 1024).formData();
+ const value = form.get("text");
+ const body = typeof value === "string" ? value.trim() : "";
+ const image = form.get("image");
+ if (body.length > 4000 || (image && !(image instanceof File)))
+ throw new HttpError(400, "invalid-message");
+ if (!body && !image) throw new HttpError(400, "empty-message");
+ let imageObjectKey: string | null = null;
+ if (image instanceof File) {
+ if (image.size === 0 || image.size > 6 * 1024 * 1024 ||
+ !["image/png", "image/jpeg", "image/webp"].includes(image.type))
+ throw new HttpError(415, "invalid-message-image");
+ const bytes = new Uint8Array(await image.arrayBuffer());
+ await inspectImage(bytes, image.type as "image/png" | "image/jpeg" | "image/webp");
+ imageObjectKey = `commission/messages/${crypto.randomUUID()}`;
+ await (await getMediaStorage()).write(imageObjectKey, bytes, { type: image.type, acl: "private" });
+ }
+ let messageId: string;
+ try {
+ messageId = await getDb().transaction(async (tx) => {
+ const [message] = await tx.insert(commissionMessages).values({ ticketId: id, authorId: user.id,
+ text: body || null, imageObjectKey, imageMimeType: image instanceof File ? image.type : null })
+ .returning({ id: commissionMessages.id });
+ await tx.update(commissionTickets).set({ updatedAt: new Date() }).where(eq(commissionTickets.id, id));
+ return message.id;
+ });
+ } catch (cause) {
+ if (imageObjectKey) await (await getMediaStorage()).delete(imageObjectKey).catch(() => undefined);
+ throw cause;
+ }
+ await notifyCommission(id, ticket.userId);
+ return Response.json({ ok: true, messageId }, { status: 201 });
+ });
+ } catch (cause) { return errorResponse(cause); }
+}
diff --git a/app/api/commission/tickets/[id]/reactions/route.ts b/app/api/commission/tickets/[id]/reactions/route.ts
new file mode 100644
index 0000000..9179282
--- /dev/null
+++ b/app/api/commission/tickets/[id]/reactions/route.ts
@@ -0,0 +1,34 @@
+import { and, eq } from "drizzle-orm";
+import { getDb } from "@/db";
+import { commissionMessages, commissionReactions } from "@/db/schema";
+import { notifyCommission } from "@/lib/commission/server";
+import { authorizeTicket } from "@/lib/commission/tickets";
+import { errorResponse, HttpError, readJson, requireSameOrigin } from "@/lib/security/http";
+import { limitRequest } from "@/lib/security/rate-limit";
+import * as z from "zod";
+
+const inputSchema = z.object({ messageId: z.uuid(), emoji: z.enum(["👍", "❤️", "😂", "😮", "😢", "🎉"]) });
+
+export async function POST(request: Request, context: RouteContext<"/api/commission/tickets/[id]/reactions">) {
+ try {
+ requireSameOrigin(request);
+ const { id } = await context.params;
+ const { ticket, user } = await authorizeTicket(id);
+ if (ticket.status !== "open") throw new HttpError(409, "ticket-closed");
+ await limitRequest("commission-reaction", user.id, 60);
+ const parsed = inputSchema.safeParse(await readJson(request));
+ if (!parsed.success) throw new HttpError(400, "invalid-reaction");
+ const [message] = await getDb().select({ id: commissionMessages.id }).from(commissionMessages)
+ .where(and(eq(commissionMessages.id, parsed.data.messageId), eq(commissionMessages.ticketId, id))).limit(1);
+ if (!message) throw new HttpError(404, "message-not-found");
+ const where = and(eq(commissionReactions.messageId, message.id),
+ eq(commissionReactions.userId, user.id), eq(commissionReactions.emoji, parsed.data.emoji));
+ const [existing] = await getDb().select().from(commissionReactions).where(where).limit(1);
+ if (existing) await getDb().delete(commissionReactions).where(where);
+ else await getDb().insert(commissionReactions).values({
+ messageId: message.id, userId: user.id, emoji: parsed.data.emoji,
+ }).onConflictDoNothing();
+ await notifyCommission(id, ticket.userId);
+ return Response.json({ active: !existing });
+ } catch (cause) { return errorResponse(cause); }
+}
diff --git a/app/api/commission/tickets/[id]/slip/route.ts b/app/api/commission/tickets/[id]/slip/route.ts
new file mode 100644
index 0000000..a04cd35
--- /dev/null
+++ b/app/api/commission/tickets/[id]/slip/route.ts
@@ -0,0 +1,20 @@
+import { eq } from "drizzle-orm";
+import { getDb } from "@/db";
+import { commissionPayments } from "@/db/schema";
+import { authorizeTicket } from "@/lib/commission/tickets";
+import { getMediaStorage } from "@/lib/media/storage";
+import { errorResponse, HttpError } from "@/lib/security/http";
+
+export async function GET(_request: Request, context: RouteContext<"/api/commission/tickets/[id]/slip">) {
+ try {
+ const { id } = await context.params;
+ await authorizeTicket(id);
+ const [payment] = await getDb().select({ key: commissionPayments.slipObjectKey,
+ type: commissionPayments.slipMimeType }).from(commissionPayments)
+ .where(eq(commissionPayments.ticketId, id)).limit(1);
+ if (!payment) throw new HttpError(404, "slip-not-found");
+ const file = (await getMediaStorage()).file(payment.key);
+ return new Response(await file.bytes(), { headers: { "Content-Type": payment.type,
+ "Cache-Control": "private, no-store", "X-Content-Type-Options": "nosniff" } });
+ } catch (cause) { return errorResponse(cause); }
+}
diff --git a/app/api/commission/tickets/[id]/status/route.ts b/app/api/commission/tickets/[id]/status/route.ts
new file mode 100644
index 0000000..2a999d6
--- /dev/null
+++ b/app/api/commission/tickets/[id]/status/route.ts
@@ -0,0 +1,23 @@
+import { eq } from "drizzle-orm";
+import * as z from "zod";
+import { getDb } from "@/db";
+import { commissionTickets } from "@/db/schema";
+import { requireAdmin } from "@/lib/auth/server";
+import { notifyCommission } from "@/lib/commission/server";
+import { errorResponse, HttpError, readJson, requireSameOrigin } from "@/lib/security/http";
+
+export async function PATCH(request: Request, context: RouteContext<"/api/commission/tickets/[id]/status">) {
+ try {
+ requireSameOrigin(request);
+ await requireAdmin();
+ const { id } = await context.params;
+ const parsed = z.object({ status: z.enum(["open", "closed"]) }).safeParse(await readJson(request));
+ if (!parsed.success) throw new HttpError(400, "invalid-status");
+ const [ticket] = await getDb().update(commissionTickets)
+ .set({ status: parsed.data.status, updatedAt: new Date() })
+ .where(eq(commissionTickets.id, id)).returning({ userId: commissionTickets.userId });
+ if (!ticket) throw new HttpError(404, "ticket-not-found");
+ await notifyCommission(id, ticket.userId);
+ return Response.json({ status: parsed.data.status });
+ } catch (cause) { return errorResponse(cause); }
+}
diff --git a/app/api/commission/tickets/[id]/title/route.test.ts b/app/api/commission/tickets/[id]/title/route.test.ts
new file mode 100644
index 0000000..eef47df
--- /dev/null
+++ b/app/api/commission/tickets/[id]/title/route.test.ts
@@ -0,0 +1,65 @@
+import { beforeEach, describe, expect, it, vi } from "vitest";
+import { HttpError } from "@/lib/security/http";
+
+const requireAdmin = vi.fn();
+const update = vi.fn();
+const set = vi.fn();
+const where = vi.fn();
+const returning = vi.fn();
+const notifyCommission = vi.fn();
+
+vi.mock("@/lib/auth/server", () => ({ requireAdmin }));
+vi.mock("@/db", () => ({ getDb: () => ({ update }) }));
+vi.mock("@/lib/commission/server", () => ({ notifyCommission }));
+
+const { PATCH } = await import("./route");
+const ticketId = "11111111-1111-4111-8111-111111111111";
+const userId = "customer-1";
+
+function request(title: unknown, origin = "https://guide.sudloh.com") {
+ return new Request(`https://guide.sudloh.com/api/commission/tickets/${ticketId}/title`, {
+ method: "PATCH", headers: { Origin: origin, "Content-Type": "application/json" },
+ body: JSON.stringify({ title }),
+ });
+}
+
+function context() {
+ return { params: Promise.resolve({ id: ticketId }) } as RouteContext<"/api/commission/tickets/[id]/title">;
+}
+
+describe("commission ticket rename", () => {
+ beforeEach(() => {
+ process.env.BETTER_AUTH_URL = "https://guide.sudloh.com";
+ vi.clearAllMocks();
+ requireAdmin.mockResolvedValue({ user: { id: "admin-1" } });
+ update.mockReturnValue({ set });
+ set.mockReturnValue({ where });
+ where.mockReturnValue({ returning });
+ returning.mockResolvedValue([{ title: "New name", userId }]);
+ notifyCommission.mockResolvedValue(undefined);
+ });
+
+ it("rejects cross-origin and non-admin requests", async () => {
+ expect((await PATCH(request("New name", "https://example.com"), context())).status).toBe(403);
+ expect(requireAdmin).not.toHaveBeenCalled();
+ requireAdmin.mockRejectedValueOnce(new HttpError(401, "unauthorized"));
+ expect((await PATCH(request("New name"), context())).status).toBe(401);
+ expect(update).not.toHaveBeenCalled();
+ });
+
+ it("normalizes and saves a name, then notifies ticket viewers", async () => {
+ const response = await PATCH(request(" New name "), context());
+ expect(response.status).toBe(200);
+ expect(set).toHaveBeenCalledWith({ title: "New name", updatedAt: expect.any(Date) });
+ expect(notifyCommission).toHaveBeenCalledWith(ticketId, userId);
+ });
+
+ it("allows clearing the name and rejects names over 80 characters", async () => {
+ returning.mockResolvedValueOnce([{ title: null, userId }]);
+ expect((await PATCH(request(" "), context())).status).toBe(200);
+ expect(set).toHaveBeenCalledWith({ title: null, updatedAt: expect.any(Date) });
+ vi.clearAllMocks();
+ expect((await PATCH(request("x".repeat(81)), context())).status).toBe(400);
+ expect(update).not.toHaveBeenCalled();
+ });
+});
diff --git a/app/api/commission/tickets/[id]/title/route.ts b/app/api/commission/tickets/[id]/title/route.ts
new file mode 100644
index 0000000..3f09880
--- /dev/null
+++ b/app/api/commission/tickets/[id]/title/route.ts
@@ -0,0 +1,25 @@
+import { eq } from "drizzle-orm";
+import * as z from "zod";
+import { getDb } from "@/db";
+import { commissionTickets } from "@/db/schema";
+import { requireAdmin } from "@/lib/auth/server";
+import { notifyCommission } from "@/lib/commission/server";
+import { errorResponse, HttpError, readJson, requireSameOrigin } from "@/lib/security/http";
+
+const inputSchema = z.object({ title: z.string().trim().max(80) });
+
+export async function PATCH(request: Request, context: RouteContext<"/api/commission/tickets/[id]/title">) {
+ try {
+ requireSameOrigin(request);
+ await requireAdmin();
+ const { id } = await context.params;
+ const parsed = inputSchema.safeParse(await readJson(request, 512));
+ if (!parsed.success) throw new HttpError(400, "invalid-ticket-title");
+ const title = parsed.data.title.replace(/\s+/gu, " ") || null;
+ const [ticket] = await getDb().update(commissionTickets).set({ title, updatedAt: new Date() })
+ .where(eq(commissionTickets.id, id)).returning({ title: commissionTickets.title, userId: commissionTickets.userId });
+ if (!ticket) throw new HttpError(404, "ticket-not-found");
+ await notifyCommission(id, ticket.userId);
+ return Response.json({ title: ticket.title });
+ } catch (cause) { return errorResponse(cause); }
+}
diff --git a/app/api/commission/tickets/[id]/typing/route.ts b/app/api/commission/tickets/[id]/typing/route.ts
new file mode 100644
index 0000000..30458c3
--- /dev/null
+++ b/app/api/commission/tickets/[id]/typing/route.ts
@@ -0,0 +1,20 @@
+import { authorizeTicket } from "@/lib/commission/tickets";
+import { getRedisClient, redisEventChannel } from "@/lib/redis/client";
+import { errorResponse, HttpError, readJson, requireSameOrigin } from "@/lib/security/http";
+import { limitRequest } from "@/lib/security/rate-limit";
+
+export async function POST(request: Request, context: RouteContext<"/api/commission/tickets/[id]/typing">) {
+ try {
+ requireSameOrigin(request);
+ const { id } = await context.params;
+ const { ticket, user } = await authorizeTicket(id);
+ if (ticket.status !== "open") throw new HttpError(409, "ticket-closed");
+ await limitRequest("commission-typing", user.id, 30);
+ const body = await readJson(request, 128);
+ if (!body || typeof body !== "object" || !("active" in body) || typeof body.active !== "boolean")
+ throw new HttpError(400, "invalid-typing-state");
+ await (await getRedisClient()).publish(redisEventChannel(`commission:ticket:${id}`),
+ `typing:${JSON.stringify({ userId: user.id, name: user.name, active: body.active })}`);
+ return new Response(null, { status: 204 });
+ } catch (cause) { return errorResponse(cause); }
+}
diff --git a/app/commission/login/page.tsx b/app/commission/login/page.tsx
new file mode 100644
index 0000000..d3c7e11
--- /dev/null
+++ b/app/commission/login/page.tsx
@@ -0,0 +1,5 @@
+import { redirect } from "next/navigation";
+
+export default function LegacyCommissionLoginPage() {
+ redirect("/login?next=%2Fcommission");
+}
diff --git a/app/commission/page.tsx b/app/commission/page.tsx
new file mode 100644
index 0000000..db51961
--- /dev/null
+++ b/app/commission/page.tsx
@@ -0,0 +1,39 @@
+import Link from "next/link";
+import { Suspense } from "react";
+import { connection } from "next/server";
+import { SiteHeader } from "@/components/public/site-header";
+import { CommissionRequestForm } from "@/components/commission/request-form";
+import { CommissionSignOut } from "@/components/commission/sign-out";
+import { Button } from "@/components/ui/button";
+import { getCustomerSession } from "@/lib/auth/server";
+import { getCatalogOptions } from "@/lib/guides/queries";
+
+export const instant = false;
+
+async function CommissionContent() {
+ await connection();
+ const session = await getCustomerSession();
+ if (!session) return
+
เข้าสู่ระบบหรือสมัครสมาชิกก่อนเริ่มคำขอคอมมิชชัน
+
+ }>เข้าสู่ระบบ
+ }>สมัครสมาชิก
+
+
;
+ const catalog = await getCatalogOptions();
+ if (!catalog) return ยังไม่มีข้อมูลตัวละครสำหรับสร้างคำขอ
;
+ return ;
+}
+
+export default function CommissionPage() {
+ return ;
+}
diff --git a/app/commission/pay/[id]/page.tsx b/app/commission/pay/[id]/page.tsx
new file mode 100644
index 0000000..7b8c155
--- /dev/null
+++ b/app/commission/pay/[id]/page.tsx
@@ -0,0 +1,47 @@
+import QRCode from "qrcode";
+import Link from "next/link";
+import { and, eq } from "drizzle-orm";
+import { notFound, redirect } from "next/navigation";
+import { connection } from "next/server";
+import { getDb } from "@/db";
+import { commissionCheckouts, commissionTickets } from "@/db/schema";
+import { CommissionPaymentForm } from "@/components/commission/payment-form";
+import { CommissionRequestSummary } from "@/components/commission/request-summary";
+import { SiteHeader } from "@/components/public/site-header";
+import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
+import { getCustomerSession } from "@/lib/auth/server";
+import { promptPayConfig } from "@/lib/commission/payment";
+import { generatePromptPayPayload } from "@/lib/commission/promptpay";
+import { getCommissionLabels } from "@/lib/commission/catalog";
+
+export const instant = false;
+
+export default async function CommissionPayPage({ params }: PageProps<"/commission/pay/[id]">) {
+ await connection();
+ const { id } = await params;
+ const session = await getCustomerSession();
+ if (!session) redirect(`/login?next=${encodeURIComponent(`/commission/pay/${id}`)}`);
+ const [checkout] = await getDb().select().from(commissionCheckouts)
+ .where(and(eq(commissionCheckouts.id, id), eq(commissionCheckouts.userId, session.user.id))).limit(1);
+ if (!checkout) notFound();
+ const [ticket] = await getDb().select({ id: commissionTickets.id }).from(commissionTickets)
+ .where(eq(commissionTickets.checkoutId, id)).limit(1);
+ if (ticket) redirect(`/commission/tickets/${ticket.id}`);
+ const { identifier } = promptPayConfig();
+ const payload = generatePromptPayPayload({ identifier, amount: checkout.amountBaht });
+ const qr = await QRCode.toDataURL(payload, { margin: 2, width: 300 });
+ const catalog = await getCommissionLabels(checkout.request);
+ return
+ กลับไป Commission
+ ชำระเงิน
+ รายการคำขอ - ฿{checkout.amountBaht}
+ {catalog && }
+ สแกน PromptPay
+ {/* Generated locally from the fixed checkout amount and configured recipient. */}
+ {/* eslint-disable-next-line @next/next/no-img-element */}
+
+ ฿{checkout.amountBaht} โอนเงินแล้วอัปโหลดรูปสลิปเพื่อยืนยัน
+
+
+ ;
+}
diff --git a/app/commission/tickets/[id]/page.tsx b/app/commission/tickets/[id]/page.tsx
new file mode 100644
index 0000000..c080aa9
--- /dev/null
+++ b/app/commission/tickets/[id]/page.tsx
@@ -0,0 +1,14 @@
+import Link from "next/link";
+import { connection } from "next/server";
+import { CommissionTicketDetail } from "@/components/commission/ticket-detail";
+import { SiteHeader } from "@/components/public/site-header";
+
+export const instant = false;
+export default async function CommissionTicketPage({ params }: PageProps<"/commission/tickets/[id]">) {
+ await connection();
+ const { id } = await params;
+ return
+ กลับไปหน้า Ticket ของฉัน
+
+
;
+}
diff --git a/app/commission/tickets/page.tsx b/app/commission/tickets/page.tsx
new file mode 100644
index 0000000..1248860
--- /dev/null
+++ b/app/commission/tickets/page.tsx
@@ -0,0 +1,53 @@
+import Link from "next/link";
+import { and, desc, eq, isNull } from "drizzle-orm";
+import { redirect } from "next/navigation";
+import { connection } from "next/server";
+import { getDb } from "@/db";
+import { commissionCheckouts, commissionTickets } from "@/db/schema";
+import { CommissionLiveRefresh } from "@/components/commission/live-refresh";
+import { CommissionSignOut } from "@/components/commission/sign-out";
+import { SiteHeader } from "@/components/public/site-header";
+import { Badge } from "@/components/ui/badge";
+import { Card, CardAction, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
+import { getCustomerSession } from "@/lib/auth/server";
+
+export const instant = false;
+export default async function CommissionTicketsPage() {
+ await connection();
+ const session = await getCustomerSession();
+ if (!session) redirect("/login?next=%2Fcommission%2Ftickets");
+ const [tickets, pending] = await Promise.all([
+ getDb().select({ ticket: commissionTickets, amount: commissionCheckouts.amountBaht })
+ .from(commissionTickets).innerJoin(commissionCheckouts, eq(commissionTickets.checkoutId, commissionCheckouts.id))
+ .where(eq(commissionTickets.userId, session.user.id)).orderBy(desc(commissionTickets.updatedAt)).limit(50),
+ getDb().select({ id: commissionCheckouts.id, amount: commissionCheckouts.amountBaht,
+ createdAt: commissionCheckouts.createdAt }).from(commissionCheckouts)
+ .leftJoin(commissionTickets, eq(commissionTickets.checkoutId, commissionCheckouts.id))
+ .where(and(eq(commissionCheckouts.userId, session.user.id), isNull(commissionTickets.id)))
+ .orderBy(desc(commissionCheckouts.createdAt)).limit(20),
+ ]);
+ return
+
+
+
Ticket ของฉัน
+
+
+ สร้างคำขอเพิ่ม
+ {pending.length > 0 && รอชำระเงิน }
+ {pending.map((checkout) =>
+ รอชำระเงิน - ฿{checkout.amount}
+ {checkout.createdAt.toLocaleString("th-TH")}
+ )}
+ Ticket ที่ชำระเงินแล้ว
+ {tickets.length === 0 && ยังไม่มี Ticket
}
+ {tickets.map(({ ticket, amount }) =>
+ {ticket.title || `Ticket #${ticket.id.slice(0, 8)}`}
+
+ {ticket.status === "open" ? "เปิดอยู่" : "ปิดแล้ว"}
+
+ ฿{amount} - {ticket.updatedAt.toLocaleString("th-TH")}
+ )}
+
+
;
+}
diff --git a/app/login/page.tsx b/app/login/page.tsx
new file mode 100644
index 0000000..cbb73fb
--- /dev/null
+++ b/app/login/page.tsx
@@ -0,0 +1,10 @@
+import { connection } from "next/server";
+import { AccountPage } from "@/components/auth/account-page";
+
+export const instant = false;
+
+export default async function LoginPage({ searchParams }: PageProps<"/login">) {
+ await connection();
+ const { next } = await searchParams;
+ return ;
+}
diff --git a/app/register/page.tsx b/app/register/page.tsx
index 176a33f..3984f2a 100644
--- a/app/register/page.tsx
+++ b/app/register/page.tsx
@@ -1,5 +1,10 @@
-import { redirect } from "next/navigation";
+import { connection } from "next/server";
+import { AccountPage } from "@/components/auth/account-page";
-export default async function RegisterPage() {
- redirect("/admin/register");
+export const instant = false;
+
+export default async function RegisterPage({ searchParams }: PageProps<"/register">) {
+ await connection();
+ const { next } = await searchParams;
+ return ;
}
diff --git a/app/robots.ts b/app/robots.ts
index 96aa6f5..7fc54b5 100644
--- a/app/robots.ts
+++ b/app/robots.ts
@@ -7,7 +7,7 @@ export default function robots(): MetadataRoute.Robots {
rules: {
userAgent: "*",
allow: "/",
- disallow: ["/admin", "/api", "/register"],
+ disallow: ["/admin", "/api", "/login", "/register"],
},
sitemap: `${siteUrl}/sitemap.xml`,
};
diff --git a/bun.lock b/bun.lock
index 2faf33d..95c889d 100644
--- a/bun.lock
+++ b/bun.lock
@@ -23,6 +23,7 @@
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"cmdk": "^1.1.1",
+ "cn": "^0.4.0",
"color": "^5.0.3",
"discord.js": "^14.27.0",
"drizzle-orm": "^0.45.2",
@@ -33,6 +34,7 @@
"modern-screenshot": "^4.7.0",
"next": "^16.3.7",
"postgres": "^3.4.9",
+ "qrcode": "^1.5.4",
"radix-ui": "^1.6.7",
"react": "19.2.8",
"react-dom": "19.2.8",
@@ -52,6 +54,7 @@
"@tailwindcss/postcss": "^4",
"@types/bun": "^1.4.0",
"@types/node": "^20",
+ "@types/qrcode": "^1.5.6",
"@types/react": "^19",
"@types/react-dom": "^19",
"@types/sanitize-html": "^2.16.1",
@@ -692,6 +695,8 @@
"@types/node": ["@types/node@20.19.43", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-6oYBAi5ikg4Pl+kGsoYtawUMBT2zZMCvPNF7pVLnHZfd1zf38DRiWn/gT01RYCdUqkv7Fhr+C9ot4/tb+2sVvA=="],
+ "@types/qrcode": ["@types/qrcode@1.5.6", "", { "dependencies": { "@types/node": "*" } }, "sha512-te7NQcV2BOvdj2b1hCAHzAoMNuj65kNBMz0KBaxM6c3VGBOhU0dURQKOtH8CFNI/dsKkwlv32p26qYQTWoB5bw=="],
+
"@types/react": ["@types/react@19.2.18", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-AnzbBERsrLKtk2XSfTbYRLjQPdy116Sty4q+T+Bp3IC4l6jNBvreVPAHmpq9qhXQM7CXZPjLVmGMw9sy+hxQ3w=="],
"@types/react-dom": ["@types/react-dom@19.2.5", "", { "peerDependencies": { "@types/react": "^19.2.0" } }, "sha512-fMPwH9v7r/pp43yUd2/Mbiex5KouJwwR3dzHkhLREUC6764VyDsqxhAxv6OFEYR1RhjOyD1naqba8ECDBe7ZQg=="],
@@ -878,6 +883,8 @@
"callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="],
+ "camelcase": ["camelcase@5.3.1", "", {}, "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg=="],
+
"caniuse-lite": ["caniuse-lite@1.0.30001810", "", {}, "sha512-TITQPUkaz+aVk5GL6NhOdwk1aEaNTSDPsGFWrTuhKGtjTF70jL/Oht2W4c6rXUe5fu7Ie19VIahAXHIIiWWNeg=="],
"chai": ["chai@6.2.2", "", {}, "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg=="],
@@ -896,12 +903,16 @@
"client-only": ["client-only@0.0.1", "", {}, "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA=="],
+ "cliui": ["cliui@6.0.0", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.0", "wrap-ansi": "^6.2.0" } }, "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ=="],
+
"clsx": ["clsx@2.1.1", "", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="],
"cluster-key-slot": ["cluster-key-slot@1.1.1", "", {}, "sha512-rwHwUfXL40Chm1r08yrhU3qpUvdVlgkKNeyeGPOxnW8/SyVDvgRaed/Uz54AqWNaTCAThlj6QAs3TZcKI0xDEw=="],
"cmdk": ["cmdk@1.1.1", "", { "dependencies": { "@radix-ui/react-compose-refs": "^1.1.1", "@radix-ui/react-dialog": "^1.1.6", "@radix-ui/react-id": "^1.1.0", "@radix-ui/react-primitive": "^2.0.2" }, "peerDependencies": { "react": "^18 || ^19 || ^19.0.0-rc", "react-dom": "^18 || ^19 || ^19.0.0-rc" } }, "sha512-Vsv7kFaXm+ptHDMZ7izaRsP70GgrW9NBNGswt9OZaVBLlE0SNpDq8eu/VGXyF9r7M0azK3Wy7OlYXsuyYLFzHg=="],
+ "cn": ["cn@0.4.0", "", { "bin": { "cn": "bin/cn.mjs" } }, "sha512-qOAeUhPwPzBxx6jaeQdo646XIKliKHc2j1ZF7Ngb1o0pJZKH5gq8FkA0aVdUdPFUDXNXyUCXpCtoJISo6dhizg=="],
+
"code-block-writer": ["code-block-writer@13.0.3", "", {}, "sha512-Oofo0pq3IKnsFtuHqSF7TqBfr71aeyZDVJ0HpmqB7FBM2qEigL0iPONSCZSO9pE9dZTAxANe5XHG9Uy0YMv8cg=="],
"color": ["color@5.0.3", "", { "dependencies": { "color-convert": "^3.1.3", "color-string": "^2.1.3" } }, "sha512-ezmVcLR3xAVp8kYOm4GS45ZLLgIE6SPAFoduLr6hTDajwb3KZ2F46gulK3XpcwRFb5KKGCSezCBAY4Dw4HsyXA=="],
@@ -974,6 +985,8 @@
"debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" }, "peerDependencies": { "supports-color": "*" }, "optionalPeers": ["supports-color"] }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
+ "decamelize": ["decamelize@1.2.0", "", {}, "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA=="],
+
"decimal.js-light": ["decimal.js-light@2.5.1", "", {}, "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg=="],
"dedent": ["dedent@1.7.2", "", { "peerDependencies": { "babel-plugin-macros": "^3.1.0" }, "optionalPeers": ["babel-plugin-macros"] }, "sha512-WzMx3mW98SN+zn3hgemf4OzdmyNhhhKz5Ay0pUfQiMQ3e1g+xmTJWp/pKdwKVXhdSkAEGIIzqeuWrL3mV/AXbA=="],
@@ -1004,6 +1017,8 @@
"diff": ["diff@8.0.4", "", {}, "sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw=="],
+ "dijkstrajs": ["dijkstrajs@1.0.3", "", {}, "sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA=="],
+
"discord-api-types": ["discord-api-types@0.38.55", "", {}, "sha512-ytuaRTzdnHUCXJ6KjL9MrItQX0xKncBKEeYI1Bst4+ud47eejH3cG6gaesYakjpPcUhh68XYS2YwGq3mmujFsA=="],
"discord.js": ["discord.js@14.27.0", "", { "dependencies": { "@discordjs/builders": "^1.14.1", "@discordjs/collection": "1.5.3", "@discordjs/formatters": "^0.6.2", "@discordjs/rest": "^2.6.2", "@discordjs/util": "^1.2.0", "@discordjs/ws": "^1.2.3", "@sapphire/snowflake": "3.5.5", "discord-api-types": "^0.38.49", "fast-deep-equal": "3.1.3", "lodash.snakecase": "4.1.1", "magic-bytes.js": "^1.13.0", "tslib": "^2.6.3", "undici": "^6.27.0" } }, "sha512-qHbFlFG2N7y3LjPySYsL6A1+BnX6bkTVgo842EX0CqVPk/KTMwZkojPHEXKsQUpWZNyz5BISNHK1cPpQw0+m4A=="],
@@ -1188,6 +1203,8 @@
"gensync": ["gensync@1.0.0-beta.2", "", {}, "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="],
+ "get-caller-file": ["get-caller-file@2.0.5", "", {}, "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="],
+
"get-east-asian-width": ["get-east-asian-width@1.6.0", "", {}, "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA=="],
"get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="],
@@ -1292,6 +1309,8 @@
"is-finalizationregistry": ["is-finalizationregistry@1.1.1", "", { "dependencies": { "call-bound": "^1.0.3" } }, "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg=="],
+ "is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="],
+
"is-generator-function": ["is-generator-function@1.1.2", "", { "dependencies": { "call-bound": "^1.0.4", "generator-function": "^2.0.0", "get-proto": "^1.0.1", "has-tostringtag": "^1.0.2", "safe-regex-test": "^1.1.0" } }, "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA=="],
"is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="],
@@ -1568,6 +1587,8 @@
"pkg-up": ["pkg-up@3.1.0", "", { "dependencies": { "find-up": "^3.0.0" } }, "sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA=="],
+ "pngjs": ["pngjs@5.0.0", "", {}, "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw=="],
+
"possible-typed-array-names": ["possible-typed-array-names@1.1.0", "", {}, "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg=="],
"postcss": ["postcss@8.5.23", "", { "dependencies": { "nanoid": "^3.3.16", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-g50586zr4bZmwFiTlflMu8E0bDTb5I5gertgwAKmsdUlTQIhZtunzUlD1WSzwcVWPoAVpsrA6vlfCD7oXvRwgg=="],
@@ -1616,6 +1637,8 @@
"punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="],
+ "qrcode": ["qrcode@1.5.4", "", { "dependencies": { "dijkstrajs": "^1.0.1", "pngjs": "^5.0.0", "yargs": "^15.3.1" }, "bin": { "qrcode": "bin/qrcode" } }, "sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg=="],
+
"qs": ["qs@6.16.0", "", { "dependencies": { "es-define-property": "^1.0.1", "side-channel": "^1.1.1" } }, "sha512-h6fhOIaRrID2CbEY2fqs+7t+UXZo+MLAnU5gRIq85uFtdiUPCdsApMlHhXogKVM4HM2DVbIjGNTTYH2OcmP1vA=="],
"queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="],
@@ -1660,8 +1683,12 @@
"regexp.prototype.flags": ["regexp.prototype.flags@1.5.4", "", { "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-errors": "^1.3.0", "get-proto": "^1.0.1", "gopd": "^1.2.0", "set-function-name": "^2.0.2" } }, "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA=="],
+ "require-directory": ["require-directory@2.1.1", "", {}, "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="],
+
"require-from-string": ["require-from-string@2.0.2", "", {}, "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="],
+ "require-main-filename": ["require-main-filename@2.0.0", "", {}, "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg=="],
+
"reselect": ["reselect@5.1.1", "", {}, "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w=="],
"resolve": ["resolve@2.0.0-next.7", "", { "dependencies": { "es-errors": "^1.3.0", "is-core-module": "^2.16.2", "node-exports-info": "^1.6.0", "object-keys": "^1.1.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-tqt+NBWwyaMgw3zDsnygx4CByWjQEJHOPMdslYhppaQSJUtL/D4JO9CcBBlhPoI8lz9oJIDXkwXfhF4aWqP8xQ=="],
@@ -1706,6 +1733,8 @@
"server-only": ["server-only@0.0.1", "", {}, "sha512-qepMx2JxAa5jjfzxG79yPPq+8BuFToHd1hm7kI+Z4zAq1ftQiP7HcxMhDDItrbtwVeLg/cY2JnKnrcFkmiswNA=="],
+ "set-blocking": ["set-blocking@2.0.0", "", {}, "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw=="],
+
"set-cookie-parser": ["set-cookie-parser@3.1.2", "", {}, "sha512-5/r/lTwbJ3zQ+qwdUFZYeRNqda7P5HD8zQKqlSjdGt1/S0cjLAphHusj4Y58ahDtWn/g32xrIS58/ikOvwl0Lw=="],
"set-function-length": ["set-function-length@1.2.2", "", { "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", "function-bind": "^1.1.2", "get-intrinsic": "^1.2.4", "gopd": "^1.0.1", "has-property-descriptors": "^1.0.2" } }, "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg=="],
@@ -1894,20 +1923,30 @@
"which-collection": ["which-collection@1.0.2", "", { "dependencies": { "is-map": "^2.0.3", "is-set": "^2.0.3", "is-weakmap": "^2.0.2", "is-weakset": "^2.0.3" } }, "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw=="],
+ "which-module": ["which-module@2.0.1", "", {}, "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ=="],
+
"which-typed-array": ["which-typed-array@1.1.22", "", { "dependencies": { "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.9", "call-bound": "^1.0.4", "for-each": "^0.3.5", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-tostringtag": "^1.0.2" } }, "sha512-fvO4ExWMFsqyhG3AiPAObMuY1lxaqgYcxbc49CNdWDDECOJNgQyvsOWVwbZc+qf3rzRtxojBK+CMEv0Ld5CYpw=="],
"why-is-node-running": ["why-is-node-running@2.3.0", "", { "dependencies": { "siginfo": "^2.0.0", "stackback": "0.0.2" }, "bin": { "why-is-node-running": "cli.js" } }, "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w=="],
"word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="],
+ "wrap-ansi": ["wrap-ansi@6.2.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA=="],
+
"wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="],
"ws": ["ws@8.21.3", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-201TZ/kPWxoPr/OKWjquZR1SWKXcvxdH+e1xrx89b3YbmzLMFCLfnaG1HFIgWzJOEWZ7MvpK++odZufgYR50Rw=="],
"wsl-utils": ["wsl-utils@1.0.0", "", { "dependencies": { "is-wsl": "^3.1.0", "powershell-utils": "^0.1.0" } }, "sha512-Hl0ZOAs672vg+06kfujwRhoS6/jehvULrlFkuF2dRu6pHgA8U06h3xqNIqNNU1LTXPcedxByAR4GS6pwQK0mgA=="],
+ "y18n": ["y18n@4.0.3", "", {}, "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ=="],
+
"yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="],
+ "yargs": ["yargs@15.4.1", "", { "dependencies": { "cliui": "^6.0.0", "decamelize": "^1.2.0", "find-up": "^4.1.0", "get-caller-file": "^2.0.1", "require-directory": "^2.1.1", "require-main-filename": "^2.0.0", "set-blocking": "^2.0.0", "string-width": "^4.2.0", "which-module": "^2.0.0", "y18n": "^4.0.0", "yargs-parser": "^18.1.2" } }, "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A=="],
+
+ "yargs-parser": ["yargs-parser@18.1.3", "", { "dependencies": { "camelcase": "^5.0.0", "decamelize": "^1.2.0" } }, "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ=="],
+
"yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="],
"yocto-spinner": ["yocto-spinner@1.2.2", "", { "dependencies": { "yoctocolors": "^2.1.1" } }, "sha512-DODGl1wJjA/s5pnJFKau9lIYHT81lnhob1i3e1TjxZRxEhWRKl74nTbWE6H5KlkViQQTo/Z29YFdxzTZAMY3ng=="],
@@ -1998,6 +2037,10 @@
"body-parser/content-type": ["content-type@2.1.0", "", {}, "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag=="],
+ "cliui/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
+
+ "cliui/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
+
"conf/ajv": ["ajv@8.20.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA=="],
"conf/ajv-formats": ["ajv-formats@2.1.1", "", { "dependencies": { "ajv": "^8.0.0" } }, "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA=="],
@@ -2070,10 +2113,18 @@
"vite/postcss": ["postcss@8.5.26", "", { "dependencies": { "nanoid": "^3.3.17", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ=="],
+ "wrap-ansi/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
+
+ "wrap-ansi/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
+
"wsl-utils/is-wsl": ["is-wsl@3.1.1", "", { "dependencies": { "is-inside-container": "^1.0.0" } }, "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw=="],
"wsl-utils/powershell-utils": ["powershell-utils@0.1.0", "", {}, "sha512-dM0jVuXJPsDN6DvRpea484tCUaMiXWjuCn++HGTqUWzGDjv5tZkEZldAJ/UMlqRYGFrD/etByo4/xOuC/snX2A=="],
+ "yargs/find-up": ["find-up@4.1.0", "", { "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" } }, "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw=="],
+
+ "yargs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
+
"@dotenvx/dotenvx/execa/get-stream": ["get-stream@6.0.1", "", {}, "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg=="],
"@dotenvx/dotenvx/execa/human-signals": ["human-signals@2.1.0", "", {}, "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw=="],
@@ -2148,6 +2199,10 @@
"ansi-styles/color-convert/color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="],
+ "cliui/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
+
+ "cliui/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
+
"conf/ajv/json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="],
"enquirer/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
@@ -2238,6 +2293,16 @@
"vite/lightningcss/lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.33.0", "", { "os": "win32", "cpu": "x64" }, "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA=="],
+ "wrap-ansi/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
+
+ "wrap-ansi/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
+
+ "yargs/find-up/locate-path": ["locate-path@5.0.0", "", { "dependencies": { "p-locate": "^4.1.0" } }, "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g=="],
+
+ "yargs/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
+
+ "yargs/string-width/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
+
"@ts-morph/common/minimatch/brace-expansion/balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="],
"@typescript-eslint/typescript-estree/minimatch/brace-expansion/balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="],
@@ -2248,6 +2313,12 @@
"sanitize-html/htmlparser2/domutils/dom-serializer": ["dom-serializer@3.1.1", "", { "dependencies": { "domelementtype": "^3.0.0", "domhandler": "^6.0.0", "entities": "^8.0.0" } }, "sha512-4MEa38/QexBob6gFNwu+EGdWvhJ1OKuNwdYY3Y3NyeWDQfnGeDYQUDfIRzWu5B5gsv03so2Uxd28YC6zrsx3Lw=="],
+ "yargs/find-up/locate-path/p-locate": ["p-locate@4.1.0", "", { "dependencies": { "p-limit": "^2.2.0" } }, "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A=="],
+
+ "yargs/string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
+
"pkg-up/find-up/locate-path/p-locate/p-limit": ["p-limit@2.3.0", "", { "dependencies": { "p-try": "^2.0.0" } }, "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w=="],
+
+ "yargs/find-up/locate-path/p-locate/p-limit": ["p-limit@2.3.0", "", { "dependencies": { "p-try": "^2.0.0" } }, "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w=="],
}
}
diff --git a/components/admin/admin-header.tsx b/components/admin/admin-header.tsx
index 0270f01..8550242 100644
--- a/components/admin/admin-header.tsx
+++ b/components/admin/admin-header.tsx
@@ -9,6 +9,7 @@ import {
LayoutDashboardIcon,
BookOpenIcon,
ClipboardListIcon,
+ MessagesSquareIcon,
LogOutIcon,
PlusIcon,
UserPlusIcon,
@@ -33,6 +34,10 @@ export function AdminHeader() {
ไกด์เกนชินไม่ใช่เกมมือถือ
+ }>
+ Commission
+
(
{editor.name}
- {editor.sections.length ? ` · ${editor.sections.join(", ")}` : ""}
+ {editor.sections.length ? ` - ${editor.sections.join(", ")}` : ""}
))}
diff --git a/components/admin/guide-overview-form.tsx b/components/admin/guide-overview-form.tsx
index aab19e0..4d21c5d 100644
--- a/components/admin/guide-overview-form.tsx
+++ b/components/admin/guide-overview-form.tsx
@@ -414,7 +414,7 @@ export function GuideOverviewForm({
YouTube videos
- ไม่บังคับ รองรับ YouTube, youtu.be, Shorts และ Live · ลากเพื่อจัดลำดับ
+ ไม่บังคับ รองรับ YouTube, youtu.be, Shorts และ Live - ลากเพื่อจัดลำดับ
= 20} onClick={() => setYoutubeLinks((current) => [...current, { id: createVideoLinkId(), url: "" }])}>
@@ -472,7 +472,7 @@ export function GuideOverviewForm({
Bleeding layers
-
ใช้ภาพตัวละครเดียวกันด้วย mix-blend-mode · รายการบนสุดจะแสดงก่อน
+
ใช้ภาพตัวละครเดียวกันด้วย mix-blend-mode - รายการบนสุดจะแสดงก่อน
เพิ่ม effect
diff --git a/components/admin/structured-dashboard.tsx b/components/admin/structured-dashboard.tsx
index 9380234..f0a5fd4 100644
--- a/components/admin/structured-dashboard.tsx
+++ b/components/admin/structured-dashboard.tsx
@@ -233,7 +233,7 @@ export function StructuredDashboard({
{(presence?.editorsByGuide[guide.id] ?? []).map((editor) => (
{editor.name}
- {editor.sections.length ? ` · ${editor.sections.join(", ")}` : ""}
+ {editor.sections.length ? ` - ${editor.sections.join(", ")}` : ""}
))}
diff --git a/components/admin/ui/catalog-select.tsx b/components/admin/ui/catalog-select.tsx
index 3d14fca..a559fa0 100644
--- a/components/admin/ui/catalog-select.tsx
+++ b/components/admin/ui/catalog-select.tsx
@@ -57,6 +57,8 @@ type CatalogDropdownProps- = CommonSelectProps
void;
showSelectedImage?: boolean;
+ showClear?: boolean;
+ label?: string;
};
export function CatalogDropdown- ({
@@ -68,6 +70,8 @@ export function CatalogDropdown
- ({
invalid = false,
rarityBackground = false,
showSelectedImage = false,
+ showClear = true,
+ label,
searchNames = (item) => [item.name, ...(item.aliases ?? [])],
}: CatalogDropdownProps
- ) {
const selected = items.find((item) => item.key === value) ?? null;
@@ -97,7 +101,8 @@ export function CatalogDropdown
- ({
@@ -186,6 +191,7 @@ type DomainSelectProps- = CommonSelectProps
-
label?: string;
className?: string;
showSelectedImage?: boolean;
+ showClear?: boolean;
};
function DomainCatalogSelect
- ({
@@ -206,7 +212,7 @@ function DomainCatalogSelect
- ({
/>
);
}
- return
;
+ return ;
}
export function CharacterSelect- (props: DomainSelectProps
- ) {
diff --git a/components/auth/account-form.tsx b/components/auth/account-form.tsx
new file mode 100644
index 0000000..d585112
--- /dev/null
+++ b/components/auth/account-form.tsx
@@ -0,0 +1,73 @@
+"use client";
+
+import Link from "next/link";
+import { useRouter } from "next/navigation";
+import { useEffect, useRef, useState, type FormEvent } from "react";
+import { Alert, AlertDescription } from "@/components/ui/alert";
+import { Button } from "@/components/ui/button";
+import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
+import { Field, FieldGroup, FieldLabel } from "@/components/ui/field";
+import { Input } from "@/components/ui/input";
+import { authClient } from "@/lib/auth/client";
+
+export function AccountForm({ mode, siteKey, nextPath }: {
+ mode: "login" | "register"; siteKey: string; nextPath: string;
+}) {
+ const router = useRouter();
+ const register = mode === "register";
+ const [busy, setBusy] = useState(false);
+ const [error, setError] = useState("");
+ const [token, setToken] = useState("");
+ const container = useRef
(null);
+ const widget = useRef(null);
+ const captcha = process.env.NODE_ENV !== "development";
+ useEffect(() => {
+ if (!captcha) return;
+ const script = document.createElement("script");
+ script.src = "https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit";
+ script.async = true;
+ script.onload = () => {
+ if (container.current && window.turnstile && !widget.current) widget.current = window.turnstile.render(container.current, {
+ sitekey: siteKey, callback: (value: string) => setToken(value),
+ "expired-callback": () => setToken(""), "error-callback": () => setToken(""),
+ });
+ };
+ document.head.append(script);
+ return () => { script.remove(); };
+ }, [captcha, siteKey]);
+
+ async function submit(event: FormEvent) {
+ event.preventDefault();
+ if (captcha && !token) return;
+ setBusy(true); setError("");
+ const form = new FormData(event.currentTarget);
+ const email = String(form.get("email") ?? "").trim();
+ const password = String(form.get("password") ?? "");
+ const fetchOptions = { headers: captcha ? { "x-captcha-response": token } : undefined };
+ try {
+ const result = register
+ ? await authClient.signUp.email({ name: String(form.get("name") ?? "").trim(), email, password, fetchOptions })
+ : await authClient.signIn.email({ email, password, fetchOptions });
+ if (result.error) throw new Error(result.error.message ?? "เข้าสู่ระบบไม่สำเร็จ");
+ router.push(nextPath); router.refresh();
+ } catch (cause) {
+ setError(cause instanceof Error ? cause.message : "ลองอีกครั้ง");
+ setToken("");
+ if (widget.current) window.turnstile?.reset(widget.current);
+ } finally { setBusy(false); }
+ }
+
+ return {register ? "สมัครสมาชิก" : "เข้าสู่ระบบ"}
+
+ }>
+ {register ? "มีบัญชีแล้ว? เข้าสู่ระบบ" : "ยังไม่มีบัญชี? สมัครสมาชิก"}
+
+ {error && {error} }
+ ;
+}
diff --git a/components/auth/account-page.tsx b/components/auth/account-page.tsx
new file mode 100644
index 0000000..c0f2a70
--- /dev/null
+++ b/components/auth/account-page.tsx
@@ -0,0 +1,15 @@
+import { redirect } from "next/navigation";
+import { SiteHeader } from "@/components/public/site-header";
+import { getCustomerSession } from "@/lib/auth/server";
+import { safeAuthReturnPath } from "@/lib/auth/return-path";
+import { AccountForm } from "./account-form";
+
+export async function AccountPage({ mode, next }: { mode: "login" | "register"; next: unknown }) {
+ const nextPath = safeAuthReturnPath(next);
+ if (await getCustomerSession()) redirect(nextPath);
+ const siteKey = process.env.TURNSTILE_SITE_KEY ?? "";
+ if (process.env.NODE_ENV !== "development" && !siteKey) throw new Error("TURNSTILE_SITE_KEY is required");
+ return ;
+}
diff --git a/components/commission/live-refresh.tsx b/components/commission/live-refresh.tsx
new file mode 100644
index 0000000..c76f7b1
--- /dev/null
+++ b/components/commission/live-refresh.tsx
@@ -0,0 +1,16 @@
+"use client";
+
+import { useEffect } from "react";
+import { useRouter } from "next/navigation";
+
+export function CommissionLiveRefresh({ endpoint }: { endpoint: string }) {
+ const router = useRouter();
+ useEffect(() => {
+ const source = new EventSource(endpoint);
+ const refresh = () => router.refresh();
+ source.addEventListener("changed", refresh);
+ source.addEventListener("open", refresh);
+ return () => { source.removeEventListener("changed", refresh); source.removeEventListener("open", refresh); source.close(); };
+ }, [endpoint, router]);
+ return null;
+}
diff --git a/components/commission/payment-form.tsx b/components/commission/payment-form.tsx
new file mode 100644
index 0000000..44641cc
--- /dev/null
+++ b/components/commission/payment-form.tsx
@@ -0,0 +1,56 @@
+"use client";
+
+import { useState, type FormEvent } from "react";
+import { useRouter } from "next/navigation";
+import { Alert, AlertDescription } from "@/components/ui/alert";
+import { Button } from "@/components/ui/button";
+import { Field, FieldGroup, FieldLabel } from "@/components/ui/field";
+import { Input } from "@/components/ui/input";
+
+function verificationError(code: string | undefined): string {
+ const reason = code?.startsWith("slip-rejected:") ? code.slice("slip-rejected:".length)
+ : code?.startsWith("slip2go-service:") ? code.slice("slip2go-service:".length) : code;
+ switch (reason) {
+ case "200000": return "พบสลิป แต่ Slip2Go ยังไม่ได้ยืนยันเงื่อนไขผู้รับและยอดเงิน กรุณาแจ้งผู้ดูแลระบบ (200000)";
+ case "200401": return "บัญชีผู้รับในสลิปไม่ตรงกับบัญชีที่ตั้งไว้ (200401)";
+ case "200402": return "ยอดเงินในสลิปไม่ตรงกับยอดคำขอ (200402)";
+ case "200403": return "วันที่โอนในสลิปไม่ตรงเงื่อนไข (200403)";
+ case "200404": return "Slip2Go ไม่พบข้อมูลสลิปในระบบธนาคาร (200404)";
+ case "200500": return "Slip2Go ระบุว่าสลิปไม่ถูกต้อง (200500)";
+ case "200501": return "สลิปนี้เคยถูกตรวจสอบแล้ว หากชำระเงินแล้ว กรุณาติดต่อผู้ดูแลก่อนโอนซ้ำ (200501)";
+ case "200502": return "ธนาคารไม่สามารถตรวจสอบสลิปได้ในขณะนี้ กรุณาลองอีกครั้ง (200502)";
+ case "400001":
+ case "400002": return `อ่าน QR ในรูปสลิปไม่ได้ กรุณาอัปโหลดรูปสลิปจากแอปธนาคาร (${reason})`;
+ case "400400": return "ตั้งค่าเงื่อนไขตรวจสอบสลิปไม่ถูกต้อง กรุณาแจ้งผู้ดูแลระบบ (400400)";
+ case "401001": return "Slip2Go ไม่ยอมรับ API Secret ที่ตั้งไว้ กรุณาแจ้งผู้ดูแลระบบ (401001)";
+ case "401005": return "โทเคน Slip2Go หมด กรุณาแจ้งผู้ดูแลระบบ (401005)";
+ case "slip-amount-or-date-mismatch": return "ยอดเงินหรือวันเวลาในสลิปไม่ตรงกับคำขอนี้ กรุณาใช้สลิปจากการชำระเงินครั้งนี้";
+ case "payment-already-used": return "สลิปนี้ถูกใช้กับคำขออื่นแล้ว";
+ default: return reason && /^\d{6}$/.test(reason)
+ ? `ตรวจสอบสลิปไม่ผ่าน (รหัส ${reason}) กรุณาแจ้งผู้ดูแลระบบ`
+ : "ตรวจสอบสลิปไม่สำเร็จ กรุณาลองอีกครั้ง";
+ }
+}
+
+export function CommissionPaymentForm({ checkoutId }: { checkoutId: string }) {
+ const router = useRouter();
+ const [busy, setBusy] = useState(false);
+ const [error, setError] = useState("");
+ async function submit(event: FormEvent) {
+ event.preventDefault(); setBusy(true); setError("");
+ try {
+ const response = await fetch(`/api/commission/checkouts/${checkoutId}/verify`, {
+ method: "POST", body: new FormData(event.currentTarget),
+ });
+ const result = await response.json();
+ if (!response.ok) throw new Error(verificationError(result.error));
+ router.push(`/commission/tickets/${result.ticketId}`);
+ } catch (cause) { setError(cause instanceof Error ? cause.message : "เกิดข้อผิดพลาด"); }
+ finally { setBusy(false); }
+ }
+ return ;
+}
diff --git a/components/commission/request-form.tsx b/components/commission/request-form.tsx
new file mode 100644
index 0000000..169d1c4
--- /dev/null
+++ b/components/commission/request-form.tsx
@@ -0,0 +1,211 @@
+"use client";
+
+import { useState } from "react";
+import { useRouter } from "next/navigation";
+import { XIcon } from "lucide-react";
+import { ArtifactSelect, CharacterSelect, WeaponSelect } from "@/components/admin/ui/catalog-select";
+import { ConstellationSelect, RefinementSelect } from "@/components/admin/ui/option-select";
+import { Button } from "@/components/ui/button";
+import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
+import { Checkbox } from "@/components/ui/checkbox";
+import { FieldGroup } from "@/components/ui/field";
+import { Alert, AlertDescription } from "@/components/ui/alert";
+import { commissionPrice, commissionRequestSchema, type CommissionRequest } from "@/lib/commission/request";
+import { elementBackgroundColor } from "@/lib/catalog/element";
+
+type Item = { key: string; name: string; aliases: string[]; imageUrl: string; rarity?: number | null;
+ element?: string | null; weaponType?: string | null };
+type Catalog = { characters: Item[]; weapons: Item[]; artifacts: Item[] };
+const blankMember = (): CommissionRequest["teams"][number]["members"][number] => ({
+ characterKey: "", weaponKey: "", artifactKey: "", constellation: "0", refinement: "1",
+});
+const blankTeam = () => ({ members: Array.from({ length: 4 }, blankMember) });
+
+function requestError(request: CommissionRequest): string | null {
+ const parsed = commissionRequestSchema.safeParse(request);
+ if (parsed.success) return null;
+ const path = parsed.error.issues[0]?.path;
+ if (path?.[0] === "teams") {
+ const teamNumber = Number(path[1]) + 1;
+ const memberNumber = typeof path[3] === "number" ? path[3] + 1 : null;
+ if (memberNumber && path[4] === "characterKey") return `ทีม ${teamNumber}: เลือกตัวละครที่ ${memberNumber}`;
+ if (memberNumber && path[4] === "weaponKey") return `ทีม ${teamNumber}: เลือกอาวุธให้ตัวละครที่ ${memberNumber}`;
+ if (memberNumber && path[4] === "artifactKey") return `ทีม ${teamNumber}: เลือกอาร์ติแฟกต์ให้ตัวละครที่ ${memberNumber}`;
+ return `ทีม ${teamNumber}: เลือกตัวละคร 4 ตัวที่ไม่ซ้ำกัน พร้อมอาวุธและอาร์ติแฟกต์`;
+ }
+ if (path?.[0] === "weapons") return `ชุดเปรียบเทียบอาวุธ ${Number(path[1]) + 1}: เลือกตัวละครและอาวุธต่างกันอย่างน้อย 2 ชิ้น`;
+ if (path?.[0] === "constellations") return `ชุดเปรียบเทียบกลุ่มดาว ${Number(path[1]) + 1}: เลือกตัวละครและกลุ่มดาวอย่างน้อย 1 ระดับ`;
+ return "เพิ่มทีมหรือรายการเปรียบเทียบอย่างน้อยหนึ่งรายการ";
+}
+
+function checkoutError(code: string | undefined, status: number): string {
+ if (status === 401) return "เซสชันหมดอายุ กรุณาเข้าสู่ระบบอีกครั้ง";
+ if (status === 403) return "ไม่สามารถส่งคำขอจากหน้านี้ได้ กรุณาโหลดหน้าใหม่แล้วลองอีกครั้ง";
+ if (status === 429) return "ส่งคำขอบ่อยเกินไป กรุณารอสักครู่แล้วลองอีกครั้ง";
+ if (code === "invalid-promptpay-value" || code === "invalid-promptpay-type" ||
+ code === "COMMISSION_PROMPTPAY_VALUE-not-configured")
+ return "ระบบยังตั้งค่าบัญชี PromptPay ไม่ถูกต้อง กรุณาแจ้งผู้ดูแลระบบ";
+ if (code === "invalid-commission-request") return "รายการที่เลือกไม่ตรงกับข้อมูลล่าสุด กรุณาโหลดหน้าใหม่แล้วเลือกอีกครั้ง";
+ return "สร้างคำขอไม่สำเร็จ กรุณาลองอีกครั้ง";
+}
+
+export function CommissionRequestForm({ catalog }: { catalog: Catalog }) {
+ const router = useRouter();
+ const [request, setRequest] = useState({ teams: [blankTeam()], weapons: [], constellations: [] });
+ const [busy, setBusy] = useState(false);
+ const [error, setError] = useState("");
+ const patch = (next: Partial) => setRequest((old) => ({ ...old, ...next }));
+ const compatibleWeapons = (characterKey: string) => {
+ const type = catalog.characters.find((item) => item.key === characterKey)?.weaponType;
+ return catalog.weapons.filter((item) => type && item.weaponType === type);
+ };
+
+ async function submit() {
+ const validationError = requestError(request);
+ if (validationError) { setError(validationError); return; }
+ setBusy(true); setError("");
+ try {
+ const response = await fetch("/api/commission/checkouts", { method: "POST",
+ headers: { "Content-Type": "application/json" }, body: JSON.stringify(request) });
+ if (!response.ok) {
+ const body = await response.json().catch(() => null) as { error?: string } | null;
+ throw new Error(checkoutError(body?.error, response.status));
+ }
+ const { checkoutId } = await response.json();
+ router.push(`/commission/pay/${checkoutId}`);
+ } catch (cause) { setError(cause instanceof Error ? cause.message : "สร้างคำขอไม่สำเร็จ"); }
+ finally { setBusy(false); }
+ }
+
+ return
+
ทีม - ฿100 ต่อทีม เลือก 4 ตัวละคร พร้อมอาวุธและอาร์ติแฟกต์ของแต่ละตัว
+
+ {request.teams.map((team, teamIndex) => ทีม {teamIndex + 1}
+
+ {team.members.map((member, memberIndex) => {
+ const character = catalog.characters.find((item) => item.key === member.characterKey);
+ return
+
#{memberIndex + 1}
+
+
{
+ const teams = structuredClone(request.teams); teams[teamIndex].members[memberIndex] = { ...blankMember(), characterKey }; patch({ teams });
+ }} />
+
+
{
+ const teams = structuredClone(request.teams); teams[teamIndex].members[memberIndex].weaponKey = weaponKey;
+ teams[teamIndex].members[memberIndex].refinement = "1"; patch({ teams });
+ }} />
+ {
+ const teams = structuredClone(request.teams); teams[teamIndex].members[memberIndex].artifactKey = artifactKey; patch({ teams });
+ }} />
+
+
+
+ {
+ const teams = structuredClone(request.teams);
+ teams[teamIndex].members[memberIndex].constellation = constellation as typeof member.constellation;
+ patch({ teams });
+ }} triggerProps={{ size: "sm", className: "h-6 min-w-0 flex-1 justify-center px-1 text-xs [&>svg]:hidden" }} />
+ {
+ const teams = structuredClone(request.teams);
+ teams[teamIndex].members[memberIndex].refinement = refinement as typeof member.refinement;
+ patch({ teams });
+ }} triggerProps={{ size: "sm", className: "h-6 min-w-0 flex-1 justify-center px-1 text-xs [&>svg]:hidden" }} />
+
+
{character?.name ?? "เลือกตัวละคร"}
+
;
+ })}
+
+ patch({ teams: request.teams.filter((_, index) => index !== teamIndex) })}>ลบทีม
+ )}
+ patch({ teams: [...request.teams, blankTeam()] })}>เพิ่มทีม
+
+
เปรียบเทียบอาวุธ - ฿20 ต่ออาวุธ เลือกตัวละครและอาวุธอย่างน้อย 2 ชิ้น หากไม่มีทีมจะมีค่าบริการพื้นฐาน ฿100
+ {request.weapons.map((set, index) =>
+
+
+ {set.weaponKeys.map((weaponKey, weaponIndex) => {
+ return
+
+ !set.weaponKeys.includes(item.key) || item.key === weaponKey)}
+ value={weaponKey} label={`เลือกอาวุธเปรียบเทียบชิ้นที่ ${weaponIndex + 1}`}
+ placeholder={`อาวุธชิ้นที่ ${weaponIndex + 1}`} emptyLabel={set.characterKey ? "ไม่พบอาวุธ" : "เลือกตัวละครก่อน"}
+ onValueChange={(next) => {
+ const weapons = structuredClone(request.weapons); weapons[index].weaponKeys[weaponIndex] = next; patch({ weapons });
+ }} />
+
+
{
+ const weapons = structuredClone(request.weapons);
+ if (set.weaponKeys.length > 1) weapons[index].weaponKeys.splice(weaponIndex, 1);
+ else weapons[index].weaponKeys[weaponIndex] = "";
+ patch({ weapons });
+ }}>
+
;
+ })}
+
+
+
{
+ const weapons = structuredClone(request.weapons); weapons[index] = { characterKey, weaponKeys: [""] }; patch({ weapons });
+ }} />
+ {set.characterKey ? {
+ const weapons = structuredClone(request.weapons); weapons[index] = { characterKey: "", weaponKeys: [""] }; patch({ weapons });
+ }}> : null}
+ item.key === set.characterKey)?.name}>
+ {catalog.characters.find((item) => item.key === set.characterKey)?.name ?? "เลือกตัวละคร"}
+
+
+ = 100} onClick={() => {
+ const weapons = structuredClone(request.weapons); weapons[index].weaponKeys.push(""); patch({ weapons });
+ }}>เพิ่มอาวุธ
+ patch({ weapons: request.weapons.filter((_, i) => i !== index) })}>ลบชุดนี้
+ )}
+ patch({ weapons: [...request.weapons, { characterKey: "", weaponKeys: [""] }] })}>เพิ่มชุดอาวุธ
+
+
เปรียบเทียบกลุ่มดาว - ฿20 ต่อระดับ เลือกตัวละครและกลุ่มดาวอย่างน้อย 1 ระดับ หากไม่มีทีมจะมีค่าบริการพื้นฐาน ฿100
+ {request.constellations.map((set, index) =>
+
+
{Array.from({ length: 7 }, (_, level) =>
+ {
+ const constellations = structuredClone(request.constellations);
+ constellations[index].levels = checked ? [...set.levels, level].sort() : set.levels.filter((item) => item !== level);
+ patch({ constellations });
+ }} /> C{level}
+ )}
+
+
{
+ const constellations = structuredClone(request.constellations); constellations[index] = { characterKey, levels: [] }; patch({ constellations });
+ }} />
+ item.key === set.characterKey)?.name}>
+ {catalog.characters.find((item) => item.key === set.characterKey)?.name ?? "เลือกตัวละคร"}
+
+
+ patch({ constellations: request.constellations.filter((_, i) => i !== index) })}>ลบชุดนี้
+ )}
+ patch({ constellations: [...request.constellations, { characterKey: "", levels: [] }] })}>เพิ่มชุดกลุ่มดาว
+
+
รวม ฿{commissionPrice(request)}
+ {request.teams.length === 0 && request.weapons.length + request.constellations.length > 0 ?
+
รวมค่าบริการพื้นฐาน ฿100
: null}
+ {busy ? "กำลังสร้างคำขอ..." : "ยืนยันและชำระเงิน"}
+
+ {error &&
{error} }
+
;
+}
diff --git a/components/commission/request-summary.tsx b/components/commission/request-summary.tsx
new file mode 100644
index 0000000..5539416
--- /dev/null
+++ b/components/commission/request-summary.tsx
@@ -0,0 +1,95 @@
+import Image from "next/image";
+import { Badge } from "@/components/ui/badge";
+import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
+import { elementBackgroundColor } from "@/lib/catalog/element";
+import { rarityGradientClass } from "@/lib/catalog/rarity";
+import type { CommissionRequest } from "@/lib/commission/request";
+import { cn } from "@/lib/utils";
+
+type Item = { key: string; name: string; imageUrl: string; rarity?: number | null; element?: string | null };
+type Catalog = { characters: Item[]; weapons: Item[]; artifacts: Item[] };
+
+function ItemImage({ item, size = "small" }: { item: Item; size?: "small" | "large" }) {
+ return
+
+
;
+}
+
+function CommissionTeamCard({ team, index, catalog }: { team: CommissionRequest["teams"][number]; index: number; catalog: Catalog }) {
+ const characters = new Map(catalog.characters.map((item) => [item.key, item]));
+ const weapons = new Map(catalog.weapons.map((item) => [item.key, item]));
+ const artifacts = new Map(catalog.artifacts.map((item) => [item.key, item]));
+ return
+
+ ทีม {index + 1} ฿100
+
+
+ {team.members.map((member, memberIndex) => {
+ const character = characters.get(member.characterKey);
+ const weapon = weapons.get(member.weaponKey);
+ const artifact = artifacts.get(member.artifactKey);
+ return
+
+ {character ?
:
}
+
+ {weapon ?
:
}
+ {artifact ?
:
}
+
+
+
+ C{member.constellation ?? "0"}
+ R{member.refinement ?? "1"}
+
+
{character?.name ?? member.characterKey}
+
;
+ })}
+
+ ;
+}
+
+export function CommissionRequestSummary({ request, catalog, amountBaht }: {
+ request: CommissionRequest; catalog: Catalog; amountBaht: number;
+}) {
+ const characters = new Map(catalog.characters.map((item) => [item.key, item]));
+ const weapons = new Map(catalog.weapons.map((item) => [item.key, item]));
+ const comparisonTotal = request.weapons.reduce((sum, set) => sum + set.weaponKeys.length * 20, 0) +
+ request.constellations.reduce((sum, set) => sum + set.levels.length * 20, 0);
+
+ return
+ {request.teams.length === 0 && amountBaht > comparisonTotal ?
+
+ ค่าบริการพื้นฐาน ฿100
+ : null}
+ {request.teams.map((team, index) =>
)}
+ {request.weapons.map((set, index) => {
+ const character = characters.get(set.characterKey);
+ return
+
+ เปรียบเทียบอาวุธ {index + 1} ฿{set.weaponKeys.length * 20}
+
+
+
+ {set.weaponKeys.map((key) => {
+ const weapon = weapons.get(key);
+ return weapon ?
+ :
{key}
;
+ })}
+
+ {character ? : null}
+
+ ;
+ })}
+ {request.constellations.map((set, index) => {
+ const character = characters.get(set.characterKey);
+ return
+
+ เปรียบเทียบกลุ่มดาว {index + 1} ฿{set.levels.length * 20}
+
+
+ {set.levels.map((level) => C{level} )}
+ {character ? : null}
+
+ ;
+ })}
+
;
+}
diff --git a/components/commission/sign-out.tsx b/components/commission/sign-out.tsx
new file mode 100644
index 0000000..4c3f6ae
--- /dev/null
+++ b/components/commission/sign-out.tsx
@@ -0,0 +1,15 @@
+"use client";
+
+import { useRouter } from "next/navigation";
+import { LogOutIcon } from "lucide-react";
+import { Button } from "@/components/ui/button";
+import { authClient } from "@/lib/auth/client";
+
+export function CommissionSignOut() {
+ const router = useRouter();
+ return {
+ await authClient.signOut();
+ router.push("/commission");
+ router.refresh();
+ }}> ;
+}
diff --git a/components/commission/slip-dialog.tsx b/components/commission/slip-dialog.tsx
new file mode 100644
index 0000000..36e76c6
--- /dev/null
+++ b/components/commission/slip-dialog.tsx
@@ -0,0 +1,25 @@
+"use client";
+
+import { useState } from "react";
+import { Button } from "@/components/ui/button";
+import { Dialog, DialogContent, DialogTitle, DialogTrigger } from "@/components/ui/dialog";
+import { Skeleton } from "@/components/ui/skeleton";
+
+export function CommissionSlipDialog({ ticketId }: { ticketId: string }) {
+ const [imageState, setImageState] = useState<"loading" | "loaded" | "error">("loading");
+
+ return
+ }>
+ ดูสลิป
+
+
+ สลิปการชำระเงิน
+ {imageState === "loading" && }
+ {imageState === "error" && โหลดสลิปไม่สำเร็จ
}
+ {/* eslint-disable-next-line @next/next/no-img-element */}
+ setImageState("loaded")} onError={() => setImageState("error")}
+ className={imageState === "loaded" ? "max-h-[calc(90svh-7rem)] w-full object-contain" : "hidden"} />
+
+ ;
+}
diff --git a/components/commission/ticket-chat.tsx b/components/commission/ticket-chat.tsx
new file mode 100644
index 0000000..be1d75a
--- /dev/null
+++ b/components/commission/ticket-chat.tsx
@@ -0,0 +1,423 @@
+"use client";
+
+import { useEffect, useRef, useState, type ClipboardEvent, type FormEvent, type KeyboardEvent } from "react";
+import { useRouter } from "next/navigation";
+import { ImagePlus, MessageCircle, SendHorizontal, SmilePlus, Trash2, X } from "lucide-react";
+import { Avatar, AvatarFallback } from "@/components/ui/avatar";
+import { Alert, AlertDescription } from "@/components/ui/alert";
+import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription,
+ AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger } from "@/components/ui/alert-dialog";
+import { Badge } from "@/components/ui/badge";
+import { Bubble, BubbleContent } from "@/components/ui/bubble";
+import { Button } from "@/components/ui/button";
+import { Card, CardAction, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card";
+import { Dialog, DialogContent, DialogTitle, DialogTrigger } from "@/components/ui/dialog";
+import { Empty, EmptyDescription, EmptyHeader, EmptyMedia, EmptyTitle } from "@/components/ui/empty";
+import { Field, FieldGroup, FieldLabel } from "@/components/ui/field";
+import { InputGroup, InputGroupAddon, InputGroupButton, InputGroupTextarea } from "@/components/ui/input-group";
+import { Message, MessageAvatar, MessageContent, MessageFooter, MessageGroup, MessageHeader } from "@/components/ui/message";
+import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
+import { Skeleton } from "@/components/ui/skeleton";
+import { cn } from "@/lib/utils";
+
+type ChatMessage = { id: string; authorId: string; authorName: string; text: string | null;
+ imageObjectKey: string | null; createdAt: Date; reactions: { userId: string; emoji: string }[] };
+type OptimisticMessage = { localId: string; serverId: string | null; text: string; imageUrl: string | null;
+ status: "sending" | "sent" };
+type OptimisticReaction = { active: boolean; status: "sending" | "sent" };
+const emojiChoices = ["👍", "❤️", "😂", "😮", "😢", "🎉"];
+const reactionKey = (messageId: string, emoji: string) => `${messageId}:${emoji}`;
+
+function CommissionMessageImage({ src }: { src: string }) {
+ const imageRef = useRef(null);
+ const [state, setState] = useState<"loading" | "loaded" | "error">("loading");
+
+ useEffect(() => {
+ if (imageRef.current?.complete)
+ setState(imageRef.current.naturalWidth > 0 ? "loaded" : "error");
+ }, [src]);
+
+ return
+ }>
+ {state === "loading" && }
+ {state === "error" && โหลดรูปภาพไม่สำเร็จ }
+ {/* eslint-disable-next-line @next/next/no-img-element */}
+ setState("loaded")} onError={() => setState("error")}
+ className={cn("max-h-72 max-w-full object-contain", state === "loaded" ? "block" : "hidden")} />
+
+
+ รูปภาพที่แนบในข้อความ
+ {/* eslint-disable-next-line @next/next/no-img-element */}
+
+
+ ;
+}
+
+export function CommissionTicketChat({ ticketId, userId, status, messages, admin }: {
+ ticketId: string; userId: string; status: string; messages: ChatMessage[]; admin: boolean;
+}) {
+ const router = useRouter();
+ const scrollRef = useRef(null);
+ const fileRef = useRef(null);
+ const stayAtBottom = useRef(true);
+ const [busy, setBusy] = useState(false);
+ const [error, setError] = useState("");
+ const [text, setText] = useState("");
+ const [image, setImage] = useState(null);
+ const [imagePreview, setImagePreview] = useState(null);
+ const imagePreviewRef = useRef(null);
+ const optimisticImageUrls = useRef(new Set());
+ const [optimisticMessages, setOptimisticMessages] = useState([]);
+ const nextOptimisticId = useRef(0);
+ const [reactionFor, setReactionFor] = useState(null);
+ const [optimisticReactions, setOptimisticReactions] = useState>({});
+ const pendingReactions = useRef(new Set());
+ const [deleteFor, setDeleteFor] = useState(null);
+ const [deletingId, setDeletingId] = useState(null);
+ const [deletedMessageIds, setDeletedMessageIds] = useState>(() => new Set());
+ const [typing, setTyping] = useState<{ userId: string; name: string } | null>(null);
+ const lastTypingSent = useRef(0);
+ const typingActive = useRef(false);
+ const sendingRef = useRef(false);
+ const deferredRefresh = useRef(false);
+ const visibleOptimisticMessages = optimisticMessages.filter((message) => !message.serverId ||
+ !messages.some((saved) => saved.id === message.serverId));
+ const visibleMessages = messages.filter((message) => !deletedMessageIds.has(message.id));
+
+ useEffect(() => () => {
+ if (imagePreviewRef.current) URL.revokeObjectURL(imagePreviewRef.current);
+ for (const url of optimisticImageUrls.current) URL.revokeObjectURL(url);
+ }, []);
+
+ useEffect(() => {
+ for (const message of optimisticMessages) {
+ if (message.serverId && messages.some((saved) => saved.id === message.serverId) &&
+ message.imageUrl && optimisticImageUrls.current.has(message.imageUrl)) {
+ URL.revokeObjectURL(message.imageUrl);
+ optimisticImageUrls.current.delete(message.imageUrl);
+ }
+ }
+ }, [messages, optimisticMessages]);
+
+ useEffect(() => {
+ const source = new EventSource(`/api/commission/events?ticketId=${ticketId}`);
+ let typingTimeout: ReturnType | undefined;
+ const refresh = () => {
+ if (sendingRef.current) deferredRefresh.current = true;
+ else router.refresh();
+ };
+ const receive = (event: Event) => {
+ const value = (event as MessageEvent).data;
+ if (!value.startsWith("typing:")) { refresh(); return; }
+ try {
+ const update = JSON.parse(value.slice(7)) as { userId: string; name: string; active: boolean };
+ if (update.userId === userId || typeof update.name !== "string" || typeof update.active !== "boolean") return;
+ if (typingTimeout) clearTimeout(typingTimeout);
+ if (update.active) {
+ setTyping({ userId: update.userId, name: update.name });
+ typingTimeout = setTimeout(() => setTyping(null), 6000);
+ } else setTyping((current) => current?.userId === update.userId ? null : current);
+ } catch { /* Ignore malformed transient signals. */ }
+ };
+ source.addEventListener("changed", receive);
+ source.addEventListener("open", refresh);
+ return () => {
+ if (typingTimeout) clearTimeout(typingTimeout);
+ source.removeEventListener("changed", receive);
+ source.removeEventListener("open", refresh);
+ source.close();
+ };
+ }, [ticketId, userId, router]);
+
+ useEffect(() => {
+ const viewport = scrollRef.current;
+ if (viewport && stayAtBottom.current) viewport.scrollTop = viewport.scrollHeight;
+ }, [messages, optimisticMessages]);
+
+ function sendTyping(active: boolean) {
+ if (status !== "open") return;
+ const now = Date.now();
+ if (active && typingActive.current && now - lastTypingSent.current < 4000) return;
+ if (!active && !typingActive.current) return;
+ typingActive.current = active;
+ if (active) lastTypingSent.current = now;
+ void fetch(`/api/commission/tickets/${ticketId}/typing`, {
+ method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ active }),
+ }).catch(() => undefined);
+ }
+
+ function setAttachment(file: File | null) {
+ if (imagePreviewRef.current) URL.revokeObjectURL(imagePreviewRef.current);
+ const preview = file ? URL.createObjectURL(file) : null;
+ imagePreviewRef.current = preview;
+ setImage(file);
+ setImagePreview(preview);
+ }
+
+ function chooseAttachment(file: File | null) {
+ if (file && (file.size > 6 * 1024 * 1024 ||
+ !["image/png", "image/jpeg", "image/webp"].includes(file.type))) {
+ setError("แนบได้เฉพาะรูป PNG, JPEG หรือ WebP ขนาดไม่เกิน 6 MB");
+ return;
+ }
+ setError("");
+ setAttachment(file);
+ }
+
+ function handleComposerPaste(event: ClipboardEvent) {
+ const pastedImage = Array.from(event.clipboardData.items)
+ .find((item) => item.kind === "file" && item.type.startsWith("image/"))?.getAsFile();
+ if (!pastedImage) return;
+ event.preventDefault();
+ if (fileRef.current) fileRef.current.value = "";
+ chooseAttachment(pastedImage);
+ }
+
+ async function send(event: FormEvent) {
+ event.preventDefault();
+ if (busy || (!text.trim() && !image)) return;
+ setBusy(true); setError("");
+ sendingRef.current = true;
+ const form = event.currentTarget;
+ const originalText = text;
+ const originalImage = image;
+ const localId = String(++nextOptimisticId.current);
+ const imageUrl = image ? URL.createObjectURL(image) : null;
+ if (imageUrl) optimisticImageUrls.current.add(imageUrl);
+ setOptimisticMessages((current) => [...current.filter((message) => !message.serverId ||
+ !messages.some((saved) => saved.id === message.serverId)), {
+ localId, serverId: null, text: text.trim(), imageUrl, status: "sending",
+ }]);
+ stayAtBottom.current = true;
+ try {
+ const data = new FormData(form);
+ data.set("text", text.trim());
+ if (image) data.set("image", image);
+ else data.delete("image");
+ form.reset(); setText(""); setAttachment(null); sendTyping(false);
+ const response = await fetch(`/api/commission/tickets/${ticketId}/messages`, { method: "POST", body: data });
+ if (!response.ok) throw new Error("ส่งข้อความไม่สำเร็จ กรุณาลองอีกครั้ง");
+ const result = await response.json() as { messageId: string };
+ setOptimisticMessages((current) => current.map((message) => message.localId === localId ?
+ { ...message, serverId: result.messageId, status: "sent" } : message));
+ stayAtBottom.current = true;
+ sendingRef.current = false; deferredRefresh.current = false; router.refresh();
+ } catch (cause) {
+ if (imageUrl) { URL.revokeObjectURL(imageUrl); optimisticImageUrls.current.delete(imageUrl); }
+ setOptimisticMessages((current) => current.filter((message) => message.localId !== localId));
+ setText(originalText); setAttachment(originalImage);
+ setError(cause instanceof Error ? cause.message : "ส่งข้อความไม่สำเร็จ");
+ sendingRef.current = false;
+ if (deferredRefresh.current) { deferredRefresh.current = false; router.refresh(); }
+ }
+ finally { setBusy(false); }
+ }
+
+ async function react(messageId: string, emoji: string) {
+ setReactionFor(null);
+ const key = reactionKey(messageId, emoji);
+ if (pendingReactions.current.has(key)) return;
+ const message = messages.find((item) => item.id === messageId);
+ if (!message) return;
+ const savedActive = message.reactions.some((item) => item.emoji === emoji && item.userId === userId);
+ const previous = optimisticReactions[key];
+ const currentActive = previous?.status === "sent" && previous.active === savedActive
+ ? savedActive : previous?.active ?? savedActive;
+ pendingReactions.current.add(key);
+ setError("");
+ setOptimisticReactions((current) => ({ ...current, [key]: { active: !currentActive, status: "sending" } }));
+ try {
+ const response = await fetch(`/api/commission/tickets/${ticketId}/reactions`, { method: "POST",
+ headers: { "Content-Type": "application/json" }, body: JSON.stringify({ messageId, emoji }) });
+ if (!response.ok) throw new Error();
+ const result = await response.json() as { active: boolean };
+ setOptimisticReactions((current) => ({ ...current, [key]: { active: result.active, status: "sent" } }));
+ router.refresh();
+ } catch {
+ setOptimisticReactions((current) => {
+ const next = { ...current };
+ delete next[key];
+ return next;
+ });
+ setError("เปลี่ยนรีแอ็กชันไม่สำเร็จ กรุณาลองอีกครั้ง");
+ } finally { pendingReactions.current.delete(key); }
+ }
+
+ async function deleteMessage(messageId: string) {
+ if (deletingId) return;
+ setDeleteFor(null); setDeletingId(messageId); setError("");
+ try {
+ const response = await fetch(`/api/commission/tickets/${ticketId}/messages/${messageId}`, { method: "DELETE" });
+ if (!response.ok) throw new Error();
+ setDeletedMessageIds((current) => new Set(current).add(messageId));
+ setReactionFor(null);
+ router.refresh();
+ } catch { setError("ลบข้อความไม่สำเร็จ กรุณาลองอีกครั้ง"); }
+ finally { setDeletingId(null); }
+ }
+
+ async function toggleStatus() {
+ try {
+ const response = await fetch(`/api/commission/tickets/${ticketId}/status`, { method: "PATCH",
+ headers: { "Content-Type": "application/json" }, body: JSON.stringify({ status: status === "open" ? "closed" : "open" }) });
+ if (!response.ok) throw new Error();
+ router.refresh();
+ } catch { setError("เปลี่ยนสถานะ Ticket ไม่สำเร็จ กรุณาลองอีกครั้ง"); }
+ }
+
+ function handleComposerKeyDown(event: KeyboardEvent) {
+ if (event.key === "Enter" && !event.shiftKey && !event.nativeEvent.isComposing) {
+ event.preventDefault();
+ event.currentTarget.form?.requestSubmit();
+ }
+ }
+
+ return
+
+ แชต{admin ? "กับลูกค้า" : "กับทีมงาน"}
+ พูดคุยและส่งรูปภาพเกี่ยวกับคำขอนี้
+
+ {status === "open" ? "เปิดอยู่" : "ปิดแล้ว"}
+ {admin &&
+ {status === "open" ? "ปิด Ticket" : "เปิดอีกครั้ง"}
+ }
+
+
+ { const viewport = event.currentTarget;
+ stayAtBottom.current = viewport.scrollHeight - viewport.scrollTop - viewport.clientHeight < 80; }}
+ className="min-h-0 flex-1 overflow-x-hidden overflow-y-auto overscroll-y-contain bg-muted/20 px-3 py-5 sm:px-5">
+ {visibleMessages.length === 0 && visibleOptimisticMessages.length === 0 ?
+
+ เริ่มบทสนทนา
+ ส่งข้อความหรือรูปภาพเพื่อพูดคุยเกี่ยวกับคำขอนี้
+
+ :
+ {visibleMessages.map((message) => {
+ const mine = message.authorId === userId;
+ const sentAt = new Date(message.createdAt);
+ const counts = emojiChoices.map((emoji) => {
+ const savedCount = message.reactions.filter((reaction) => reaction.emoji === emoji).length;
+ const savedActive = message.reactions.some((reaction) => reaction.emoji === emoji && reaction.userId === userId);
+ const update = optimisticReactions[reactionKey(message.id, emoji)];
+ const visibleUpdate = update && (update.status === "sending" || update.active !== savedActive) ? update : null;
+ const active = visibleUpdate?.active ?? savedActive;
+ const count = savedCount + (visibleUpdate && active !== savedActive ? active ? 1 : -1 : 0);
+ return { emoji, count, active, pending: update?.status === "sending" };
+ }).filter(({ count }) => count > 0);
+ return
+ {!mine &&
+ {message.authorName.trim().charAt(0).toUpperCase() || "?"}
+ }
+
+ {mine ? "คุณ" : message.authorName}
+
+
+ {message.text && {message.text}
}
+ {message.imageObjectKey && }
+
+
+ {counts.length > 0 &&
+ {counts.map(({ emoji, count, active, pending }) => react(message.id, emoji)}>
+ {emoji} {count}
+ )}
+
}
+
+
+ {sentAt.toLocaleTimeString("th-TH", { hour: "2-digit", minute: "2-digit" })}
+
+ {status === "open" && setReactionFor(open ? message.id : null)}>
+ }>
+
+
+
+
+ {emojiChoices.map((emoji) => react(message.id, emoji)}>{emoji} )}
+
+
+ }
+ {mine && setDeleteFor(open ? message.id : null)}>
+ }>
+
+
+ ลบข้อความนี้?
+ ข้อความและรูปภาพที่แนบจะถูกลบถาวร
+
+
+ ยกเลิก
+ void deleteMessage(message.id)}>ลบข้อความ
+
+
+ }
+
+
+ ;
+ })}
+ {visibleOptimisticMessages.map((message) =>
+
+ คุณ
+
+
+ {message.text && {message.text}
}
+ {message.imageUrl &&
+
+
}
+
+
+ {message.status === "sending" ? "กำลังส่ง..." : "ส่งแล้ว"}
+
+ )}
+ }
+
+
+ {typing && status === "open" && {typing.name} typing...
}
+ {status === "open" ? : Ticket นี้ปิดแล้ว หากต้องการพูดคุยต่อ ให้ผู้ดูแลเปิด Ticket อีกครั้ง
}
+ {error && {error} }
+
+ ;
+}
diff --git a/components/commission/ticket-detail.tsx b/components/commission/ticket-detail.tsx
new file mode 100644
index 0000000..cca9bc8
--- /dev/null
+++ b/components/commission/ticket-detail.tsx
@@ -0,0 +1,39 @@
+import { notFound } from "next/navigation";
+import { getCommissionLabels } from "@/lib/commission/catalog";
+import { getCommissionTicket } from "@/lib/commission/tickets";
+import { HttpError } from "@/lib/security/http";
+import { CommissionRequestSummary } from "./request-summary";
+import { CommissionTicketChat } from "./ticket-chat";
+import { CommissionTicketRename } from "./ticket-rename";
+import { CommissionSlipDialog } from "./slip-dialog";
+import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
+
+export async function CommissionTicketDetail({ id, admin }: { id: string; admin: boolean }) {
+ let data: Awaited>;
+ try {
+ data = await getCommissionTicket(id);
+ } catch (cause) {
+ if (cause instanceof HttpError && (cause.status === 401 || cause.status === 403 || cause.status === 404))
+ notFound();
+ throw cause;
+ }
+ const catalog = await getCommissionLabels(data.checkout.request);
+ return
+
+
+
คำขอที่ชำระเงินแล้ว - ฿{data.checkout.amountBaht}
+ {catalog && }
+ การชำระเงิน: {data.payment.transRef} - {data.payment.transferredAt.toLocaleString("th-TH")}
+
+
+
+
+
;
+}
diff --git a/components/commission/ticket-rename.tsx b/components/commission/ticket-rename.tsx
new file mode 100644
index 0000000..7ebcf5a
--- /dev/null
+++ b/components/commission/ticket-rename.tsx
@@ -0,0 +1,62 @@
+"use client";
+
+import { useState, type FormEvent } from "react";
+import { useRouter } from "next/navigation";
+import { Pencil } from "lucide-react";
+import { Button } from "@/components/ui/button";
+import { Dialog, DialogContent, DialogDescription, DialogTitle, DialogTrigger } from "@/components/ui/dialog";
+import { Input } from "@/components/ui/input";
+import { Label } from "@/components/ui/label";
+
+export function CommissionTicketRename({ ticketId, title }: { ticketId: string; title: string | null }) {
+ const router = useRouter();
+ const [open, setOpen] = useState(false);
+ const [value, setValue] = useState(title ?? "");
+ const [busy, setBusy] = useState(false);
+ const [error, setError] = useState("");
+
+ function changeOpen(next: boolean) {
+ if (busy) return;
+ setOpen(next);
+ if (next) { setValue(title ?? ""); setError(""); }
+ }
+
+ async function save(event: FormEvent) {
+ event.preventDefault();
+ if (busy) return;
+ setBusy(true); setError("");
+ try {
+ const response = await fetch(`/api/commission/tickets/${ticketId}/title`, {
+ method: "PATCH", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ title: value }),
+ });
+ if (!response.ok) throw new Error();
+ setOpen(false);
+ router.refresh();
+ } catch { setError("เปลี่ยนชื่อ Ticket ไม่สำเร็จ กรุณาลองอีกครั้ง"); }
+ finally { setBusy(false); }
+ }
+
+ return
+ }>
+ เปลี่ยนชื่อ
+
+
+
+
+ ;
+}
diff --git a/components/public/site-header.tsx b/components/public/site-header.tsx
index 2518c46..f627d86 100644
--- a/components/public/site-header.tsx
+++ b/components/public/site-header.tsx
@@ -33,6 +33,10 @@ export function SiteHeader({ editHref = "/admin", guideId }: { editHref?: string
ไกด์เกนชินไม่ใช่เกมมือถือ
+ }>
+
+ Commission
+
) {
+ return (
+
+ )
+}
+
+const bubbleVariants = cva(
+ "group/bubble relative flex w-fit max-w-[80%] min-w-0 flex-col gap-1 group-data-[align=end]/message:self-end data-[align=end]:self-end data-[variant=ghost]:max-w-full",
+ {
+ variants: {
+ variant: {
+ default:
+ "*:data-[slot=bubble-content]:bg-primary *:data-[slot=bubble-content]:text-primary-foreground [&>[data-slot=bubble-content]:is(button,a):hover]:bg-primary/80",
+ secondary:
+ "*:data-[slot=bubble-content]:bg-secondary *:data-[slot=bubble-content]:text-secondary-foreground [&>[data-slot=bubble-content]:is(button,a):hover]:bg-[color-mix(in_oklch,var(--secondary),var(--foreground)_5%)]",
+ muted:
+ "*:data-[slot=bubble-content]:bg-muted [&>[data-slot=bubble-content]:is(button,a):hover]:bg-[color-mix(in_oklch,var(--muted),var(--foreground)_5%)]",
+ tinted:
+ "*:data-[slot=bubble-content]:bg-[oklch(from_var(--primary)_0.93_calc(c*0.4)_h)] *:data-[slot=bubble-content]:text-foreground dark:*:data-[slot=bubble-content]:bg-[oklch(from_var(--primary)_0.3_calc(c*0.4)_h)] [&>[data-slot=bubble-content]:is(button,a):hover]:bg-[oklch(from_var(--primary)_0.88_calc(c*0.5)_h)] dark:[&>[data-slot=bubble-content]:is(button,a):hover]:bg-[oklch(from_var(--primary)_0.35_calc(c*0.5)_h)]",
+ outline:
+ "*:data-[slot=bubble-content]:border-border *:data-[slot=bubble-content]:bg-background [&>[data-slot=bubble-content]:is(button,a):hover]:bg-muted [&>[data-slot=bubble-content]:is(button,a):hover]:text-foreground dark:[&>[data-slot=bubble-content]:is(button,a):hover]:bg-input/30",
+ ghost:
+ "border-none *:data-[slot=bubble-content]:rounded-none *:data-[slot=bubble-content]:bg-transparent *:data-[slot=bubble-content]:p-0 [&>[data-slot=bubble-content]:is(button,a):hover]:bg-muted [&>[data-slot=bubble-content]:is(button,a):hover]:text-foreground dark:[&>[data-slot=bubble-content]:is(button,a):hover]:bg-muted/50",
+ destructive:
+ "*:data-[slot=bubble-content]:bg-destructive/10 *:data-[slot=bubble-content]:text-destructive dark:*:data-[slot=bubble-content]:bg-destructive/20 [&>[data-slot=bubble-content]:is(button,a):hover]:bg-destructive/20 dark:[&>[data-slot=bubble-content]:is(button,a):hover]:bg-destructive/30",
+ },
+ },
+ defaultVariants: {
+ variant: "default",
+ },
+ }
+)
+
+function Bubble({
+ variant = "default",
+ align = "start",
+ className,
+ ...props
+}: React.ComponentProps<"div"> &
+ VariantProps & {
+ align?: "start" | "end"
+ }) {
+ return (
+
+ )
+}
+
+function BubbleContent({
+ className,
+ render,
+ ...props
+}: useRender.ComponentProps<"div">) {
+ return useRender({
+ defaultTagName: "div",
+ props: mergeProps<"div">(
+ {
+ className: cn(
+ "w-fit max-w-full min-w-0 overflow-hidden rounded-xl border border-transparent px-3 py-2 text-sm leading-relaxed wrap-break-word group-data-[align=end]/bubble:self-end [button]:text-left [button,a]:transition-colors [button,a]:outline-none [button,a]:focus-visible:border-ring [button,a]:focus-visible:ring-3 [button,a]:focus-visible:ring-ring/50",
+ className
+ ),
+ },
+ props
+ ),
+ render,
+ state: {
+ slot: "bubble-content",
+ },
+ })
+}
+
+const bubbleReactionsVariants = cva(
+ "absolute z-10 flex w-fit shrink-0 items-center justify-center gap-1 rounded-full bg-muted px-1.5 py-0.5 text-sm ring-3 ring-card has-[button]:p-0",
+ {
+ variants: {
+ side: {
+ top: "top-0 -translate-y-3/4",
+ bottom: "bottom-0 translate-y-3/4",
+ },
+ align: {
+ start: "left-3",
+ end: "right-3",
+ },
+ },
+ defaultVariants: {
+ side: "bottom",
+ align: "end",
+ },
+ }
+)
+
+function BubbleReactions({
+ side = "bottom",
+ align = "end",
+ className,
+ ...props
+}: React.ComponentProps<"div"> & {
+ align?: "start" | "end"
+ side?: "top" | "bottom"
+}) {
+ return (
+
+ )
+}
+
+export { BubbleGroup, Bubble, BubbleContent, BubbleReactions }
diff --git a/components/ui/message.tsx b/components/ui/message.tsx
new file mode 100644
index 0000000..c308b84
--- /dev/null
+++ b/components/ui/message.tsx
@@ -0,0 +1,91 @@
+import * as React from "react"
+import { cn } from "@/lib/utils"
+
+function MessageGroup({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+function Message({
+ className,
+ align = "start",
+ ...props
+}: React.ComponentProps<"div"> & { align?: "start" | "end" }) {
+ return (
+
+ )
+}
+
+function MessageAvatar({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+function MessageContent({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+function MessageHeader({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+function MessageFooter({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+export {
+ MessageGroup,
+ Message,
+ MessageAvatar,
+ MessageContent,
+ MessageFooter,
+ MessageHeader,
+}
diff --git a/db/schema.ts b/db/schema.ts
index f204bff..da36fb7 100644
--- a/db/schema.ts
+++ b/db/schema.ts
@@ -21,6 +21,7 @@ import type {
CatalogTalent,
} from "@/lib/catalog/lunaris";
import type { ExtraSheet } from "@/lib/guides/extra-sheet";
+import type { CommissionRequest } from "@/lib/commission/request";
export type AuditLogValue =
| string
@@ -33,6 +34,7 @@ export type AuditLogValue =
const authSchema = pgSchema("auth");
const catalogSchema = pgSchema("catalog");
const guidesSchema = pgSchema("guides");
+const commissionSchema = pgSchema("commission");
const timestamps = {
createdAt: timestamp("created_at", { withTimezone: true })
@@ -44,8 +46,7 @@ const timestamps = {
.notNull(),
};
-// Better Auth core tables. Public registration is disabled; authenticated
-// administrators create additional credential accounts from the admin area.
+// Better Auth core tables. Public accounts default to the customer role.
export const users = authSchema.table(
"user",
{
@@ -54,7 +55,7 @@ export const users = authSchema.table(
email: text("email").notNull(),
emailVerified: boolean("email_verified").default(false).notNull(),
image: text("image"),
- role: text("role").default("admin"),
+ role: text("role").default("user"),
banned: boolean("banned").default(false),
banReason: text("ban_reason"),
banExpires: timestamp("ban_expires", { withTimezone: true }),
@@ -153,6 +154,62 @@ export const media = pgTable(
],
);
+export const commissionCheckouts = commissionSchema.table("checkout", {
+ id: uuid("id").defaultRandom().primaryKey(),
+ userId: text("user_id").notNull().references(() => users.id),
+ request: jsonb("request").$type().notNull(),
+ amountBaht: integer("amount_baht").notNull(),
+ createdAt: timestamp("created_at", { withTimezone: true }).defaultNow().notNull(),
+}, (table) => [index("commission_checkout_user_idx").on(table.userId, table.createdAt)]);
+
+export const commissionTickets = commissionSchema.table("ticket", {
+ id: uuid("id").defaultRandom().primaryKey(),
+ checkoutId: uuid("checkout_id").notNull().references(() => commissionCheckouts.id),
+ userId: text("user_id").notNull().references(() => users.id),
+ title: varchar("title", { length: 80 }),
+ status: varchar("status", { length: 16 }).default("open").notNull(),
+ createdAt: timestamp("created_at", { withTimezone: true }).defaultNow().notNull(),
+ updatedAt: timestamp("updated_at", { withTimezone: true }).defaultNow().notNull(),
+}, (table) => [
+ uniqueIndex("commission_ticket_checkout_unique").on(table.checkoutId),
+ index("commission_ticket_user_idx").on(table.userId, table.createdAt),
+ index("commission_ticket_status_idx").on(table.status, table.updatedAt),
+]);
+
+export const commissionPayments = commissionSchema.table("payment", {
+ id: uuid("id").defaultRandom().primaryKey(),
+ checkoutId: uuid("checkout_id").notNull().references(() => commissionCheckouts.id),
+ ticketId: uuid("ticket_id").notNull().references(() => commissionTickets.id),
+ referenceId: text("reference_id").notNull(),
+ transRef: text("trans_ref").notNull(),
+ amountBaht: integer("amount_baht").notNull(),
+ transferredAt: timestamp("transferred_at", { withTimezone: true }).notNull(),
+ slipObjectKey: text("slip_object_key").notNull(),
+ slipMimeType: varchar("slip_mime_type", { length: 32 }).notNull(),
+ providerData: jsonb("provider_data").$type>().notNull(),
+ createdAt: timestamp("created_at", { withTimezone: true }).defaultNow().notNull(),
+}, (table) => [
+ uniqueIndex("commission_payment_checkout_unique").on(table.checkoutId),
+ uniqueIndex("commission_payment_reference_unique").on(table.referenceId),
+ uniqueIndex("commission_payment_trans_ref_unique").on(table.transRef),
+]);
+
+export const commissionMessages = commissionSchema.table("message", {
+ id: uuid("id").defaultRandom().primaryKey(),
+ ticketId: uuid("ticket_id").notNull().references(() => commissionTickets.id),
+ authorId: text("author_id").notNull().references(() => users.id),
+ text: text("text"),
+ imageObjectKey: text("image_object_key"),
+ imageMimeType: varchar("image_mime_type", { length: 32 }),
+ createdAt: timestamp("created_at", { withTimezone: true }).defaultNow().notNull(),
+}, (table) => [index("commission_message_ticket_time_idx").on(table.ticketId, table.createdAt)]);
+
+export const commissionReactions = commissionSchema.table("reaction", {
+ messageId: uuid("message_id").notNull().references(() => commissionMessages.id, { onDelete: "cascade" }),
+ userId: text("user_id").notNull().references(() => users.id),
+ emoji: varchar("emoji", { length: 16 }).notNull(),
+}, (table) => [primaryKey({ columns: [table.messageId, table.userId, table.emoji] })]);
+
export const outboxEvents = pgTable(
"outbox_event",
diff --git a/drizzle/0063_overconfident_george_stacy.sql b/drizzle/0063_overconfident_george_stacy.sql
new file mode 100644
index 0000000..76dd206
--- /dev/null
+++ b/drizzle/0063_overconfident_george_stacy.sql
@@ -0,0 +1,66 @@
+CREATE TABLE "commission_checkout" (
+ "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
+ "user_id" text NOT NULL,
+ "request" jsonb NOT NULL,
+ "amount_baht" integer NOT NULL,
+ "created_at" timestamp with time zone DEFAULT now() NOT NULL
+);
+--> statement-breakpoint
+CREATE TABLE "commission_message" (
+ "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
+ "ticket_id" uuid NOT NULL,
+ "author_id" text NOT NULL,
+ "text" text,
+ "image_object_key" text,
+ "image_mime_type" varchar(32),
+ "created_at" timestamp with time zone DEFAULT now() NOT NULL
+);
+--> statement-breakpoint
+CREATE TABLE "commission_payment" (
+ "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
+ "checkout_id" uuid NOT NULL,
+ "ticket_id" uuid NOT NULL,
+ "reference_id" text NOT NULL,
+ "trans_ref" text NOT NULL,
+ "amount_baht" integer NOT NULL,
+ "transferred_at" timestamp with time zone NOT NULL,
+ "slip_object_key" text NOT NULL,
+ "slip_mime_type" varchar(32) NOT NULL,
+ "provider_data" jsonb NOT NULL,
+ "created_at" timestamp with time zone DEFAULT now() NOT NULL
+);
+--> statement-breakpoint
+CREATE TABLE "commission_reaction" (
+ "message_id" uuid NOT NULL,
+ "user_id" text NOT NULL,
+ "emoji" varchar(16) NOT NULL,
+ CONSTRAINT "commission_reaction_message_id_user_id_emoji_pk" PRIMARY KEY("message_id","user_id","emoji")
+);
+--> statement-breakpoint
+CREATE TABLE "commission_ticket" (
+ "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
+ "checkout_id" uuid NOT NULL,
+ "user_id" text NOT NULL,
+ "status" varchar(16) DEFAULT 'open' NOT NULL,
+ "created_at" timestamp with time zone DEFAULT now() NOT NULL,
+ "updated_at" timestamp with time zone DEFAULT now() NOT NULL
+);
+--> statement-breakpoint
+ALTER TABLE "auth"."user" ALTER COLUMN "role" SET DEFAULT 'user';--> statement-breakpoint
+ALTER TABLE "commission_checkout" ADD CONSTRAINT "commission_checkout_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "auth"."user"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "commission_message" ADD CONSTRAINT "commission_message_ticket_id_commission_ticket_id_fk" FOREIGN KEY ("ticket_id") REFERENCES "public"."commission_ticket"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "commission_message" ADD CONSTRAINT "commission_message_author_id_user_id_fk" FOREIGN KEY ("author_id") REFERENCES "auth"."user"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "commission_payment" ADD CONSTRAINT "commission_payment_checkout_id_commission_checkout_id_fk" FOREIGN KEY ("checkout_id") REFERENCES "public"."commission_checkout"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "commission_payment" ADD CONSTRAINT "commission_payment_ticket_id_commission_ticket_id_fk" FOREIGN KEY ("ticket_id") REFERENCES "public"."commission_ticket"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "commission_reaction" ADD CONSTRAINT "commission_reaction_message_id_commission_message_id_fk" FOREIGN KEY ("message_id") REFERENCES "public"."commission_message"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "commission_reaction" ADD CONSTRAINT "commission_reaction_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "auth"."user"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "commission_ticket" ADD CONSTRAINT "commission_ticket_checkout_id_commission_checkout_id_fk" FOREIGN KEY ("checkout_id") REFERENCES "public"."commission_checkout"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "commission_ticket" ADD CONSTRAINT "commission_ticket_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "auth"."user"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
+CREATE INDEX "commission_checkout_user_idx" ON "commission_checkout" USING btree ("user_id","created_at");--> statement-breakpoint
+CREATE INDEX "commission_message_ticket_time_idx" ON "commission_message" USING btree ("ticket_id","created_at");--> statement-breakpoint
+CREATE UNIQUE INDEX "commission_payment_checkout_unique" ON "commission_payment" USING btree ("checkout_id");--> statement-breakpoint
+CREATE UNIQUE INDEX "commission_payment_reference_unique" ON "commission_payment" USING btree ("reference_id");--> statement-breakpoint
+CREATE UNIQUE INDEX "commission_payment_trans_ref_unique" ON "commission_payment" USING btree ("trans_ref");--> statement-breakpoint
+CREATE UNIQUE INDEX "commission_ticket_checkout_unique" ON "commission_ticket" USING btree ("checkout_id");--> statement-breakpoint
+CREATE INDEX "commission_ticket_user_idx" ON "commission_ticket" USING btree ("user_id","created_at");--> statement-breakpoint
+CREATE INDEX "commission_ticket_status_idx" ON "commission_ticket" USING btree ("status","updated_at");
\ No newline at end of file
diff --git a/drizzle/0064_great_dorian_gray.sql b/drizzle/0064_great_dorian_gray.sql
new file mode 100644
index 0000000..f256ea6
--- /dev/null
+++ b/drizzle/0064_great_dorian_gray.sql
@@ -0,0 +1,11 @@
+CREATE SCHEMA IF NOT EXISTS "commission";
+--> statement-breakpoint
+ALTER TABLE "public"."commission_checkout" SET SCHEMA "commission";
+--> statement-breakpoint
+ALTER TABLE "public"."commission_message" SET SCHEMA "commission";
+--> statement-breakpoint
+ALTER TABLE "public"."commission_payment" SET SCHEMA "commission";
+--> statement-breakpoint
+ALTER TABLE "public"."commission_reaction" SET SCHEMA "commission";
+--> statement-breakpoint
+ALTER TABLE "public"."commission_ticket" SET SCHEMA "commission";
diff --git a/drizzle/0065_clumsy_misty_knight.sql b/drizzle/0065_clumsy_misty_knight.sql
new file mode 100644
index 0000000..a9a6c67
--- /dev/null
+++ b/drizzle/0065_clumsy_misty_knight.sql
@@ -0,0 +1,29 @@
+ALTER TABLE "commission"."commission_checkout" RENAME TO "checkout";
+--> statement-breakpoint
+ALTER TABLE "commission"."commission_message" RENAME TO "message";
+--> statement-breakpoint
+ALTER TABLE "commission"."commission_payment" RENAME TO "payment";
+--> statement-breakpoint
+ALTER TABLE "commission"."commission_reaction" RENAME TO "reaction";
+--> statement-breakpoint
+ALTER TABLE "commission"."commission_ticket" RENAME TO "ticket";
+--> statement-breakpoint
+ALTER TABLE "commission"."checkout" RENAME CONSTRAINT "commission_checkout_user_id_user_id_fk" TO "checkout_user_id_user_id_fk";
+--> statement-breakpoint
+ALTER TABLE "commission"."message" RENAME CONSTRAINT "commission_message_ticket_id_commission_ticket_id_fk" TO "message_ticket_id_ticket_id_fk";
+--> statement-breakpoint
+ALTER TABLE "commission"."message" RENAME CONSTRAINT "commission_message_author_id_user_id_fk" TO "message_author_id_user_id_fk";
+--> statement-breakpoint
+ALTER TABLE "commission"."payment" RENAME CONSTRAINT "commission_payment_checkout_id_commission_checkout_id_fk" TO "payment_checkout_id_checkout_id_fk";
+--> statement-breakpoint
+ALTER TABLE "commission"."payment" RENAME CONSTRAINT "commission_payment_ticket_id_commission_ticket_id_fk" TO "payment_ticket_id_ticket_id_fk";
+--> statement-breakpoint
+ALTER TABLE "commission"."reaction" RENAME CONSTRAINT "commission_reaction_message_id_commission_message_id_fk" TO "reaction_message_id_message_id_fk";
+--> statement-breakpoint
+ALTER TABLE "commission"."reaction" RENAME CONSTRAINT "commission_reaction_user_id_user_id_fk" TO "reaction_user_id_user_id_fk";
+--> statement-breakpoint
+ALTER TABLE "commission"."reaction" RENAME CONSTRAINT "commission_reaction_message_id_user_id_emoji_pk" TO "reaction_message_id_user_id_emoji_pk";
+--> statement-breakpoint
+ALTER TABLE "commission"."ticket" RENAME CONSTRAINT "commission_ticket_checkout_id_commission_checkout_id_fk" TO "ticket_checkout_id_checkout_id_fk";
+--> statement-breakpoint
+ALTER TABLE "commission"."ticket" RENAME CONSTRAINT "commission_ticket_user_id_user_id_fk" TO "ticket_user_id_user_id_fk";
diff --git a/drizzle/0066_burly_william_stryker.sql b/drizzle/0066_burly_william_stryker.sql
new file mode 100644
index 0000000..8358496
--- /dev/null
+++ b/drizzle/0066_burly_william_stryker.sql
@@ -0,0 +1 @@
+ALTER TABLE "commission"."ticket" ADD COLUMN "title" varchar(80);
\ No newline at end of file
diff --git a/drizzle/meta/0063_snapshot.json b/drizzle/meta/0063_snapshot.json
new file mode 100644
index 0000000..2608b8d
--- /dev/null
+++ b/drizzle/meta/0063_snapshot.json
@@ -0,0 +1,3835 @@
+{
+ "id": "b6e8b62b-0301-4c0f-b53d-8f116f2592a0",
+ "prevId": "a1d24bea-72c4-4e72-9128-48702780bbd6",
+ "version": "7",
+ "dialect": "postgresql",
+ "tables": {
+ "auth.account": {
+ "name": "account",
+ "schema": "auth",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "issuer": {
+ "name": "issuer",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "account_id": {
+ "name": "account_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "provider_id": {
+ "name": "provider_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "access_token": {
+ "name": "access_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "refresh_token": {
+ "name": "refresh_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "id_token": {
+ "name": "id_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "access_token_expires_at": {
+ "name": "access_token_expires_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "refresh_token_expires_at": {
+ "name": "refresh_token_expires_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "scope": {
+ "name": "scope",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "password": {
+ "name": "password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "account_provider_unique": {
+ "name": "account_provider_unique",
+ "columns": [
+ {
+ "expression": "provider_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "account_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "account_user_id_idx": {
+ "name": "account_user_id_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "account_user_id_user_id_fk": {
+ "name": "account_user_id_user_id_fk",
+ "tableFrom": "account",
+ "tableTo": "user",
+ "schemaTo": "auth",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "guides.artifact_condition": {
+ "name": "artifact_condition",
+ "schema": "guides",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "guide_id": {
+ "name": "guide_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "varchar(120)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "note": {
+ "name": "note",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "sort_order": {
+ "name": "sort_order",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ }
+ },
+ "indexes": {
+ "artifact_condition_order_idx": {
+ "name": "artifact_condition_order_idx",
+ "columns": [
+ {
+ "expression": "guide_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "sort_order",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "artifact_condition_guide_id_guide_id_fk": {
+ "name": "artifact_condition_guide_id_guide_id_fk",
+ "tableFrom": "artifact_condition",
+ "tableTo": "guide",
+ "schemaTo": "guides",
+ "columnsFrom": [
+ "guide_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "guides.artifact_group": {
+ "name": "artifact_group",
+ "schema": "guides",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "condition_id": {
+ "name": "condition_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "sort_order": {
+ "name": "sort_order",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ }
+ },
+ "indexes": {
+ "artifact_group_order_idx": {
+ "name": "artifact_group_order_idx",
+ "columns": [
+ {
+ "expression": "condition_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "sort_order",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "artifact_group_condition_id_artifact_condition_id_fk": {
+ "name": "artifact_group_condition_id_artifact_condition_id_fk",
+ "tableFrom": "artifact_group",
+ "tableTo": "artifact_condition",
+ "schemaTo": "guides",
+ "columnsFrom": [
+ "condition_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "guides.artifact_profile": {
+ "name": "artifact_profile",
+ "schema": "guides",
+ "columns": {
+ "guide_id": {
+ "name": "guide_id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "sands": {
+ "name": "sands",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'[]'::jsonb"
+ },
+ "goblet": {
+ "name": "goblet",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'[]'::jsonb"
+ },
+ "circlet": {
+ "name": "circlet",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'[]'::jsonb"
+ },
+ "substats": {
+ "name": "substats",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'[]'::jsonb"
+ },
+ "recommended_conditions": {
+ "name": "recommended_conditions",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'[]'::jsonb"
+ },
+ "targets": {
+ "name": "targets",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'[]'::jsonb"
+ },
+ "target_conditions": {
+ "name": "target_conditions",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'[]'::jsonb"
+ },
+ "talents": {
+ "name": "talents",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'[]'::jsonb"
+ },
+ "note": {
+ "name": "note",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "artifact_profile_guide_id_guide_id_fk": {
+ "name": "artifact_profile_guide_id_guide_id_fk",
+ "tableFrom": "artifact_profile",
+ "tableTo": "guide",
+ "schemaTo": "guides",
+ "columnsFrom": [
+ "guide_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "guides.artifact_recommendation": {
+ "name": "artifact_recommendation",
+ "schema": "guides",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "group_id": {
+ "name": "group_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "first_artifact_key": {
+ "name": "first_artifact_key",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "second_artifact_key": {
+ "name": "second_artifact_key",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "percent": {
+ "name": "percent",
+ "type": "numeric(8, 2)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "note": {
+ "name": "note",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "sort_order": {
+ "name": "sort_order",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ }
+ },
+ "indexes": {
+ "artifact_recommendation_order_idx": {
+ "name": "artifact_recommendation_order_idx",
+ "columns": [
+ {
+ "expression": "group_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "sort_order",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "artifact_recommendation_group_id_artifact_group_id_fk": {
+ "name": "artifact_recommendation_group_id_artifact_group_id_fk",
+ "tableFrom": "artifact_recommendation",
+ "tableTo": "artifact_group",
+ "schemaTo": "guides",
+ "columnsFrom": [
+ "group_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.audit_log": {
+ "name": "audit_log",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "bigint",
+ "primaryKey": true,
+ "notNull": true,
+ "identity": {
+ "type": "always",
+ "name": "audit_log_id_seq",
+ "schema": "public",
+ "increment": "1",
+ "startWith": "1",
+ "minValue": "1",
+ "maxValue": "9223372036854775807",
+ "cache": "1",
+ "cycle": false
+ }
+ },
+ "author_id": {
+ "name": "author_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "author": {
+ "name": "author",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "text": {
+ "name": "text",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "details": {
+ "name": "details",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'{}'::jsonb"
+ },
+ "time": {
+ "name": "time",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "audit_log_time_idx": {
+ "name": "audit_log_time_idx",
+ "columns": [
+ {
+ "expression": "time",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "audit_log_author_time_idx": {
+ "name": "audit_log_author_time_idx",
+ "columns": [
+ {
+ "expression": "author_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "time",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "catalog.artifact": {
+ "name": "artifact",
+ "schema": "catalog",
+ "columns": {
+ "key": {
+ "name": "key",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "rarity": {
+ "name": "rarity",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "image_key": {
+ "name": "image_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {
+ "catalog_artifact_name_idx": {
+ "name": "catalog_artifact_name_idx",
+ "columns": [
+ {
+ "expression": "name",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "artifact_key_pk": {
+ "name": "artifact_key_pk",
+ "columns": [
+ "key"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "catalog.character": {
+ "name": "character",
+ "schema": "catalog",
+ "columns": {
+ "key": {
+ "name": "key",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "element": {
+ "name": "element",
+ "type": "varchar(48)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "rarity": {
+ "name": "rarity",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "weapon_type": {
+ "name": "weapon_type",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "image_key": {
+ "name": "image_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "element_icon_key": {
+ "name": "element_icon_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "coop_image_key": {
+ "name": "coop_image_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "name_card_image_key": {
+ "name": "name_card_image_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "constellations": {
+ "name": "constellations",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'[]'::jsonb"
+ },
+ "talents": {
+ "name": "talents",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'[]'::jsonb"
+ },
+ "ascension": {
+ "name": "ascension",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'[]'::jsonb"
+ },
+ "leveling": {
+ "name": "leveling",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'[]'::jsonb"
+ },
+ "base_stats": {
+ "name": "base_stats",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'[]'::jsonb"
+ }
+ },
+ "indexes": {
+ "catalog_character_name_idx": {
+ "name": "catalog_character_name_idx",
+ "columns": [
+ {
+ "expression": "name",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "character_key_pk": {
+ "name": "character_key_pk",
+ "columns": [
+ "key"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "catalog.material": {
+ "name": "material",
+ "schema": "catalog",
+ "columns": {
+ "key": {
+ "name": "key",
+ "type": "varchar(64)",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "rarity": {
+ "name": "rarity",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "type": {
+ "name": "type",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "image_key": {
+ "name": "image_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "catalog_material_name_idx": {
+ "name": "catalog_material_name_idx",
+ "columns": [
+ {
+ "expression": "name",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "catalog.weapon": {
+ "name": "weapon",
+ "schema": "catalog",
+ "columns": {
+ "key": {
+ "name": "key",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "rarity": {
+ "name": "rarity",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "weapon_type": {
+ "name": "weapon_type",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "image_key": {
+ "name": "image_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {
+ "catalog_weapon_name_idx": {
+ "name": "catalog_weapon_name_idx",
+ "columns": [
+ {
+ "expression": "name",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "weapon_key_pk": {
+ "name": "weapon_key_pk",
+ "columns": [
+ "key"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.commission_checkout": {
+ "name": "commission_checkout",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "request": {
+ "name": "request",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "amount_baht": {
+ "name": "amount_baht",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "commission_checkout_user_idx": {
+ "name": "commission_checkout_user_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "commission_checkout_user_id_user_id_fk": {
+ "name": "commission_checkout_user_id_user_id_fk",
+ "tableFrom": "commission_checkout",
+ "tableTo": "user",
+ "schemaTo": "auth",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.commission_message": {
+ "name": "commission_message",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "ticket_id": {
+ "name": "ticket_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "author_id": {
+ "name": "author_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "text": {
+ "name": "text",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "image_object_key": {
+ "name": "image_object_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "image_mime_type": {
+ "name": "image_mime_type",
+ "type": "varchar(32)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "commission_message_ticket_time_idx": {
+ "name": "commission_message_ticket_time_idx",
+ "columns": [
+ {
+ "expression": "ticket_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "commission_message_ticket_id_commission_ticket_id_fk": {
+ "name": "commission_message_ticket_id_commission_ticket_id_fk",
+ "tableFrom": "commission_message",
+ "tableTo": "commission_ticket",
+ "columnsFrom": [
+ "ticket_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ },
+ "commission_message_author_id_user_id_fk": {
+ "name": "commission_message_author_id_user_id_fk",
+ "tableFrom": "commission_message",
+ "tableTo": "user",
+ "schemaTo": "auth",
+ "columnsFrom": [
+ "author_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.commission_payment": {
+ "name": "commission_payment",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "checkout_id": {
+ "name": "checkout_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "ticket_id": {
+ "name": "ticket_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "reference_id": {
+ "name": "reference_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "trans_ref": {
+ "name": "trans_ref",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "amount_baht": {
+ "name": "amount_baht",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "transferred_at": {
+ "name": "transferred_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "slip_object_key": {
+ "name": "slip_object_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "slip_mime_type": {
+ "name": "slip_mime_type",
+ "type": "varchar(32)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "provider_data": {
+ "name": "provider_data",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "commission_payment_checkout_unique": {
+ "name": "commission_payment_checkout_unique",
+ "columns": [
+ {
+ "expression": "checkout_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "commission_payment_reference_unique": {
+ "name": "commission_payment_reference_unique",
+ "columns": [
+ {
+ "expression": "reference_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "commission_payment_trans_ref_unique": {
+ "name": "commission_payment_trans_ref_unique",
+ "columns": [
+ {
+ "expression": "trans_ref",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "commission_payment_checkout_id_commission_checkout_id_fk": {
+ "name": "commission_payment_checkout_id_commission_checkout_id_fk",
+ "tableFrom": "commission_payment",
+ "tableTo": "commission_checkout",
+ "columnsFrom": [
+ "checkout_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ },
+ "commission_payment_ticket_id_commission_ticket_id_fk": {
+ "name": "commission_payment_ticket_id_commission_ticket_id_fk",
+ "tableFrom": "commission_payment",
+ "tableTo": "commission_ticket",
+ "columnsFrom": [
+ "ticket_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.commission_reaction": {
+ "name": "commission_reaction",
+ "schema": "",
+ "columns": {
+ "message_id": {
+ "name": "message_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "emoji": {
+ "name": "emoji",
+ "type": "varchar(16)",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "commission_reaction_message_id_commission_message_id_fk": {
+ "name": "commission_reaction_message_id_commission_message_id_fk",
+ "tableFrom": "commission_reaction",
+ "tableTo": "commission_message",
+ "columnsFrom": [
+ "message_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "commission_reaction_user_id_user_id_fk": {
+ "name": "commission_reaction_user_id_user_id_fk",
+ "tableFrom": "commission_reaction",
+ "tableTo": "user",
+ "schemaTo": "auth",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "commission_reaction_message_id_user_id_emoji_pk": {
+ "name": "commission_reaction_message_id_user_id_emoji_pk",
+ "columns": [
+ "message_id",
+ "user_id",
+ "emoji"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.commission_ticket": {
+ "name": "commission_ticket",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "checkout_id": {
+ "name": "checkout_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "status": {
+ "name": "status",
+ "type": "varchar(16)",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'open'"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "commission_ticket_checkout_unique": {
+ "name": "commission_ticket_checkout_unique",
+ "columns": [
+ {
+ "expression": "checkout_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "commission_ticket_user_idx": {
+ "name": "commission_ticket_user_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "commission_ticket_status_idx": {
+ "name": "commission_ticket_status_idx",
+ "columns": [
+ {
+ "expression": "status",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "updated_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "commission_ticket_checkout_id_commission_checkout_id_fk": {
+ "name": "commission_ticket_checkout_id_commission_checkout_id_fk",
+ "tableFrom": "commission_ticket",
+ "tableTo": "commission_checkout",
+ "columnsFrom": [
+ "checkout_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ },
+ "commission_ticket_user_id_user_id_fk": {
+ "name": "commission_ticket_user_id_user_id_fk",
+ "tableFrom": "commission_ticket",
+ "tableTo": "user",
+ "schemaTo": "auth",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "guides.constellation_condition": {
+ "name": "constellation_condition",
+ "schema": "guides",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "guide_id": {
+ "name": "guide_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "varchar(120)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "note": {
+ "name": "note",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "sort_order": {
+ "name": "sort_order",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ }
+ },
+ "indexes": {
+ "constellation_condition_order_idx": {
+ "name": "constellation_condition_order_idx",
+ "columns": [
+ {
+ "expression": "guide_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "sort_order",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "constellation_condition_guide_id_guide_id_fk": {
+ "name": "constellation_condition_guide_id_guide_id_fk",
+ "tableFrom": "constellation_condition",
+ "tableTo": "guide",
+ "schemaTo": "guides",
+ "columnsFrom": [
+ "guide_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "guides.constellation_recommendation": {
+ "name": "constellation_recommendation",
+ "schema": "guides",
+ "columns": {
+ "condition_id": {
+ "name": "condition_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "level": {
+ "name": "level",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "detail": {
+ "name": "detail",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "cumulative_percent": {
+ "name": "cumulative_percent",
+ "type": "numeric(8, 2)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "is_great": {
+ "name": "is_great",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "constellation_recommendation_condition_id_constellation_condition_id_fk": {
+ "name": "constellation_recommendation_condition_id_constellation_condition_id_fk",
+ "tableFrom": "constellation_recommendation",
+ "tableTo": "constellation_condition",
+ "schemaTo": "guides",
+ "columnsFrom": [
+ "condition_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "constellation_recommendation_condition_id_level_pk": {
+ "name": "constellation_recommendation_condition_id_level_pk",
+ "columns": [
+ "condition_id",
+ "level"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "guides.extra_section_row": {
+ "name": "extra_section_row",
+ "schema": "guides",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "section_id": {
+ "name": "section_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "media_id": {
+ "name": "media_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "heading": {
+ "name": "heading",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "body": {
+ "name": "body",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "percent": {
+ "name": "percent",
+ "type": "numeric(8, 2)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "background_color": {
+ "name": "background_color",
+ "type": "varchar(7)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "text_color": {
+ "name": "text_color",
+ "type": "varchar(7)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "highlights": {
+ "name": "highlights",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'[]'::jsonb"
+ },
+ "catalog_items": {
+ "name": "catalog_items",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'[]'::jsonb"
+ },
+ "sheet": {
+ "name": "sheet",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "sort_order": {
+ "name": "sort_order",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ }
+ },
+ "indexes": {
+ "extra_section_row_order_idx": {
+ "name": "extra_section_row_order_idx",
+ "columns": [
+ {
+ "expression": "section_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "sort_order",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "extra_section_row_section_id_extra_section_section_id_fk": {
+ "name": "extra_section_row_section_id_extra_section_section_id_fk",
+ "tableFrom": "extra_section_row",
+ "tableTo": "extra_section",
+ "schemaTo": "guides",
+ "columnsFrom": [
+ "section_id"
+ ],
+ "columnsTo": [
+ "section_id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "extra_section_row_media_id_media_id_fk": {
+ "name": "extra_section_row_media_id_media_id_fk",
+ "tableFrom": "extra_section_row",
+ "tableTo": "media",
+ "columnsFrom": [
+ "media_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "guides.extra_section": {
+ "name": "extra_section",
+ "schema": "guides",
+ "columns": {
+ "section_id": {
+ "name": "section_id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "extra_section_section_id_guide_section_id_fk": {
+ "name": "extra_section_section_id_guide_section_id_fk",
+ "tableFrom": "extra_section",
+ "tableTo": "guide_section",
+ "schemaTo": "guides",
+ "columnsFrom": [
+ "section_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "guides.glossary_alias": {
+ "name": "glossary_alias",
+ "schema": "guides",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "kind": {
+ "name": "kind",
+ "type": "varchar(16)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "catalog_key": {
+ "name": "catalog_key",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "alias": {
+ "name": "alias",
+ "type": "varchar(120)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "normalized_alias": {
+ "name": "normalized_alias",
+ "type": "varchar(120)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "glossary_alias_item_normalized_unique": {
+ "name": "glossary_alias_item_normalized_unique",
+ "columns": [
+ {
+ "expression": "kind",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "catalog_key",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "normalized_alias",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "glossary_alias_item_idx": {
+ "name": "glossary_alias_item_idx",
+ "columns": [
+ {
+ "expression": "kind",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "catalog_key",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "guides.guide_poster_bleeding": {
+ "name": "guide_poster_bleeding",
+ "schema": "guides",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "guide_id": {
+ "name": "guide_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "media_id": {
+ "name": "media_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "amount": {
+ "name": "amount",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 100
+ },
+ "scale": {
+ "name": "scale",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 100
+ },
+ "blend_mode": {
+ "name": "blend_mode",
+ "type": "varchar(32)",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'multiply'"
+ },
+ "sort_order": {
+ "name": "sort_order",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "guide_poster_bleeding_order_idx": {
+ "name": "guide_poster_bleeding_order_idx",
+ "columns": [
+ {
+ "expression": "guide_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "sort_order",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "guide_poster_bleeding_guide_id_guide_id_fk": {
+ "name": "guide_poster_bleeding_guide_id_guide_id_fk",
+ "tableFrom": "guide_poster_bleeding",
+ "tableTo": "guide",
+ "schemaTo": "guides",
+ "columnsFrom": [
+ "guide_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "guide_poster_bleeding_media_id_media_id_fk": {
+ "name": "guide_poster_bleeding_media_id_media_id_fk",
+ "tableFrom": "guide_poster_bleeding",
+ "tableTo": "media",
+ "columnsFrom": [
+ "media_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "restrict",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "guides.guide_section": {
+ "name": "guide_section",
+ "schema": "guides",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "guide_id": {
+ "name": "guide_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "kind": {
+ "name": "kind",
+ "type": "varchar(32)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "slug": {
+ "name": "slug",
+ "type": "varchar(120)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "note": {
+ "name": "note",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "sort_order": {
+ "name": "sort_order",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ }
+ },
+ "indexes": {
+ "guide_section_slug_unique": {
+ "name": "guide_section_slug_unique",
+ "columns": [
+ {
+ "expression": "guide_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "slug",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "guide_section_order_idx": {
+ "name": "guide_section_order_idx",
+ "columns": [
+ {
+ "expression": "guide_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "sort_order",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "guide_section_guide_id_guide_id_fk": {
+ "name": "guide_section_guide_id_guide_id_fk",
+ "tableFrom": "guide_section",
+ "tableTo": "guide",
+ "schemaTo": "guides",
+ "columnsFrom": [
+ "guide_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "guides.guide": {
+ "name": "guide",
+ "schema": "guides",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "character_key": {
+ "name": "character_key",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "slug": {
+ "name": "slug",
+ "type": "varchar(120)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "overview": {
+ "name": "overview",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "youtube_urls": {
+ "name": "youtube_urls",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'[]'::jsonb"
+ },
+ "cover_media_id": {
+ "name": "cover_media_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "poster_art_media_id": {
+ "name": "poster_art_media_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "poster_art_offset_x": {
+ "name": "poster_art_offset_x",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "poster_art_offset_y": {
+ "name": "poster_art_offset_y",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "poster_art_scale": {
+ "name": "poster_art_scale",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 100
+ },
+ "poster_art_rotation": {
+ "name": "poster_art_rotation",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "poster_top_colors": {
+ "name": "poster_top_colors",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'[\"#000000\",\"#000000\"]'::jsonb"
+ },
+ "poster_bottom_colors": {
+ "name": "poster_bottom_colors",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'[\"#000000\",\"#000000\"]'::jsonb"
+ },
+ "version": {
+ "name": "version",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 1
+ },
+ "is_public": {
+ "name": "is_public",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "published_at": {
+ "name": "published_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "sort_order": {
+ "name": "sort_order",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "trashed_at": {
+ "name": "trashed_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "guide_character_key_unique": {
+ "name": "guide_character_key_unique",
+ "columns": [
+ {
+ "expression": "character_key",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "guide_slug_unique": {
+ "name": "guide_slug_unique",
+ "columns": [
+ {
+ "expression": "slug",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "guide_directory_idx": {
+ "name": "guide_directory_idx",
+ "columns": [
+ {
+ "expression": "trashed_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "sort_order",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "guide_cover_media_id_media_id_fk": {
+ "name": "guide_cover_media_id_media_id_fk",
+ "tableFrom": "guide",
+ "tableTo": "media",
+ "columnsFrom": [
+ "cover_media_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "restrict",
+ "onUpdate": "no action"
+ },
+ "guide_poster_art_media_id_media_id_fk": {
+ "name": "guide_poster_art_media_id_media_id_fk",
+ "tableFrom": "guide",
+ "tableTo": "media",
+ "columnsFrom": [
+ "poster_art_media_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.media": {
+ "name": "media",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "object_key": {
+ "name": "object_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "file_name": {
+ "name": "file_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "mime_type": {
+ "name": "mime_type",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "byte_size": {
+ "name": "byte_size",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "checksum": {
+ "name": "checksum",
+ "type": "varchar(128)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "width": {
+ "name": "width",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "height": {
+ "name": "height",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "status": {
+ "name": "status",
+ "type": "varchar(24)",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'pending'"
+ },
+ "current_reference_count": {
+ "name": "current_reference_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "created_by_id": {
+ "name": "created_by_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "media_object_key_unique": {
+ "name": "media_object_key_unique",
+ "columns": [
+ {
+ "expression": "object_key",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "media_visibility_idx": {
+ "name": "media_visibility_idx",
+ "columns": [
+ {
+ "expression": "status",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "current_reference_count",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "media_created_by_id_user_id_fk": {
+ "name": "media_created_by_id_user_id_fk",
+ "tableFrom": "media",
+ "tableTo": "user",
+ "schemaTo": "auth",
+ "columnsFrom": [
+ "created_by_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.outbox_event": {
+ "name": "outbox_event",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "bigint",
+ "primaryKey": true,
+ "notNull": true,
+ "identity": {
+ "type": "always",
+ "name": "outbox_event_id_seq",
+ "schema": "public",
+ "increment": "1",
+ "startWith": "1",
+ "minValue": "1",
+ "maxValue": "9223372036854775807",
+ "cache": "1",
+ "cycle": false
+ }
+ },
+ "topic": {
+ "name": "topic",
+ "type": "varchar(160)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "aggregate_id": {
+ "name": "aggregate_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "event_type": {
+ "name": "event_type",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "payload": {
+ "name": "payload",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "status": {
+ "name": "status",
+ "type": "varchar(24)",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'pending'"
+ },
+ "attempts": {
+ "name": "attempts",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "available_at": {
+ "name": "available_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "processed_at": {
+ "name": "processed_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_error": {
+ "name": "last_error",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "outbox_pending_idx": {
+ "name": "outbox_pending_idx",
+ "columns": [
+ {
+ "expression": "status",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "available_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "auth.session": {
+ "name": "session",
+ "schema": "auth",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "token": {
+ "name": "token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "ip_address": {
+ "name": "ip_address",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "user_agent": {
+ "name": "user_agent",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "impersonated_by": {
+ "name": "impersonated_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "session_token_unique": {
+ "name": "session_token_unique",
+ "columns": [
+ {
+ "expression": "token",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "session_user_id_idx": {
+ "name": "session_user_id_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "session_user_id_user_id_fk": {
+ "name": "session_user_id_user_id_fk",
+ "tableFrom": "session",
+ "tableTo": "user",
+ "schemaTo": "auth",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "guides.stygian_boss": {
+ "name": "stygian_boss",
+ "schema": "guides",
+ "columns": {
+ "schedule_id": {
+ "name": "schedule_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "level": {
+ "name": "level",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "position": {
+ "name": "position",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "config_id": {
+ "name": "config_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "hp": {
+ "name": "hp",
+ "type": "numeric(18, 2)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "mechanics": {
+ "name": "mechanics",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'[]'::jsonb"
+ },
+ "recommended_mechanics": {
+ "name": "recommended_mechanics",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'[]'::jsonb"
+ },
+ "recommended_mechanic_types": {
+ "name": "recommended_mechanic_types",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'[]'::jsonb"
+ },
+ "image_key": {
+ "name": "image_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {
+ "stygian_boss_schedule_idx": {
+ "name": "stygian_boss_schedule_idx",
+ "columns": [
+ {
+ "expression": "schedule_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "level",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "stygian_boss_schedule_id_stygian_schedule_schedule_id_fk": {
+ "name": "stygian_boss_schedule_id_stygian_schedule_schedule_id_fk",
+ "tableFrom": "stygian_boss",
+ "tableTo": "stygian_schedule",
+ "schemaTo": "guides",
+ "columnsFrom": [
+ "schedule_id"
+ ],
+ "columnsTo": [
+ "schedule_id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "stygian_boss_schedule_id_level_position_pk": {
+ "name": "stygian_boss_schedule_id_level_position_pk",
+ "columns": [
+ "schedule_id",
+ "level",
+ "position"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "guides.stygian_recommendation_group": {
+ "name": "stygian_recommendation_group",
+ "schema": "guides",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "schedule_id": {
+ "name": "schedule_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "level": {
+ "name": "level",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "boss_position": {
+ "name": "boss_position",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "kind": {
+ "name": "kind",
+ "type": "varchar(16)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "sort_order": {
+ "name": "sort_order",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ }
+ },
+ "indexes": {
+ "stygian_recommendation_group_order_idx": {
+ "name": "stygian_recommendation_group_order_idx",
+ "columns": [
+ {
+ "expression": "schedule_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "level",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "boss_position",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "kind",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "sort_order",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "stygian_recommendation_group_schedule_id_stygian_schedule_schedule_id_fk": {
+ "name": "stygian_recommendation_group_schedule_id_stygian_schedule_schedule_id_fk",
+ "tableFrom": "stygian_recommendation_group",
+ "tableTo": "stygian_schedule",
+ "schemaTo": "guides",
+ "columnsFrom": [
+ "schedule_id"
+ ],
+ "columnsTo": [
+ "schedule_id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "guides.stygian_recommendation_item": {
+ "name": "stygian_recommendation_item",
+ "schema": "guides",
+ "columns": {
+ "group_id": {
+ "name": "group_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "position": {
+ "name": "position",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "kind": {
+ "name": "kind",
+ "type": "varchar(24)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "catalog_key": {
+ "name": "catalog_key",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "weapon_key": {
+ "name": "weapon_key",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "artifact_key": {
+ "name": "artifact_key",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "refinement": {
+ "name": "refinement",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "constellation_level": {
+ "name": "constellation_level",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "element": {
+ "name": "element",
+ "type": "varchar(24)",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "stygian_recommendation_item_group_id_stygian_recommendation_group_id_fk": {
+ "name": "stygian_recommendation_item_group_id_stygian_recommendation_group_id_fk",
+ "tableFrom": "stygian_recommendation_item",
+ "tableTo": "stygian_recommendation_group",
+ "schemaTo": "guides",
+ "columnsFrom": [
+ "group_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "stygian_recommendation_item_group_id_position_pk": {
+ "name": "stygian_recommendation_item_group_id_position_pk",
+ "columns": [
+ "group_id",
+ "position"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "guides.stygian_schedule": {
+ "name": "stygian_schedule",
+ "schema": "guides",
+ "columns": {
+ "schedule_id": {
+ "name": "schedule_id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "challenge_name": {
+ "name": "challenge_name",
+ "type": "varchar(32)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "schedule_start_time": {
+ "name": "schedule_start_time",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "schedule_end_time": {
+ "name": "schedule_end_time",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "version": {
+ "name": "version",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 1
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "guides.team_composition": {
+ "name": "team_composition",
+ "schema": "guides",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "guide_id": {
+ "name": "guide_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "condition_id": {
+ "name": "condition_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "rotation_seconds": {
+ "name": "rotation_seconds",
+ "type": "numeric(8, 2)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "combo": {
+ "name": "combo",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "sort_order": {
+ "name": "sort_order",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ }
+ },
+ "indexes": {
+ "team_composition_order_idx": {
+ "name": "team_composition_order_idx",
+ "columns": [
+ {
+ "expression": "guide_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "sort_order",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "team_composition_guide_id_guide_id_fk": {
+ "name": "team_composition_guide_id_guide_id_fk",
+ "tableFrom": "team_composition",
+ "tableTo": "guide",
+ "schemaTo": "guides",
+ "columnsFrom": [
+ "guide_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "team_composition_condition_id_team_condition_id_fk": {
+ "name": "team_composition_condition_id_team_condition_id_fk",
+ "tableFrom": "team_composition",
+ "tableTo": "team_condition",
+ "schemaTo": "guides",
+ "columnsFrom": [
+ "condition_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "guides.team_condition": {
+ "name": "team_condition",
+ "schema": "guides",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "guide_id": {
+ "name": "guide_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "varchar(120)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "note": {
+ "name": "note",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "sort_order": {
+ "name": "sort_order",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ }
+ },
+ "indexes": {
+ "team_condition_order_idx": {
+ "name": "team_condition_order_idx",
+ "columns": [
+ {
+ "expression": "guide_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "sort_order",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "team_condition_guide_id_guide_id_fk": {
+ "name": "team_condition_guide_id_guide_id_fk",
+ "tableFrom": "team_condition",
+ "tableTo": "guide",
+ "schemaTo": "guides",
+ "columnsFrom": [
+ "guide_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "guides.team_member": {
+ "name": "team_member",
+ "schema": "guides",
+ "columns": {
+ "team_id": {
+ "name": "team_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "position": {
+ "name": "position",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "character_key": {
+ "name": "character_key",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "weapon_key": {
+ "name": "weapon_key",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "artifact_key": {
+ "name": "artifact_key",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "second_artifact_key": {
+ "name": "second_artifact_key",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "constellation": {
+ "name": "constellation",
+ "type": "varchar(8)",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'0'"
+ },
+ "refinement": {
+ "name": "refinement",
+ "type": "varchar(8)",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'1'"
+ },
+ "damage": {
+ "name": "damage",
+ "type": "numeric(18, 2)",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "team_member_team_id_team_composition_id_fk": {
+ "name": "team_member_team_id_team_composition_id_fk",
+ "tableFrom": "team_member",
+ "tableTo": "team_composition",
+ "schemaTo": "guides",
+ "columnsFrom": [
+ "team_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "team_member_team_id_position_pk": {
+ "name": "team_member_team_id_position_pk",
+ "columns": [
+ "team_id",
+ "position"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "auth.user": {
+ "name": "user",
+ "schema": "auth",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "email": {
+ "name": "email",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "email_verified": {
+ "name": "email_verified",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "image": {
+ "name": "image",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "role": {
+ "name": "role",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'user'"
+ },
+ "banned": {
+ "name": "banned",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": false
+ },
+ "ban_reason": {
+ "name": "ban_reason",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "ban_expires": {
+ "name": "ban_expires",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "user_email_unique": {
+ "name": "user_email_unique",
+ "columns": [
+ {
+ "expression": "email",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "auth.verification": {
+ "name": "verification",
+ "schema": "auth",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "identifier": {
+ "name": "identifier",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "value": {
+ "name": "value",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "verification_identifier_idx": {
+ "name": "verification_identifier_idx",
+ "columns": [
+ {
+ "expression": "identifier",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "guides.weapon_condition": {
+ "name": "weapon_condition",
+ "schema": "guides",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "guide_id": {
+ "name": "guide_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "varchar(120)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "note": {
+ "name": "note",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "sort_order": {
+ "name": "sort_order",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ }
+ },
+ "indexes": {
+ "weapon_condition_order_idx": {
+ "name": "weapon_condition_order_idx",
+ "columns": [
+ {
+ "expression": "guide_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "sort_order",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "weapon_condition_guide_id_guide_id_fk": {
+ "name": "weapon_condition_guide_id_guide_id_fk",
+ "tableFrom": "weapon_condition",
+ "tableTo": "guide",
+ "schemaTo": "guides",
+ "columnsFrom": [
+ "guide_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "guides.weapon_recommendation": {
+ "name": "weapon_recommendation",
+ "schema": "guides",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "condition_id": {
+ "name": "condition_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "weapon_key": {
+ "name": "weapon_key",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "refinement": {
+ "name": "refinement",
+ "type": "varchar(8)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "overall_percent": {
+ "name": "overall_percent",
+ "type": "numeric(8, 2)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "personal_percent": {
+ "name": "personal_percent",
+ "type": "numeric(8, 2)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "note": {
+ "name": "note",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "sort_order": {
+ "name": "sort_order",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ }
+ },
+ "indexes": {
+ "weapon_recommendation_order_idx": {
+ "name": "weapon_recommendation_order_idx",
+ "columns": [
+ {
+ "expression": "condition_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "sort_order",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "weapon_recommendation_condition_id_weapon_condition_id_fk": {
+ "name": "weapon_recommendation_condition_id_weapon_condition_id_fk",
+ "tableFrom": "weapon_recommendation",
+ "tableTo": "weapon_condition",
+ "schemaTo": "guides",
+ "columnsFrom": [
+ "condition_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ }
+ },
+ "enums": {},
+ "schemas": {},
+ "sequences": {},
+ "roles": {},
+ "policies": {},
+ "views": {},
+ "_meta": {
+ "columns": {},
+ "schemas": {},
+ "tables": {}
+ }
+}
\ No newline at end of file
diff --git a/drizzle/meta/0064_snapshot.json b/drizzle/meta/0064_snapshot.json
new file mode 100644
index 0000000..0ef8b9b
--- /dev/null
+++ b/drizzle/meta/0064_snapshot.json
@@ -0,0 +1,3840 @@
+{
+ "id": "a5fa5593-934e-47a3-ba1b-26e121f603a2",
+ "prevId": "b6e8b62b-0301-4c0f-b53d-8f116f2592a0",
+ "version": "7",
+ "dialect": "postgresql",
+ "tables": {
+ "auth.account": {
+ "name": "account",
+ "schema": "auth",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "issuer": {
+ "name": "issuer",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "account_id": {
+ "name": "account_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "provider_id": {
+ "name": "provider_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "access_token": {
+ "name": "access_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "refresh_token": {
+ "name": "refresh_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "id_token": {
+ "name": "id_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "access_token_expires_at": {
+ "name": "access_token_expires_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "refresh_token_expires_at": {
+ "name": "refresh_token_expires_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "scope": {
+ "name": "scope",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "password": {
+ "name": "password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "account_provider_unique": {
+ "name": "account_provider_unique",
+ "columns": [
+ {
+ "expression": "provider_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "account_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "account_user_id_idx": {
+ "name": "account_user_id_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "account_user_id_user_id_fk": {
+ "name": "account_user_id_user_id_fk",
+ "tableFrom": "account",
+ "tableTo": "user",
+ "schemaTo": "auth",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "guides.artifact_condition": {
+ "name": "artifact_condition",
+ "schema": "guides",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "guide_id": {
+ "name": "guide_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "varchar(120)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "note": {
+ "name": "note",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "sort_order": {
+ "name": "sort_order",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ }
+ },
+ "indexes": {
+ "artifact_condition_order_idx": {
+ "name": "artifact_condition_order_idx",
+ "columns": [
+ {
+ "expression": "guide_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "sort_order",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "artifact_condition_guide_id_guide_id_fk": {
+ "name": "artifact_condition_guide_id_guide_id_fk",
+ "tableFrom": "artifact_condition",
+ "tableTo": "guide",
+ "schemaTo": "guides",
+ "columnsFrom": [
+ "guide_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "guides.artifact_group": {
+ "name": "artifact_group",
+ "schema": "guides",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "condition_id": {
+ "name": "condition_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "sort_order": {
+ "name": "sort_order",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ }
+ },
+ "indexes": {
+ "artifact_group_order_idx": {
+ "name": "artifact_group_order_idx",
+ "columns": [
+ {
+ "expression": "condition_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "sort_order",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "artifact_group_condition_id_artifact_condition_id_fk": {
+ "name": "artifact_group_condition_id_artifact_condition_id_fk",
+ "tableFrom": "artifact_group",
+ "tableTo": "artifact_condition",
+ "schemaTo": "guides",
+ "columnsFrom": [
+ "condition_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "guides.artifact_profile": {
+ "name": "artifact_profile",
+ "schema": "guides",
+ "columns": {
+ "guide_id": {
+ "name": "guide_id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "sands": {
+ "name": "sands",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'[]'::jsonb"
+ },
+ "goblet": {
+ "name": "goblet",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'[]'::jsonb"
+ },
+ "circlet": {
+ "name": "circlet",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'[]'::jsonb"
+ },
+ "substats": {
+ "name": "substats",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'[]'::jsonb"
+ },
+ "recommended_conditions": {
+ "name": "recommended_conditions",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'[]'::jsonb"
+ },
+ "targets": {
+ "name": "targets",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'[]'::jsonb"
+ },
+ "target_conditions": {
+ "name": "target_conditions",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'[]'::jsonb"
+ },
+ "talents": {
+ "name": "talents",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'[]'::jsonb"
+ },
+ "note": {
+ "name": "note",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "artifact_profile_guide_id_guide_id_fk": {
+ "name": "artifact_profile_guide_id_guide_id_fk",
+ "tableFrom": "artifact_profile",
+ "tableTo": "guide",
+ "schemaTo": "guides",
+ "columnsFrom": [
+ "guide_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "guides.artifact_recommendation": {
+ "name": "artifact_recommendation",
+ "schema": "guides",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "group_id": {
+ "name": "group_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "first_artifact_key": {
+ "name": "first_artifact_key",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "second_artifact_key": {
+ "name": "second_artifact_key",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "percent": {
+ "name": "percent",
+ "type": "numeric(8, 2)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "note": {
+ "name": "note",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "sort_order": {
+ "name": "sort_order",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ }
+ },
+ "indexes": {
+ "artifact_recommendation_order_idx": {
+ "name": "artifact_recommendation_order_idx",
+ "columns": [
+ {
+ "expression": "group_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "sort_order",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "artifact_recommendation_group_id_artifact_group_id_fk": {
+ "name": "artifact_recommendation_group_id_artifact_group_id_fk",
+ "tableFrom": "artifact_recommendation",
+ "tableTo": "artifact_group",
+ "schemaTo": "guides",
+ "columnsFrom": [
+ "group_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.audit_log": {
+ "name": "audit_log",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "bigint",
+ "primaryKey": true,
+ "notNull": true,
+ "identity": {
+ "type": "always",
+ "name": "audit_log_id_seq",
+ "schema": "public",
+ "increment": "1",
+ "startWith": "1",
+ "minValue": "1",
+ "maxValue": "9223372036854775807",
+ "cache": "1",
+ "cycle": false
+ }
+ },
+ "author_id": {
+ "name": "author_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "author": {
+ "name": "author",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "text": {
+ "name": "text",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "details": {
+ "name": "details",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'{}'::jsonb"
+ },
+ "time": {
+ "name": "time",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "audit_log_time_idx": {
+ "name": "audit_log_time_idx",
+ "columns": [
+ {
+ "expression": "time",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "audit_log_author_time_idx": {
+ "name": "audit_log_author_time_idx",
+ "columns": [
+ {
+ "expression": "author_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "time",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "catalog.artifact": {
+ "name": "artifact",
+ "schema": "catalog",
+ "columns": {
+ "key": {
+ "name": "key",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "rarity": {
+ "name": "rarity",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "image_key": {
+ "name": "image_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {
+ "catalog_artifact_name_idx": {
+ "name": "catalog_artifact_name_idx",
+ "columns": [
+ {
+ "expression": "name",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "artifact_key_pk": {
+ "name": "artifact_key_pk",
+ "columns": [
+ "key"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "catalog.character": {
+ "name": "character",
+ "schema": "catalog",
+ "columns": {
+ "key": {
+ "name": "key",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "element": {
+ "name": "element",
+ "type": "varchar(48)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "rarity": {
+ "name": "rarity",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "weapon_type": {
+ "name": "weapon_type",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "image_key": {
+ "name": "image_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "element_icon_key": {
+ "name": "element_icon_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "coop_image_key": {
+ "name": "coop_image_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "name_card_image_key": {
+ "name": "name_card_image_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "constellations": {
+ "name": "constellations",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'[]'::jsonb"
+ },
+ "talents": {
+ "name": "talents",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'[]'::jsonb"
+ },
+ "ascension": {
+ "name": "ascension",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'[]'::jsonb"
+ },
+ "leveling": {
+ "name": "leveling",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'[]'::jsonb"
+ },
+ "base_stats": {
+ "name": "base_stats",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'[]'::jsonb"
+ }
+ },
+ "indexes": {
+ "catalog_character_name_idx": {
+ "name": "catalog_character_name_idx",
+ "columns": [
+ {
+ "expression": "name",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "character_key_pk": {
+ "name": "character_key_pk",
+ "columns": [
+ "key"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "catalog.material": {
+ "name": "material",
+ "schema": "catalog",
+ "columns": {
+ "key": {
+ "name": "key",
+ "type": "varchar(64)",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "rarity": {
+ "name": "rarity",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "type": {
+ "name": "type",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "image_key": {
+ "name": "image_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "catalog_material_name_idx": {
+ "name": "catalog_material_name_idx",
+ "columns": [
+ {
+ "expression": "name",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "catalog.weapon": {
+ "name": "weapon",
+ "schema": "catalog",
+ "columns": {
+ "key": {
+ "name": "key",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "rarity": {
+ "name": "rarity",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "weapon_type": {
+ "name": "weapon_type",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "image_key": {
+ "name": "image_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {
+ "catalog_weapon_name_idx": {
+ "name": "catalog_weapon_name_idx",
+ "columns": [
+ {
+ "expression": "name",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "weapon_key_pk": {
+ "name": "weapon_key_pk",
+ "columns": [
+ "key"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "commission.commission_checkout": {
+ "name": "commission_checkout",
+ "schema": "commission",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "request": {
+ "name": "request",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "amount_baht": {
+ "name": "amount_baht",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "commission_checkout_user_idx": {
+ "name": "commission_checkout_user_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "commission_checkout_user_id_user_id_fk": {
+ "name": "commission_checkout_user_id_user_id_fk",
+ "tableFrom": "commission_checkout",
+ "tableTo": "user",
+ "schemaTo": "auth",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "commission.commission_message": {
+ "name": "commission_message",
+ "schema": "commission",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "ticket_id": {
+ "name": "ticket_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "author_id": {
+ "name": "author_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "text": {
+ "name": "text",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "image_object_key": {
+ "name": "image_object_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "image_mime_type": {
+ "name": "image_mime_type",
+ "type": "varchar(32)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "commission_message_ticket_time_idx": {
+ "name": "commission_message_ticket_time_idx",
+ "columns": [
+ {
+ "expression": "ticket_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "commission_message_ticket_id_commission_ticket_id_fk": {
+ "name": "commission_message_ticket_id_commission_ticket_id_fk",
+ "tableFrom": "commission_message",
+ "tableTo": "commission_ticket",
+ "schemaTo": "commission",
+ "columnsFrom": [
+ "ticket_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ },
+ "commission_message_author_id_user_id_fk": {
+ "name": "commission_message_author_id_user_id_fk",
+ "tableFrom": "commission_message",
+ "tableTo": "user",
+ "schemaTo": "auth",
+ "columnsFrom": [
+ "author_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "commission.commission_payment": {
+ "name": "commission_payment",
+ "schema": "commission",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "checkout_id": {
+ "name": "checkout_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "ticket_id": {
+ "name": "ticket_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "reference_id": {
+ "name": "reference_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "trans_ref": {
+ "name": "trans_ref",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "amount_baht": {
+ "name": "amount_baht",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "transferred_at": {
+ "name": "transferred_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "slip_object_key": {
+ "name": "slip_object_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "slip_mime_type": {
+ "name": "slip_mime_type",
+ "type": "varchar(32)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "provider_data": {
+ "name": "provider_data",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "commission_payment_checkout_unique": {
+ "name": "commission_payment_checkout_unique",
+ "columns": [
+ {
+ "expression": "checkout_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "commission_payment_reference_unique": {
+ "name": "commission_payment_reference_unique",
+ "columns": [
+ {
+ "expression": "reference_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "commission_payment_trans_ref_unique": {
+ "name": "commission_payment_trans_ref_unique",
+ "columns": [
+ {
+ "expression": "trans_ref",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "commission_payment_checkout_id_commission_checkout_id_fk": {
+ "name": "commission_payment_checkout_id_commission_checkout_id_fk",
+ "tableFrom": "commission_payment",
+ "tableTo": "commission_checkout",
+ "schemaTo": "commission",
+ "columnsFrom": [
+ "checkout_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ },
+ "commission_payment_ticket_id_commission_ticket_id_fk": {
+ "name": "commission_payment_ticket_id_commission_ticket_id_fk",
+ "tableFrom": "commission_payment",
+ "tableTo": "commission_ticket",
+ "schemaTo": "commission",
+ "columnsFrom": [
+ "ticket_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "commission.commission_reaction": {
+ "name": "commission_reaction",
+ "schema": "commission",
+ "columns": {
+ "message_id": {
+ "name": "message_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "emoji": {
+ "name": "emoji",
+ "type": "varchar(16)",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "commission_reaction_message_id_commission_message_id_fk": {
+ "name": "commission_reaction_message_id_commission_message_id_fk",
+ "tableFrom": "commission_reaction",
+ "tableTo": "commission_message",
+ "schemaTo": "commission",
+ "columnsFrom": [
+ "message_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "commission_reaction_user_id_user_id_fk": {
+ "name": "commission_reaction_user_id_user_id_fk",
+ "tableFrom": "commission_reaction",
+ "tableTo": "user",
+ "schemaTo": "auth",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "commission_reaction_message_id_user_id_emoji_pk": {
+ "name": "commission_reaction_message_id_user_id_emoji_pk",
+ "columns": [
+ "message_id",
+ "user_id",
+ "emoji"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "commission.commission_ticket": {
+ "name": "commission_ticket",
+ "schema": "commission",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "checkout_id": {
+ "name": "checkout_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "status": {
+ "name": "status",
+ "type": "varchar(16)",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'open'"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "commission_ticket_checkout_unique": {
+ "name": "commission_ticket_checkout_unique",
+ "columns": [
+ {
+ "expression": "checkout_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "commission_ticket_user_idx": {
+ "name": "commission_ticket_user_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "commission_ticket_status_idx": {
+ "name": "commission_ticket_status_idx",
+ "columns": [
+ {
+ "expression": "status",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "updated_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "commission_ticket_checkout_id_commission_checkout_id_fk": {
+ "name": "commission_ticket_checkout_id_commission_checkout_id_fk",
+ "tableFrom": "commission_ticket",
+ "tableTo": "commission_checkout",
+ "schemaTo": "commission",
+ "columnsFrom": [
+ "checkout_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ },
+ "commission_ticket_user_id_user_id_fk": {
+ "name": "commission_ticket_user_id_user_id_fk",
+ "tableFrom": "commission_ticket",
+ "tableTo": "user",
+ "schemaTo": "auth",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "guides.constellation_condition": {
+ "name": "constellation_condition",
+ "schema": "guides",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "guide_id": {
+ "name": "guide_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "varchar(120)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "note": {
+ "name": "note",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "sort_order": {
+ "name": "sort_order",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ }
+ },
+ "indexes": {
+ "constellation_condition_order_idx": {
+ "name": "constellation_condition_order_idx",
+ "columns": [
+ {
+ "expression": "guide_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "sort_order",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "constellation_condition_guide_id_guide_id_fk": {
+ "name": "constellation_condition_guide_id_guide_id_fk",
+ "tableFrom": "constellation_condition",
+ "tableTo": "guide",
+ "schemaTo": "guides",
+ "columnsFrom": [
+ "guide_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "guides.constellation_recommendation": {
+ "name": "constellation_recommendation",
+ "schema": "guides",
+ "columns": {
+ "condition_id": {
+ "name": "condition_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "level": {
+ "name": "level",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "detail": {
+ "name": "detail",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "cumulative_percent": {
+ "name": "cumulative_percent",
+ "type": "numeric(8, 2)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "is_great": {
+ "name": "is_great",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "constellation_recommendation_condition_id_constellation_condition_id_fk": {
+ "name": "constellation_recommendation_condition_id_constellation_condition_id_fk",
+ "tableFrom": "constellation_recommendation",
+ "tableTo": "constellation_condition",
+ "schemaTo": "guides",
+ "columnsFrom": [
+ "condition_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "constellation_recommendation_condition_id_level_pk": {
+ "name": "constellation_recommendation_condition_id_level_pk",
+ "columns": [
+ "condition_id",
+ "level"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "guides.extra_section_row": {
+ "name": "extra_section_row",
+ "schema": "guides",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "section_id": {
+ "name": "section_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "media_id": {
+ "name": "media_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "heading": {
+ "name": "heading",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "body": {
+ "name": "body",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "percent": {
+ "name": "percent",
+ "type": "numeric(8, 2)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "background_color": {
+ "name": "background_color",
+ "type": "varchar(7)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "text_color": {
+ "name": "text_color",
+ "type": "varchar(7)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "highlights": {
+ "name": "highlights",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'[]'::jsonb"
+ },
+ "catalog_items": {
+ "name": "catalog_items",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'[]'::jsonb"
+ },
+ "sheet": {
+ "name": "sheet",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "sort_order": {
+ "name": "sort_order",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ }
+ },
+ "indexes": {
+ "extra_section_row_order_idx": {
+ "name": "extra_section_row_order_idx",
+ "columns": [
+ {
+ "expression": "section_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "sort_order",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "extra_section_row_section_id_extra_section_section_id_fk": {
+ "name": "extra_section_row_section_id_extra_section_section_id_fk",
+ "tableFrom": "extra_section_row",
+ "tableTo": "extra_section",
+ "schemaTo": "guides",
+ "columnsFrom": [
+ "section_id"
+ ],
+ "columnsTo": [
+ "section_id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "extra_section_row_media_id_media_id_fk": {
+ "name": "extra_section_row_media_id_media_id_fk",
+ "tableFrom": "extra_section_row",
+ "tableTo": "media",
+ "columnsFrom": [
+ "media_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "guides.extra_section": {
+ "name": "extra_section",
+ "schema": "guides",
+ "columns": {
+ "section_id": {
+ "name": "section_id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "extra_section_section_id_guide_section_id_fk": {
+ "name": "extra_section_section_id_guide_section_id_fk",
+ "tableFrom": "extra_section",
+ "tableTo": "guide_section",
+ "schemaTo": "guides",
+ "columnsFrom": [
+ "section_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "guides.glossary_alias": {
+ "name": "glossary_alias",
+ "schema": "guides",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "kind": {
+ "name": "kind",
+ "type": "varchar(16)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "catalog_key": {
+ "name": "catalog_key",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "alias": {
+ "name": "alias",
+ "type": "varchar(120)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "normalized_alias": {
+ "name": "normalized_alias",
+ "type": "varchar(120)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "glossary_alias_item_normalized_unique": {
+ "name": "glossary_alias_item_normalized_unique",
+ "columns": [
+ {
+ "expression": "kind",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "catalog_key",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "normalized_alias",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "glossary_alias_item_idx": {
+ "name": "glossary_alias_item_idx",
+ "columns": [
+ {
+ "expression": "kind",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "catalog_key",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "guides.guide_poster_bleeding": {
+ "name": "guide_poster_bleeding",
+ "schema": "guides",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "guide_id": {
+ "name": "guide_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "media_id": {
+ "name": "media_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "amount": {
+ "name": "amount",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 100
+ },
+ "scale": {
+ "name": "scale",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 100
+ },
+ "blend_mode": {
+ "name": "blend_mode",
+ "type": "varchar(32)",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'multiply'"
+ },
+ "sort_order": {
+ "name": "sort_order",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "guide_poster_bleeding_order_idx": {
+ "name": "guide_poster_bleeding_order_idx",
+ "columns": [
+ {
+ "expression": "guide_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "sort_order",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "guide_poster_bleeding_guide_id_guide_id_fk": {
+ "name": "guide_poster_bleeding_guide_id_guide_id_fk",
+ "tableFrom": "guide_poster_bleeding",
+ "tableTo": "guide",
+ "schemaTo": "guides",
+ "columnsFrom": [
+ "guide_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "guide_poster_bleeding_media_id_media_id_fk": {
+ "name": "guide_poster_bleeding_media_id_media_id_fk",
+ "tableFrom": "guide_poster_bleeding",
+ "tableTo": "media",
+ "columnsFrom": [
+ "media_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "restrict",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "guides.guide_section": {
+ "name": "guide_section",
+ "schema": "guides",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "guide_id": {
+ "name": "guide_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "kind": {
+ "name": "kind",
+ "type": "varchar(32)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "slug": {
+ "name": "slug",
+ "type": "varchar(120)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "note": {
+ "name": "note",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "sort_order": {
+ "name": "sort_order",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ }
+ },
+ "indexes": {
+ "guide_section_slug_unique": {
+ "name": "guide_section_slug_unique",
+ "columns": [
+ {
+ "expression": "guide_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "slug",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "guide_section_order_idx": {
+ "name": "guide_section_order_idx",
+ "columns": [
+ {
+ "expression": "guide_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "sort_order",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "guide_section_guide_id_guide_id_fk": {
+ "name": "guide_section_guide_id_guide_id_fk",
+ "tableFrom": "guide_section",
+ "tableTo": "guide",
+ "schemaTo": "guides",
+ "columnsFrom": [
+ "guide_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "guides.guide": {
+ "name": "guide",
+ "schema": "guides",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "character_key": {
+ "name": "character_key",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "slug": {
+ "name": "slug",
+ "type": "varchar(120)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "overview": {
+ "name": "overview",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "youtube_urls": {
+ "name": "youtube_urls",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'[]'::jsonb"
+ },
+ "cover_media_id": {
+ "name": "cover_media_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "poster_art_media_id": {
+ "name": "poster_art_media_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "poster_art_offset_x": {
+ "name": "poster_art_offset_x",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "poster_art_offset_y": {
+ "name": "poster_art_offset_y",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "poster_art_scale": {
+ "name": "poster_art_scale",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 100
+ },
+ "poster_art_rotation": {
+ "name": "poster_art_rotation",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "poster_top_colors": {
+ "name": "poster_top_colors",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'[\"#000000\",\"#000000\"]'::jsonb"
+ },
+ "poster_bottom_colors": {
+ "name": "poster_bottom_colors",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'[\"#000000\",\"#000000\"]'::jsonb"
+ },
+ "version": {
+ "name": "version",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 1
+ },
+ "is_public": {
+ "name": "is_public",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "published_at": {
+ "name": "published_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "sort_order": {
+ "name": "sort_order",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "trashed_at": {
+ "name": "trashed_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "guide_character_key_unique": {
+ "name": "guide_character_key_unique",
+ "columns": [
+ {
+ "expression": "character_key",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "guide_slug_unique": {
+ "name": "guide_slug_unique",
+ "columns": [
+ {
+ "expression": "slug",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "guide_directory_idx": {
+ "name": "guide_directory_idx",
+ "columns": [
+ {
+ "expression": "trashed_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "sort_order",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "guide_cover_media_id_media_id_fk": {
+ "name": "guide_cover_media_id_media_id_fk",
+ "tableFrom": "guide",
+ "tableTo": "media",
+ "columnsFrom": [
+ "cover_media_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "restrict",
+ "onUpdate": "no action"
+ },
+ "guide_poster_art_media_id_media_id_fk": {
+ "name": "guide_poster_art_media_id_media_id_fk",
+ "tableFrom": "guide",
+ "tableTo": "media",
+ "columnsFrom": [
+ "poster_art_media_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.media": {
+ "name": "media",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "object_key": {
+ "name": "object_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "file_name": {
+ "name": "file_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "mime_type": {
+ "name": "mime_type",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "byte_size": {
+ "name": "byte_size",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "checksum": {
+ "name": "checksum",
+ "type": "varchar(128)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "width": {
+ "name": "width",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "height": {
+ "name": "height",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "status": {
+ "name": "status",
+ "type": "varchar(24)",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'pending'"
+ },
+ "current_reference_count": {
+ "name": "current_reference_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "created_by_id": {
+ "name": "created_by_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "media_object_key_unique": {
+ "name": "media_object_key_unique",
+ "columns": [
+ {
+ "expression": "object_key",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "media_visibility_idx": {
+ "name": "media_visibility_idx",
+ "columns": [
+ {
+ "expression": "status",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "current_reference_count",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "media_created_by_id_user_id_fk": {
+ "name": "media_created_by_id_user_id_fk",
+ "tableFrom": "media",
+ "tableTo": "user",
+ "schemaTo": "auth",
+ "columnsFrom": [
+ "created_by_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.outbox_event": {
+ "name": "outbox_event",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "bigint",
+ "primaryKey": true,
+ "notNull": true,
+ "identity": {
+ "type": "always",
+ "name": "outbox_event_id_seq",
+ "schema": "public",
+ "increment": "1",
+ "startWith": "1",
+ "minValue": "1",
+ "maxValue": "9223372036854775807",
+ "cache": "1",
+ "cycle": false
+ }
+ },
+ "topic": {
+ "name": "topic",
+ "type": "varchar(160)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "aggregate_id": {
+ "name": "aggregate_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "event_type": {
+ "name": "event_type",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "payload": {
+ "name": "payload",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "status": {
+ "name": "status",
+ "type": "varchar(24)",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'pending'"
+ },
+ "attempts": {
+ "name": "attempts",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "available_at": {
+ "name": "available_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "processed_at": {
+ "name": "processed_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_error": {
+ "name": "last_error",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "outbox_pending_idx": {
+ "name": "outbox_pending_idx",
+ "columns": [
+ {
+ "expression": "status",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "available_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "auth.session": {
+ "name": "session",
+ "schema": "auth",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "token": {
+ "name": "token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "ip_address": {
+ "name": "ip_address",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "user_agent": {
+ "name": "user_agent",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "impersonated_by": {
+ "name": "impersonated_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "session_token_unique": {
+ "name": "session_token_unique",
+ "columns": [
+ {
+ "expression": "token",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "session_user_id_idx": {
+ "name": "session_user_id_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "session_user_id_user_id_fk": {
+ "name": "session_user_id_user_id_fk",
+ "tableFrom": "session",
+ "tableTo": "user",
+ "schemaTo": "auth",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "guides.stygian_boss": {
+ "name": "stygian_boss",
+ "schema": "guides",
+ "columns": {
+ "schedule_id": {
+ "name": "schedule_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "level": {
+ "name": "level",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "position": {
+ "name": "position",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "config_id": {
+ "name": "config_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "hp": {
+ "name": "hp",
+ "type": "numeric(18, 2)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "mechanics": {
+ "name": "mechanics",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'[]'::jsonb"
+ },
+ "recommended_mechanics": {
+ "name": "recommended_mechanics",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'[]'::jsonb"
+ },
+ "recommended_mechanic_types": {
+ "name": "recommended_mechanic_types",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'[]'::jsonb"
+ },
+ "image_key": {
+ "name": "image_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {
+ "stygian_boss_schedule_idx": {
+ "name": "stygian_boss_schedule_idx",
+ "columns": [
+ {
+ "expression": "schedule_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "level",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "stygian_boss_schedule_id_stygian_schedule_schedule_id_fk": {
+ "name": "stygian_boss_schedule_id_stygian_schedule_schedule_id_fk",
+ "tableFrom": "stygian_boss",
+ "tableTo": "stygian_schedule",
+ "schemaTo": "guides",
+ "columnsFrom": [
+ "schedule_id"
+ ],
+ "columnsTo": [
+ "schedule_id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "stygian_boss_schedule_id_level_position_pk": {
+ "name": "stygian_boss_schedule_id_level_position_pk",
+ "columns": [
+ "schedule_id",
+ "level",
+ "position"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "guides.stygian_recommendation_group": {
+ "name": "stygian_recommendation_group",
+ "schema": "guides",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "schedule_id": {
+ "name": "schedule_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "level": {
+ "name": "level",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "boss_position": {
+ "name": "boss_position",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "kind": {
+ "name": "kind",
+ "type": "varchar(16)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "sort_order": {
+ "name": "sort_order",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ }
+ },
+ "indexes": {
+ "stygian_recommendation_group_order_idx": {
+ "name": "stygian_recommendation_group_order_idx",
+ "columns": [
+ {
+ "expression": "schedule_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "level",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "boss_position",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "kind",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "sort_order",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "stygian_recommendation_group_schedule_id_stygian_schedule_schedule_id_fk": {
+ "name": "stygian_recommendation_group_schedule_id_stygian_schedule_schedule_id_fk",
+ "tableFrom": "stygian_recommendation_group",
+ "tableTo": "stygian_schedule",
+ "schemaTo": "guides",
+ "columnsFrom": [
+ "schedule_id"
+ ],
+ "columnsTo": [
+ "schedule_id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "guides.stygian_recommendation_item": {
+ "name": "stygian_recommendation_item",
+ "schema": "guides",
+ "columns": {
+ "group_id": {
+ "name": "group_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "position": {
+ "name": "position",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "kind": {
+ "name": "kind",
+ "type": "varchar(24)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "catalog_key": {
+ "name": "catalog_key",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "weapon_key": {
+ "name": "weapon_key",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "artifact_key": {
+ "name": "artifact_key",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "refinement": {
+ "name": "refinement",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "constellation_level": {
+ "name": "constellation_level",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "element": {
+ "name": "element",
+ "type": "varchar(24)",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "stygian_recommendation_item_group_id_stygian_recommendation_group_id_fk": {
+ "name": "stygian_recommendation_item_group_id_stygian_recommendation_group_id_fk",
+ "tableFrom": "stygian_recommendation_item",
+ "tableTo": "stygian_recommendation_group",
+ "schemaTo": "guides",
+ "columnsFrom": [
+ "group_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "stygian_recommendation_item_group_id_position_pk": {
+ "name": "stygian_recommendation_item_group_id_position_pk",
+ "columns": [
+ "group_id",
+ "position"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "guides.stygian_schedule": {
+ "name": "stygian_schedule",
+ "schema": "guides",
+ "columns": {
+ "schedule_id": {
+ "name": "schedule_id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "challenge_name": {
+ "name": "challenge_name",
+ "type": "varchar(32)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "schedule_start_time": {
+ "name": "schedule_start_time",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "schedule_end_time": {
+ "name": "schedule_end_time",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "version": {
+ "name": "version",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 1
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "guides.team_composition": {
+ "name": "team_composition",
+ "schema": "guides",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "guide_id": {
+ "name": "guide_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "condition_id": {
+ "name": "condition_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "rotation_seconds": {
+ "name": "rotation_seconds",
+ "type": "numeric(8, 2)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "combo": {
+ "name": "combo",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "sort_order": {
+ "name": "sort_order",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ }
+ },
+ "indexes": {
+ "team_composition_order_idx": {
+ "name": "team_composition_order_idx",
+ "columns": [
+ {
+ "expression": "guide_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "sort_order",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "team_composition_guide_id_guide_id_fk": {
+ "name": "team_composition_guide_id_guide_id_fk",
+ "tableFrom": "team_composition",
+ "tableTo": "guide",
+ "schemaTo": "guides",
+ "columnsFrom": [
+ "guide_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "team_composition_condition_id_team_condition_id_fk": {
+ "name": "team_composition_condition_id_team_condition_id_fk",
+ "tableFrom": "team_composition",
+ "tableTo": "team_condition",
+ "schemaTo": "guides",
+ "columnsFrom": [
+ "condition_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "guides.team_condition": {
+ "name": "team_condition",
+ "schema": "guides",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "guide_id": {
+ "name": "guide_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "varchar(120)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "note": {
+ "name": "note",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "sort_order": {
+ "name": "sort_order",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ }
+ },
+ "indexes": {
+ "team_condition_order_idx": {
+ "name": "team_condition_order_idx",
+ "columns": [
+ {
+ "expression": "guide_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "sort_order",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "team_condition_guide_id_guide_id_fk": {
+ "name": "team_condition_guide_id_guide_id_fk",
+ "tableFrom": "team_condition",
+ "tableTo": "guide",
+ "schemaTo": "guides",
+ "columnsFrom": [
+ "guide_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "guides.team_member": {
+ "name": "team_member",
+ "schema": "guides",
+ "columns": {
+ "team_id": {
+ "name": "team_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "position": {
+ "name": "position",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "character_key": {
+ "name": "character_key",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "weapon_key": {
+ "name": "weapon_key",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "artifact_key": {
+ "name": "artifact_key",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "second_artifact_key": {
+ "name": "second_artifact_key",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "constellation": {
+ "name": "constellation",
+ "type": "varchar(8)",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'0'"
+ },
+ "refinement": {
+ "name": "refinement",
+ "type": "varchar(8)",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'1'"
+ },
+ "damage": {
+ "name": "damage",
+ "type": "numeric(18, 2)",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "team_member_team_id_team_composition_id_fk": {
+ "name": "team_member_team_id_team_composition_id_fk",
+ "tableFrom": "team_member",
+ "tableTo": "team_composition",
+ "schemaTo": "guides",
+ "columnsFrom": [
+ "team_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "team_member_team_id_position_pk": {
+ "name": "team_member_team_id_position_pk",
+ "columns": [
+ "team_id",
+ "position"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "auth.user": {
+ "name": "user",
+ "schema": "auth",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "email": {
+ "name": "email",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "email_verified": {
+ "name": "email_verified",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "image": {
+ "name": "image",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "role": {
+ "name": "role",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'user'"
+ },
+ "banned": {
+ "name": "banned",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": false
+ },
+ "ban_reason": {
+ "name": "ban_reason",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "ban_expires": {
+ "name": "ban_expires",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "user_email_unique": {
+ "name": "user_email_unique",
+ "columns": [
+ {
+ "expression": "email",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "auth.verification": {
+ "name": "verification",
+ "schema": "auth",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "identifier": {
+ "name": "identifier",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "value": {
+ "name": "value",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "verification_identifier_idx": {
+ "name": "verification_identifier_idx",
+ "columns": [
+ {
+ "expression": "identifier",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "guides.weapon_condition": {
+ "name": "weapon_condition",
+ "schema": "guides",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "guide_id": {
+ "name": "guide_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "varchar(120)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "note": {
+ "name": "note",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "sort_order": {
+ "name": "sort_order",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ }
+ },
+ "indexes": {
+ "weapon_condition_order_idx": {
+ "name": "weapon_condition_order_idx",
+ "columns": [
+ {
+ "expression": "guide_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "sort_order",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "weapon_condition_guide_id_guide_id_fk": {
+ "name": "weapon_condition_guide_id_guide_id_fk",
+ "tableFrom": "weapon_condition",
+ "tableTo": "guide",
+ "schemaTo": "guides",
+ "columnsFrom": [
+ "guide_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "guides.weapon_recommendation": {
+ "name": "weapon_recommendation",
+ "schema": "guides",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "condition_id": {
+ "name": "condition_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "weapon_key": {
+ "name": "weapon_key",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "refinement": {
+ "name": "refinement",
+ "type": "varchar(8)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "overall_percent": {
+ "name": "overall_percent",
+ "type": "numeric(8, 2)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "personal_percent": {
+ "name": "personal_percent",
+ "type": "numeric(8, 2)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "note": {
+ "name": "note",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "sort_order": {
+ "name": "sort_order",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ }
+ },
+ "indexes": {
+ "weapon_recommendation_order_idx": {
+ "name": "weapon_recommendation_order_idx",
+ "columns": [
+ {
+ "expression": "condition_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "sort_order",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "weapon_recommendation_condition_id_weapon_condition_id_fk": {
+ "name": "weapon_recommendation_condition_id_weapon_condition_id_fk",
+ "tableFrom": "weapon_recommendation",
+ "tableTo": "weapon_condition",
+ "schemaTo": "guides",
+ "columnsFrom": [
+ "condition_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ }
+ },
+ "enums": {},
+ "schemas": {},
+ "sequences": {},
+ "roles": {},
+ "policies": {},
+ "views": {},
+ "_meta": {
+ "columns": {},
+ "schemas": {},
+ "tables": {}
+ }
+}
\ No newline at end of file
diff --git a/drizzle/meta/0065_snapshot.json b/drizzle/meta/0065_snapshot.json
new file mode 100644
index 0000000..ab2d5ba
--- /dev/null
+++ b/drizzle/meta/0065_snapshot.json
@@ -0,0 +1,3840 @@
+{
+ "id": "34b1f9a3-9b86-4eb0-8e44-64b3814f9ea9",
+ "prevId": "a5fa5593-934e-47a3-ba1b-26e121f603a2",
+ "version": "7",
+ "dialect": "postgresql",
+ "tables": {
+ "auth.account": {
+ "name": "account",
+ "schema": "auth",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "issuer": {
+ "name": "issuer",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "account_id": {
+ "name": "account_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "provider_id": {
+ "name": "provider_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "access_token": {
+ "name": "access_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "refresh_token": {
+ "name": "refresh_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "id_token": {
+ "name": "id_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "access_token_expires_at": {
+ "name": "access_token_expires_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "refresh_token_expires_at": {
+ "name": "refresh_token_expires_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "scope": {
+ "name": "scope",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "password": {
+ "name": "password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "account_provider_unique": {
+ "name": "account_provider_unique",
+ "columns": [
+ {
+ "expression": "provider_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "account_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "account_user_id_idx": {
+ "name": "account_user_id_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "account_user_id_user_id_fk": {
+ "name": "account_user_id_user_id_fk",
+ "tableFrom": "account",
+ "tableTo": "user",
+ "schemaTo": "auth",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "guides.artifact_condition": {
+ "name": "artifact_condition",
+ "schema": "guides",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "guide_id": {
+ "name": "guide_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "varchar(120)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "note": {
+ "name": "note",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "sort_order": {
+ "name": "sort_order",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ }
+ },
+ "indexes": {
+ "artifact_condition_order_idx": {
+ "name": "artifact_condition_order_idx",
+ "columns": [
+ {
+ "expression": "guide_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "sort_order",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "artifact_condition_guide_id_guide_id_fk": {
+ "name": "artifact_condition_guide_id_guide_id_fk",
+ "tableFrom": "artifact_condition",
+ "tableTo": "guide",
+ "schemaTo": "guides",
+ "columnsFrom": [
+ "guide_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "guides.artifact_group": {
+ "name": "artifact_group",
+ "schema": "guides",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "condition_id": {
+ "name": "condition_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "sort_order": {
+ "name": "sort_order",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ }
+ },
+ "indexes": {
+ "artifact_group_order_idx": {
+ "name": "artifact_group_order_idx",
+ "columns": [
+ {
+ "expression": "condition_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "sort_order",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "artifact_group_condition_id_artifact_condition_id_fk": {
+ "name": "artifact_group_condition_id_artifact_condition_id_fk",
+ "tableFrom": "artifact_group",
+ "tableTo": "artifact_condition",
+ "schemaTo": "guides",
+ "columnsFrom": [
+ "condition_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "guides.artifact_profile": {
+ "name": "artifact_profile",
+ "schema": "guides",
+ "columns": {
+ "guide_id": {
+ "name": "guide_id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "sands": {
+ "name": "sands",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'[]'::jsonb"
+ },
+ "goblet": {
+ "name": "goblet",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'[]'::jsonb"
+ },
+ "circlet": {
+ "name": "circlet",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'[]'::jsonb"
+ },
+ "substats": {
+ "name": "substats",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'[]'::jsonb"
+ },
+ "recommended_conditions": {
+ "name": "recommended_conditions",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'[]'::jsonb"
+ },
+ "targets": {
+ "name": "targets",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'[]'::jsonb"
+ },
+ "target_conditions": {
+ "name": "target_conditions",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'[]'::jsonb"
+ },
+ "talents": {
+ "name": "talents",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'[]'::jsonb"
+ },
+ "note": {
+ "name": "note",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "artifact_profile_guide_id_guide_id_fk": {
+ "name": "artifact_profile_guide_id_guide_id_fk",
+ "tableFrom": "artifact_profile",
+ "tableTo": "guide",
+ "schemaTo": "guides",
+ "columnsFrom": [
+ "guide_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "guides.artifact_recommendation": {
+ "name": "artifact_recommendation",
+ "schema": "guides",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "group_id": {
+ "name": "group_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "first_artifact_key": {
+ "name": "first_artifact_key",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "second_artifact_key": {
+ "name": "second_artifact_key",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "percent": {
+ "name": "percent",
+ "type": "numeric(8, 2)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "note": {
+ "name": "note",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "sort_order": {
+ "name": "sort_order",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ }
+ },
+ "indexes": {
+ "artifact_recommendation_order_idx": {
+ "name": "artifact_recommendation_order_idx",
+ "columns": [
+ {
+ "expression": "group_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "sort_order",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "artifact_recommendation_group_id_artifact_group_id_fk": {
+ "name": "artifact_recommendation_group_id_artifact_group_id_fk",
+ "tableFrom": "artifact_recommendation",
+ "tableTo": "artifact_group",
+ "schemaTo": "guides",
+ "columnsFrom": [
+ "group_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.audit_log": {
+ "name": "audit_log",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "bigint",
+ "primaryKey": true,
+ "notNull": true,
+ "identity": {
+ "type": "always",
+ "name": "audit_log_id_seq",
+ "schema": "public",
+ "increment": "1",
+ "startWith": "1",
+ "minValue": "1",
+ "maxValue": "9223372036854775807",
+ "cache": "1",
+ "cycle": false
+ }
+ },
+ "author_id": {
+ "name": "author_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "author": {
+ "name": "author",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "text": {
+ "name": "text",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "details": {
+ "name": "details",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'{}'::jsonb"
+ },
+ "time": {
+ "name": "time",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "audit_log_time_idx": {
+ "name": "audit_log_time_idx",
+ "columns": [
+ {
+ "expression": "time",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "audit_log_author_time_idx": {
+ "name": "audit_log_author_time_idx",
+ "columns": [
+ {
+ "expression": "author_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "time",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "catalog.artifact": {
+ "name": "artifact",
+ "schema": "catalog",
+ "columns": {
+ "key": {
+ "name": "key",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "rarity": {
+ "name": "rarity",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "image_key": {
+ "name": "image_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {
+ "catalog_artifact_name_idx": {
+ "name": "catalog_artifact_name_idx",
+ "columns": [
+ {
+ "expression": "name",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "artifact_key_pk": {
+ "name": "artifact_key_pk",
+ "columns": [
+ "key"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "catalog.character": {
+ "name": "character",
+ "schema": "catalog",
+ "columns": {
+ "key": {
+ "name": "key",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "element": {
+ "name": "element",
+ "type": "varchar(48)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "rarity": {
+ "name": "rarity",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "weapon_type": {
+ "name": "weapon_type",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "image_key": {
+ "name": "image_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "element_icon_key": {
+ "name": "element_icon_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "coop_image_key": {
+ "name": "coop_image_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "name_card_image_key": {
+ "name": "name_card_image_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "constellations": {
+ "name": "constellations",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'[]'::jsonb"
+ },
+ "talents": {
+ "name": "talents",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'[]'::jsonb"
+ },
+ "ascension": {
+ "name": "ascension",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'[]'::jsonb"
+ },
+ "leveling": {
+ "name": "leveling",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'[]'::jsonb"
+ },
+ "base_stats": {
+ "name": "base_stats",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'[]'::jsonb"
+ }
+ },
+ "indexes": {
+ "catalog_character_name_idx": {
+ "name": "catalog_character_name_idx",
+ "columns": [
+ {
+ "expression": "name",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "character_key_pk": {
+ "name": "character_key_pk",
+ "columns": [
+ "key"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "catalog.material": {
+ "name": "material",
+ "schema": "catalog",
+ "columns": {
+ "key": {
+ "name": "key",
+ "type": "varchar(64)",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "rarity": {
+ "name": "rarity",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "type": {
+ "name": "type",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "image_key": {
+ "name": "image_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "catalog_material_name_idx": {
+ "name": "catalog_material_name_idx",
+ "columns": [
+ {
+ "expression": "name",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "catalog.weapon": {
+ "name": "weapon",
+ "schema": "catalog",
+ "columns": {
+ "key": {
+ "name": "key",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "rarity": {
+ "name": "rarity",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "weapon_type": {
+ "name": "weapon_type",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "image_key": {
+ "name": "image_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {
+ "catalog_weapon_name_idx": {
+ "name": "catalog_weapon_name_idx",
+ "columns": [
+ {
+ "expression": "name",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "weapon_key_pk": {
+ "name": "weapon_key_pk",
+ "columns": [
+ "key"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "commission.checkout": {
+ "name": "checkout",
+ "schema": "commission",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "request": {
+ "name": "request",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "amount_baht": {
+ "name": "amount_baht",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "commission_checkout_user_idx": {
+ "name": "commission_checkout_user_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "checkout_user_id_user_id_fk": {
+ "name": "checkout_user_id_user_id_fk",
+ "tableFrom": "checkout",
+ "tableTo": "user",
+ "schemaTo": "auth",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "commission.message": {
+ "name": "message",
+ "schema": "commission",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "ticket_id": {
+ "name": "ticket_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "author_id": {
+ "name": "author_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "text": {
+ "name": "text",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "image_object_key": {
+ "name": "image_object_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "image_mime_type": {
+ "name": "image_mime_type",
+ "type": "varchar(32)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "commission_message_ticket_time_idx": {
+ "name": "commission_message_ticket_time_idx",
+ "columns": [
+ {
+ "expression": "ticket_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "message_ticket_id_ticket_id_fk": {
+ "name": "message_ticket_id_ticket_id_fk",
+ "tableFrom": "message",
+ "tableTo": "ticket",
+ "schemaTo": "commission",
+ "columnsFrom": [
+ "ticket_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ },
+ "message_author_id_user_id_fk": {
+ "name": "message_author_id_user_id_fk",
+ "tableFrom": "message",
+ "tableTo": "user",
+ "schemaTo": "auth",
+ "columnsFrom": [
+ "author_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "commission.payment": {
+ "name": "payment",
+ "schema": "commission",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "checkout_id": {
+ "name": "checkout_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "ticket_id": {
+ "name": "ticket_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "reference_id": {
+ "name": "reference_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "trans_ref": {
+ "name": "trans_ref",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "amount_baht": {
+ "name": "amount_baht",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "transferred_at": {
+ "name": "transferred_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "slip_object_key": {
+ "name": "slip_object_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "slip_mime_type": {
+ "name": "slip_mime_type",
+ "type": "varchar(32)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "provider_data": {
+ "name": "provider_data",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "commission_payment_checkout_unique": {
+ "name": "commission_payment_checkout_unique",
+ "columns": [
+ {
+ "expression": "checkout_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "commission_payment_reference_unique": {
+ "name": "commission_payment_reference_unique",
+ "columns": [
+ {
+ "expression": "reference_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "commission_payment_trans_ref_unique": {
+ "name": "commission_payment_trans_ref_unique",
+ "columns": [
+ {
+ "expression": "trans_ref",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "payment_checkout_id_checkout_id_fk": {
+ "name": "payment_checkout_id_checkout_id_fk",
+ "tableFrom": "payment",
+ "tableTo": "checkout",
+ "schemaTo": "commission",
+ "columnsFrom": [
+ "checkout_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ },
+ "payment_ticket_id_ticket_id_fk": {
+ "name": "payment_ticket_id_ticket_id_fk",
+ "tableFrom": "payment",
+ "tableTo": "ticket",
+ "schemaTo": "commission",
+ "columnsFrom": [
+ "ticket_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "commission.reaction": {
+ "name": "reaction",
+ "schema": "commission",
+ "columns": {
+ "message_id": {
+ "name": "message_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "emoji": {
+ "name": "emoji",
+ "type": "varchar(16)",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "reaction_message_id_message_id_fk": {
+ "name": "reaction_message_id_message_id_fk",
+ "tableFrom": "reaction",
+ "tableTo": "message",
+ "schemaTo": "commission",
+ "columnsFrom": [
+ "message_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "reaction_user_id_user_id_fk": {
+ "name": "reaction_user_id_user_id_fk",
+ "tableFrom": "reaction",
+ "tableTo": "user",
+ "schemaTo": "auth",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "reaction_message_id_user_id_emoji_pk": {
+ "name": "reaction_message_id_user_id_emoji_pk",
+ "columns": [
+ "message_id",
+ "user_id",
+ "emoji"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "commission.ticket": {
+ "name": "ticket",
+ "schema": "commission",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "checkout_id": {
+ "name": "checkout_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "status": {
+ "name": "status",
+ "type": "varchar(16)",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'open'"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "commission_ticket_checkout_unique": {
+ "name": "commission_ticket_checkout_unique",
+ "columns": [
+ {
+ "expression": "checkout_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "commission_ticket_user_idx": {
+ "name": "commission_ticket_user_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "commission_ticket_status_idx": {
+ "name": "commission_ticket_status_idx",
+ "columns": [
+ {
+ "expression": "status",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "updated_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "ticket_checkout_id_checkout_id_fk": {
+ "name": "ticket_checkout_id_checkout_id_fk",
+ "tableFrom": "ticket",
+ "tableTo": "checkout",
+ "schemaTo": "commission",
+ "columnsFrom": [
+ "checkout_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ },
+ "ticket_user_id_user_id_fk": {
+ "name": "ticket_user_id_user_id_fk",
+ "tableFrom": "ticket",
+ "tableTo": "user",
+ "schemaTo": "auth",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "guides.constellation_condition": {
+ "name": "constellation_condition",
+ "schema": "guides",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "guide_id": {
+ "name": "guide_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "varchar(120)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "note": {
+ "name": "note",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "sort_order": {
+ "name": "sort_order",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ }
+ },
+ "indexes": {
+ "constellation_condition_order_idx": {
+ "name": "constellation_condition_order_idx",
+ "columns": [
+ {
+ "expression": "guide_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "sort_order",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "constellation_condition_guide_id_guide_id_fk": {
+ "name": "constellation_condition_guide_id_guide_id_fk",
+ "tableFrom": "constellation_condition",
+ "tableTo": "guide",
+ "schemaTo": "guides",
+ "columnsFrom": [
+ "guide_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "guides.constellation_recommendation": {
+ "name": "constellation_recommendation",
+ "schema": "guides",
+ "columns": {
+ "condition_id": {
+ "name": "condition_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "level": {
+ "name": "level",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "detail": {
+ "name": "detail",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "cumulative_percent": {
+ "name": "cumulative_percent",
+ "type": "numeric(8, 2)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "is_great": {
+ "name": "is_great",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "constellation_recommendation_condition_id_constellation_condition_id_fk": {
+ "name": "constellation_recommendation_condition_id_constellation_condition_id_fk",
+ "tableFrom": "constellation_recommendation",
+ "tableTo": "constellation_condition",
+ "schemaTo": "guides",
+ "columnsFrom": [
+ "condition_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "constellation_recommendation_condition_id_level_pk": {
+ "name": "constellation_recommendation_condition_id_level_pk",
+ "columns": [
+ "condition_id",
+ "level"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "guides.extra_section_row": {
+ "name": "extra_section_row",
+ "schema": "guides",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "section_id": {
+ "name": "section_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "media_id": {
+ "name": "media_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "heading": {
+ "name": "heading",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "body": {
+ "name": "body",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "percent": {
+ "name": "percent",
+ "type": "numeric(8, 2)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "background_color": {
+ "name": "background_color",
+ "type": "varchar(7)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "text_color": {
+ "name": "text_color",
+ "type": "varchar(7)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "highlights": {
+ "name": "highlights",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'[]'::jsonb"
+ },
+ "catalog_items": {
+ "name": "catalog_items",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'[]'::jsonb"
+ },
+ "sheet": {
+ "name": "sheet",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "sort_order": {
+ "name": "sort_order",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ }
+ },
+ "indexes": {
+ "extra_section_row_order_idx": {
+ "name": "extra_section_row_order_idx",
+ "columns": [
+ {
+ "expression": "section_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "sort_order",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "extra_section_row_section_id_extra_section_section_id_fk": {
+ "name": "extra_section_row_section_id_extra_section_section_id_fk",
+ "tableFrom": "extra_section_row",
+ "tableTo": "extra_section",
+ "schemaTo": "guides",
+ "columnsFrom": [
+ "section_id"
+ ],
+ "columnsTo": [
+ "section_id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "extra_section_row_media_id_media_id_fk": {
+ "name": "extra_section_row_media_id_media_id_fk",
+ "tableFrom": "extra_section_row",
+ "tableTo": "media",
+ "columnsFrom": [
+ "media_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "guides.extra_section": {
+ "name": "extra_section",
+ "schema": "guides",
+ "columns": {
+ "section_id": {
+ "name": "section_id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "extra_section_section_id_guide_section_id_fk": {
+ "name": "extra_section_section_id_guide_section_id_fk",
+ "tableFrom": "extra_section",
+ "tableTo": "guide_section",
+ "schemaTo": "guides",
+ "columnsFrom": [
+ "section_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "guides.glossary_alias": {
+ "name": "glossary_alias",
+ "schema": "guides",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "kind": {
+ "name": "kind",
+ "type": "varchar(16)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "catalog_key": {
+ "name": "catalog_key",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "alias": {
+ "name": "alias",
+ "type": "varchar(120)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "normalized_alias": {
+ "name": "normalized_alias",
+ "type": "varchar(120)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "glossary_alias_item_normalized_unique": {
+ "name": "glossary_alias_item_normalized_unique",
+ "columns": [
+ {
+ "expression": "kind",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "catalog_key",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "normalized_alias",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "glossary_alias_item_idx": {
+ "name": "glossary_alias_item_idx",
+ "columns": [
+ {
+ "expression": "kind",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "catalog_key",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "guides.guide_poster_bleeding": {
+ "name": "guide_poster_bleeding",
+ "schema": "guides",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "guide_id": {
+ "name": "guide_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "media_id": {
+ "name": "media_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "amount": {
+ "name": "amount",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 100
+ },
+ "scale": {
+ "name": "scale",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 100
+ },
+ "blend_mode": {
+ "name": "blend_mode",
+ "type": "varchar(32)",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'multiply'"
+ },
+ "sort_order": {
+ "name": "sort_order",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "guide_poster_bleeding_order_idx": {
+ "name": "guide_poster_bleeding_order_idx",
+ "columns": [
+ {
+ "expression": "guide_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "sort_order",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "guide_poster_bleeding_guide_id_guide_id_fk": {
+ "name": "guide_poster_bleeding_guide_id_guide_id_fk",
+ "tableFrom": "guide_poster_bleeding",
+ "tableTo": "guide",
+ "schemaTo": "guides",
+ "columnsFrom": [
+ "guide_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "guide_poster_bleeding_media_id_media_id_fk": {
+ "name": "guide_poster_bleeding_media_id_media_id_fk",
+ "tableFrom": "guide_poster_bleeding",
+ "tableTo": "media",
+ "columnsFrom": [
+ "media_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "restrict",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "guides.guide_section": {
+ "name": "guide_section",
+ "schema": "guides",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "guide_id": {
+ "name": "guide_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "kind": {
+ "name": "kind",
+ "type": "varchar(32)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "slug": {
+ "name": "slug",
+ "type": "varchar(120)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "note": {
+ "name": "note",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "sort_order": {
+ "name": "sort_order",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ }
+ },
+ "indexes": {
+ "guide_section_slug_unique": {
+ "name": "guide_section_slug_unique",
+ "columns": [
+ {
+ "expression": "guide_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "slug",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "guide_section_order_idx": {
+ "name": "guide_section_order_idx",
+ "columns": [
+ {
+ "expression": "guide_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "sort_order",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "guide_section_guide_id_guide_id_fk": {
+ "name": "guide_section_guide_id_guide_id_fk",
+ "tableFrom": "guide_section",
+ "tableTo": "guide",
+ "schemaTo": "guides",
+ "columnsFrom": [
+ "guide_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "guides.guide": {
+ "name": "guide",
+ "schema": "guides",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "character_key": {
+ "name": "character_key",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "slug": {
+ "name": "slug",
+ "type": "varchar(120)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "overview": {
+ "name": "overview",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "youtube_urls": {
+ "name": "youtube_urls",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'[]'::jsonb"
+ },
+ "cover_media_id": {
+ "name": "cover_media_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "poster_art_media_id": {
+ "name": "poster_art_media_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "poster_art_offset_x": {
+ "name": "poster_art_offset_x",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "poster_art_offset_y": {
+ "name": "poster_art_offset_y",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "poster_art_scale": {
+ "name": "poster_art_scale",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 100
+ },
+ "poster_art_rotation": {
+ "name": "poster_art_rotation",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "poster_top_colors": {
+ "name": "poster_top_colors",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'[\"#000000\",\"#000000\"]'::jsonb"
+ },
+ "poster_bottom_colors": {
+ "name": "poster_bottom_colors",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'[\"#000000\",\"#000000\"]'::jsonb"
+ },
+ "version": {
+ "name": "version",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 1
+ },
+ "is_public": {
+ "name": "is_public",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "published_at": {
+ "name": "published_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "sort_order": {
+ "name": "sort_order",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "trashed_at": {
+ "name": "trashed_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "guide_character_key_unique": {
+ "name": "guide_character_key_unique",
+ "columns": [
+ {
+ "expression": "character_key",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "guide_slug_unique": {
+ "name": "guide_slug_unique",
+ "columns": [
+ {
+ "expression": "slug",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "guide_directory_idx": {
+ "name": "guide_directory_idx",
+ "columns": [
+ {
+ "expression": "trashed_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "sort_order",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "guide_cover_media_id_media_id_fk": {
+ "name": "guide_cover_media_id_media_id_fk",
+ "tableFrom": "guide",
+ "tableTo": "media",
+ "columnsFrom": [
+ "cover_media_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "restrict",
+ "onUpdate": "no action"
+ },
+ "guide_poster_art_media_id_media_id_fk": {
+ "name": "guide_poster_art_media_id_media_id_fk",
+ "tableFrom": "guide",
+ "tableTo": "media",
+ "columnsFrom": [
+ "poster_art_media_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.media": {
+ "name": "media",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "object_key": {
+ "name": "object_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "file_name": {
+ "name": "file_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "mime_type": {
+ "name": "mime_type",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "byte_size": {
+ "name": "byte_size",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "checksum": {
+ "name": "checksum",
+ "type": "varchar(128)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "width": {
+ "name": "width",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "height": {
+ "name": "height",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "status": {
+ "name": "status",
+ "type": "varchar(24)",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'pending'"
+ },
+ "current_reference_count": {
+ "name": "current_reference_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "created_by_id": {
+ "name": "created_by_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "media_object_key_unique": {
+ "name": "media_object_key_unique",
+ "columns": [
+ {
+ "expression": "object_key",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "media_visibility_idx": {
+ "name": "media_visibility_idx",
+ "columns": [
+ {
+ "expression": "status",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "current_reference_count",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "media_created_by_id_user_id_fk": {
+ "name": "media_created_by_id_user_id_fk",
+ "tableFrom": "media",
+ "tableTo": "user",
+ "schemaTo": "auth",
+ "columnsFrom": [
+ "created_by_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.outbox_event": {
+ "name": "outbox_event",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "bigint",
+ "primaryKey": true,
+ "notNull": true,
+ "identity": {
+ "type": "always",
+ "name": "outbox_event_id_seq",
+ "schema": "public",
+ "increment": "1",
+ "startWith": "1",
+ "minValue": "1",
+ "maxValue": "9223372036854775807",
+ "cache": "1",
+ "cycle": false
+ }
+ },
+ "topic": {
+ "name": "topic",
+ "type": "varchar(160)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "aggregate_id": {
+ "name": "aggregate_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "event_type": {
+ "name": "event_type",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "payload": {
+ "name": "payload",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "status": {
+ "name": "status",
+ "type": "varchar(24)",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'pending'"
+ },
+ "attempts": {
+ "name": "attempts",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "available_at": {
+ "name": "available_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "processed_at": {
+ "name": "processed_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_error": {
+ "name": "last_error",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "outbox_pending_idx": {
+ "name": "outbox_pending_idx",
+ "columns": [
+ {
+ "expression": "status",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "available_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "auth.session": {
+ "name": "session",
+ "schema": "auth",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "token": {
+ "name": "token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "ip_address": {
+ "name": "ip_address",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "user_agent": {
+ "name": "user_agent",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "impersonated_by": {
+ "name": "impersonated_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "session_token_unique": {
+ "name": "session_token_unique",
+ "columns": [
+ {
+ "expression": "token",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "session_user_id_idx": {
+ "name": "session_user_id_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "session_user_id_user_id_fk": {
+ "name": "session_user_id_user_id_fk",
+ "tableFrom": "session",
+ "tableTo": "user",
+ "schemaTo": "auth",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "guides.stygian_boss": {
+ "name": "stygian_boss",
+ "schema": "guides",
+ "columns": {
+ "schedule_id": {
+ "name": "schedule_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "level": {
+ "name": "level",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "position": {
+ "name": "position",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "config_id": {
+ "name": "config_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "hp": {
+ "name": "hp",
+ "type": "numeric(18, 2)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "mechanics": {
+ "name": "mechanics",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'[]'::jsonb"
+ },
+ "recommended_mechanics": {
+ "name": "recommended_mechanics",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'[]'::jsonb"
+ },
+ "recommended_mechanic_types": {
+ "name": "recommended_mechanic_types",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'[]'::jsonb"
+ },
+ "image_key": {
+ "name": "image_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {
+ "stygian_boss_schedule_idx": {
+ "name": "stygian_boss_schedule_idx",
+ "columns": [
+ {
+ "expression": "schedule_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "level",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "stygian_boss_schedule_id_stygian_schedule_schedule_id_fk": {
+ "name": "stygian_boss_schedule_id_stygian_schedule_schedule_id_fk",
+ "tableFrom": "stygian_boss",
+ "tableTo": "stygian_schedule",
+ "schemaTo": "guides",
+ "columnsFrom": [
+ "schedule_id"
+ ],
+ "columnsTo": [
+ "schedule_id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "stygian_boss_schedule_id_level_position_pk": {
+ "name": "stygian_boss_schedule_id_level_position_pk",
+ "columns": [
+ "schedule_id",
+ "level",
+ "position"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "guides.stygian_recommendation_group": {
+ "name": "stygian_recommendation_group",
+ "schema": "guides",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "schedule_id": {
+ "name": "schedule_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "level": {
+ "name": "level",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "boss_position": {
+ "name": "boss_position",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "kind": {
+ "name": "kind",
+ "type": "varchar(16)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "sort_order": {
+ "name": "sort_order",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ }
+ },
+ "indexes": {
+ "stygian_recommendation_group_order_idx": {
+ "name": "stygian_recommendation_group_order_idx",
+ "columns": [
+ {
+ "expression": "schedule_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "level",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "boss_position",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "kind",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "sort_order",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "stygian_recommendation_group_schedule_id_stygian_schedule_schedule_id_fk": {
+ "name": "stygian_recommendation_group_schedule_id_stygian_schedule_schedule_id_fk",
+ "tableFrom": "stygian_recommendation_group",
+ "tableTo": "stygian_schedule",
+ "schemaTo": "guides",
+ "columnsFrom": [
+ "schedule_id"
+ ],
+ "columnsTo": [
+ "schedule_id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "guides.stygian_recommendation_item": {
+ "name": "stygian_recommendation_item",
+ "schema": "guides",
+ "columns": {
+ "group_id": {
+ "name": "group_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "position": {
+ "name": "position",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "kind": {
+ "name": "kind",
+ "type": "varchar(24)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "catalog_key": {
+ "name": "catalog_key",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "weapon_key": {
+ "name": "weapon_key",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "artifact_key": {
+ "name": "artifact_key",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "refinement": {
+ "name": "refinement",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "constellation_level": {
+ "name": "constellation_level",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "element": {
+ "name": "element",
+ "type": "varchar(24)",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "stygian_recommendation_item_group_id_stygian_recommendation_group_id_fk": {
+ "name": "stygian_recommendation_item_group_id_stygian_recommendation_group_id_fk",
+ "tableFrom": "stygian_recommendation_item",
+ "tableTo": "stygian_recommendation_group",
+ "schemaTo": "guides",
+ "columnsFrom": [
+ "group_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "stygian_recommendation_item_group_id_position_pk": {
+ "name": "stygian_recommendation_item_group_id_position_pk",
+ "columns": [
+ "group_id",
+ "position"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "guides.stygian_schedule": {
+ "name": "stygian_schedule",
+ "schema": "guides",
+ "columns": {
+ "schedule_id": {
+ "name": "schedule_id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "challenge_name": {
+ "name": "challenge_name",
+ "type": "varchar(32)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "schedule_start_time": {
+ "name": "schedule_start_time",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "schedule_end_time": {
+ "name": "schedule_end_time",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "version": {
+ "name": "version",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 1
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "guides.team_composition": {
+ "name": "team_composition",
+ "schema": "guides",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "guide_id": {
+ "name": "guide_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "condition_id": {
+ "name": "condition_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "rotation_seconds": {
+ "name": "rotation_seconds",
+ "type": "numeric(8, 2)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "combo": {
+ "name": "combo",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "sort_order": {
+ "name": "sort_order",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ }
+ },
+ "indexes": {
+ "team_composition_order_idx": {
+ "name": "team_composition_order_idx",
+ "columns": [
+ {
+ "expression": "guide_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "sort_order",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "team_composition_guide_id_guide_id_fk": {
+ "name": "team_composition_guide_id_guide_id_fk",
+ "tableFrom": "team_composition",
+ "tableTo": "guide",
+ "schemaTo": "guides",
+ "columnsFrom": [
+ "guide_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "team_composition_condition_id_team_condition_id_fk": {
+ "name": "team_composition_condition_id_team_condition_id_fk",
+ "tableFrom": "team_composition",
+ "tableTo": "team_condition",
+ "schemaTo": "guides",
+ "columnsFrom": [
+ "condition_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "guides.team_condition": {
+ "name": "team_condition",
+ "schema": "guides",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "guide_id": {
+ "name": "guide_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "varchar(120)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "note": {
+ "name": "note",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "sort_order": {
+ "name": "sort_order",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ }
+ },
+ "indexes": {
+ "team_condition_order_idx": {
+ "name": "team_condition_order_idx",
+ "columns": [
+ {
+ "expression": "guide_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "sort_order",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "team_condition_guide_id_guide_id_fk": {
+ "name": "team_condition_guide_id_guide_id_fk",
+ "tableFrom": "team_condition",
+ "tableTo": "guide",
+ "schemaTo": "guides",
+ "columnsFrom": [
+ "guide_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "guides.team_member": {
+ "name": "team_member",
+ "schema": "guides",
+ "columns": {
+ "team_id": {
+ "name": "team_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "position": {
+ "name": "position",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "character_key": {
+ "name": "character_key",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "weapon_key": {
+ "name": "weapon_key",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "artifact_key": {
+ "name": "artifact_key",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "second_artifact_key": {
+ "name": "second_artifact_key",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "constellation": {
+ "name": "constellation",
+ "type": "varchar(8)",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'0'"
+ },
+ "refinement": {
+ "name": "refinement",
+ "type": "varchar(8)",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'1'"
+ },
+ "damage": {
+ "name": "damage",
+ "type": "numeric(18, 2)",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "team_member_team_id_team_composition_id_fk": {
+ "name": "team_member_team_id_team_composition_id_fk",
+ "tableFrom": "team_member",
+ "tableTo": "team_composition",
+ "schemaTo": "guides",
+ "columnsFrom": [
+ "team_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "team_member_team_id_position_pk": {
+ "name": "team_member_team_id_position_pk",
+ "columns": [
+ "team_id",
+ "position"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "auth.user": {
+ "name": "user",
+ "schema": "auth",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "email": {
+ "name": "email",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "email_verified": {
+ "name": "email_verified",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "image": {
+ "name": "image",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "role": {
+ "name": "role",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'user'"
+ },
+ "banned": {
+ "name": "banned",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": false
+ },
+ "ban_reason": {
+ "name": "ban_reason",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "ban_expires": {
+ "name": "ban_expires",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "user_email_unique": {
+ "name": "user_email_unique",
+ "columns": [
+ {
+ "expression": "email",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "auth.verification": {
+ "name": "verification",
+ "schema": "auth",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "identifier": {
+ "name": "identifier",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "value": {
+ "name": "value",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "verification_identifier_idx": {
+ "name": "verification_identifier_idx",
+ "columns": [
+ {
+ "expression": "identifier",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "guides.weapon_condition": {
+ "name": "weapon_condition",
+ "schema": "guides",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "guide_id": {
+ "name": "guide_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "varchar(120)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "note": {
+ "name": "note",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "sort_order": {
+ "name": "sort_order",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ }
+ },
+ "indexes": {
+ "weapon_condition_order_idx": {
+ "name": "weapon_condition_order_idx",
+ "columns": [
+ {
+ "expression": "guide_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "sort_order",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "weapon_condition_guide_id_guide_id_fk": {
+ "name": "weapon_condition_guide_id_guide_id_fk",
+ "tableFrom": "weapon_condition",
+ "tableTo": "guide",
+ "schemaTo": "guides",
+ "columnsFrom": [
+ "guide_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "guides.weapon_recommendation": {
+ "name": "weapon_recommendation",
+ "schema": "guides",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "condition_id": {
+ "name": "condition_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "weapon_key": {
+ "name": "weapon_key",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "refinement": {
+ "name": "refinement",
+ "type": "varchar(8)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "overall_percent": {
+ "name": "overall_percent",
+ "type": "numeric(8, 2)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "personal_percent": {
+ "name": "personal_percent",
+ "type": "numeric(8, 2)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "note": {
+ "name": "note",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "sort_order": {
+ "name": "sort_order",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ }
+ },
+ "indexes": {
+ "weapon_recommendation_order_idx": {
+ "name": "weapon_recommendation_order_idx",
+ "columns": [
+ {
+ "expression": "condition_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "sort_order",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "weapon_recommendation_condition_id_weapon_condition_id_fk": {
+ "name": "weapon_recommendation_condition_id_weapon_condition_id_fk",
+ "tableFrom": "weapon_recommendation",
+ "tableTo": "weapon_condition",
+ "schemaTo": "guides",
+ "columnsFrom": [
+ "condition_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ }
+ },
+ "enums": {},
+ "schemas": {},
+ "sequences": {},
+ "roles": {},
+ "policies": {},
+ "views": {},
+ "_meta": {
+ "columns": {},
+ "schemas": {},
+ "tables": {}
+ }
+}
\ No newline at end of file
diff --git a/drizzle/meta/0066_snapshot.json b/drizzle/meta/0066_snapshot.json
new file mode 100644
index 0000000..6929ff1
--- /dev/null
+++ b/drizzle/meta/0066_snapshot.json
@@ -0,0 +1,3846 @@
+{
+ "id": "58b70443-cb55-4acd-852a-201b3ee9167f",
+ "prevId": "34b1f9a3-9b86-4eb0-8e44-64b3814f9ea9",
+ "version": "7",
+ "dialect": "postgresql",
+ "tables": {
+ "auth.account": {
+ "name": "account",
+ "schema": "auth",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "issuer": {
+ "name": "issuer",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "account_id": {
+ "name": "account_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "provider_id": {
+ "name": "provider_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "access_token": {
+ "name": "access_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "refresh_token": {
+ "name": "refresh_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "id_token": {
+ "name": "id_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "access_token_expires_at": {
+ "name": "access_token_expires_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "refresh_token_expires_at": {
+ "name": "refresh_token_expires_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "scope": {
+ "name": "scope",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "password": {
+ "name": "password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "account_provider_unique": {
+ "name": "account_provider_unique",
+ "columns": [
+ {
+ "expression": "provider_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "account_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "account_user_id_idx": {
+ "name": "account_user_id_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "account_user_id_user_id_fk": {
+ "name": "account_user_id_user_id_fk",
+ "tableFrom": "account",
+ "tableTo": "user",
+ "schemaTo": "auth",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "guides.artifact_condition": {
+ "name": "artifact_condition",
+ "schema": "guides",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "guide_id": {
+ "name": "guide_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "varchar(120)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "note": {
+ "name": "note",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "sort_order": {
+ "name": "sort_order",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ }
+ },
+ "indexes": {
+ "artifact_condition_order_idx": {
+ "name": "artifact_condition_order_idx",
+ "columns": [
+ {
+ "expression": "guide_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "sort_order",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "artifact_condition_guide_id_guide_id_fk": {
+ "name": "artifact_condition_guide_id_guide_id_fk",
+ "tableFrom": "artifact_condition",
+ "tableTo": "guide",
+ "schemaTo": "guides",
+ "columnsFrom": [
+ "guide_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "guides.artifact_group": {
+ "name": "artifact_group",
+ "schema": "guides",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "condition_id": {
+ "name": "condition_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "sort_order": {
+ "name": "sort_order",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ }
+ },
+ "indexes": {
+ "artifact_group_order_idx": {
+ "name": "artifact_group_order_idx",
+ "columns": [
+ {
+ "expression": "condition_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "sort_order",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "artifact_group_condition_id_artifact_condition_id_fk": {
+ "name": "artifact_group_condition_id_artifact_condition_id_fk",
+ "tableFrom": "artifact_group",
+ "tableTo": "artifact_condition",
+ "schemaTo": "guides",
+ "columnsFrom": [
+ "condition_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "guides.artifact_profile": {
+ "name": "artifact_profile",
+ "schema": "guides",
+ "columns": {
+ "guide_id": {
+ "name": "guide_id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "sands": {
+ "name": "sands",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'[]'::jsonb"
+ },
+ "goblet": {
+ "name": "goblet",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'[]'::jsonb"
+ },
+ "circlet": {
+ "name": "circlet",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'[]'::jsonb"
+ },
+ "substats": {
+ "name": "substats",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'[]'::jsonb"
+ },
+ "recommended_conditions": {
+ "name": "recommended_conditions",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'[]'::jsonb"
+ },
+ "targets": {
+ "name": "targets",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'[]'::jsonb"
+ },
+ "target_conditions": {
+ "name": "target_conditions",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'[]'::jsonb"
+ },
+ "talents": {
+ "name": "talents",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'[]'::jsonb"
+ },
+ "note": {
+ "name": "note",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "artifact_profile_guide_id_guide_id_fk": {
+ "name": "artifact_profile_guide_id_guide_id_fk",
+ "tableFrom": "artifact_profile",
+ "tableTo": "guide",
+ "schemaTo": "guides",
+ "columnsFrom": [
+ "guide_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "guides.artifact_recommendation": {
+ "name": "artifact_recommendation",
+ "schema": "guides",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "group_id": {
+ "name": "group_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "first_artifact_key": {
+ "name": "first_artifact_key",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "second_artifact_key": {
+ "name": "second_artifact_key",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "percent": {
+ "name": "percent",
+ "type": "numeric(8, 2)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "note": {
+ "name": "note",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "sort_order": {
+ "name": "sort_order",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ }
+ },
+ "indexes": {
+ "artifact_recommendation_order_idx": {
+ "name": "artifact_recommendation_order_idx",
+ "columns": [
+ {
+ "expression": "group_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "sort_order",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "artifact_recommendation_group_id_artifact_group_id_fk": {
+ "name": "artifact_recommendation_group_id_artifact_group_id_fk",
+ "tableFrom": "artifact_recommendation",
+ "tableTo": "artifact_group",
+ "schemaTo": "guides",
+ "columnsFrom": [
+ "group_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.audit_log": {
+ "name": "audit_log",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "bigint",
+ "primaryKey": true,
+ "notNull": true,
+ "identity": {
+ "type": "always",
+ "name": "audit_log_id_seq",
+ "schema": "public",
+ "increment": "1",
+ "startWith": "1",
+ "minValue": "1",
+ "maxValue": "9223372036854775807",
+ "cache": "1",
+ "cycle": false
+ }
+ },
+ "author_id": {
+ "name": "author_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "author": {
+ "name": "author",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "text": {
+ "name": "text",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "details": {
+ "name": "details",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'{}'::jsonb"
+ },
+ "time": {
+ "name": "time",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "audit_log_time_idx": {
+ "name": "audit_log_time_idx",
+ "columns": [
+ {
+ "expression": "time",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "audit_log_author_time_idx": {
+ "name": "audit_log_author_time_idx",
+ "columns": [
+ {
+ "expression": "author_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "time",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "catalog.artifact": {
+ "name": "artifact",
+ "schema": "catalog",
+ "columns": {
+ "key": {
+ "name": "key",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "rarity": {
+ "name": "rarity",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "image_key": {
+ "name": "image_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {
+ "catalog_artifact_name_idx": {
+ "name": "catalog_artifact_name_idx",
+ "columns": [
+ {
+ "expression": "name",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "artifact_key_pk": {
+ "name": "artifact_key_pk",
+ "columns": [
+ "key"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "catalog.character": {
+ "name": "character",
+ "schema": "catalog",
+ "columns": {
+ "key": {
+ "name": "key",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "element": {
+ "name": "element",
+ "type": "varchar(48)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "rarity": {
+ "name": "rarity",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "weapon_type": {
+ "name": "weapon_type",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "image_key": {
+ "name": "image_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "element_icon_key": {
+ "name": "element_icon_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "coop_image_key": {
+ "name": "coop_image_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "name_card_image_key": {
+ "name": "name_card_image_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "constellations": {
+ "name": "constellations",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'[]'::jsonb"
+ },
+ "talents": {
+ "name": "talents",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'[]'::jsonb"
+ },
+ "ascension": {
+ "name": "ascension",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'[]'::jsonb"
+ },
+ "leveling": {
+ "name": "leveling",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'[]'::jsonb"
+ },
+ "base_stats": {
+ "name": "base_stats",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'[]'::jsonb"
+ }
+ },
+ "indexes": {
+ "catalog_character_name_idx": {
+ "name": "catalog_character_name_idx",
+ "columns": [
+ {
+ "expression": "name",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "character_key_pk": {
+ "name": "character_key_pk",
+ "columns": [
+ "key"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "catalog.material": {
+ "name": "material",
+ "schema": "catalog",
+ "columns": {
+ "key": {
+ "name": "key",
+ "type": "varchar(64)",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "rarity": {
+ "name": "rarity",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "type": {
+ "name": "type",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "image_key": {
+ "name": "image_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "catalog_material_name_idx": {
+ "name": "catalog_material_name_idx",
+ "columns": [
+ {
+ "expression": "name",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "catalog.weapon": {
+ "name": "weapon",
+ "schema": "catalog",
+ "columns": {
+ "key": {
+ "name": "key",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "rarity": {
+ "name": "rarity",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "weapon_type": {
+ "name": "weapon_type",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "image_key": {
+ "name": "image_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {
+ "catalog_weapon_name_idx": {
+ "name": "catalog_weapon_name_idx",
+ "columns": [
+ {
+ "expression": "name",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "weapon_key_pk": {
+ "name": "weapon_key_pk",
+ "columns": [
+ "key"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "commission.checkout": {
+ "name": "checkout",
+ "schema": "commission",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "request": {
+ "name": "request",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "amount_baht": {
+ "name": "amount_baht",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "commission_checkout_user_idx": {
+ "name": "commission_checkout_user_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "checkout_user_id_user_id_fk": {
+ "name": "checkout_user_id_user_id_fk",
+ "tableFrom": "checkout",
+ "tableTo": "user",
+ "schemaTo": "auth",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "commission.message": {
+ "name": "message",
+ "schema": "commission",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "ticket_id": {
+ "name": "ticket_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "author_id": {
+ "name": "author_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "text": {
+ "name": "text",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "image_object_key": {
+ "name": "image_object_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "image_mime_type": {
+ "name": "image_mime_type",
+ "type": "varchar(32)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "commission_message_ticket_time_idx": {
+ "name": "commission_message_ticket_time_idx",
+ "columns": [
+ {
+ "expression": "ticket_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "message_ticket_id_ticket_id_fk": {
+ "name": "message_ticket_id_ticket_id_fk",
+ "tableFrom": "message",
+ "tableTo": "ticket",
+ "schemaTo": "commission",
+ "columnsFrom": [
+ "ticket_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ },
+ "message_author_id_user_id_fk": {
+ "name": "message_author_id_user_id_fk",
+ "tableFrom": "message",
+ "tableTo": "user",
+ "schemaTo": "auth",
+ "columnsFrom": [
+ "author_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "commission.payment": {
+ "name": "payment",
+ "schema": "commission",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "checkout_id": {
+ "name": "checkout_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "ticket_id": {
+ "name": "ticket_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "reference_id": {
+ "name": "reference_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "trans_ref": {
+ "name": "trans_ref",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "amount_baht": {
+ "name": "amount_baht",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "transferred_at": {
+ "name": "transferred_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "slip_object_key": {
+ "name": "slip_object_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "slip_mime_type": {
+ "name": "slip_mime_type",
+ "type": "varchar(32)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "provider_data": {
+ "name": "provider_data",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "commission_payment_checkout_unique": {
+ "name": "commission_payment_checkout_unique",
+ "columns": [
+ {
+ "expression": "checkout_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "commission_payment_reference_unique": {
+ "name": "commission_payment_reference_unique",
+ "columns": [
+ {
+ "expression": "reference_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "commission_payment_trans_ref_unique": {
+ "name": "commission_payment_trans_ref_unique",
+ "columns": [
+ {
+ "expression": "trans_ref",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "payment_checkout_id_checkout_id_fk": {
+ "name": "payment_checkout_id_checkout_id_fk",
+ "tableFrom": "payment",
+ "tableTo": "checkout",
+ "schemaTo": "commission",
+ "columnsFrom": [
+ "checkout_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ },
+ "payment_ticket_id_ticket_id_fk": {
+ "name": "payment_ticket_id_ticket_id_fk",
+ "tableFrom": "payment",
+ "tableTo": "ticket",
+ "schemaTo": "commission",
+ "columnsFrom": [
+ "ticket_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "commission.reaction": {
+ "name": "reaction",
+ "schema": "commission",
+ "columns": {
+ "message_id": {
+ "name": "message_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "emoji": {
+ "name": "emoji",
+ "type": "varchar(16)",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "reaction_message_id_message_id_fk": {
+ "name": "reaction_message_id_message_id_fk",
+ "tableFrom": "reaction",
+ "tableTo": "message",
+ "schemaTo": "commission",
+ "columnsFrom": [
+ "message_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "reaction_user_id_user_id_fk": {
+ "name": "reaction_user_id_user_id_fk",
+ "tableFrom": "reaction",
+ "tableTo": "user",
+ "schemaTo": "auth",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "reaction_message_id_user_id_emoji_pk": {
+ "name": "reaction_message_id_user_id_emoji_pk",
+ "columns": [
+ "message_id",
+ "user_id",
+ "emoji"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "commission.ticket": {
+ "name": "ticket",
+ "schema": "commission",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "checkout_id": {
+ "name": "checkout_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "title": {
+ "name": "title",
+ "type": "varchar(80)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "status": {
+ "name": "status",
+ "type": "varchar(16)",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'open'"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "commission_ticket_checkout_unique": {
+ "name": "commission_ticket_checkout_unique",
+ "columns": [
+ {
+ "expression": "checkout_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "commission_ticket_user_idx": {
+ "name": "commission_ticket_user_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "commission_ticket_status_idx": {
+ "name": "commission_ticket_status_idx",
+ "columns": [
+ {
+ "expression": "status",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "updated_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "ticket_checkout_id_checkout_id_fk": {
+ "name": "ticket_checkout_id_checkout_id_fk",
+ "tableFrom": "ticket",
+ "tableTo": "checkout",
+ "schemaTo": "commission",
+ "columnsFrom": [
+ "checkout_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ },
+ "ticket_user_id_user_id_fk": {
+ "name": "ticket_user_id_user_id_fk",
+ "tableFrom": "ticket",
+ "tableTo": "user",
+ "schemaTo": "auth",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "guides.constellation_condition": {
+ "name": "constellation_condition",
+ "schema": "guides",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "guide_id": {
+ "name": "guide_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "varchar(120)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "note": {
+ "name": "note",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "sort_order": {
+ "name": "sort_order",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ }
+ },
+ "indexes": {
+ "constellation_condition_order_idx": {
+ "name": "constellation_condition_order_idx",
+ "columns": [
+ {
+ "expression": "guide_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "sort_order",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "constellation_condition_guide_id_guide_id_fk": {
+ "name": "constellation_condition_guide_id_guide_id_fk",
+ "tableFrom": "constellation_condition",
+ "tableTo": "guide",
+ "schemaTo": "guides",
+ "columnsFrom": [
+ "guide_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "guides.constellation_recommendation": {
+ "name": "constellation_recommendation",
+ "schema": "guides",
+ "columns": {
+ "condition_id": {
+ "name": "condition_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "level": {
+ "name": "level",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "detail": {
+ "name": "detail",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "cumulative_percent": {
+ "name": "cumulative_percent",
+ "type": "numeric(8, 2)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "is_great": {
+ "name": "is_great",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "constellation_recommendation_condition_id_constellation_condition_id_fk": {
+ "name": "constellation_recommendation_condition_id_constellation_condition_id_fk",
+ "tableFrom": "constellation_recommendation",
+ "tableTo": "constellation_condition",
+ "schemaTo": "guides",
+ "columnsFrom": [
+ "condition_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "constellation_recommendation_condition_id_level_pk": {
+ "name": "constellation_recommendation_condition_id_level_pk",
+ "columns": [
+ "condition_id",
+ "level"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "guides.extra_section_row": {
+ "name": "extra_section_row",
+ "schema": "guides",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "section_id": {
+ "name": "section_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "media_id": {
+ "name": "media_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "heading": {
+ "name": "heading",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "body": {
+ "name": "body",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "percent": {
+ "name": "percent",
+ "type": "numeric(8, 2)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "background_color": {
+ "name": "background_color",
+ "type": "varchar(7)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "text_color": {
+ "name": "text_color",
+ "type": "varchar(7)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "highlights": {
+ "name": "highlights",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'[]'::jsonb"
+ },
+ "catalog_items": {
+ "name": "catalog_items",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'[]'::jsonb"
+ },
+ "sheet": {
+ "name": "sheet",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "sort_order": {
+ "name": "sort_order",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ }
+ },
+ "indexes": {
+ "extra_section_row_order_idx": {
+ "name": "extra_section_row_order_idx",
+ "columns": [
+ {
+ "expression": "section_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "sort_order",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "extra_section_row_section_id_extra_section_section_id_fk": {
+ "name": "extra_section_row_section_id_extra_section_section_id_fk",
+ "tableFrom": "extra_section_row",
+ "tableTo": "extra_section",
+ "schemaTo": "guides",
+ "columnsFrom": [
+ "section_id"
+ ],
+ "columnsTo": [
+ "section_id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "extra_section_row_media_id_media_id_fk": {
+ "name": "extra_section_row_media_id_media_id_fk",
+ "tableFrom": "extra_section_row",
+ "tableTo": "media",
+ "columnsFrom": [
+ "media_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "guides.extra_section": {
+ "name": "extra_section",
+ "schema": "guides",
+ "columns": {
+ "section_id": {
+ "name": "section_id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "extra_section_section_id_guide_section_id_fk": {
+ "name": "extra_section_section_id_guide_section_id_fk",
+ "tableFrom": "extra_section",
+ "tableTo": "guide_section",
+ "schemaTo": "guides",
+ "columnsFrom": [
+ "section_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "guides.glossary_alias": {
+ "name": "glossary_alias",
+ "schema": "guides",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "kind": {
+ "name": "kind",
+ "type": "varchar(16)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "catalog_key": {
+ "name": "catalog_key",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "alias": {
+ "name": "alias",
+ "type": "varchar(120)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "normalized_alias": {
+ "name": "normalized_alias",
+ "type": "varchar(120)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "glossary_alias_item_normalized_unique": {
+ "name": "glossary_alias_item_normalized_unique",
+ "columns": [
+ {
+ "expression": "kind",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "catalog_key",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "normalized_alias",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "glossary_alias_item_idx": {
+ "name": "glossary_alias_item_idx",
+ "columns": [
+ {
+ "expression": "kind",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "catalog_key",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "guides.guide_poster_bleeding": {
+ "name": "guide_poster_bleeding",
+ "schema": "guides",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "guide_id": {
+ "name": "guide_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "media_id": {
+ "name": "media_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "amount": {
+ "name": "amount",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 100
+ },
+ "scale": {
+ "name": "scale",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 100
+ },
+ "blend_mode": {
+ "name": "blend_mode",
+ "type": "varchar(32)",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'multiply'"
+ },
+ "sort_order": {
+ "name": "sort_order",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "guide_poster_bleeding_order_idx": {
+ "name": "guide_poster_bleeding_order_idx",
+ "columns": [
+ {
+ "expression": "guide_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "sort_order",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "guide_poster_bleeding_guide_id_guide_id_fk": {
+ "name": "guide_poster_bleeding_guide_id_guide_id_fk",
+ "tableFrom": "guide_poster_bleeding",
+ "tableTo": "guide",
+ "schemaTo": "guides",
+ "columnsFrom": [
+ "guide_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "guide_poster_bleeding_media_id_media_id_fk": {
+ "name": "guide_poster_bleeding_media_id_media_id_fk",
+ "tableFrom": "guide_poster_bleeding",
+ "tableTo": "media",
+ "columnsFrom": [
+ "media_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "restrict",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "guides.guide_section": {
+ "name": "guide_section",
+ "schema": "guides",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "guide_id": {
+ "name": "guide_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "kind": {
+ "name": "kind",
+ "type": "varchar(32)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "slug": {
+ "name": "slug",
+ "type": "varchar(120)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "note": {
+ "name": "note",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "sort_order": {
+ "name": "sort_order",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ }
+ },
+ "indexes": {
+ "guide_section_slug_unique": {
+ "name": "guide_section_slug_unique",
+ "columns": [
+ {
+ "expression": "guide_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "slug",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "guide_section_order_idx": {
+ "name": "guide_section_order_idx",
+ "columns": [
+ {
+ "expression": "guide_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "sort_order",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "guide_section_guide_id_guide_id_fk": {
+ "name": "guide_section_guide_id_guide_id_fk",
+ "tableFrom": "guide_section",
+ "tableTo": "guide",
+ "schemaTo": "guides",
+ "columnsFrom": [
+ "guide_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "guides.guide": {
+ "name": "guide",
+ "schema": "guides",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "character_key": {
+ "name": "character_key",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "slug": {
+ "name": "slug",
+ "type": "varchar(120)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "overview": {
+ "name": "overview",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "youtube_urls": {
+ "name": "youtube_urls",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'[]'::jsonb"
+ },
+ "cover_media_id": {
+ "name": "cover_media_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "poster_art_media_id": {
+ "name": "poster_art_media_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "poster_art_offset_x": {
+ "name": "poster_art_offset_x",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "poster_art_offset_y": {
+ "name": "poster_art_offset_y",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "poster_art_scale": {
+ "name": "poster_art_scale",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 100
+ },
+ "poster_art_rotation": {
+ "name": "poster_art_rotation",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "poster_top_colors": {
+ "name": "poster_top_colors",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'[\"#000000\",\"#000000\"]'::jsonb"
+ },
+ "poster_bottom_colors": {
+ "name": "poster_bottom_colors",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'[\"#000000\",\"#000000\"]'::jsonb"
+ },
+ "version": {
+ "name": "version",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 1
+ },
+ "is_public": {
+ "name": "is_public",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "published_at": {
+ "name": "published_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "sort_order": {
+ "name": "sort_order",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "trashed_at": {
+ "name": "trashed_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "guide_character_key_unique": {
+ "name": "guide_character_key_unique",
+ "columns": [
+ {
+ "expression": "character_key",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "guide_slug_unique": {
+ "name": "guide_slug_unique",
+ "columns": [
+ {
+ "expression": "slug",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "guide_directory_idx": {
+ "name": "guide_directory_idx",
+ "columns": [
+ {
+ "expression": "trashed_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "sort_order",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "guide_cover_media_id_media_id_fk": {
+ "name": "guide_cover_media_id_media_id_fk",
+ "tableFrom": "guide",
+ "tableTo": "media",
+ "columnsFrom": [
+ "cover_media_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "restrict",
+ "onUpdate": "no action"
+ },
+ "guide_poster_art_media_id_media_id_fk": {
+ "name": "guide_poster_art_media_id_media_id_fk",
+ "tableFrom": "guide",
+ "tableTo": "media",
+ "columnsFrom": [
+ "poster_art_media_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.media": {
+ "name": "media",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "object_key": {
+ "name": "object_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "file_name": {
+ "name": "file_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "mime_type": {
+ "name": "mime_type",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "byte_size": {
+ "name": "byte_size",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "checksum": {
+ "name": "checksum",
+ "type": "varchar(128)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "width": {
+ "name": "width",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "height": {
+ "name": "height",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "status": {
+ "name": "status",
+ "type": "varchar(24)",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'pending'"
+ },
+ "current_reference_count": {
+ "name": "current_reference_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "created_by_id": {
+ "name": "created_by_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "media_object_key_unique": {
+ "name": "media_object_key_unique",
+ "columns": [
+ {
+ "expression": "object_key",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "media_visibility_idx": {
+ "name": "media_visibility_idx",
+ "columns": [
+ {
+ "expression": "status",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "current_reference_count",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "media_created_by_id_user_id_fk": {
+ "name": "media_created_by_id_user_id_fk",
+ "tableFrom": "media",
+ "tableTo": "user",
+ "schemaTo": "auth",
+ "columnsFrom": [
+ "created_by_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.outbox_event": {
+ "name": "outbox_event",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "bigint",
+ "primaryKey": true,
+ "notNull": true,
+ "identity": {
+ "type": "always",
+ "name": "outbox_event_id_seq",
+ "schema": "public",
+ "increment": "1",
+ "startWith": "1",
+ "minValue": "1",
+ "maxValue": "9223372036854775807",
+ "cache": "1",
+ "cycle": false
+ }
+ },
+ "topic": {
+ "name": "topic",
+ "type": "varchar(160)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "aggregate_id": {
+ "name": "aggregate_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "event_type": {
+ "name": "event_type",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "payload": {
+ "name": "payload",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "status": {
+ "name": "status",
+ "type": "varchar(24)",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'pending'"
+ },
+ "attempts": {
+ "name": "attempts",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "available_at": {
+ "name": "available_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "processed_at": {
+ "name": "processed_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_error": {
+ "name": "last_error",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "outbox_pending_idx": {
+ "name": "outbox_pending_idx",
+ "columns": [
+ {
+ "expression": "status",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "available_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "auth.session": {
+ "name": "session",
+ "schema": "auth",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "token": {
+ "name": "token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "ip_address": {
+ "name": "ip_address",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "user_agent": {
+ "name": "user_agent",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "impersonated_by": {
+ "name": "impersonated_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "session_token_unique": {
+ "name": "session_token_unique",
+ "columns": [
+ {
+ "expression": "token",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "session_user_id_idx": {
+ "name": "session_user_id_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "session_user_id_user_id_fk": {
+ "name": "session_user_id_user_id_fk",
+ "tableFrom": "session",
+ "tableTo": "user",
+ "schemaTo": "auth",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "guides.stygian_boss": {
+ "name": "stygian_boss",
+ "schema": "guides",
+ "columns": {
+ "schedule_id": {
+ "name": "schedule_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "level": {
+ "name": "level",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "position": {
+ "name": "position",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "config_id": {
+ "name": "config_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "hp": {
+ "name": "hp",
+ "type": "numeric(18, 2)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "mechanics": {
+ "name": "mechanics",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'[]'::jsonb"
+ },
+ "recommended_mechanics": {
+ "name": "recommended_mechanics",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'[]'::jsonb"
+ },
+ "recommended_mechanic_types": {
+ "name": "recommended_mechanic_types",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'[]'::jsonb"
+ },
+ "image_key": {
+ "name": "image_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {
+ "stygian_boss_schedule_idx": {
+ "name": "stygian_boss_schedule_idx",
+ "columns": [
+ {
+ "expression": "schedule_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "level",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "stygian_boss_schedule_id_stygian_schedule_schedule_id_fk": {
+ "name": "stygian_boss_schedule_id_stygian_schedule_schedule_id_fk",
+ "tableFrom": "stygian_boss",
+ "tableTo": "stygian_schedule",
+ "schemaTo": "guides",
+ "columnsFrom": [
+ "schedule_id"
+ ],
+ "columnsTo": [
+ "schedule_id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "stygian_boss_schedule_id_level_position_pk": {
+ "name": "stygian_boss_schedule_id_level_position_pk",
+ "columns": [
+ "schedule_id",
+ "level",
+ "position"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "guides.stygian_recommendation_group": {
+ "name": "stygian_recommendation_group",
+ "schema": "guides",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "schedule_id": {
+ "name": "schedule_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "level": {
+ "name": "level",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "boss_position": {
+ "name": "boss_position",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "kind": {
+ "name": "kind",
+ "type": "varchar(16)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "sort_order": {
+ "name": "sort_order",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ }
+ },
+ "indexes": {
+ "stygian_recommendation_group_order_idx": {
+ "name": "stygian_recommendation_group_order_idx",
+ "columns": [
+ {
+ "expression": "schedule_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "level",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "boss_position",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "kind",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "sort_order",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "stygian_recommendation_group_schedule_id_stygian_schedule_schedule_id_fk": {
+ "name": "stygian_recommendation_group_schedule_id_stygian_schedule_schedule_id_fk",
+ "tableFrom": "stygian_recommendation_group",
+ "tableTo": "stygian_schedule",
+ "schemaTo": "guides",
+ "columnsFrom": [
+ "schedule_id"
+ ],
+ "columnsTo": [
+ "schedule_id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "guides.stygian_recommendation_item": {
+ "name": "stygian_recommendation_item",
+ "schema": "guides",
+ "columns": {
+ "group_id": {
+ "name": "group_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "position": {
+ "name": "position",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "kind": {
+ "name": "kind",
+ "type": "varchar(24)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "catalog_key": {
+ "name": "catalog_key",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "weapon_key": {
+ "name": "weapon_key",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "artifact_key": {
+ "name": "artifact_key",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "refinement": {
+ "name": "refinement",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "constellation_level": {
+ "name": "constellation_level",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "element": {
+ "name": "element",
+ "type": "varchar(24)",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "stygian_recommendation_item_group_id_stygian_recommendation_group_id_fk": {
+ "name": "stygian_recommendation_item_group_id_stygian_recommendation_group_id_fk",
+ "tableFrom": "stygian_recommendation_item",
+ "tableTo": "stygian_recommendation_group",
+ "schemaTo": "guides",
+ "columnsFrom": [
+ "group_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "stygian_recommendation_item_group_id_position_pk": {
+ "name": "stygian_recommendation_item_group_id_position_pk",
+ "columns": [
+ "group_id",
+ "position"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "guides.stygian_schedule": {
+ "name": "stygian_schedule",
+ "schema": "guides",
+ "columns": {
+ "schedule_id": {
+ "name": "schedule_id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "challenge_name": {
+ "name": "challenge_name",
+ "type": "varchar(32)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "schedule_start_time": {
+ "name": "schedule_start_time",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "schedule_end_time": {
+ "name": "schedule_end_time",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "version": {
+ "name": "version",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 1
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "guides.team_composition": {
+ "name": "team_composition",
+ "schema": "guides",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "guide_id": {
+ "name": "guide_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "condition_id": {
+ "name": "condition_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "rotation_seconds": {
+ "name": "rotation_seconds",
+ "type": "numeric(8, 2)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "combo": {
+ "name": "combo",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "sort_order": {
+ "name": "sort_order",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ }
+ },
+ "indexes": {
+ "team_composition_order_idx": {
+ "name": "team_composition_order_idx",
+ "columns": [
+ {
+ "expression": "guide_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "sort_order",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "team_composition_guide_id_guide_id_fk": {
+ "name": "team_composition_guide_id_guide_id_fk",
+ "tableFrom": "team_composition",
+ "tableTo": "guide",
+ "schemaTo": "guides",
+ "columnsFrom": [
+ "guide_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "team_composition_condition_id_team_condition_id_fk": {
+ "name": "team_composition_condition_id_team_condition_id_fk",
+ "tableFrom": "team_composition",
+ "tableTo": "team_condition",
+ "schemaTo": "guides",
+ "columnsFrom": [
+ "condition_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "guides.team_condition": {
+ "name": "team_condition",
+ "schema": "guides",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "guide_id": {
+ "name": "guide_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "varchar(120)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "note": {
+ "name": "note",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "sort_order": {
+ "name": "sort_order",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ }
+ },
+ "indexes": {
+ "team_condition_order_idx": {
+ "name": "team_condition_order_idx",
+ "columns": [
+ {
+ "expression": "guide_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "sort_order",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "team_condition_guide_id_guide_id_fk": {
+ "name": "team_condition_guide_id_guide_id_fk",
+ "tableFrom": "team_condition",
+ "tableTo": "guide",
+ "schemaTo": "guides",
+ "columnsFrom": [
+ "guide_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "guides.team_member": {
+ "name": "team_member",
+ "schema": "guides",
+ "columns": {
+ "team_id": {
+ "name": "team_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "position": {
+ "name": "position",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "character_key": {
+ "name": "character_key",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "weapon_key": {
+ "name": "weapon_key",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "artifact_key": {
+ "name": "artifact_key",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "second_artifact_key": {
+ "name": "second_artifact_key",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "constellation": {
+ "name": "constellation",
+ "type": "varchar(8)",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'0'"
+ },
+ "refinement": {
+ "name": "refinement",
+ "type": "varchar(8)",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'1'"
+ },
+ "damage": {
+ "name": "damage",
+ "type": "numeric(18, 2)",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "team_member_team_id_team_composition_id_fk": {
+ "name": "team_member_team_id_team_composition_id_fk",
+ "tableFrom": "team_member",
+ "tableTo": "team_composition",
+ "schemaTo": "guides",
+ "columnsFrom": [
+ "team_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "team_member_team_id_position_pk": {
+ "name": "team_member_team_id_position_pk",
+ "columns": [
+ "team_id",
+ "position"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "auth.user": {
+ "name": "user",
+ "schema": "auth",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "email": {
+ "name": "email",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "email_verified": {
+ "name": "email_verified",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "image": {
+ "name": "image",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "role": {
+ "name": "role",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'user'"
+ },
+ "banned": {
+ "name": "banned",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": false
+ },
+ "ban_reason": {
+ "name": "ban_reason",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "ban_expires": {
+ "name": "ban_expires",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "user_email_unique": {
+ "name": "user_email_unique",
+ "columns": [
+ {
+ "expression": "email",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "auth.verification": {
+ "name": "verification",
+ "schema": "auth",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "identifier": {
+ "name": "identifier",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "value": {
+ "name": "value",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "verification_identifier_idx": {
+ "name": "verification_identifier_idx",
+ "columns": [
+ {
+ "expression": "identifier",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "guides.weapon_condition": {
+ "name": "weapon_condition",
+ "schema": "guides",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "guide_id": {
+ "name": "guide_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "varchar(120)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "note": {
+ "name": "note",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "sort_order": {
+ "name": "sort_order",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ }
+ },
+ "indexes": {
+ "weapon_condition_order_idx": {
+ "name": "weapon_condition_order_idx",
+ "columns": [
+ {
+ "expression": "guide_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "sort_order",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "weapon_condition_guide_id_guide_id_fk": {
+ "name": "weapon_condition_guide_id_guide_id_fk",
+ "tableFrom": "weapon_condition",
+ "tableTo": "guide",
+ "schemaTo": "guides",
+ "columnsFrom": [
+ "guide_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "guides.weapon_recommendation": {
+ "name": "weapon_recommendation",
+ "schema": "guides",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "condition_id": {
+ "name": "condition_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "weapon_key": {
+ "name": "weapon_key",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "refinement": {
+ "name": "refinement",
+ "type": "varchar(8)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "overall_percent": {
+ "name": "overall_percent",
+ "type": "numeric(8, 2)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "personal_percent": {
+ "name": "personal_percent",
+ "type": "numeric(8, 2)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "note": {
+ "name": "note",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "sort_order": {
+ "name": "sort_order",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ }
+ },
+ "indexes": {
+ "weapon_recommendation_order_idx": {
+ "name": "weapon_recommendation_order_idx",
+ "columns": [
+ {
+ "expression": "condition_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "sort_order",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "weapon_recommendation_condition_id_weapon_condition_id_fk": {
+ "name": "weapon_recommendation_condition_id_weapon_condition_id_fk",
+ "tableFrom": "weapon_recommendation",
+ "tableTo": "weapon_condition",
+ "schemaTo": "guides",
+ "columnsFrom": [
+ "condition_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ }
+ },
+ "enums": {},
+ "schemas": {},
+ "sequences": {},
+ "roles": {},
+ "policies": {},
+ "views": {},
+ "_meta": {
+ "columns": {},
+ "schemas": {},
+ "tables": {}
+ }
+}
\ No newline at end of file
diff --git a/drizzle/meta/_journal.json b/drizzle/meta/_journal.json
index 36d9247..2f8c211 100644
--- a/drizzle/meta/_journal.json
+++ b/drizzle/meta/_journal.json
@@ -442,6 +442,34 @@
"when": 1790785825070,
"tag": "0062_large_catseye",
"breakpoints": true
+ },
+ {
+ "idx": 63,
+ "version": "7",
+ "when": 1790836969797,
+ "tag": "0063_overconfident_george_stacy",
+ "breakpoints": true
+ },
+ {
+ "idx": 64,
+ "version": "7",
+ "when": 1790841058666,
+ "tag": "0064_great_dorian_gray",
+ "breakpoints": true
+ },
+ {
+ "idx": 65,
+ "version": "7",
+ "when": 1790841340169,
+ "tag": "0065_clumsy_misty_knight",
+ "breakpoints": true
+ },
+ {
+ "idx": 66,
+ "version": "7",
+ "when": 1790849456239,
+ "tag": "0066_burly_william_stryker",
+ "breakpoints": true
}
]
}
\ No newline at end of file
diff --git a/k8s/base/deployment.yaml b/k8s/base/deployment.yaml
index 616e0fc..794e10b 100644
--- a/k8s/base/deployment.yaml
+++ b/k8s/base/deployment.yaml
@@ -97,6 +97,37 @@ spec:
secretKeyRef:
name: buzz-sheet-env
key: TURNSTILE_SECRET_KEY
+ - name: COMMISSION_PROMPTPAY_TYPE
+ valueFrom:
+ secretKeyRef:
+ name: buzz-sheet-env
+ key: COMMISSION_PROMPTPAY_TYPE
+ - name: COMMISSION_PROMPTPAY_VALUE
+ valueFrom:
+ secretKeyRef:
+ name: buzz-sheet-env
+ key: COMMISSION_PROMPTPAY_VALUE
+ - name: COMMISSION_RECEIVER_ACCOUNT_NUMBER
+ valueFrom:
+ secretKeyRef:
+ name: buzz-sheet-env
+ key: COMMISSION_RECEIVER_ACCOUNT_NUMBER
+ - name: SLIP2GO_VERIFY_URL
+ valueFrom:
+ secretKeyRef:
+ name: buzz-sheet-env
+ key: SLIP2GO_VERIFY_URL
+ - name: SLIP2GO_API_SECRET
+ valueFrom:
+ secretKeyRef:
+ name: buzz-sheet-env
+ key: SLIP2GO_API_SECRET
+ - name: COMMISSION_DISCORD_WEBHOOK_URL
+ valueFrom:
+ secretKeyRef:
+ name: buzz-sheet-env
+ key: COMMISSION_DISCORD_WEBHOOK_URL
+ optional: true
- name: NEXT_SERVER_ACTIONS_ENCRYPTION_KEY
valueFrom:
secretKeyRef:
diff --git a/lib/auth/return-path.test.ts b/lib/auth/return-path.test.ts
new file mode 100644
index 0000000..8979f8c
--- /dev/null
+++ b/lib/auth/return-path.test.ts
@@ -0,0 +1,13 @@
+import { describe, expect, it } from "vitest";
+import { safeAuthReturnPath } from "./return-path";
+
+describe("public auth return path", () => {
+ it("keeps site-local destinations", () => {
+ expect(safeAuthReturnPath("/commission/tickets?from=login")).toBe("/commission/tickets?from=login");
+ });
+
+ it("rejects external destinations and auth loops", () => {
+ for (const value of ["https://example.com", "//example.com", "/\\example.com", "/login", "/lo%67in", "/register", 4])
+ expect(safeAuthReturnPath(value)).toBe("/");
+ });
+});
diff --git a/lib/auth/return-path.ts b/lib/auth/return-path.ts
new file mode 100644
index 0000000..ae11c9b
--- /dev/null
+++ b/lib/auth/return-path.ts
@@ -0,0 +1,12 @@
+export function safeAuthReturnPath(value: unknown): string {
+ if (typeof value !== "string" || !value.startsWith("/")) return "/";
+ try {
+ const url = new URL(value, "https://auth.local");
+ const path = decodeURIComponent(url.pathname);
+ if (url.origin !== "https://auth.local" || ["/login", "/register", "/commission/login"].includes(path))
+ return "/";
+ return `${url.pathname}${url.search}${url.hash}`;
+ } catch {
+ return "/";
+ }
+}
diff --git a/lib/auth/server.test.ts b/lib/auth/server.test.ts
index 79072c4..038cc14 100644
--- a/lib/auth/server.test.ts
+++ b/lib/auth/server.test.ts
@@ -50,7 +50,7 @@ describe("actual administrator session boundary", () => {
it("defaults accounts to user and requires strong new passwords", async () => {
(await import("./server")).getAuth();
const options = mocks.auth.mock.calls.at(-1)![0];
- expect(options.emailAndPassword).toMatchObject({ disableSignUp: true, minPasswordLength: 12 });
+ expect(options.emailAndPassword).toMatchObject({ disableSignUp: false, minPasswordLength: 12 });
expect(options.plugins).toContainEqual({ defaultRole: "user" });
expect(options.rateLimit.customStorage.consume).toBeTypeOf("function");
expect(options.databaseHooks).toBeUndefined();
diff --git a/lib/auth/server.ts b/lib/auth/server.ts
index 5032bbb..1bf7005 100644
--- a/lib/auth/server.ts
+++ b/lib/auth/server.ts
@@ -52,7 +52,7 @@ function createAuth() {
secret: required("BETTER_AUTH_SECRET"),
emailAndPassword: {
enabled: true,
- disableSignUp: true,
+ disableSignUp: false,
minPasswordLength: 12,
maxPasswordLength: 128,
},
@@ -72,7 +72,7 @@ function createAuth() {
captcha({
provider: "cloudflare-turnstile",
secretKey: required("TURNSTILE_SECRET_KEY"),
- endpoints: ["/sign-in/email"],
+ endpoints: ["/sign-in/email", "/sign-up/email"],
}),
]),
admin({ defaultRole: "user" }),
@@ -125,3 +125,10 @@ export async function requireAdmin(): Promise {
if (!session) throw new AdminAuthorizationError();
return session;
}
+
+export const getCustomerSession = cache(async () => {
+ if (!hasAuthConfiguration()) return null;
+ const session = await getAuth().api.getSession({ headers: await headers() });
+ if (!session?.session) return null;
+ return { user: session.user, session: { id: session.session.id } };
+});
diff --git a/lib/commission/catalog.ts b/lib/commission/catalog.ts
new file mode 100644
index 0000000..d2c1c17
--- /dev/null
+++ b/lib/commission/catalog.ts
@@ -0,0 +1,62 @@
+import "server-only";
+
+import { getDb } from "@/db";
+import { catalogArtifacts, catalogCharacters, catalogWeapons } from "@/db/schema";
+import { inArray } from "drizzle-orm";
+import { publicMediaUrl } from "@/lib/media/storage";
+import type { CommissionRequest } from "./request";
+
+export async function validateCommissionCatalog(request: CommissionRequest): Promise {
+ const [characters, weapons, artifacts] = await Promise.all([
+ getDb().select({ key: catalogCharacters.key, weaponType: catalogCharacters.weaponType,
+ constellations: catalogCharacters.constellations }).from(catalogCharacters),
+ getDb().select({ key: catalogWeapons.key, weaponType: catalogWeapons.weaponType }).from(catalogWeapons),
+ getDb().select({ key: catalogArtifacts.key }).from(catalogArtifacts),
+ ]);
+ const characterByKey = new Map(characters.map((item) => [item.key, item]));
+ const weaponByKey = new Map(weapons.map((item) => [item.key, item]));
+ const artifactKeys = new Set(artifacts.map((item) => item.key));
+ const compatible = (characterKey: string, weaponKey: string) => {
+ const character = characterByKey.get(characterKey);
+ const weapon = weaponByKey.get(weaponKey);
+ return Boolean(character?.weaponType && weapon?.weaponType === character.weaponType);
+ };
+ return request.teams.every(({ members }) => members.every((member) =>
+ compatible(member.characterKey, member.weaponKey) && artifactKeys.has(member.artifactKey))) &&
+ request.weapons.every(({ characterKey, weaponKeys }) =>
+ weaponKeys.every((weaponKey) => compatible(characterKey, weaponKey))) &&
+ request.constellations.every(({ characterKey, levels }) => {
+ const character = characterByKey.get(characterKey);
+ return Boolean(character && levels.every((level) => level === 0 ||
+ character.constellations.some((item) => item.level === level)));
+ });
+}
+
+export async function getCommissionLabels(request: CommissionRequest) {
+ const characterKeys = [...new Set([
+ ...request.teams.flatMap(({ members }) => members.map(({ characterKey }) => characterKey)),
+ ...request.weapons.map(({ characterKey }) => characterKey),
+ ...request.constellations.map(({ characterKey }) => characterKey),
+ ])];
+ const weaponKeys = [...new Set([
+ ...request.teams.flatMap(({ members }) => members.map(({ weaponKey }) => weaponKey)),
+ ...request.weapons.flatMap(({ weaponKeys }) => weaponKeys),
+ ])];
+ const artifactKeys = [...new Set(request.teams.flatMap(({ members }) => members.map(({ artifactKey }) => artifactKey)))];
+ const [characters, weapons, artifacts] = await Promise.all([
+ characterKeys.length ? getDb().select({ key: catalogCharacters.key, name: catalogCharacters.name,
+ imageKey: catalogCharacters.imageKey, element: catalogCharacters.element, rarity: catalogCharacters.rarity })
+ .from(catalogCharacters).where(inArray(catalogCharacters.key, characterKeys)) : Promise.resolve([]),
+ weaponKeys.length ? getDb().select({ key: catalogWeapons.key, name: catalogWeapons.name,
+ imageKey: catalogWeapons.imageKey, rarity: catalogWeapons.rarity })
+ .from(catalogWeapons).where(inArray(catalogWeapons.key, weaponKeys)) : Promise.resolve([]),
+ artifactKeys.length ? getDb().select({ key: catalogArtifacts.key, name: catalogArtifacts.name,
+ imageKey: catalogArtifacts.imageKey, rarity: catalogArtifacts.rarity })
+ .from(catalogArtifacts).where(inArray(catalogArtifacts.key, artifactKeys)) : Promise.resolve([]),
+ ]);
+ return {
+ characters: characters.map(({ imageKey, ...item }) => ({ ...item, imageUrl: publicMediaUrl(imageKey) })),
+ weapons: weapons.map(({ imageKey, ...item }) => ({ ...item, imageUrl: publicMediaUrl(imageKey) })),
+ artifacts: artifacts.map(({ imageKey, ...item }) => ({ ...item, imageUrl: publicMediaUrl(imageKey) })),
+ };
+}
diff --git a/lib/commission/discord.test.ts b/lib/commission/discord.test.ts
new file mode 100644
index 0000000..20a59e3
--- /dev/null
+++ b/lib/commission/discord.test.ts
@@ -0,0 +1,57 @@
+import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
+
+vi.mock("server-only", () => ({}));
+
+const { notifyPaidTicketDiscord } = await import("./discord");
+const ticketId = "11111111-1111-4111-8111-111111111111";
+
+describe("paid commission Discord notification", () => {
+ const fetchMock = vi.fn();
+ const originalFetch = globalThis.fetch;
+ const originalWebhookUrl = process.env.COMMISSION_DISCORD_WEBHOOK_URL;
+ const originalAuthUrl = process.env.BETTER_AUTH_URL;
+
+ beforeEach(() => {
+ process.env.COMMISSION_DISCORD_WEBHOOK_URL = "https://discord.example.test/webhook";
+ process.env.BETTER_AUTH_URL = "https://guide.sudloh.com";
+ globalThis.fetch = fetchMock as unknown as typeof fetch;
+ fetchMock.mockReset().mockResolvedValue(new Response(null, { status: 204 }));
+ });
+
+ afterEach(() => {
+ if (originalWebhookUrl === undefined) delete process.env.COMMISSION_DISCORD_WEBHOOK_URL;
+ else process.env.COMMISSION_DISCORD_WEBHOOK_URL = originalWebhookUrl;
+ if (originalAuthUrl === undefined) delete process.env.BETTER_AUTH_URL;
+ else process.env.BETTER_AUTH_URL = originalAuthUrl;
+ globalThis.fetch = originalFetch;
+ vi.restoreAllMocks();
+ });
+
+ it("sends an embed linking to the admin ticket chat", async () => {
+ await notifyPaidTicketDiscord(ticketId, 140);
+
+ expect(fetchMock).toHaveBeenCalledOnce();
+ const [url, options] = fetchMock.mock.calls[0] as [string, RequestInit];
+ expect(url).toBe("https://discord.example.test/webhook");
+ expect(options.method).toBe("POST");
+ const payload = JSON.parse(options.body as string);
+ expect(payload.allowed_mentions).toEqual({ parse: [] });
+ expect(payload.embeds[0]).toMatchObject({
+ url: `https://guide.sudloh.com/admin/commission/${ticketId}`,
+ description: expect.stringContaining("฿140"),
+ });
+ });
+
+ it("skips the request when no webhook is configured", async () => {
+ delete process.env.COMMISSION_DISCORD_WEBHOOK_URL;
+ await notifyPaidTicketDiscord(ticketId, 140);
+ expect(fetchMock).not.toHaveBeenCalled();
+ });
+
+ it("does not turn a paid ticket into an error when Discord fails", async () => {
+ vi.spyOn(console, "error").mockImplementation(() => undefined);
+ fetchMock.mockRejectedValueOnce(new Error("network unavailable"));
+ await expect(notifyPaidTicketDiscord(ticketId, 140)).resolves.toBeUndefined();
+ expect(console.error).toHaveBeenCalledWith("Commission Discord notification failed");
+ });
+});
diff --git a/lib/commission/discord.ts b/lib/commission/discord.ts
new file mode 100644
index 0000000..526f10a
--- /dev/null
+++ b/lib/commission/discord.ts
@@ -0,0 +1,29 @@
+import "server-only";
+
+import { siteUrl } from "@/lib/site-url";
+
+export async function notifyPaidTicketDiscord(ticketId: string, amountBaht: number) {
+ const webhookUrl = process.env.COMMISSION_DISCORD_WEBHOOK_URL;
+ if (!webhookUrl) return;
+
+ try {
+ const response = await fetch(webhookUrl, {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({
+ allowed_mentions: { parse: [] },
+ embeds: [{
+ title: "มี Ticket คอมมิชชันใหม่ที่ชำระเงินแล้ว",
+ url: new URL(`/admin/commission/${ticketId}`, process.env.BETTER_AUTH_URL ?? siteUrl).toString(),
+ description: `Ticket #${ticketId.slice(0, 8)} · ฿${amountBaht}`,
+ color: 0x57f287,
+ timestamp: new Date().toISOString(),
+ }],
+ }),
+ signal: AbortSignal.timeout(5000),
+ });
+ if (!response.ok) console.error(`Commission Discord notification failed (${response.status})`);
+ } catch {
+ console.error("Commission Discord notification failed");
+ }
+}
diff --git a/lib/commission/payment.test.ts b/lib/commission/payment.test.ts
new file mode 100644
index 0000000..4caf91f
--- /dev/null
+++ b/lib/commission/payment.test.ts
@@ -0,0 +1,68 @@
+import { afterEach, describe, expect, it, vi } from "vitest";
+
+vi.mock("server-only", () => ({}));
+import { promptPayConfig, verifyCommissionSlip } from "./payment";
+
+const names = ["SLIP2GO_VERIFY_URL", "SLIP2GO_API_SECRET", "COMMISSION_PROMPTPAY_TYPE",
+ "COMMISSION_PROMPTPAY_VALUE", "COMMISSION_RECEIVER_ACCOUNT_NUMBER"] as const;
+const original = names.map((name) => process.env[name]);
+const checkoutDate = new Date("2026-09-01T12:00:00.000Z");
+const validData = { code: "200200", data: { referenceId: "provider-ref", transRef: "bank-ref",
+ amount: 100, dateTime: "2026-09-01T12:01:00.000Z" } };
+
+afterEach(() => {
+ names.forEach((name, index) => {
+ if (original[index] === undefined) delete process.env[name];
+ else process.env[name] = original[index];
+ });
+ vi.unstubAllGlobals();
+});
+
+describe("commission slip verification", () => {
+ it("rejects a non-mobile PromptPay value before checkout", () => {
+ process.env.COMMISSION_PROMPTPAY_TYPE = "mobile";
+ process.env.COMMISSION_PROMPTPAY_VALUE = "123456789012345";
+ process.env.COMMISSION_RECEIVER_ACCOUNT_NUMBER = "0812345678";
+ expect(() => promptPayConfig()).toThrow("invalid-promptpay-value");
+ process.env.COMMISSION_PROMPTPAY_VALUE = "0812345678";
+ expect(promptPayConfig().identifier).toEqual({ type: "mobile", value: "0066812345678" });
+ });
+ async function check(response: unknown) {
+ process.env.SLIP2GO_VERIFY_URL = "https://example.test/api/verify-slip/qr-image/info";
+ process.env.SLIP2GO_API_SECRET = "secret";
+ process.env.COMMISSION_PROMPTPAY_TYPE = "mobile";
+ process.env.COMMISSION_PROMPTPAY_VALUE = "0812345678";
+ process.env.COMMISSION_RECEIVER_ACCOUNT_NUMBER = "0812345678";
+ const fetch = vi.fn(async (_url: URL, init: RequestInit) => {
+ expect(init.headers).toEqual({ Authorization: "Bearer secret" });
+ const payload = JSON.parse((init.body as FormData).get("payload") as string);
+ expect(payload).toMatchObject({ checkDuplicate: true,
+ checkReceiver: [{ accountType: "02001", accountNumber: "0812345678" }],
+ checkAmount: { type: "eq", amount: "100.00" } });
+ return Response.json(response);
+ });
+ vi.stubGlobal("fetch", fetch);
+ return verifyCommissionSlip(new File(["image"], "slip.png", { type: "image/png" }), 100, checkoutDate);
+ }
+ it("accepts a matching verified transfer", async () => {
+ await expect(check(validData)).resolves.toMatchObject({ referenceId: "provider-ref",
+ transRef: "bank-ref", amountBaht: 100 });
+ });
+ it("rejects duplicate and amount mismatch results", async () => {
+ await expect(check({ code: "200501" })).rejects.toMatchObject({ status: 422 });
+ await expect(check({ code: "200401", data: { referenceId: "provider-ref" } }))
+ .rejects.toMatchObject({ status: 422, message: "slip-rejected:200401" });
+ await expect(check({ ...validData, code: "200000" })).rejects.toMatchObject({ status: 422 });
+ await expect(check({ ...validData, data: { ...validData.data, amount: 80 } }))
+ .rejects.toMatchObject({ status: 422 });
+ });
+ it("treats a rejected API secret as a service error, not an invalid slip", async () => {
+ await expect(check({ code: "401001", message: "Authentication Token Mismatch",
+ data: { referenceId: "provider-ref" } }))
+ .rejects.toMatchObject({ status: 503, message: "slip2go-service:401001" });
+ });
+ it("reports which required field is missing from a successful provider response", async () => {
+ await expect(check({ code: "200200", data: { referenceId: "provider-ref" } }))
+ .rejects.toMatchObject({ status: 503, message: "invalid-slip-verification-response:transRef" });
+ });
+});
diff --git a/lib/commission/payment.ts b/lib/commission/payment.ts
new file mode 100644
index 0000000..d15cad5
--- /dev/null
+++ b/lib/commission/payment.ts
@@ -0,0 +1,75 @@
+import "server-only";
+
+import * as z from "zod";
+import { HttpError } from "@/lib/security/http";
+import { normalizePromptPayIdentifier } from "@/lib/commission/promptpay";
+
+const slipResponse = z.object({ code: z.string() }).passthrough();
+const verifiedSlip = z.object({
+ referenceId: z.string().min(1),
+ transRef: z.string().min(1),
+ amount: z.number(),
+ dateTime: z.string().refine((value) => !Number.isNaN(Date.parse(value))),
+}).passthrough();
+
+function required(name: string) {
+ const value = process.env[name];
+ if (!value) throw new HttpError(503, `${name}-not-configured`);
+ return value;
+}
+
+export function promptPayConfig() {
+ const type = required("COMMISSION_PROMPTPAY_TYPE");
+ if (type !== "mobile" && type !== "nationalId" && type !== "ewallet")
+ throw new HttpError(503, "invalid-promptpay-type");
+ let identifier;
+ try {
+ identifier = normalizePromptPayIdentifier({ type, value: required("COMMISSION_PROMPTPAY_VALUE") });
+ } catch {
+ throw new HttpError(503, "invalid-promptpay-value");
+ }
+ return { identifier, receiverNumber: required("COMMISSION_RECEIVER_ACCOUNT_NUMBER") };
+}
+
+export async function verifyCommissionSlip(file: File, amountBaht: number, checkoutCreatedAt: Date) {
+ const url = new URL(required("SLIP2GO_VERIFY_URL"));
+ if (url.protocol !== "https:") throw new HttpError(503, "invalid-slip2go-url");
+ const { identifier, receiverNumber } = promptPayConfig();
+ const form = new FormData();
+ form.set("file", file);
+ form.set("payload", JSON.stringify({
+ checkDuplicate: true,
+ checkReceiver: [{
+ accountType: { mobile: "02001", nationalId: "02003", ewallet: "02004" }[identifier.type],
+ accountNumber: receiverNumber,
+ }],
+ checkAmount: { type: "eq", amount: amountBaht.toFixed(2) },
+ }));
+ let response: Response;
+ try {
+ response = await fetch(url, { method: "POST", headers: { Authorization: `Bearer ${required("SLIP2GO_API_SECRET")}` },
+ body: form, signal: AbortSignal.timeout(15000), cache: "no-store" });
+ } catch {
+ throw new HttpError(503, "slip-verification-unavailable");
+ }
+ const body = slipResponse.safeParse(await response.json().catch(() => null));
+ if (!body.success) throw new HttpError(503, "invalid-slip-verification-response");
+ const code = body.data.code;
+ if (response.status >= 500 || response.status === 401 || code.startsWith("401") ||
+ code === "400400" || code === "200502")
+ throw new HttpError(503, `slip2go-service:${code}`);
+ if (!response.ok || code !== "200200")
+ throw new HttpError(422, `slip-rejected:${code}`);
+ const parsedSlip = verifiedSlip.safeParse(body.data.data);
+ if (!parsedSlip.success) {
+ const field = parsedSlip.error.issues[0]?.path[0] ?? "data";
+ throw new HttpError(503, `invalid-slip-verification-response:${String(field)}`);
+ }
+ const slip = parsedSlip.data;
+ if (slip.amount !== amountBaht ||
+ Date.parse(slip.dateTime) < checkoutCreatedAt.getTime() - 120000 ||
+ Date.parse(slip.dateTime) > Date.now() + 120000)
+ throw new HttpError(422, "slip-amount-or-date-mismatch");
+ return { referenceId: slip.referenceId, transRef: slip.transRef, amountBaht,
+ transferredAt: new Date(slip.dateTime), providerData: body.data as Record };
+}
diff --git a/lib/commission/promptpay.ts b/lib/commission/promptpay.ts
new file mode 100644
index 0000000..ccdb9b2
--- /dev/null
+++ b/lib/commission/promptpay.ts
@@ -0,0 +1,483 @@
+import * as z from "zod";
+
+// #region Public types
+export type DataObject =
+ | {
+ type: "primitive";
+ tag: number;
+ value: string;
+ }
+ | {
+ type: "template";
+ tag: number;
+ value: DataObject[];
+ };
+
+export type PaymentNetworkSpecific = {
+ tag: number;
+ value: DataObject[];
+};
+
+export type EMVCoData = {
+ payloadFormatIndicator: "01";
+ pointOfInitiationMethod?: "11" | "12";
+ paymentNetworkSpecific: PaymentNetworkSpecific[];
+ merchantCategoryCode?: string;
+ transactionCurrency?: string;
+ transactionAmount?: string;
+ countryCode: string;
+ additionalData?: DataObject[];
+};
+
+export type PromptPayIdentifier =
+ | { type: "mobile"; value: string }
+ | { type: "nationalId"; value: string }
+ | { type: "ewallet"; value: string }
+ | { type: "bankAccount"; value: string };
+
+export type EncodablePromptPayIdentifier = Exclude<
+ PromptPayIdentifier,
+ { type: "bankAccount" }
+>;
+
+export type PromptPayData = {
+ aid: "A000000677010111";
+ identifier: PromptPayIdentifier;
+};
+
+export type GeneratePromptPayOptions = {
+ identifier: EncodablePromptPayIdentifier;
+ amount?: number | string;
+ merchantCategoryCode?: string;
+};
+
+export type ParsedPromptPayPayload = {
+ payload: string;
+ dataObjects: DataObject[];
+ emvco: EMVCoData;
+ promptPay: PromptPayData;
+};
+
+// #region TLV
+function serializeTLV(objects: DataObject[]): string {
+ return [...objects]
+ .sort((left, right) => left.tag - right.tag)
+ .map((object, index) => {
+ if (!Number.isInteger(object.tag) || object.tag < 0 || object.tag > 99)
+ throw new Error(`Tag ${index} must be 00-99`);
+
+ const value =
+ object.type === "primitive" ? object.value : serializeTLV(object.value);
+
+ if (value.length > 99)
+ throw new Error(`Value ${index} exceeds TLV length limit`);
+
+ const tag = object.tag.toString().padStart(2, "0");
+ const length = value.length.toString().padStart(2, "0");
+ return `${tag}${length}${value}`;
+ })
+ .join("");
+}
+
+function parseTLV(payload: string): DataObject[] {
+ const objects: DataObject[] = [];
+ let index = 0;
+
+ while (index < payload.length) {
+ if (index + 4 > payload.length)
+ throw new Error(
+ `Malformed TLV payload: incomplete headers at index ${index}`,
+ );
+
+ const tagString = payload.slice(index, index + 2);
+ if (!/^\d{2}$/.test(tagString))
+ throw new Error(
+ `Malformed TLV payload: invalid tag "${tagString}" at index ${index}`,
+ );
+ const tag = Number.parseInt(tagString, 10);
+ index += 2;
+
+ const lengthString = payload.slice(index, index + 2);
+ if (!/^\d{2}$/.test(lengthString))
+ throw new Error(
+ `Malformed TLV payload: invalid length "${lengthString}" at index ${index}`,
+ );
+ const length = Number.parseInt(lengthString, 10);
+ index += 2;
+
+ if (index + length > payload.length)
+ throw new Error(
+ `Malformed TLV payload: expected value length ${length} at index ${index}, but reached end of string`,
+ );
+
+ const value = payload.slice(index, index + length);
+ index += length;
+ objects.push({ type: "primitive", tag, value });
+ }
+
+ return objects;
+}
+
+// #region Utilities
+function primitive(
+ data: DataObject[] | undefined,
+ tag: number,
+): string | undefined {
+ return data?.find(
+ (object): object is Extract =>
+ object.tag === tag && object.type === "primitive",
+ )?.value;
+}
+
+function optionalProperty(key: string, value: T | undefined) {
+ return value === undefined ? {} : { [key]: value };
+}
+
+// #region EMVCo
+const TEMPLATE_TAGS = new Set([
+ ...Array.from({ length: 50 }, (_, index) => index + 2),
+ 62,
+ 64,
+ ...Array.from({ length: 20 }, (_, index) => index + 80),
+]);
+
+function parseEMVCo(payload: string): DataObject[] {
+ return parseTLV(payload).map((object) =>
+ TEMPLATE_TAGS.has(object.tag) && object.type === "primitive"
+ ? {
+ type: "template",
+ tag: object.tag,
+ value: parseTLV(object.value),
+ }
+ : object,
+ );
+}
+
+const emvCoSchema = z.object({
+ payloadFormatIndicator: z.literal("01"),
+ pointOfInitiationMethod: z.enum(["11", "12"]).optional(),
+ paymentNetworkSpecific: z.array(
+ z.object({
+ tag: z.number().int().min(2).max(51),
+ value: z.custom((value) => Array.isArray(value)),
+ }),
+ ),
+ merchantCategoryCode: z.string().length(4).optional(),
+ transactionCurrency: z.string().length(3).optional(),
+ transactionAmount: z.string().optional(),
+ countryCode: z.string().length(2),
+ additionalData: z.array(z.custom()).optional(),
+});
+
+const MODELED_EMVCO_TAGS = new Set([0, 1, 52, 53, 54, 58]);
+
+function decodeEMVCo(objects: DataObject[]): EMVCoData {
+ const additionalData = objects.filter(
+ (object) =>
+ !MODELED_EMVCO_TAGS.has(object.tag) &&
+ !(object.tag >= 2 && object.tag <= 51),
+ );
+
+ return emvCoSchema.parse({
+ payloadFormatIndicator: primitive(objects, 0),
+ ...optionalProperty("pointOfInitiationMethod", primitive(objects, 1)),
+ paymentNetworkSpecific: objects
+ .filter(
+ (object): object is Extract =>
+ object.type === "template" && object.tag >= 2 && object.tag <= 51,
+ )
+ .map(({ tag, value }) => ({ tag, value })),
+ ...optionalProperty("merchantCategoryCode", primitive(objects, 52)),
+ ...optionalProperty("transactionCurrency", primitive(objects, 53)),
+ ...optionalProperty("transactionAmount", primitive(objects, 54)),
+ countryCode: primitive(objects, 58),
+ ...(additionalData.length === 0 ? {} : { additionalData }),
+ });
+}
+
+function encodeEMVCo(data: EMVCoData): DataObject[] {
+ const parsed = emvCoSchema.parse(data);
+ const objects: DataObject[] = [
+ { type: "primitive", tag: 0, value: parsed.payloadFormatIndicator },
+ ];
+
+ if (parsed.pointOfInitiationMethod !== undefined)
+ objects.push({
+ type: "primitive",
+ tag: 1,
+ value: parsed.pointOfInitiationMethod,
+ });
+
+ objects.push(
+ ...parsed.paymentNetworkSpecific.map(
+ ({ tag, value }): DataObject => ({ type: "template", tag, value }),
+ ),
+ );
+
+ for (const [tag, value] of [
+ [52, parsed.merchantCategoryCode],
+ [53, parsed.transactionCurrency],
+ [54, parsed.transactionAmount],
+ [58, parsed.countryCode],
+ ] as const) {
+ if (value !== undefined) objects.push({ type: "primitive", tag, value });
+ }
+
+ objects.push(...(parsed.additionalData ?? []));
+ return objects;
+}
+
+// #region CRC
+function crc16(data: Uint8Array): number {
+ let crc = 0xffff;
+ const polynomial = 0x1021;
+
+ for (const byte of data) {
+ crc ^= byte << 8;
+ for (let bit = 0; bit < 8; bit++)
+ crc =
+ (crc & 0x8000) !== 0
+ ? ((crc << 1) ^ polynomial) & 0xffff
+ : (crc << 1) & 0xffff;
+ }
+
+ return crc;
+}
+
+function formatCRC(crc: number): string {
+ return crc.toString(16).toUpperCase().padStart(4, "0");
+}
+
+function verifyCRC(payload: string): boolean {
+ if (payload.length < 8 || payload.slice(-8, -4) !== "6304") return false;
+
+ const expected = payload.slice(-4);
+ const calculated = formatCRC(
+ crc16(new TextEncoder().encode(payload.slice(0, -4))),
+ );
+ return calculated === expected;
+}
+
+function appendCRC(payload: string): string {
+ const body = `${payload}6304`;
+ return `${body}${formatCRC(crc16(new TextEncoder().encode(body)))}`;
+}
+
+// #region PromptPay
+const PROMPTPAY_AID = "A000000677010111" as const;
+
+const promptPaySchema = z.object({
+ aid: z.literal(PROMPTPAY_AID),
+ identifier: z.discriminatedUnion("type", [
+ z.object({
+ type: z.literal("mobile"),
+ value: z.string().regex(/^\d{13}$/),
+ }),
+ z.object({
+ type: z.literal("nationalId"),
+ value: z.string().regex(/^\d{13}$/),
+ }),
+ z.object({
+ type: z.literal("ewallet"),
+ value: z.string().regex(/^\d{15}$/),
+ }),
+ z.object({
+ type: z.literal("bankAccount"),
+ value: z.string().regex(/^\d{1,43}$/),
+ }),
+ ]),
+});
+
+const IDENTIFIER_TYPES = {
+ 1: "mobile",
+ 2: "nationalId",
+ 3: "ewallet",
+ 4: "bankAccount",
+} as const;
+
+const IDENTIFIER_TAGS = {
+ mobile: 1,
+ nationalId: 2,
+ ewallet: 3,
+} as const;
+
+function decodePromptPay(merchantAccount: DataObject[]): PromptPayData {
+ const identifiers = merchantAccount.filter(
+ (object): object is Extract =>
+ object.type === "primitive" && object.tag in IDENTIFIER_TYPES,
+ );
+
+ if (identifiers.length !== 1)
+ throw new Error("Expected exactly one PromptPay identifier.");
+
+ const identifier = identifiers[0];
+ return promptPaySchema.parse({
+ aid: primitive(merchantAccount, 0),
+ identifier: {
+ type: IDENTIFIER_TYPES[identifier.tag as keyof typeof IDENTIFIER_TYPES],
+ value: identifier.value,
+ },
+ });
+}
+
+function encodePromptPay(
+ identifier: EncodablePromptPayIdentifier,
+): DataObject[] {
+ const parsed = promptPaySchema.shape.identifier.parse(identifier);
+ if (parsed.type === "bankAccount")
+ throw new Error(
+ "PromptPay bank account encoding is reserved for future use.",
+ );
+
+ return [
+ { type: "primitive", tag: 0, value: PROMPTPAY_AID },
+ {
+ type: "primitive",
+ tag: IDENTIFIER_TAGS[parsed.type],
+ value: parsed.value,
+ },
+ ];
+}
+
+function normalizePromptPayIdentifier(
+ identifier: EncodablePromptPayIdentifier,
+): EncodablePromptPayIdentifier {
+ const value = identifier.value.trim();
+
+ if (identifier.type === "mobile") {
+ if (!/^\+?[\d\s()-]+$/.test(value))
+ throw new Error("PromptPay mobile number contains invalid characters.");
+
+ const digits = value.replace(/\D/g, "");
+ const canonical = /^0\d{9}$/.test(digits)
+ ? `0066${digits.slice(1)}`
+ : /^66\d{9}$/.test(digits)
+ ? `00${digits}`
+ : digits;
+
+ if (!/^0066\d{9}$/.test(canonical))
+ throw new Error("PromptPay mobile number must be a 10-digit Thai number beginning with 0.");
+
+ return promptPaySchema.shape.identifier.parse({
+ type: "mobile",
+ value: canonical,
+ }) as EncodablePromptPayIdentifier;
+ }
+
+ if (!/^[\d\s-]+$/.test(value))
+ throw new Error(
+ `PromptPay ${identifier.type} contains invalid characters.`,
+ );
+
+ return promptPaySchema.shape.identifier.parse({
+ type: identifier.type,
+ value: value.replace(/[\s-]/g, ""),
+ }) as EncodablePromptPayIdentifier;
+}
+
+function formatAmount(amount: number | string): string {
+ let formatted = "";
+ if (typeof amount === "number") {
+ if (Number.isFinite(amount)) formatted = amount.toFixed(2);
+ } else {
+ const value = amount.trim();
+ if (/^\d+(?:\.\d{1,2})?$/.test(value)) {
+ const [integer, fraction = ""] = value.split(".");
+ formatted = `${integer}.${fraction.padEnd(2, "0")}`;
+ }
+ }
+
+ if (!/^\d+\.\d{2}$/.test(formatted) || Number(formatted) <= 0)
+ throw new Error(
+ "PromptPay amount must be a positive number with at most 2 decimals.",
+ );
+ if (formatted.length > 13)
+ throw new Error("PromptPay amount exceeds the EMVCo length limit.");
+
+ return formatted;
+}
+
+function generatePromptPayPayload(options: GeneratePromptPayOptions): string {
+ const identifier = normalizePromptPayIdentifier(options.identifier);
+ const amount =
+ options.amount === undefined ? undefined : formatAmount(options.amount);
+
+ if (
+ options.merchantCategoryCode !== undefined &&
+ !/^\d{4}$/.test(options.merchantCategoryCode)
+ )
+ throw new Error("PromptPay merchant category code must be 4 digits.");
+
+ const objects: DataObject[] = [
+ { type: "primitive", tag: 0, value: "01" },
+ {
+ type: "primitive",
+ tag: 1,
+ value: amount === undefined ? "11" : "12",
+ },
+ { type: "template", tag: 29, value: encodePromptPay(identifier) },
+ ];
+
+ if (options.merchantCategoryCode !== undefined)
+ objects.push({
+ type: "primitive",
+ tag: 52,
+ value: options.merchantCategoryCode,
+ });
+
+ objects.push(
+ { type: "primitive", tag: 53, value: "764" },
+ { type: "primitive", tag: 58, value: "TH" },
+ );
+ if (amount !== undefined)
+ objects.push({ type: "primitive", tag: 54, value: amount });
+
+ return appendCRC(serializeTLV(objects));
+}
+
+function parsePromptPayPayload(payload: string): ParsedPromptPayPayload {
+ if (!verifyCRC(payload)) throw new Error("Invalid PromptPay payload CRC.");
+
+ const dataObjects = parseEMVCo(payload);
+ const crc = dataObjects.at(-1);
+ if (crc?.type !== "primitive" || crc.tag !== 63)
+ throw new Error("PromptPay payload is missing its CRC data object.");
+
+ const emvco = decodeEMVCo(dataObjects.slice(0, -1));
+ if (emvco.transactionCurrency !== "764")
+ throw new Error("PromptPay transaction currency must be THB (764).");
+ if (emvco.countryCode !== "TH")
+ throw new Error("Domestic PromptPay country code must be TH.");
+
+ const promptPayTemplates = emvco.paymentNetworkSpecific.filter(
+ ({ tag }) => tag === 29,
+ );
+ if (promptPayTemplates.length !== 1)
+ throw new Error(
+ "Expected exactly one PromptPay merchant account template.",
+ );
+
+ return {
+ payload,
+ dataObjects,
+ emvco,
+ promptPay: decodePromptPay(promptPayTemplates[0].value),
+ };
+}
+
+export {
+ appendCRC,
+ crc16,
+ decodeEMVCo,
+ decodePromptPay,
+ encodeEMVCo,
+ encodePromptPay,
+ formatCRC,
+ generatePromptPayPayload,
+ normalizePromptPayIdentifier,
+ parseEMVCo,
+ parsePromptPayPayload,
+ parseTLV,
+ serializeTLV,
+ verifyCRC,
+};
diff --git a/lib/commission/request.test.ts b/lib/commission/request.test.ts
new file mode 100644
index 0000000..60afae4
--- /dev/null
+++ b/lib/commission/request.test.ts
@@ -0,0 +1,57 @@
+import { describe, expect, it } from "vitest";
+import { commissionPrice, commissionRequestSchema } from "./request";
+
+const request = {
+ teams: [{ members: ["a", "b", "c", "d"].map((characterKey) => ({
+ characterKey, weaponKey: `weapon-${characterKey}`, artifactKey: "artifact",
+ })) }],
+ weapons: [{ characterKey: "a", weaponKeys: ["weapon-a", "weapon-b", "weapon-c"] }],
+ constellations: [{ characterKey: "a", levels: [0, 2] }],
+};
+
+describe("commission request pricing and shape", () => {
+ it("accepts one complete team without either comparison type", () => {
+ const teamOnly = { teams: request.teams, weapons: [], constellations: [] };
+ const parsed = commissionRequestSchema.safeParse(teamOnly);
+ expect(parsed.success).toBe(true);
+ if (parsed.success) {
+ expect(commissionPrice(parsed.data)).toBe(100);
+ expect(parsed.data.teams[0].members[0]).toMatchObject({ constellation: "0", refinement: "1" });
+ }
+ });
+ it("keeps selected team constellation and refinement ranges", () => {
+ const selected = { ...request, teams: [{ members: request.teams[0].members.map((member, index) =>
+ index === 0 ? { ...member, constellation: "0-6", refinement: "1-5" } : member) }] };
+ expect(commissionRequestSchema.parse(selected).teams[0].members[0]).toMatchObject({
+ constellation: "0-6", refinement: "1-5",
+ });
+ expect(commissionRequestSchema.safeParse({ ...selected, teams: [{ members: selected.teams[0].members.map((member, index) =>
+ index === 0 ? { ...member, refinement: "6" } : member) }] }).success).toBe(false);
+ });
+ it("prices independent request types together", () => {
+ expect(commissionPrice(commissionRequestSchema.parse(request))).toBe(200);
+ expect(commissionPrice(commissionRequestSchema.parse({ teams: [], weapons: [],
+ constellations: [{ characterKey: "a", levels: [0, 1] }] }))).toBe(140);
+ expect(commissionPrice(commissionRequestSchema.parse({ teams: [], weapons: [],
+ constellations: [{ characterKey: "a", levels: [0] }] }))).toBe(120);
+ expect(commissionPrice(commissionRequestSchema.parse({ teams: [], weapons: [
+ { characterKey: "a", weaponKeys: ["weapon-a", "weapon-b"] },
+ ], constellations: [] }))).toBe(140);
+ expect(commissionPrice({ teams: [], weapons: [{ characterKey: "a", weaponKeys: ["weapon-a", "", ""] }],
+ constellations: [] })).toBe(120);
+ expect(commissionPrice(commissionRequestSchema.parse({ teams: request.teams.concat(request.teams),
+ weapons: [], constellations: [] }))).toBe(200);
+ expect(commissionPrice({ teams: [], weapons: [], constellations: [] })).toBe(0);
+ });
+ it("requires four distinct team characters and valid comparison choices", () => {
+ expect(commissionRequestSchema.safeParse({ ...request, teams: [{ members: [
+ ...request.teams[0].members.slice(0, 3), request.teams[0].members[0],
+ ] }] }).success).toBe(false);
+ expect(commissionRequestSchema.safeParse({ ...request,
+ weapons: [{ characterKey: "a", weaponKeys: ["weapon-a"] }] }).success).toBe(false);
+ expect(commissionRequestSchema.safeParse({ ...request,
+ constellations: [{ characterKey: "a", levels: [0, 0] }] }).success).toBe(false);
+ expect(commissionRequestSchema.safeParse({ ...request,
+ constellations: [{ characterKey: "a", levels: [] }] }).success).toBe(false);
+ });
+});
diff --git a/lib/commission/request.ts b/lib/commission/request.ts
new file mode 100644
index 0000000..18217ad
--- /dev/null
+++ b/lib/commission/request.ts
@@ -0,0 +1,38 @@
+import * as z from "zod";
+
+const key = z.string().min(1).max(64);
+const member = z.object({
+ characterKey: key,
+ weaponKey: key,
+ artifactKey: key,
+ constellation: z.enum(["0", "1", "2", "3", "4", "5", "6", "0-6"]).default("0"),
+ refinement: z.enum(["1", "2", "3", "4", "5", "1-5"]).default("1"),
+});
+const team = z.object({ members: z.array(member).length(4) }).refine(
+ ({ members }) => new Set(members.map((item) => item.characterKey)).size === 4,
+ "A team needs four different characters",
+);
+const weaponComparison = z.object({ characterKey: key, weaponKeys: z.array(key).min(2).max(100) }).refine(
+ ({ weaponKeys }) => new Set(weaponKeys).size === weaponKeys.length,
+ "Choose different weapons",
+);
+const constellationComparison = z.object({
+ characterKey: key,
+ levels: z.array(z.number().int().min(0).max(6)).min(1).max(7),
+}).refine(({ levels }) => new Set(levels).size === levels.length, "Choose different constellation levels");
+
+export const commissionRequestSchema = z.object({
+ teams: z.array(team).max(20),
+ weapons: z.array(weaponComparison).max(20),
+ constellations: z.array(constellationComparison).max(20),
+}).refine((value) => value.teams.length + value.weapons.length + value.constellations.length > 0,
+ "Choose at least one commission item");
+
+export type CommissionRequest = z.infer;
+
+export function commissionPrice(request: CommissionRequest): number {
+ const hasItems = request.teams.length + request.weapons.length + request.constellations.length > 0;
+ return (hasItems ? Math.max(1, request.teams.length) * 100 : 0) +
+ request.weapons.reduce((sum, item) => sum + item.weaponKeys.filter(Boolean).length * 20, 0) +
+ request.constellations.reduce((sum, item) => sum + item.levels.length * 20, 0);
+}
diff --git a/lib/commission/server.ts b/lib/commission/server.ts
new file mode 100644
index 0000000..b0bca47
--- /dev/null
+++ b/lib/commission/server.ts
@@ -0,0 +1,30 @@
+import "server-only";
+
+import { headers } from "next/headers";
+import { getAuth } from "@/lib/auth/server";
+import { getDb } from "@/db";
+import { users } from "@/db/schema";
+import { eq } from "drizzle-orm";
+import { HttpError } from "@/lib/security/http";
+import { getRedisClient, redisEventChannel } from "@/lib/redis/client";
+
+export async function requireCommissionUser() {
+ const session = await getAuth().api.getSession({ headers: await headers() });
+ if (!session?.user) throw new HttpError(401, "unauthorized");
+ const [user] = await getDb().select().from(users).where(eq(users.id, session.user.id)).limit(1);
+ if (!user || user.banned) throw new HttpError(401, "unauthorized");
+ return user;
+}
+
+export async function notifyCommission(ticketId: string, userId: string) {
+ try {
+ const client = await getRedisClient();
+ await Promise.all([
+ client.publish(redisEventChannel(`commission:ticket:${ticketId}`), "changed"),
+ client.publish(redisEventChannel(`commission:user:${userId}`), "changed"),
+ client.publish(redisEventChannel("commission:admin"), "changed"),
+ ]);
+ } catch {
+ // The database is authoritative; a reconnect or page refresh catches up.
+ }
+}
diff --git a/lib/commission/tickets.ts b/lib/commission/tickets.ts
new file mode 100644
index 0000000..bea8637
--- /dev/null
+++ b/lib/commission/tickets.ts
@@ -0,0 +1,40 @@
+import "server-only";
+
+import { asc, eq } from "drizzle-orm";
+import { getDb } from "@/db";
+import { commissionCheckouts, commissionMessages, commissionPayments, commissionReactions, commissionTickets, users } from "@/db/schema";
+import { requireCommissionUser } from "./server";
+import { HttpError } from "@/lib/security/http";
+
+export async function getCommissionTicket(id: string) {
+ const user = await requireCommissionUser();
+ const [row] = await getDb().select({ ticket: commissionTickets, checkout: commissionCheckouts,
+ customerName: users.name, payment: commissionPayments })
+ .from(commissionTickets)
+ .innerJoin(commissionCheckouts, eq(commissionTickets.checkoutId, commissionCheckouts.id))
+ .innerJoin(users, eq(commissionTickets.userId, users.id))
+ .innerJoin(commissionPayments, eq(commissionPayments.ticketId, commissionTickets.id))
+ .where(eq(commissionTickets.id, id)).limit(1);
+ if (!row || (user.role !== "admin" || !user.emailVerified) && row.ticket.userId !== user.id)
+ throw new HttpError(404, "ticket-not-found");
+ const [messages, reactions] = await Promise.all([
+ getDb().select({ message: commissionMessages, authorName: users.name }).from(commissionMessages)
+ .innerJoin(users, eq(commissionMessages.authorId, users.id))
+ .where(eq(commissionMessages.ticketId, id)).orderBy(asc(commissionMessages.createdAt)),
+ getDb().select({ reaction: commissionReactions, messageId: commissionMessages.id })
+ .from(commissionReactions)
+ .innerJoin(commissionMessages, eq(commissionReactions.messageId, commissionMessages.id))
+ .where(eq(commissionMessages.ticketId, id)),
+ ]);
+ return { ...row, messages: messages.map(({ message, authorName }) => ({ ...message, authorName,
+ reactions: reactions.filter(({ messageId }) => messageId === message.id).map(({ reaction }) => reaction) })),
+ currentUserId: user.id };
+}
+
+export async function authorizeTicket(id: string) {
+ const user = await requireCommissionUser();
+ const [ticket] = await getDb().select().from(commissionTickets).where(eq(commissionTickets.id, id)).limit(1);
+ if (!ticket || ((user.role !== "admin" || !user.emailVerified) && ticket.userId !== user.id))
+ throw new HttpError(404, "ticket-not-found");
+ return { ticket, user };
+}
diff --git a/lib/guides/validation-messages.ts b/lib/guides/validation-messages.ts
index 82e9275..c6f293b 100644
--- a/lib/guides/validation-messages.ts
+++ b/lib/guides/validation-messages.ts
@@ -44,21 +44,21 @@ function issueContext(section: GuideValidationSection, path: readonly PropertyKe
if (section === "weapon" && groupIndex === null && rowIndex !== null) return `อาวุธอันดับ ${rowIndex + 1}`;
if (section === "weapon" && groupIndex !== null) {
- return rowIndex === null ? `Condition ${groupIndex + 1}` : `Condition ${groupIndex + 1} · อาวุธอันดับ ${rowIndex + 1}`;
+ return rowIndex === null ? `Condition ${groupIndex + 1}` : `Condition ${groupIndex + 1} - อาวุธอันดับ ${rowIndex + 1}`;
}
if (section === "artifact" && groupIndex !== null) {
const group = groupIndex === 0 ? "Late Game" : "Early Game";
return rowIndex === null ? `Artifact ${group}` : `Artifact ${group} อันดับ ${rowIndex + 1}`;
}
if (section === "constellations" && groupIndex !== null) {
- return rowIndex === null ? `Condition ${groupIndex + 1}` : `Condition ${groupIndex + 1} · C${rowIndex + 1}`;
+ return rowIndex === null ? `Condition ${groupIndex + 1}` : `Condition ${groupIndex + 1} - C${rowIndex + 1}`;
}
if (section === "team" && teamIndex !== null) {
const teamName = teamNames?.[teamIndex]?.trim() || `Team ${teamIndex + 1}`;
- return memberIndex === null ? teamName : `${teamName} · ตัวละคร ${memberIndex + 1}`;
+ return memberIndex === null ? teamName : `${teamName} - ตัวละคร ${memberIndex + 1}`;
}
if (section === "extra" && sectionIndex !== null) {
- return rowIndex === null ? `Extra section ${sectionIndex + 1}` : `Extra section ${sectionIndex + 1} · แถว ${rowIndex + 1}`;
+ return rowIndex === null ? `Extra section ${sectionIndex + 1}` : `Extra section ${sectionIndex + 1} - แถว ${rowIndex + 1}`;
}
return sectionLabels[section];
}
diff --git a/package.json b/package.json
index 04fcb5e..ef450f2 100644
--- a/package.json
+++ b/package.json
@@ -39,6 +39,7 @@
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"cmdk": "^1.1.1",
+ "cn": "^0.4.0",
"color": "^5.0.3",
"discord.js": "^14.27.0",
"drizzle-orm": "^0.45.2",
@@ -49,6 +50,7 @@
"modern-screenshot": "^4.7.0",
"next": "^16.3.7",
"postgres": "^3.4.9",
+ "qrcode": "^1.5.4",
"radix-ui": "^1.6.7",
"react": "19.2.8",
"react-dom": "19.2.8",
@@ -68,6 +70,7 @@
"@tailwindcss/postcss": "^4",
"@types/bun": "^1.4.0",
"@types/node": "^20",
+ "@types/qrcode": "^1.5.6",
"@types/react": "^19",
"@types/react-dom": "^19",
"@types/sanitize-html": "^2.16.1",
@@ -75,10 +78,10 @@
"drizzle-kit": "0.31.11",
"eslint": "^9",
"eslint-config-next": "16.3.3",
+ "shadcn": "4.19.0",
"tailwindcss": "^4",
"typescript": "^5",
- "vitest": "^4.1.11",
- "shadcn": "4.19.0"
+ "vitest": "^4.1.11"
},
"packageManager": "bun@1.3.14",
"trustedDependencies": [
diff --git a/public/icon/commission.webp b/public/icon/commission.webp
new file mode 100644
index 0000000..1639fb0
Binary files /dev/null and b/public/icon/commission.webp differ
diff --git a/tests/public-performance.test.ts b/tests/public-performance.test.ts
index e04a611..67e1341 100644
--- a/tests/public-performance.test.ts
+++ b/tests/public-performance.test.ts
@@ -53,7 +53,7 @@ describe("public rendering performance", () => {
rules: {
userAgent: "*",
allow: "/",
- disallow: ["/admin", "/api", "/register"],
+ disallow: ["/admin", "/api", "/login", "/register"],
},
sitemap: "https://guide.example.test/sitemap.xml",
});
diff --git a/tests/security-boundaries.test.ts b/tests/security-boundaries.test.ts
index 37db80e..67094c0 100644
--- a/tests/security-boundaries.test.ts
+++ b/tests/security-boundaries.test.ts
@@ -41,7 +41,7 @@ describe("administrative security boundaries", () => {
);
expect(source).toContain('provider: "cloudflare-turnstile"');
expect(source).toContain('secretKey: required("TURNSTILE_SECRET_KEY")');
- expect(source).toContain('endpoints: ["/sign-in/email"]');
+ expect(source).toContain('endpoints: ["/sign-in/email", "/sign-up/email"]');
});
it("requires a Turnstile token before submitting administrator login outside development", async () => {