Phase 3. 인증 및 공통 레이아웃 작업
This commit is contained in:
@@ -1,4 +1,5 @@
|
|||||||
# Local development
|
# Local development
|
||||||
DATABASE_URL="mysql://USER:PASSWORD@127.0.0.1:3306/chocomae"
|
DATABASE_URL="mysql://USER:PASSWORD@127.0.0.1:3306/chocomae"
|
||||||
|
AUTH_SECRET="replace-with-local-secret"
|
||||||
NEXTAUTH_SECRET="replace-with-local-secret"
|
NEXTAUTH_SECRET="replace-with-local-secret"
|
||||||
NEXTAUTH_URL="http://localhost:3001"
|
NEXTAUTH_URL="http://localhost:3001"
|
||||||
|
|||||||
@@ -0,0 +1,84 @@
|
|||||||
|
import Link from "next/link";
|
||||||
|
import { redirect } from "next/navigation";
|
||||||
|
import {
|
||||||
|
ArrowUpCircle,
|
||||||
|
ClipboardList,
|
||||||
|
GraduationCap,
|
||||||
|
LayoutDashboard,
|
||||||
|
LogOut,
|
||||||
|
} from "lucide-react";
|
||||||
|
|
||||||
|
import { auth, signOut } from "@/auth";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
|
||||||
|
const navItems = [
|
||||||
|
{ href: "/", label: "대시보드", icon: LayoutDashboard },
|
||||||
|
{ href: "/maestros", label: "마에스트로", icon: GraduationCap },
|
||||||
|
{ href: "/extension-requests", label: "연장 신청", icon: ClipboardList },
|
||||||
|
{ href: "/upgrade-requests", label: "업그레이드", icon: ArrowUpCircle },
|
||||||
|
];
|
||||||
|
|
||||||
|
export default async function AdminLayout({
|
||||||
|
children,
|
||||||
|
}: Readonly<{
|
||||||
|
children: React.ReactNode;
|
||||||
|
}>) {
|
||||||
|
const session = await auth();
|
||||||
|
|
||||||
|
if (!session) {
|
||||||
|
redirect("/login");
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen bg-muted/40">
|
||||||
|
<aside className="fixed inset-y-0 left-0 hidden w-64 border-r bg-background md:block">
|
||||||
|
<div className="flex h-16 items-center border-b px-5">
|
||||||
|
<Link className="text-lg font-semibold" href="/">
|
||||||
|
choco-admin
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
<nav className="space-y-1 p-3">
|
||||||
|
{navItems.map((item) => {
|
||||||
|
const Icon = item.icon;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Link
|
||||||
|
className="flex items-center gap-3 rounded-md px-3 py-2 text-sm font-medium text-muted-foreground transition-colors hover:bg-muted hover:text-foreground"
|
||||||
|
href={item.href}
|
||||||
|
key={item.href}
|
||||||
|
>
|
||||||
|
<Icon className="size-4" aria-hidden="true" />
|
||||||
|
{item.label}
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</nav>
|
||||||
|
</aside>
|
||||||
|
|
||||||
|
<div className="md:pl-64">
|
||||||
|
<header className="sticky top-0 z-10 flex h-16 items-center justify-between border-b bg-background px-5">
|
||||||
|
<div>
|
||||||
|
<p className="text-sm font-medium">{session.user.name}</p>
|
||||||
|
<p className="text-xs text-muted-foreground">
|
||||||
|
관리자 ID {session.user.adminID}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<form
|
||||||
|
action={async () => {
|
||||||
|
"use server";
|
||||||
|
|
||||||
|
await signOut({ redirectTo: "/login" });
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Button size="sm" type="submit" variant="outline">
|
||||||
|
<LogOut className="size-4" aria-hidden="true" />
|
||||||
|
로그아웃
|
||||||
|
</Button>
|
||||||
|
</form>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main className="p-5">{children}</main>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
import { Database, ShieldCheck, Table2 } from "lucide-react";
|
||||||
|
|
||||||
|
import { auth } from "@/auth";
|
||||||
|
|
||||||
|
const checks = [
|
||||||
|
{
|
||||||
|
label: "Next.js 16",
|
||||||
|
value: "App Router",
|
||||||
|
icon: ShieldCheck,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Prisma",
|
||||||
|
value: "MariaDB introspected",
|
||||||
|
icon: Database,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "TanStack Table",
|
||||||
|
value: "Installed",
|
||||||
|
icon: Table2,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export default async function Home() {
|
||||||
|
const session = await auth();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section className="space-y-6">
|
||||||
|
<div>
|
||||||
|
<h1 className="text-2xl font-semibold tracking-normal">대시보드</h1>
|
||||||
|
<p className="mt-1 text-sm text-muted-foreground">
|
||||||
|
{session?.user.name} 관리자 계정으로 로그인했습니다.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid gap-4 md:grid-cols-3">
|
||||||
|
{checks.map((item) => {
|
||||||
|
const Icon = item.icon;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<article
|
||||||
|
className="rounded-lg border bg-card p-5 text-card-foreground"
|
||||||
|
key={item.label}
|
||||||
|
>
|
||||||
|
<div className="mb-4 flex size-9 items-center justify-center rounded-md bg-muted">
|
||||||
|
<Icon className="size-5" aria-hidden="true" />
|
||||||
|
</div>
|
||||||
|
<h2 className="text-base font-medium">{item.label}</h2>
|
||||||
|
<p className="mt-1 text-sm text-muted-foreground">{item.value}</p>
|
||||||
|
</article>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
"use server";
|
||||||
|
|
||||||
|
import { AuthError } from "next-auth";
|
||||||
|
import { redirect } from "next/navigation";
|
||||||
|
|
||||||
|
import { signIn } from "@/auth";
|
||||||
|
|
||||||
|
export async function loginAction(formData: FormData) {
|
||||||
|
const callbackUrl = String(formData.get("callbackUrl") ?? "/");
|
||||||
|
|
||||||
|
try {
|
||||||
|
await signIn("credentials", {
|
||||||
|
adminName: formData.get("adminName"),
|
||||||
|
password: formData.get("password"),
|
||||||
|
redirectTo: callbackUrl,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof AuthError) {
|
||||||
|
redirect(
|
||||||
|
`/login?error=CredentialsSignin&callbackUrl=${encodeURIComponent(callbackUrl)}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
import { redirect } from "next/navigation";
|
||||||
|
|
||||||
|
import { auth } from "@/auth";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
|
||||||
|
import { loginAction } from "./actions";
|
||||||
|
|
||||||
|
type LoginPageProps = {
|
||||||
|
searchParams: Promise<{
|
||||||
|
callbackUrl?: string;
|
||||||
|
error?: string;
|
||||||
|
}>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default async function LoginPage({ searchParams }: LoginPageProps) {
|
||||||
|
const session = await auth();
|
||||||
|
|
||||||
|
if (session) {
|
||||||
|
redirect("/");
|
||||||
|
}
|
||||||
|
|
||||||
|
const params = await searchParams;
|
||||||
|
const callbackUrl = params.callbackUrl ?? "/";
|
||||||
|
const hasError = params.error === "CredentialsSignin";
|
||||||
|
|
||||||
|
return (
|
||||||
|
<main className="flex min-h-screen items-center justify-center bg-muted px-6 py-10">
|
||||||
|
<section className="w-full max-w-md rounded-lg border bg-background p-8 shadow-sm">
|
||||||
|
<div className="mb-8">
|
||||||
|
<h1 className="text-2xl font-semibold tracking-normal">
|
||||||
|
choco-admin
|
||||||
|
</h1>
|
||||||
|
<p className="mt-2 text-sm text-muted-foreground">관리자 로그인</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{hasError ? (
|
||||||
|
<div className="mb-5 rounded-md border border-destructive/30 bg-destructive/10 px-4 py-3 text-sm text-destructive">
|
||||||
|
관리자 아이디 또는 암호가 올바르지 않습니다.
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
<form action={loginAction} className="space-y-5">
|
||||||
|
<input name="callbackUrl" type="hidden" value={callbackUrl} />
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<label className="text-sm font-medium" htmlFor="adminName">
|
||||||
|
관리자 아이디
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
autoComplete="username"
|
||||||
|
autoFocus
|
||||||
|
className="h-10 w-full rounded-md border bg-background px-3 text-sm outline-none transition-colors focus:border-ring focus:ring-3 focus:ring-ring/30"
|
||||||
|
id="adminName"
|
||||||
|
name="adminName"
|
||||||
|
required
|
||||||
|
type="text"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<label className="text-sm font-medium" htmlFor="password">
|
||||||
|
암호
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
autoComplete="current-password"
|
||||||
|
className="h-10 w-full rounded-md border bg-background px-3 text-sm outline-none transition-colors focus:border-ring focus:ring-3 focus:ring-ring/30"
|
||||||
|
id="password"
|
||||||
|
name="password"
|
||||||
|
required
|
||||||
|
type="password"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Button className="w-full" type="submit">
|
||||||
|
로그인
|
||||||
|
</Button>
|
||||||
|
</form>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export { GET, POST } from "@/auth";
|
||||||
@@ -1,62 +0,0 @@
|
|||||||
import { Database, ShieldCheck, Table2 } from "lucide-react";
|
|
||||||
|
|
||||||
import { Button } from "@/components/ui/button";
|
|
||||||
|
|
||||||
const checks = [
|
|
||||||
{
|
|
||||||
label: "Next.js 16",
|
|
||||||
value: "App Router",
|
|
||||||
icon: ShieldCheck,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Prisma",
|
|
||||||
value: "MariaDB introspected",
|
|
||||||
icon: Database,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "TanStack Table",
|
|
||||||
value: "Installed",
|
|
||||||
icon: Table2,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
export default function Home() {
|
|
||||||
return (
|
|
||||||
<main className="min-h-screen bg-background text-foreground">
|
|
||||||
<section className="mx-auto flex min-h-screen w-full max-w-6xl flex-col gap-8 px-6 py-8">
|
|
||||||
<header className="flex items-center justify-between border-b pb-5">
|
|
||||||
<div>
|
|
||||||
<h1 className="text-2xl font-semibold tracking-normal">
|
|
||||||
choco-admin
|
|
||||||
</h1>
|
|
||||||
<p className="mt-1 text-sm text-muted-foreground">
|
|
||||||
Phase 1 initialization
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<Button type="button">Ready</Button>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<div className="grid gap-4 md:grid-cols-3">
|
|
||||||
{checks.map((item) => {
|
|
||||||
const Icon = item.icon;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<article
|
|
||||||
className="rounded-lg border bg-card p-5 text-card-foreground"
|
|
||||||
key={item.label}
|
|
||||||
>
|
|
||||||
<div className="mb-4 flex size-9 items-center justify-center rounded-md bg-muted">
|
|
||||||
<Icon className="size-5" aria-hidden="true" />
|
|
||||||
</div>
|
|
||||||
<h2 className="text-base font-medium">{item.label}</h2>
|
|
||||||
<p className="mt-1 text-sm text-muted-foreground">
|
|
||||||
{item.value}
|
|
||||||
</p>
|
|
||||||
</article>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
</main>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,115 @@
|
|||||||
|
import NextAuth, { type DefaultSession } from "next-auth";
|
||||||
|
import Credentials from "next-auth/providers/credentials";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { ACTIVATE_STATUSES } from "@/lib/constants";
|
||||||
|
|
||||||
|
const credentialsSchema = z.object({
|
||||||
|
adminName: z.string().trim().min(1),
|
||||||
|
password: z.string().min(1),
|
||||||
|
});
|
||||||
|
|
||||||
|
type AdminAuthRow = {
|
||||||
|
AdminID: bigint | number;
|
||||||
|
Name: string;
|
||||||
|
Email: string;
|
||||||
|
AccountType: bigint | number;
|
||||||
|
ActivateStatus: bigint | number;
|
||||||
|
};
|
||||||
|
|
||||||
|
declare module "next-auth" {
|
||||||
|
interface Session {
|
||||||
|
user: {
|
||||||
|
adminID: number;
|
||||||
|
accountType: number;
|
||||||
|
activateStatus: number;
|
||||||
|
} & DefaultSession["user"];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface User {
|
||||||
|
accountType: number;
|
||||||
|
activateStatus: number;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module "next-auth/jwt" {
|
||||||
|
interface JWT {
|
||||||
|
accountType?: number;
|
||||||
|
activateStatus?: number;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const {
|
||||||
|
handlers: { GET, POST },
|
||||||
|
auth,
|
||||||
|
signIn,
|
||||||
|
signOut,
|
||||||
|
} = NextAuth({
|
||||||
|
pages: {
|
||||||
|
signIn: "/login",
|
||||||
|
},
|
||||||
|
session: {
|
||||||
|
strategy: "jwt",
|
||||||
|
},
|
||||||
|
providers: [
|
||||||
|
Credentials({
|
||||||
|
credentials: {
|
||||||
|
adminName: { label: "관리자 아이디", type: "text" },
|
||||||
|
password: { label: "암호", type: "password" },
|
||||||
|
},
|
||||||
|
async authorize(credentials) {
|
||||||
|
const parsedCredentials = credentialsSchema.safeParse(credentials);
|
||||||
|
|
||||||
|
if (!parsedCredentials.success) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { db } = await import("@/lib/db");
|
||||||
|
const { adminName, password } = parsedCredentials.data;
|
||||||
|
const admins = await db.$queryRaw<AdminAuthRow[]>`
|
||||||
|
SELECT AdminID, Name, Email, AccountType, ActivateStatus
|
||||||
|
FROM admin
|
||||||
|
WHERE Name = ${adminName} AND Password = PASSWORD(${password})
|
||||||
|
LIMIT 1
|
||||||
|
`;
|
||||||
|
const admin = admins[0];
|
||||||
|
const adminID = Number(admin?.AdminID);
|
||||||
|
const accountType = Number(admin?.AccountType);
|
||||||
|
const activateStatus = Number(admin?.ActivateStatus);
|
||||||
|
|
||||||
|
if (!admin || activateStatus === ACTIVATE_STATUSES.PENDING) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: String(adminID),
|
||||||
|
name: admin.Name,
|
||||||
|
email: admin.Email,
|
||||||
|
accountType,
|
||||||
|
activateStatus,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
callbacks: {
|
||||||
|
jwt({ token, user }) {
|
||||||
|
if (user) {
|
||||||
|
token.accountType = user.accountType;
|
||||||
|
token.activateStatus = user.activateStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
return token;
|
||||||
|
},
|
||||||
|
session({ session, token }) {
|
||||||
|
return {
|
||||||
|
...session,
|
||||||
|
user: {
|
||||||
|
...session.user,
|
||||||
|
adminID: Number(token.sub),
|
||||||
|
accountType: Number(token.accountType),
|
||||||
|
activateStatus: Number(token.activateStatus),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -310,18 +310,18 @@ choco-admin/
|
|||||||
|
|
||||||
### Phase 2. DB 연결 및 타입 구성
|
### Phase 2. DB 연결 및 타입 구성
|
||||||
|
|
||||||
- [ ] Prisma Client 싱글톤 (`lib/db.ts`)
|
- [x] Prisma Client 싱글톤 (`lib/db.ts`)
|
||||||
- [ ] 상태값 상수 정의 (`lib/constants.ts`)
|
- [x] 상태값 상수 정의 (`lib/constants.ts`)
|
||||||
- [ ] 공통 유틸 작성 (날짜 포맷, AccountType 레이블 변환 등)
|
- [x] 공통 유틸 작성 (날짜 포맷, AccountType 레이블 변환 등)
|
||||||
- [ ] 개발 DB에서 `maestro`, `maestro_extension`, `maestro_upgrade` 조회 확인
|
- [x] 개발 DB에서 `maestro`, `maestro_extension`, `maestro_upgrade` 조회 확인
|
||||||
|
|
||||||
### Phase 3. 인증 및 공통 레이아웃
|
### Phase 3. 인증 및 공통 레이아웃
|
||||||
|
|
||||||
- [ ] 로그인 페이지 (`/login`)
|
- [x] 로그인 페이지 (`/login`)
|
||||||
- [ ] NextAuth Credentials Provider — `admin` 테이블 기반 검증
|
- [x] NextAuth Credentials Provider — `admin` 테이블 기반 검증
|
||||||
- [ ] 인증 미들웨어 (`middleware.ts`) — 비로그인 시 `/login` 리다이렉트
|
- [x] 인증 프록시 (`proxy.ts`) — 비로그인 시 `/login` 리다이렉트
|
||||||
- [ ] 공통 레이아웃 (`(admin)/layout.tsx`) — 사이드바, 헤더
|
- [x] 공통 레이아웃 (`(admin)/layout.tsx`) — 사이드바, 헤더
|
||||||
- [ ] 로그아웃 기능
|
- [x] 로그아웃 기능
|
||||||
|
|
||||||
### Phase 4. 마에스트로 목록 및 상세
|
### Phase 4. 마에스트로 목록 및 상세
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,81 @@
|
|||||||
|
export const ACCOUNT_TYPES = {
|
||||||
|
BASIC_20: 1,
|
||||||
|
STANDARD_50: 2,
|
||||||
|
PRO_100: 3,
|
||||||
|
SCHOOL_500: 4,
|
||||||
|
SCHOOL_1000: 5,
|
||||||
|
STUDENT_TRIAL: 100,
|
||||||
|
MAESTRO_TRIAL: 101,
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export const ACCOUNT_TYPE_LABELS = {
|
||||||
|
[ACCOUNT_TYPES.BASIC_20]: "20명 (1만원)",
|
||||||
|
[ACCOUNT_TYPES.STANDARD_50]: "50명 (2만원)",
|
||||||
|
[ACCOUNT_TYPES.PRO_100]: "100명 (3만원)",
|
||||||
|
[ACCOUNT_TYPES.SCHOOL_500]: "500명 (4만원)",
|
||||||
|
[ACCOUNT_TYPES.SCHOOL_1000]: "1,000명 (5만원)",
|
||||||
|
[ACCOUNT_TYPES.STUDENT_TRIAL]: "학생체험",
|
||||||
|
[ACCOUNT_TYPES.MAESTRO_TRIAL]: "마에체험",
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export const TRIAL_ACCOUNT_TYPE_LABELS = {
|
||||||
|
[ACCOUNT_TYPES.BASIC_20]: "20명 (0원)",
|
||||||
|
[ACCOUNT_TYPES.STANDARD_50]: "50명 (0원)",
|
||||||
|
[ACCOUNT_TYPES.PRO_100]: "100명 (0원)",
|
||||||
|
[ACCOUNT_TYPES.SCHOOL_500]: "500명 (0원)",
|
||||||
|
[ACCOUNT_TYPES.SCHOOL_1000]: "1,000명 (0원)",
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export const ACCOUNT_TYPE_PLAYER_COUNTS = {
|
||||||
|
[ACCOUNT_TYPES.BASIC_20]: 20,
|
||||||
|
[ACCOUNT_TYPES.STANDARD_50]: 50,
|
||||||
|
[ACCOUNT_TYPES.PRO_100]: 100,
|
||||||
|
[ACCOUNT_TYPES.SCHOOL_500]: 500,
|
||||||
|
[ACCOUNT_TYPES.SCHOOL_1000]: 1000,
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export const ACCOUNT_TYPE_PRICES = {
|
||||||
|
[ACCOUNT_TYPES.BASIC_20]: 10000,
|
||||||
|
[ACCOUNT_TYPES.STANDARD_50]: 20000,
|
||||||
|
[ACCOUNT_TYPES.PRO_100]: 30000,
|
||||||
|
[ACCOUNT_TYPES.SCHOOL_500]: 40000,
|
||||||
|
[ACCOUNT_TYPES.SCHOOL_1000]: 50000,
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export const ACTIVATE_STATUSES = {
|
||||||
|
PENDING: 0,
|
||||||
|
TRIAL: 1,
|
||||||
|
ACTIVE: 2,
|
||||||
|
CANCELED: 100,
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export const ACTIVATE_STATUS_LABELS = {
|
||||||
|
[ACTIVATE_STATUSES.PENDING]: "미승인",
|
||||||
|
[ACTIVATE_STATUSES.TRIAL]: "체험",
|
||||||
|
[ACTIVATE_STATUSES.ACTIVE]: "활성화",
|
||||||
|
[ACTIVATE_STATUSES.CANCELED]: "등록 취소",
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export const REQUEST_STATUSES = {
|
||||||
|
REQUESTED: 1,
|
||||||
|
CLOSED: 2,
|
||||||
|
APPLIED: 3,
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export const REQUEST_STATUS_LABELS = {
|
||||||
|
[REQUEST_STATUSES.REQUESTED]: "요청",
|
||||||
|
[REQUEST_STATUSES.CLOSED]: "취소",
|
||||||
|
[REQUEST_STATUSES.APPLIED]: "적용 완료",
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export type AccountType = (typeof ACCOUNT_TYPES)[keyof typeof ACCOUNT_TYPES];
|
||||||
|
export type PaidAccountType =
|
||||||
|
| typeof ACCOUNT_TYPES.BASIC_20
|
||||||
|
| typeof ACCOUNT_TYPES.STANDARD_50
|
||||||
|
| typeof ACCOUNT_TYPES.PRO_100
|
||||||
|
| typeof ACCOUNT_TYPES.SCHOOL_500
|
||||||
|
| typeof ACCOUNT_TYPES.SCHOOL_1000;
|
||||||
|
export type ActivateStatus =
|
||||||
|
(typeof ACTIVATE_STATUSES)[keyof typeof ACTIVATE_STATUSES];
|
||||||
|
export type RequestStatus =
|
||||||
|
(typeof REQUEST_STATUSES)[keyof typeof REQUEST_STATUSES];
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import { PrismaMariaDb } from "@prisma/adapter-mariadb";
|
||||||
|
|
||||||
|
import { PrismaClient } from "@/lib/generated/prisma/client";
|
||||||
|
|
||||||
|
const globalForPrisma = globalThis as unknown as {
|
||||||
|
prisma?: PrismaClient;
|
||||||
|
};
|
||||||
|
|
||||||
|
function createPrismaClient() {
|
||||||
|
const databaseUrl = process.env.DATABASE_URL;
|
||||||
|
|
||||||
|
if (!databaseUrl) {
|
||||||
|
throw new Error("DATABASE_URL is required to initialize Prisma Client.");
|
||||||
|
}
|
||||||
|
|
||||||
|
const adapter = new PrismaMariaDb(databaseUrl);
|
||||||
|
|
||||||
|
return new PrismaClient({ adapter });
|
||||||
|
}
|
||||||
|
|
||||||
|
export const db = globalForPrisma.prisma ?? createPrismaClient();
|
||||||
|
|
||||||
|
if (process.env.NODE_ENV !== "production") {
|
||||||
|
globalForPrisma.prisma = db;
|
||||||
|
}
|
||||||
+113
@@ -1,6 +1,119 @@
|
|||||||
import { clsx, type ClassValue } from "clsx";
|
import { clsx, type ClassValue } from "clsx";
|
||||||
import { twMerge } from "tailwind-merge";
|
import { twMerge } from "tailwind-merge";
|
||||||
|
|
||||||
|
import {
|
||||||
|
ACCOUNT_TYPE_LABELS,
|
||||||
|
ACCOUNT_TYPE_PLAYER_COUNTS,
|
||||||
|
ACCOUNT_TYPE_PRICES,
|
||||||
|
ACTIVATE_STATUS_LABELS,
|
||||||
|
REQUEST_STATUS_LABELS,
|
||||||
|
TRIAL_ACCOUNT_TYPE_LABELS,
|
||||||
|
type AccountType,
|
||||||
|
type ActivateStatus,
|
||||||
|
type PaidAccountType,
|
||||||
|
type RequestStatus,
|
||||||
|
} from "@/lib/constants";
|
||||||
|
|
||||||
export function cn(...inputs: ClassValue[]) {
|
export function cn(...inputs: ClassValue[]) {
|
||||||
return twMerge(clsx(inputs));
|
return twMerge(clsx(inputs));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getAccountTypeLabel(accountType: number): string {
|
||||||
|
return (
|
||||||
|
ACCOUNT_TYPE_LABELS[accountType as AccountType] ?? `${accountType}? (N/A)`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getTrialAccountTypeLabel(accountType: number): string {
|
||||||
|
return (
|
||||||
|
TRIAL_ACCOUNT_TYPE_LABELS[accountType as PaidAccountType] ??
|
||||||
|
`${accountType}? (N/A)`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getAccountTypePlayerCount(accountType: number): number {
|
||||||
|
return ACCOUNT_TYPE_PLAYER_COUNTS[accountType as PaidAccountType] ?? 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getAccountTypePrice(accountType: number): number {
|
||||||
|
return ACCOUNT_TYPE_PRICES[accountType as PaidAccountType] ?? 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getActivateStatusLabel(activateStatus: number): string {
|
||||||
|
return (
|
||||||
|
ACTIVATE_STATUS_LABELS[activateStatus as ActivateStatus] ??
|
||||||
|
`${activateStatus}? (N/A)`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getRequestStatusLabel(status: number): string {
|
||||||
|
return REQUEST_STATUS_LABELS[status as RequestStatus] ?? `${status}? (N/A)`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function calculateUpgradePrice(
|
||||||
|
requestedAccountType: number,
|
||||||
|
registeredAccountType: number
|
||||||
|
): number {
|
||||||
|
return (
|
||||||
|
getAccountTypePrice(requestedAccountType) -
|
||||||
|
getAccountTypePrice(registeredAccountType)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function formatDate(value: Date | string | null | undefined): string {
|
||||||
|
const date = toDate(value);
|
||||||
|
|
||||||
|
if (!date) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
const year = date.getFullYear();
|
||||||
|
const month = String(date.getMonth() + 1).padStart(2, "0");
|
||||||
|
const day = String(date.getDate()).padStart(2, "0");
|
||||||
|
|
||||||
|
return `${year}-${month}-${day}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function formatDateTime(
|
||||||
|
value: Date | string | null | undefined
|
||||||
|
): string {
|
||||||
|
const date = toDate(value);
|
||||||
|
|
||||||
|
if (!date) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
const hours = String(date.getHours()).padStart(2, "0");
|
||||||
|
const minutes = String(date.getMinutes()).padStart(2, "0");
|
||||||
|
const seconds = String(date.getSeconds()).padStart(2, "0");
|
||||||
|
|
||||||
|
return `${formatDate(date)} ${hours}:${minutes}:${seconds}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function calculateExtendedAvailableDate(
|
||||||
|
availableDate: Date,
|
||||||
|
now = new Date()
|
||||||
|
): Date {
|
||||||
|
const baseDate =
|
||||||
|
availableDate.getTime() < now.getTime() ? now : availableDate;
|
||||||
|
const extendedDate = new Date(baseDate);
|
||||||
|
|
||||||
|
extendedDate.setFullYear(extendedDate.getFullYear() + 1);
|
||||||
|
extendedDate.setHours(0, 0, 0, 0);
|
||||||
|
|
||||||
|
return extendedDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
function toDate(value: Date | string | null | undefined): Date | null {
|
||||||
|
if (!value) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const date = value instanceof Date ? value : new Date(value);
|
||||||
|
|
||||||
|
if (Number.isNaN(date.getTime())) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return date;
|
||||||
|
}
|
||||||
|
|||||||
@@ -7,16 +7,19 @@
|
|||||||
"build": "next build",
|
"build": "next build",
|
||||||
"start": "next start",
|
"start": "next start",
|
||||||
"lint": "eslint",
|
"lint": "eslint",
|
||||||
|
"db:check": "tsx scripts/check-db.ts",
|
||||||
"format": "prettier --write .",
|
"format": "prettier --write .",
|
||||||
"format:check": "prettier --check ."
|
"format:check": "prettier --check ."
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@base-ui/react": "^1.5.0",
|
"@base-ui/react": "^1.5.0",
|
||||||
|
"@prisma/adapter-mariadb": "^7.8.0",
|
||||||
"@prisma/client": "^7.8.0",
|
"@prisma/client": "^7.8.0",
|
||||||
"@tanstack/react-table": "^8.21.3",
|
"@tanstack/react-table": "^8.21.3",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"lucide-react": "^1.17.0",
|
"lucide-react": "^1.17.0",
|
||||||
|
"mariadb": "^3.5.2",
|
||||||
"next": "16.2.7",
|
"next": "16.2.7",
|
||||||
"next-auth": "5.0.0-beta.31",
|
"next-auth": "5.0.0-beta.31",
|
||||||
"react": "19.2.4",
|
"react": "19.2.4",
|
||||||
@@ -37,6 +40,7 @@
|
|||||||
"prettier": "^3.8.4",
|
"prettier": "^3.8.4",
|
||||||
"prisma": "^7.8.0",
|
"prisma": "^7.8.0",
|
||||||
"tailwindcss": "^4",
|
"tailwindcss": "^4",
|
||||||
|
"tsx": "^4.22.4",
|
||||||
"typescript": "^5"
|
"typescript": "^5"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Generated
+365
@@ -11,6 +11,9 @@ importers:
|
|||||||
'@base-ui/react':
|
'@base-ui/react':
|
||||||
specifier: ^1.5.0
|
specifier: ^1.5.0
|
||||||
version: 1.5.0(@types/react@19.2.17)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
version: 1.5.0(@types/react@19.2.17)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||||
|
'@prisma/adapter-mariadb':
|
||||||
|
specifier: ^7.8.0
|
||||||
|
version: 7.8.0
|
||||||
'@prisma/client':
|
'@prisma/client':
|
||||||
specifier: ^7.8.0
|
specifier: ^7.8.0
|
||||||
version: 7.8.0(prisma@7.8.0(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3))(typescript@5.9.3)
|
version: 7.8.0(prisma@7.8.0(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3))(typescript@5.9.3)
|
||||||
@@ -26,6 +29,9 @@ importers:
|
|||||||
lucide-react:
|
lucide-react:
|
||||||
specifier: ^1.17.0
|
specifier: ^1.17.0
|
||||||
version: 1.17.0(react@19.2.4)
|
version: 1.17.0(react@19.2.4)
|
||||||
|
mariadb:
|
||||||
|
specifier: ^3.5.2
|
||||||
|
version: 3.5.2
|
||||||
next:
|
next:
|
||||||
specifier: 16.2.7
|
specifier: 16.2.7
|
||||||
version: 16.2.7(@babel/core@7.29.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
version: 16.2.7(@babel/core@7.29.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||||
@@ -81,6 +87,9 @@ importers:
|
|||||||
tailwindcss:
|
tailwindcss:
|
||||||
specifier: ^4
|
specifier: ^4
|
||||||
version: 4.3.0
|
version: 4.3.0
|
||||||
|
tsx:
|
||||||
|
specifier: ^4.22.4
|
||||||
|
version: 4.22.4
|
||||||
typescript:
|
typescript:
|
||||||
specifier: ^5
|
specifier: ^5
|
||||||
version: 5.9.3
|
version: 5.9.3
|
||||||
@@ -301,6 +310,162 @@ packages:
|
|||||||
'@emnapi/wasi-threads@1.2.1':
|
'@emnapi/wasi-threads@1.2.1':
|
||||||
resolution: {integrity: sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==}
|
resolution: {integrity: sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==}
|
||||||
|
|
||||||
|
'@esbuild/aix-ppc64@0.28.0':
|
||||||
|
resolution: {integrity: sha512-lhRUCeuOyJQURhTxl4WkpFTjIsbDayJHih5kZC1giwE+MhIzAb7mEsQMqMf18rHLsrb5qI1tafG20mLxEWcWlA==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [ppc64]
|
||||||
|
os: [aix]
|
||||||
|
|
||||||
|
'@esbuild/android-arm64@0.28.0':
|
||||||
|
resolution: {integrity: sha512-+WzIXQOSaGs33tLEgYPYe/yQHf0WTU0X42Jca3y8NWMbUVhp7rUnw+vAsRC/QiDrdD31IszMrZy+qwPOPjd+rw==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [android]
|
||||||
|
|
||||||
|
'@esbuild/android-arm@0.28.0':
|
||||||
|
resolution: {integrity: sha512-wqh0ByljabXLKHeWXYLqoJ5jKC4XBaw6Hk08OfMrCRd2nP2ZQ5eleDZC41XHyCNgktBGYMbqnrJKq/K/lzPMSQ==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [arm]
|
||||||
|
os: [android]
|
||||||
|
|
||||||
|
'@esbuild/android-x64@0.28.0':
|
||||||
|
resolution: {integrity: sha512-+VJggoaKhk2VNNqVL7f6S189UzShHC/mR9EE8rDdSkdpN0KflSwWY/gWjDrNxxisg8Fp1ZCD9jLMo4m0OUfeUA==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [android]
|
||||||
|
|
||||||
|
'@esbuild/darwin-arm64@0.28.0':
|
||||||
|
resolution: {integrity: sha512-0T+A9WZm+bZ84nZBtk1ckYsOvyA3x7e2Acj1KdVfV4/2tdG4fzUp91YHx+GArWLtwqp77pBXVCPn2We7Letr0Q==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [darwin]
|
||||||
|
|
||||||
|
'@esbuild/darwin-x64@0.28.0':
|
||||||
|
resolution: {integrity: sha512-fyzLm/DLDl/84OCfp2f/XQ4flmORsjU7VKt8HLjvIXChJoFFOIL6pLJPH4Yhd1n1gGFF9mPwtlN5Wf82DZs+LQ==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [darwin]
|
||||||
|
|
||||||
|
'@esbuild/freebsd-arm64@0.28.0':
|
||||||
|
resolution: {integrity: sha512-l9GeW5UZBT9k9brBYI+0WDffcRxgHQD8ShN2Ur4xWq/NFzUKm3k5lsH4PdaRgb2w7mI9u61nr2gI2mLI27Nh3Q==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [freebsd]
|
||||||
|
|
||||||
|
'@esbuild/freebsd-x64@0.28.0':
|
||||||
|
resolution: {integrity: sha512-BXoQai/A0wPO6Es3yFJ7APCiKGc1tdAEOgeTNy3SsB491S3aHn4S4r3e976eUnPdU+NbdtmBuLncYir2tMU9Nw==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [freebsd]
|
||||||
|
|
||||||
|
'@esbuild/linux-arm64@0.28.0':
|
||||||
|
resolution: {integrity: sha512-RVyzfb3FWsGA55n6WY0MEIEPURL1FcbhFE6BffZEMEekfCzCIMtB5yyDcFnVbTnwk+CLAgTujmV/Lgvih56W+A==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@esbuild/linux-arm@0.28.0':
|
||||||
|
resolution: {integrity: sha512-CjaaREJagqJp7iTaNQjjidaNbCKYcd4IDkzbwwxtSvjI7NZm79qiHc8HqciMddQ6CKvJT6aBd8lO9kN/ZudLlw==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [arm]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@esbuild/linux-ia32@0.28.0':
|
||||||
|
resolution: {integrity: sha512-KBnSTt1kxl9x70q+ydterVdl+Cn0H18ngRMRCEQfrbqdUuntQQ0LoMZv47uB97NljZFzY6HcfqEZ2SAyIUTQBQ==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [ia32]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@esbuild/linux-loong64@0.28.0':
|
||||||
|
resolution: {integrity: sha512-zpSlUce1mnxzgBADvxKXX5sl8aYQHo2ezvMNI8I0lbblJtp8V4odlm3Yzlj7gPyt3T8ReksE6bK+pT3WD+aJRg==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [loong64]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@esbuild/linux-mips64el@0.28.0':
|
||||||
|
resolution: {integrity: sha512-2jIfP6mmjkdmeTlsX/9vmdmhBmKADrWqN7zcdtHIeNSCH1SqIoNI63cYsjQR8J+wGa4Y5izRcSHSm8K3QWmk3w==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [mips64el]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@esbuild/linux-ppc64@0.28.0':
|
||||||
|
resolution: {integrity: sha512-bc0FE9wWeC0WBm49IQMPSPILRocGTQt3j5KPCA8os6VprfuJ7KD+5PzESSrJ6GmPIPJK965ZJHTUlSA6GNYEhg==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [ppc64]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@esbuild/linux-riscv64@0.28.0':
|
||||||
|
resolution: {integrity: sha512-SQPZOwoTTT/HXFXQJG/vBX8sOFagGqvZyXcgLA3NhIqcBv1BJU1d46c0rGcrij2B56Z2rNiSLaZOYW5cUk7yLQ==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [riscv64]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@esbuild/linux-s390x@0.28.0':
|
||||||
|
resolution: {integrity: sha512-SCfR0HN8CEEjnYnySJTd2cw0k9OHB/YFzt5zgJEwa+wL/T/raGWYMBqwDNAC6dqFKmJYZoQBRfHjgwLHGSrn3Q==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [s390x]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@esbuild/linux-x64@0.28.0':
|
||||||
|
resolution: {integrity: sha512-us0dSb9iFxIi8srnpl931Nvs65it/Jd2a2K3qs7fz2WfGPHqzfzZTfec7oxZJRNPXPnNYZtanmRc4AL/JwVzHQ==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@esbuild/netbsd-arm64@0.28.0':
|
||||||
|
resolution: {integrity: sha512-CR/RYotgtCKwtftMwJlUU7xCVNg3lMYZ0RzTmAHSfLCXw3NtZtNpswLEj/Kkf6kEL3Gw+BpOekRX0BYCtklhUw==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [netbsd]
|
||||||
|
|
||||||
|
'@esbuild/netbsd-x64@0.28.0':
|
||||||
|
resolution: {integrity: sha512-nU1yhmYutL+fQ71Kxnhg8uEOdC0pwEW9entHykTgEbna2pw2dkbFSMeqjjyHZoCmt8SBkOSvV+yNmm94aUrrqw==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [netbsd]
|
||||||
|
|
||||||
|
'@esbuild/openbsd-arm64@0.28.0':
|
||||||
|
resolution: {integrity: sha512-cXb5vApOsRsxsEl4mcZ1XY3D4DzcoMxR/nnc4IyqYs0rTI8ZKmW6kyyg+11Z8yvgMfAEldKzP7AdP64HnSC/6g==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [openbsd]
|
||||||
|
|
||||||
|
'@esbuild/openbsd-x64@0.28.0':
|
||||||
|
resolution: {integrity: sha512-8wZM2qqtv9UP3mzy7HiGYNH/zjTA355mpeuA+859TyR+e+Tc08IHYpLJuMsfpDJwoLo1ikIJI8jC3GFjnRClzA==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [openbsd]
|
||||||
|
|
||||||
|
'@esbuild/openharmony-arm64@0.28.0':
|
||||||
|
resolution: {integrity: sha512-FLGfyizszcef5C3YtoyQDACyg95+dndv79i2EekILBofh5wpCa1KuBqOWKrEHZg3zrL3t5ouE5jgr94vA+Wb2w==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [openharmony]
|
||||||
|
|
||||||
|
'@esbuild/sunos-x64@0.28.0':
|
||||||
|
resolution: {integrity: sha512-1ZgjUoEdHZZl/YlV76TSCz9Hqj9h9YmMGAgAPYd+q4SicWNX3G5GCyx9uhQWSLcbvPW8Ni7lj4gDa1T40akdlw==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [sunos]
|
||||||
|
|
||||||
|
'@esbuild/win32-arm64@0.28.0':
|
||||||
|
resolution: {integrity: sha512-Q9StnDmQ/enxnpxCCLSg0oo4+34B9TdXpuyPeTedN/6+iXBJ4J+zwfQI28u/Jl40nOYAxGoNi7mFP40RUtkmUA==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [win32]
|
||||||
|
|
||||||
|
'@esbuild/win32-ia32@0.28.0':
|
||||||
|
resolution: {integrity: sha512-zF3ag/gfiCe6U2iczcRzSYJKH1DCI+ByzSENHlM2FcDbEeo5Zd2C86Aq0tKUYAJJ1obRP84ymxIAksZUcdztHA==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [ia32]
|
||||||
|
os: [win32]
|
||||||
|
|
||||||
|
'@esbuild/win32-x64@0.28.0':
|
||||||
|
resolution: {integrity: sha512-pEl1bO9mfAmIC+tW5btTmrKaujg3zGtUmWNdCw/xs70FBjwAL3o9OEKNHvNmnyylD6ubxUERiEhdsL0xBQ9efw==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [win32]
|
||||||
|
|
||||||
'@eslint-community/eslint-utils@4.9.1':
|
'@eslint-community/eslint-utils@4.9.1':
|
||||||
resolution: {integrity: sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==}
|
resolution: {integrity: sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==}
|
||||||
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
||||||
@@ -657,6 +822,9 @@ packages:
|
|||||||
'@panva/hkdf@1.2.1':
|
'@panva/hkdf@1.2.1':
|
||||||
resolution: {integrity: sha512-6oclG6Y3PiDFcoyk8srjLfVKyMfVCKJ27JwNPViuXziFpmdz+MZnZN/aKY0JGXgYuO/VghU0jcOAZgWXZ1Dmrw==}
|
resolution: {integrity: sha512-6oclG6Y3PiDFcoyk8srjLfVKyMfVCKJ27JwNPViuXziFpmdz+MZnZN/aKY0JGXgYuO/VghU0jcOAZgWXZ1Dmrw==}
|
||||||
|
|
||||||
|
'@prisma/adapter-mariadb@7.8.0':
|
||||||
|
resolution: {integrity: sha512-mWsgcfbUjxB3qSzRlLs8E03vsKrqXzYK2zpx3e8u6wIgeHJM/sE46cuOGcYvHiZGmeQLCd3xL6YSSGM9QOLI6w==}
|
||||||
|
|
||||||
'@prisma/client-runtime-utils@7.8.0':
|
'@prisma/client-runtime-utils@7.8.0':
|
||||||
resolution: {integrity: sha512-5NQZztQ0oY/ADFkmd9gPuweH5A1/CCY8YQPorLLO0Mu6a87mY5gsnDkzmFmIHs9NFaLnZojzgddFVN4RpKYrdw==}
|
resolution: {integrity: sha512-5NQZztQ0oY/ADFkmd9gPuweH5A1/CCY8YQPorLLO0Mu6a87mY5gsnDkzmFmIHs9NFaLnZojzgddFVN4RpKYrdw==}
|
||||||
|
|
||||||
@@ -684,6 +852,9 @@ packages:
|
|||||||
'@prisma/dev@0.24.3':
|
'@prisma/dev@0.24.3':
|
||||||
resolution: {integrity: sha512-ffHlQuKXZiaDt9Go0OnCTdJZrHxK0k7omJKNV86/VjpsXu5EIHZLK0T7JSWgvNlJwh56kW9JFu9v0qJciFzepg==}
|
resolution: {integrity: sha512-ffHlQuKXZiaDt9Go0OnCTdJZrHxK0k7omJKNV86/VjpsXu5EIHZLK0T7JSWgvNlJwh56kW9JFu9v0qJciFzepg==}
|
||||||
|
|
||||||
|
'@prisma/driver-adapter-utils@7.8.0':
|
||||||
|
resolution: {integrity: sha512-/Q13o0ZT0rjc1Xk0Q9KhZYwuq2EW/vSbWUBKfgEKkaCuB/Sg6bqnjmTZqC5cD4d6y1vfFAEwBRzfzoSMIVJ55A==}
|
||||||
|
|
||||||
'@prisma/engines-version@7.8.0-6.3c6e192761c0362d496ed980de936e2f3cebcd3a':
|
'@prisma/engines-version@7.8.0-6.3c6e192761c0362d496ed980de936e2f3cebcd3a':
|
||||||
resolution: {integrity: sha512-fJPQxCkLgA5EayWaW8eArgCvjJ+N+Kz3VyeNKMEeYiQC4alNkxRKFVAGxv/ZUzuJISKqdw+zGeDbS6mn6RCPOA==}
|
resolution: {integrity: sha512-fJPQxCkLgA5EayWaW8eArgCvjJ+N+Kz3VyeNKMEeYiQC4alNkxRKFVAGxv/ZUzuJISKqdw+zGeDbS6mn6RCPOA==}
|
||||||
|
|
||||||
@@ -916,6 +1087,9 @@ packages:
|
|||||||
'@types/estree@1.0.9':
|
'@types/estree@1.0.9':
|
||||||
resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==}
|
resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==}
|
||||||
|
|
||||||
|
'@types/geojson@7946.0.16':
|
||||||
|
resolution: {integrity: sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==}
|
||||||
|
|
||||||
'@types/json-schema@7.0.15':
|
'@types/json-schema@7.0.15':
|
||||||
resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
|
resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
|
||||||
|
|
||||||
@@ -925,6 +1099,9 @@ packages:
|
|||||||
'@types/node@20.19.42':
|
'@types/node@20.19.42':
|
||||||
resolution: {integrity: sha512-5L7SUaFC1RyDraj2yRhyBzHTobyXHmohD100CChNtyPyleoq37Mqab5Gn8XEKI04dfN/oqPdpHk38MgcQWHbZg==}
|
resolution: {integrity: sha512-5L7SUaFC1RyDraj2yRhyBzHTobyXHmohD100CChNtyPyleoq37Mqab5Gn8XEKI04dfN/oqPdpHk38MgcQWHbZg==}
|
||||||
|
|
||||||
|
'@types/node@24.13.1':
|
||||||
|
resolution: {integrity: sha512-RSpUJGmvsJ1ZeBehQZFhIdpsz+bIpES0nIQXko4Ybq+N+kX6XvOq3Jo+iJ82FWLdblFq85AsMikd3m35jgezYg==}
|
||||||
|
|
||||||
'@types/react-dom@19.2.3':
|
'@types/react-dom@19.2.3':
|
||||||
resolution: {integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==}
|
resolution: {integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -1592,6 +1769,11 @@ packages:
|
|||||||
resolution: {integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==}
|
resolution: {integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
esbuild@0.28.0:
|
||||||
|
resolution: {integrity: sha512-sNR9MHpXSUV/XB4zmsFKN+QgVG82Cc7+/aaxJ8Adi8hyOac+EXptIp45QBPaVyX3N70664wRbTcLTOemCAnyqw==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
escalade@3.2.0:
|
escalade@3.2.0:
|
||||||
resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==}
|
resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==}
|
||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
@@ -1852,6 +2034,11 @@ packages:
|
|||||||
resolution: {integrity: sha512-eKpRKAovdpZtR1WopLHxlBWvAgPny3c4gX1G5Jhwmmw4XJj0ifSD5qB5TOo8hmA0wlRKDAOAhEE1yVPgs6Fgcg==}
|
resolution: {integrity: sha512-eKpRKAovdpZtR1WopLHxlBWvAgPny3c4gX1G5Jhwmmw4XJj0ifSD5qB5TOo8hmA0wlRKDAOAhEE1yVPgs6Fgcg==}
|
||||||
engines: {node: '>=14.14'}
|
engines: {node: '>=14.14'}
|
||||||
|
|
||||||
|
fsevents@2.3.3:
|
||||||
|
resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
|
||||||
|
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
|
||||||
|
os: [darwin]
|
||||||
|
|
||||||
function-bind@1.1.2:
|
function-bind@1.1.2:
|
||||||
resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==}
|
resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==}
|
||||||
|
|
||||||
@@ -2003,6 +2190,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ==}
|
resolution: {integrity: sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ==}
|
||||||
engines: {node: '>=18.18.0'}
|
engines: {node: '>=18.18.0'}
|
||||||
|
|
||||||
|
iconv-lite@0.6.3:
|
||||||
|
resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==}
|
||||||
|
engines: {node: '>=0.10.0'}
|
||||||
|
|
||||||
iconv-lite@0.7.2:
|
iconv-lite@0.7.2:
|
||||||
resolution: {integrity: sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==}
|
resolution: {integrity: sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
@@ -2384,6 +2575,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==}
|
resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
|
lru-cache@10.4.3:
|
||||||
|
resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==}
|
||||||
|
|
||||||
lru-cache@5.1.1:
|
lru-cache@5.1.1:
|
||||||
resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==}
|
resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==}
|
||||||
|
|
||||||
@@ -2399,6 +2593,14 @@ packages:
|
|||||||
magic-string@0.30.21:
|
magic-string@0.30.21:
|
||||||
resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==}
|
resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==}
|
||||||
|
|
||||||
|
mariadb@3.4.5:
|
||||||
|
resolution: {integrity: sha512-gThTYkhIS5rRqkVr+Y0cIdzr+GRqJ9sA2Q34e0yzmyhMCwyApf3OKAC1jnF23aSlIOqJuyaUFUcj7O1qZslmmQ==}
|
||||||
|
engines: {node: '>= 14'}
|
||||||
|
|
||||||
|
mariadb@3.5.2:
|
||||||
|
resolution: {integrity: sha512-9rztrI4nouxAY/82a+RlzzZ5ie2vxu2eYclkBvTy1ATXH1B9cnvZ0O71Pzsy/mlfDb5P3HhOg0JzQKkDRhctyA==}
|
||||||
|
engines: {node: '>= 18'}
|
||||||
|
|
||||||
math-intrinsics@1.1.0:
|
math-intrinsics@1.1.0:
|
||||||
resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==}
|
resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
@@ -3095,6 +3297,11 @@ packages:
|
|||||||
tslib@2.8.1:
|
tslib@2.8.1:
|
||||||
resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
|
resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
|
||||||
|
|
||||||
|
tsx@4.22.4:
|
||||||
|
resolution: {integrity: sha512-X8EX+XV4QR5xCsrgxaED954zTDfY8KqlDtskKEL0cHhyS/P8b4IFOvGDQpsC9Q1XnLq915wEfwwY/zzskCtmhg==}
|
||||||
|
engines: {node: '>=18.0.0'}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
tw-animate-css@1.4.0:
|
tw-animate-css@1.4.0:
|
||||||
resolution: {integrity: sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ==}
|
resolution: {integrity: sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ==}
|
||||||
|
|
||||||
@@ -3141,6 +3348,9 @@ packages:
|
|||||||
undici-types@6.21.0:
|
undici-types@6.21.0:
|
||||||
resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==}
|
resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==}
|
||||||
|
|
||||||
|
undici-types@7.18.2:
|
||||||
|
resolution: {integrity: sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==}
|
||||||
|
|
||||||
unicorn-magic@0.3.0:
|
unicorn-magic@0.3.0:
|
||||||
resolution: {integrity: sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==}
|
resolution: {integrity: sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
@@ -3537,6 +3747,84 @@ snapshots:
|
|||||||
tslib: 2.8.1
|
tslib: 2.8.1
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/aix-ppc64@0.28.0':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/android-arm64@0.28.0':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/android-arm@0.28.0':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/android-x64@0.28.0':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/darwin-arm64@0.28.0':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/darwin-x64@0.28.0':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/freebsd-arm64@0.28.0':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/freebsd-x64@0.28.0':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/linux-arm64@0.28.0':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/linux-arm@0.28.0':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/linux-ia32@0.28.0':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/linux-loong64@0.28.0':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/linux-mips64el@0.28.0':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/linux-ppc64@0.28.0':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/linux-riscv64@0.28.0':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/linux-s390x@0.28.0':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/linux-x64@0.28.0':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/netbsd-arm64@0.28.0':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/netbsd-x64@0.28.0':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/openbsd-arm64@0.28.0':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/openbsd-x64@0.28.0':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/openharmony-arm64@0.28.0':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/sunos-x64@0.28.0':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/win32-arm64@0.28.0':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/win32-ia32@0.28.0':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/win32-x64@0.28.0':
|
||||||
|
optional: true
|
||||||
|
|
||||||
'@eslint-community/eslint-utils@4.9.1(eslint@9.39.4(jiti@2.7.0))':
|
'@eslint-community/eslint-utils@4.9.1(eslint@9.39.4(jiti@2.7.0))':
|
||||||
dependencies:
|
dependencies:
|
||||||
eslint: 9.39.4(jiti@2.7.0)
|
eslint: 9.39.4(jiti@2.7.0)
|
||||||
@@ -3821,6 +4109,11 @@ snapshots:
|
|||||||
|
|
||||||
'@panva/hkdf@1.2.1': {}
|
'@panva/hkdf@1.2.1': {}
|
||||||
|
|
||||||
|
'@prisma/adapter-mariadb@7.8.0':
|
||||||
|
dependencies:
|
||||||
|
'@prisma/driver-adapter-utils': 7.8.0
|
||||||
|
mariadb: 3.4.5
|
||||||
|
|
||||||
'@prisma/client-runtime-utils@7.8.0': {}
|
'@prisma/client-runtime-utils@7.8.0': {}
|
||||||
|
|
||||||
'@prisma/client@7.8.0(prisma@7.8.0(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3))(typescript@5.9.3)':
|
'@prisma/client@7.8.0(prisma@7.8.0(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3))(typescript@5.9.3)':
|
||||||
@@ -3865,6 +4158,10 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- typescript
|
- typescript
|
||||||
|
|
||||||
|
'@prisma/driver-adapter-utils@7.8.0':
|
||||||
|
dependencies:
|
||||||
|
'@prisma/debug': 7.8.0
|
||||||
|
|
||||||
'@prisma/engines-version@7.8.0-6.3c6e192761c0362d496ed980de936e2f3cebcd3a': {}
|
'@prisma/engines-version@7.8.0-6.3c6e192761c0362d496ed980de936e2f3cebcd3a': {}
|
||||||
|
|
||||||
'@prisma/engines@7.8.0':
|
'@prisma/engines@7.8.0':
|
||||||
@@ -4065,6 +4362,8 @@ snapshots:
|
|||||||
|
|
||||||
'@types/estree@1.0.9': {}
|
'@types/estree@1.0.9': {}
|
||||||
|
|
||||||
|
'@types/geojson@7946.0.16': {}
|
||||||
|
|
||||||
'@types/json-schema@7.0.15': {}
|
'@types/json-schema@7.0.15': {}
|
||||||
|
|
||||||
'@types/json5@0.0.29': {}
|
'@types/json5@0.0.29': {}
|
||||||
@@ -4073,6 +4372,10 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
undici-types: 6.21.0
|
undici-types: 6.21.0
|
||||||
|
|
||||||
|
'@types/node@24.13.1':
|
||||||
|
dependencies:
|
||||||
|
undici-types: 7.18.2
|
||||||
|
|
||||||
'@types/react-dom@19.2.3(@types/react@19.2.17)':
|
'@types/react-dom@19.2.3(@types/react@19.2.17)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/react': 19.2.17
|
'@types/react': 19.2.17
|
||||||
@@ -4766,6 +5069,35 @@ snapshots:
|
|||||||
is-date-object: 1.1.0
|
is-date-object: 1.1.0
|
||||||
is-symbol: 1.1.1
|
is-symbol: 1.1.1
|
||||||
|
|
||||||
|
esbuild@0.28.0:
|
||||||
|
optionalDependencies:
|
||||||
|
'@esbuild/aix-ppc64': 0.28.0
|
||||||
|
'@esbuild/android-arm': 0.28.0
|
||||||
|
'@esbuild/android-arm64': 0.28.0
|
||||||
|
'@esbuild/android-x64': 0.28.0
|
||||||
|
'@esbuild/darwin-arm64': 0.28.0
|
||||||
|
'@esbuild/darwin-x64': 0.28.0
|
||||||
|
'@esbuild/freebsd-arm64': 0.28.0
|
||||||
|
'@esbuild/freebsd-x64': 0.28.0
|
||||||
|
'@esbuild/linux-arm': 0.28.0
|
||||||
|
'@esbuild/linux-arm64': 0.28.0
|
||||||
|
'@esbuild/linux-ia32': 0.28.0
|
||||||
|
'@esbuild/linux-loong64': 0.28.0
|
||||||
|
'@esbuild/linux-mips64el': 0.28.0
|
||||||
|
'@esbuild/linux-ppc64': 0.28.0
|
||||||
|
'@esbuild/linux-riscv64': 0.28.0
|
||||||
|
'@esbuild/linux-s390x': 0.28.0
|
||||||
|
'@esbuild/linux-x64': 0.28.0
|
||||||
|
'@esbuild/netbsd-arm64': 0.28.0
|
||||||
|
'@esbuild/netbsd-x64': 0.28.0
|
||||||
|
'@esbuild/openbsd-arm64': 0.28.0
|
||||||
|
'@esbuild/openbsd-x64': 0.28.0
|
||||||
|
'@esbuild/openharmony-arm64': 0.28.0
|
||||||
|
'@esbuild/sunos-x64': 0.28.0
|
||||||
|
'@esbuild/win32-arm64': 0.28.0
|
||||||
|
'@esbuild/win32-ia32': 0.28.0
|
||||||
|
'@esbuild/win32-x64': 0.28.0
|
||||||
|
|
||||||
escalade@3.2.0: {}
|
escalade@3.2.0: {}
|
||||||
|
|
||||||
escape-html@1.0.3: {}
|
escape-html@1.0.3: {}
|
||||||
@@ -5153,6 +5485,9 @@ snapshots:
|
|||||||
jsonfile: 6.2.1
|
jsonfile: 6.2.1
|
||||||
universalify: 2.0.1
|
universalify: 2.0.1
|
||||||
|
|
||||||
|
fsevents@2.3.3:
|
||||||
|
optional: true
|
||||||
|
|
||||||
function-bind@1.1.2: {}
|
function-bind@1.1.2: {}
|
||||||
|
|
||||||
function.prototype.name@1.1.8:
|
function.prototype.name@1.1.8:
|
||||||
@@ -5295,6 +5630,10 @@ snapshots:
|
|||||||
|
|
||||||
human-signals@8.0.1: {}
|
human-signals@8.0.1: {}
|
||||||
|
|
||||||
|
iconv-lite@0.6.3:
|
||||||
|
dependencies:
|
||||||
|
safer-buffer: 2.1.2
|
||||||
|
|
||||||
iconv-lite@0.7.2:
|
iconv-lite@0.7.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
safer-buffer: 2.1.2
|
safer-buffer: 2.1.2
|
||||||
@@ -5613,6 +5952,8 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
js-tokens: 4.0.0
|
js-tokens: 4.0.0
|
||||||
|
|
||||||
|
lru-cache@10.4.3: {}
|
||||||
|
|
||||||
lru-cache@5.1.1:
|
lru-cache@5.1.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
yallist: 3.1.1
|
yallist: 3.1.1
|
||||||
@@ -5627,6 +5968,22 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@jridgewell/sourcemap-codec': 1.5.5
|
'@jridgewell/sourcemap-codec': 1.5.5
|
||||||
|
|
||||||
|
mariadb@3.4.5:
|
||||||
|
dependencies:
|
||||||
|
'@types/geojson': 7946.0.16
|
||||||
|
'@types/node': 24.13.1
|
||||||
|
denque: 2.1.0
|
||||||
|
iconv-lite: 0.6.3
|
||||||
|
lru-cache: 10.4.3
|
||||||
|
|
||||||
|
mariadb@3.5.2:
|
||||||
|
dependencies:
|
||||||
|
'@types/geojson': 7946.0.16
|
||||||
|
'@types/node': 20.19.42
|
||||||
|
denque: 2.1.0
|
||||||
|
iconv-lite: 0.7.2
|
||||||
|
lru-cache: 10.4.3
|
||||||
|
|
||||||
math-intrinsics@1.1.0: {}
|
math-intrinsics@1.1.0: {}
|
||||||
|
|
||||||
media-typer@1.1.0: {}
|
media-typer@1.1.0: {}
|
||||||
@@ -6425,6 +6782,12 @@ snapshots:
|
|||||||
|
|
||||||
tslib@2.8.1: {}
|
tslib@2.8.1: {}
|
||||||
|
|
||||||
|
tsx@4.22.4:
|
||||||
|
dependencies:
|
||||||
|
esbuild: 0.28.0
|
||||||
|
optionalDependencies:
|
||||||
|
fsevents: 2.3.3
|
||||||
|
|
||||||
tw-animate-css@1.4.0: {}
|
tw-animate-css@1.4.0: {}
|
||||||
|
|
||||||
type-check@0.4.0:
|
type-check@0.4.0:
|
||||||
@@ -6492,6 +6855,8 @@ snapshots:
|
|||||||
|
|
||||||
undici-types@6.21.0: {}
|
undici-types@6.21.0: {}
|
||||||
|
|
||||||
|
undici-types@7.18.2: {}
|
||||||
|
|
||||||
unicorn-magic@0.3.0: {}
|
unicorn-magic@0.3.0: {}
|
||||||
|
|
||||||
universalify@2.0.1: {}
|
universalify@2.0.1: {}
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
import { getToken } from "next-auth/jwt";
|
||||||
|
import { NextResponse, type NextRequest } from "next/server";
|
||||||
|
|
||||||
|
const PUBLIC_PATHS = ["/login"];
|
||||||
|
|
||||||
|
export async function proxy(request: NextRequest) {
|
||||||
|
const { pathname, search } = request.nextUrl;
|
||||||
|
const isPublicPath = PUBLIC_PATHS.includes(pathname);
|
||||||
|
const token = await getToken({
|
||||||
|
req: request,
|
||||||
|
secret: process.env.AUTH_SECRET ?? process.env.NEXTAUTH_SECRET,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!token && !isPublicPath) {
|
||||||
|
const loginUrl = new URL("/login", request.url);
|
||||||
|
loginUrl.searchParams.set("callbackUrl", `${pathname}${search}`);
|
||||||
|
|
||||||
|
return NextResponse.redirect(loginUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (token && isPublicPath) {
|
||||||
|
return NextResponse.redirect(new URL("/", request.url));
|
||||||
|
}
|
||||||
|
|
||||||
|
return NextResponse.next();
|
||||||
|
}
|
||||||
|
|
||||||
|
export const config = {
|
||||||
|
matcher: ["/((?!api/auth|_next/static|_next/image|favicon.ico|.*\\..*).*)"],
|
||||||
|
};
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
import { config } from "dotenv";
|
||||||
|
|
||||||
|
config({ path: ".env.local" });
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
const { db } = await import("@/lib/db");
|
||||||
|
const { getAccountTypeLabel, getActivateStatusLabel, getRequestStatusLabel } =
|
||||||
|
await import("@/lib/utils");
|
||||||
|
|
||||||
|
const [maestroCount, extensionCount, upgradeCount] = await Promise.all([
|
||||||
|
db.maestro.count(),
|
||||||
|
db.maestro_extension.count(),
|
||||||
|
db.maestro_upgrade.count(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const [maestros, extensionRequests, upgradeRequests] = await Promise.all([
|
||||||
|
db.maestro.findMany({
|
||||||
|
orderBy: { MaestroID: "desc" },
|
||||||
|
select: {
|
||||||
|
MaestroID: true,
|
||||||
|
Name: true,
|
||||||
|
AccountType: true,
|
||||||
|
ActivateStatus: true,
|
||||||
|
PlayerCount: true,
|
||||||
|
},
|
||||||
|
take: 3,
|
||||||
|
}),
|
||||||
|
db.maestro_extension.findMany({
|
||||||
|
orderBy: { MaestroExtensionID: "desc" },
|
||||||
|
select: {
|
||||||
|
MaestroExtensionID: true,
|
||||||
|
MaestroID: true,
|
||||||
|
AccountType: true,
|
||||||
|
Status: true,
|
||||||
|
},
|
||||||
|
take: 3,
|
||||||
|
}),
|
||||||
|
db.maestro_upgrade.findMany({
|
||||||
|
orderBy: { MaestroUpgradeID: "desc" },
|
||||||
|
select: {
|
||||||
|
MaestroUpgradeID: true,
|
||||||
|
MaestroID: true,
|
||||||
|
RegisteredAccountType: true,
|
||||||
|
RequestedAccountType: true,
|
||||||
|
Status: true,
|
||||||
|
},
|
||||||
|
take: 3,
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
|
||||||
|
console.log("DB connection OK");
|
||||||
|
console.log({
|
||||||
|
counts: {
|
||||||
|
maestro: maestroCount,
|
||||||
|
maestro_extension: extensionCount,
|
||||||
|
maestro_upgrade: upgradeCount,
|
||||||
|
},
|
||||||
|
maestros: maestros.map((maestro) => ({
|
||||||
|
id: maestro.MaestroID,
|
||||||
|
name: maestro.Name,
|
||||||
|
accountType: getAccountTypeLabel(maestro.AccountType),
|
||||||
|
activateStatus: getActivateStatusLabel(maestro.ActivateStatus),
|
||||||
|
playerCount: maestro.PlayerCount,
|
||||||
|
})),
|
||||||
|
extensionRequests: extensionRequests.map((request) => ({
|
||||||
|
id: request.MaestroExtensionID,
|
||||||
|
maestroId: request.MaestroID,
|
||||||
|
accountType: getAccountTypeLabel(request.AccountType),
|
||||||
|
status: getRequestStatusLabel(request.Status),
|
||||||
|
})),
|
||||||
|
upgradeRequests: upgradeRequests.map((request) => ({
|
||||||
|
id: request.MaestroUpgradeID,
|
||||||
|
maestroId: request.MaestroID,
|
||||||
|
from: getAccountTypeLabel(request.RegisteredAccountType),
|
||||||
|
to: getAccountTypeLabel(request.RequestedAccountType),
|
||||||
|
status: getRequestStatusLabel(request.Status),
|
||||||
|
})),
|
||||||
|
});
|
||||||
|
|
||||||
|
await db.$disconnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
main().catch((error) => {
|
||||||
|
console.error("DB connection check failed");
|
||||||
|
console.error(error);
|
||||||
|
process.exitCode = 1;
|
||||||
|
});
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
export type {
|
||||||
|
admin,
|
||||||
|
maestro,
|
||||||
|
maestro_extension,
|
||||||
|
maestro_log,
|
||||||
|
maestro_upgrade,
|
||||||
|
player,
|
||||||
|
} from "@/lib/generated/prisma/client";
|
||||||
|
|
||||||
|
export type {
|
||||||
|
AccountType,
|
||||||
|
ActivateStatus,
|
||||||
|
PaidAccountType,
|
||||||
|
RequestStatus,
|
||||||
|
} from "@/lib/constants";
|
||||||
Reference in New Issue
Block a user