diff --git a/Dockerfile b/Dockerfile index 655c1cd..ee61310 100644 --- a/Dockerfile +++ b/Dockerfile @@ -7,7 +7,7 @@ FROM node:22-alpine AS builder WORKDIR /app COPY --from=deps /app/node_modules ./node_modules COPY . . -RUN npx prisma generate && npm run build +RUN npm run build FROM node:22-alpine AS runner WORKDIR /app @@ -17,8 +17,8 @@ COPY --from=builder /app/package*.json ./ COPY --from=builder /app/node_modules ./node_modules COPY --from=builder /app/.next ./.next COPY --from=builder /app/public ./public -COPY --from=builder /app/prisma ./prisma -COPY --from=builder /app/prisma.config.ts ./prisma.config.ts +COPY --from=builder /app/drizzle.config.ts ./drizzle.config.ts +COPY --from=builder /app/lib/db ./lib/db COPY --from=builder /app/next.config.mjs ./next.config.mjs EXPOSE 3000 diff --git a/Makefile b/Makefile index 236c4d9..6ad9ee3 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,4 @@ -.PHONY: start stop restart deploy logs build ps port health clean-orphans app-shell db-shell db-init db-migrate db-seed prisma-generate prisma-migrate help - -MIGRATION_NAME ?= init +.PHONY: start stop restart deploy logs build ps port health clean-orphans app-shell db-shell db-init db-generate db-migrate db-push db-seed help start: docker compose up -d --build @@ -38,19 +36,19 @@ db-shell: docker compose exec db psql -U postgres -d moh_sass db-init: - docker compose exec app sh -lc "npx prisma generate && npx prisma migrate deploy && npx prisma db seed" + docker compose exec app sh -lc "npm run db:migrate && npm run db:seed" + +db-generate: + docker compose exec app npm run db:generate db-migrate: - docker compose exec app npx prisma migrate deploy + docker compose exec app npm run db:migrate + +db-push: + docker compose exec app npm run db:push db-seed: - docker compose exec app npx prisma db seed - -prisma-generate: - docker compose exec app npx prisma generate - -prisma-migrate: - docker compose exec app npx prisma migrate dev --name $(MIGRATION_NAME) + docker compose exec app npm run db:seed health: curl -sS https://mohfarawati.de/api/health @@ -68,9 +66,9 @@ help: @echo " make clean-orphans Remove orphaned old containers" @echo " make app-shell Open shell in app container" @echo " make db-shell Open PostgreSQL shell" - @echo " make db-init Generate client, apply migrations, run seed" - @echo " make db-migrate Apply prisma migrations" + @echo " make db-init Apply migrations and run seed (first run)" + @echo " make db-generate Generate a Drizzle migration from schema changes" + @echo " make db-migrate Apply pending Drizzle migrations" + @echo " make db-push Push schema directly (dev convenience)" @echo " make db-seed Seed database data" - @echo " make prisma-generate Run prisma generate" - @echo " make prisma-migrate Create/apply dev migration" @echo " make health Check app health endpoint via public domain" diff --git a/app/_admin/media/actions.ts b/app/_admin/media/actions.ts index b299b9a..4ce7ae9 100644 --- a/app/_admin/media/actions.ts +++ b/app/_admin/media/actions.ts @@ -1,6 +1,6 @@ "use server"; -import { MediaKind } from "@prisma/client"; +import { eq } from "drizzle-orm"; import { revalidatePath } from "next/cache"; import { redirect } from "next/navigation"; import { isRedirectError } from "next/dist/client/components/redirect-error"; @@ -11,7 +11,9 @@ import { withFlash } from "@/lib/admin-feedback"; import { countMediaUsageReferences, getMediaAssetById } from "@/lib/media"; import { createStandaloneMediaAsset, deleteMediaAssetAndFile } from "@/lib/media-service"; import { isManagedMediaFilePath } from "@/lib/media-storage"; -import { prisma } from "@/lib/prisma"; +import { db } from "@/lib/db"; +import { mediaAsset } from "@/lib/db/schema"; +import { MediaKind } from "@/lib/db/enums"; async function ensureAdmin() { if (!(await isAdminAuthenticated())) { @@ -71,11 +73,7 @@ export async function deleteMediaAssetAction(formData: FormData) { redirect(withFlash(getAdminAppPath("/media"), { error: "Datei wird noch verwendet." })); } - await prisma.mediaAsset.delete({ - where: { - id: asset.id, - }, - }); + await db.delete(mediaAsset).where(eq(mediaAsset.id, asset.id)); if (isManagedMediaFilePath(asset.url)) { await deleteMediaAssetAndFile({ diff --git a/app/_admin/portfolio/actions.ts b/app/_admin/portfolio/actions.ts index 3342bd1..a8502f5 100644 --- a/app/_admin/portfolio/actions.ts +++ b/app/_admin/portfolio/actions.ts @@ -1,6 +1,6 @@ "use server"; -import { MediaUsageType, Prisma } from "@prisma/client"; +import { and, count, eq, inArray } from "drizzle-orm"; import { revalidatePath } from "next/cache"; import { redirect } from "next/navigation"; import { isRedirectError } from "next/dist/client/components/redirect-error"; @@ -15,7 +15,16 @@ import { resolveMediaSelection } from "@/lib/media-service"; import { getLocalizedPath } from "@/lib/locale"; import { removeManagedMediaFile } from "@/lib/media-storage"; import { mediaFieldInputSchema } from "@/lib/media-validation"; -import { prisma } from "@/lib/prisma"; +import { db } from "@/lib/db"; +import { + category, + mediaAsset, + mediaUsage, + portfolioAsset, + portfolioProject, + portfolioSection, +} from "@/lib/db/schema"; +import { MediaUsageType } from "@/lib/db/enums"; import { isCheckedFormValue } from "@/lib/form-data"; import { getSiteSettings } from "@/lib/app-config"; import { @@ -81,6 +90,16 @@ function parseZodError(error: ZodError) { return error.issues[0]?.message ?? "Validierung fehlgeschlagen."; } +// Postgres unique-violation SQLSTATE (was Prisma's P2002). +function isUniqueViolation(error: unknown): boolean { + return ( + typeof error === "object" && + error !== null && + "code" in error && + (error as { code?: string }).code === "23505" + ); +} + async function revalidatePortfolioPages() { revalidatePath(toInternalAdminPath("/")); revalidatePath(toInternalAdminPath("/media")); @@ -120,17 +139,15 @@ export async function upsertCategoryAction(formData: FormData) { isActive: normalizeCheckboxValue(formData, "isActive"), }); - if (parsed.id) { - await prisma.category.update({ - where: { - id: parsed.id, - }, - data: parsed, - }); + const { id: categoryId, ...categoryValues } = parsed; + + if (categoryId) { + await db + .update(category) + .set({ ...categoryValues, updatedAt: new Date() }) + .where(eq(category.id, categoryId)); } else { - await prisma.category.create({ - data: parsed, - }); + await db.insert(category).values(categoryValues); } await revalidatePortfolioPages(); @@ -143,7 +160,7 @@ export async function upsertCategoryAction(formData: FormData) { const message = error instanceof ZodError ? parseZodError(error) - : error instanceof Prisma.PrismaClientKnownRequestError && error.code === "P2002" + : isUniqueViolation(error) ? "Kategorie Slug muss eindeutig sein." : "Kategorie konnte nicht gespeichert werden."; @@ -158,21 +175,16 @@ export async function deleteCategoryAction(formData: FormData) { const id = String(formData.get("id") ?? ""); try { - const projectCount = await prisma.portfolioProject.count({ - where: { - categoryId: id, - }, - }); + const [projectCountRow] = await db + .select({ value: count() }) + .from(portfolioProject) + .where(eq(portfolioProject.categoryId, id)); - if (projectCount > 0) { + if ((projectCountRow?.value ?? 0) > 0) { redirect(withFlash(redirectPath, { error: "Kategorie mit Projekten kann nicht geloescht werden." })); } - await prisma.category.delete({ - where: { - id, - }, - }); + await db.delete(category).where(eq(category.id, id)); await revalidatePortfolioPages(); redirect(withFlash(redirectPath, { success: "Kategorie geloescht." })); @@ -241,15 +253,16 @@ export async function saveProjectAction(formData: FormData) { }); const existingProject = parsed.id - ? await prisma.portfolioProject.findUnique({ - where: { - id: parsed.id, - }, - select: { - isPublished: true, - publishedAt: true, - }, - }) + ? ( + await db + .select({ + isPublished: portfolioProject.isPublished, + publishedAt: portfolioProject.publishedAt, + }) + .from(portfolioProject) + .where(eq(portfolioProject.id, parsed.id)) + .limit(1) + )[0] ?? null : null; const shouldPublishNow = parsed.isPublished && !existingProject?.publishedAt; @@ -359,81 +372,60 @@ export async function saveProjectAction(formData: FormData) { }); } - const projectResult = await prisma.$transaction(async (tx) => { - const currentProject = parsed.id - ? await tx.portfolioProject.update({ - where: { - id: parsed.id, - }, - data: { - categoryId: parsed.categoryId, - slug: parsed.slug, - viewMode: parsed.viewMode, - titleAr: parsed.titleAr, - titleEn: parsed.titleEn, - titleDe: parsed.titleDe, - summaryAr: parsed.summaryAr, - summaryEn: parsed.summaryEn, - summaryDe: parsed.summaryDe, - clientName: parsed.clientName, - projectYear: parsed.projectYear, - serviceLabelAr: parsed.serviceLabelAr, - serviceLabelEn: parsed.serviceLabelEn, - serviceLabelDe: parsed.serviceLabelDe, - previewUrl: parsed.previewUrl || null, - coverImagePath: coverSelection.url || null, - isFeatured: parsed.isFeatured, - isPublished: parsed.isPublished, + const projectResult = await db.transaction(async (tx) => { + const projectValues = { + categoryId: parsed.categoryId, + slug: parsed.slug, + viewMode: parsed.viewMode, + titleAr: parsed.titleAr, + titleEn: parsed.titleEn, + titleDe: parsed.titleDe, + summaryAr: parsed.summaryAr, + summaryEn: parsed.summaryEn, + summaryDe: parsed.summaryDe, + clientName: parsed.clientName, + projectYear: parsed.projectYear, + serviceLabelAr: parsed.serviceLabelAr, + serviceLabelEn: parsed.serviceLabelEn, + serviceLabelDe: parsed.serviceLabelDe, + previewUrl: parsed.previewUrl || null, + coverImagePath: coverSelection.url || null, + isFeatured: parsed.isFeatured, + isPublished: parsed.isPublished, + sortOrder: parsed.sortOrder, + }; + + const [currentProject] = parsed.id + ? await tx + .update(portfolioProject) + .set({ + ...projectValues, publishedAt: parsed.isPublished ? shouldPublishNow ? new Date() : existingProject?.publishedAt ?? new Date() : null, - sortOrder: parsed.sortOrder, - }, - }) - : await tx.portfolioProject.create({ - data: { - categoryId: parsed.categoryId, - slug: parsed.slug, - viewMode: parsed.viewMode, - titleAr: parsed.titleAr, - titleEn: parsed.titleEn, - titleDe: parsed.titleDe, - summaryAr: parsed.summaryAr, - summaryEn: parsed.summaryEn, - summaryDe: parsed.summaryDe, - clientName: parsed.clientName, - projectYear: parsed.projectYear, - serviceLabelAr: parsed.serviceLabelAr, - serviceLabelEn: parsed.serviceLabelEn, - serviceLabelDe: parsed.serviceLabelDe, - previewUrl: parsed.previewUrl || null, - coverImagePath: coverSelection.url || null, - isFeatured: parsed.isFeatured, - isPublished: parsed.isPublished, + updatedAt: new Date(), + }) + .where(eq(portfolioProject.id, parsed.id)) + .returning() + : await tx + .insert(portfolioProject) + .values({ + ...projectValues, publishedAt: parsed.isPublished ? new Date() : null, - sortOrder: parsed.sortOrder, - }, - }); + }) + .returning(); - await tx.portfolioSection.deleteMany({ - where: { - projectId: currentProject.id, - }, - }); - - await tx.portfolioAsset.deleteMany({ - where: { - projectId: currentProject.id, - }, - }); + await tx.delete(portfolioSection).where(eq(portfolioSection.projectId, currentProject.id)); + await tx.delete(portfolioAsset).where(eq(portfolioAsset.projectId, currentProject.id)); const createdSections = []; for (const section of sectionRows) { - const createdSection = await tx.portfolioSection.create({ - data: { + const [createdSection] = await tx + .insert(portfolioSection) + .values({ projectId: currentProject.id, type: section.type, titleAr: section.titleAr, @@ -445,8 +437,8 @@ export async function saveProjectAction(formData: FormData) { imagePath: section.imagePath || null, linkUrl: section.linkUrl || null, sortOrder: section.sortOrder, - }, - }); + }) + .returning(); createdSections.push(createdSection); } @@ -454,8 +446,9 @@ export async function saveProjectAction(formData: FormData) { const createdAssets = []; for (const asset of assetRows) { - const createdAsset = await tx.portfolioAsset.create({ - data: { + const [createdAsset] = await tx + .insert(portfolioAsset) + .values({ projectId: currentProject.id, kind: asset.kind, filePath: asset.filePath, @@ -463,8 +456,8 @@ export async function saveProjectAction(formData: FormData) { altEn: asset.altEn, altDe: asset.altDe, sortOrder: asset.sortOrder, - }, - }); + }) + .returning(); createdAssets.push(createdAsset); } @@ -536,7 +529,7 @@ export async function saveProjectAction(formData: FormData) { const message = error instanceof ZodError ? parseZodError(error) - : error instanceof Prisma.PrismaClientKnownRequestError && error.code === "P2002" + : isUniqueViolation(error) ? "Projekt Slug muss eindeutig sein." : error instanceof Error ? error.message @@ -544,20 +537,8 @@ export async function saveProjectAction(formData: FormData) { await removeManagedPaths(uploadedPaths); if (createdMediaAssetIds.length > 0) { - await prisma.mediaUsage.deleteMany({ - where: { - assetId: { - in: createdMediaAssetIds, - }, - }, - }); - await prisma.mediaAsset.deleteMany({ - where: { - id: { - in: createdMediaAssetIds, - }, - }, - }); + await db.delete(mediaUsage).where(inArray(mediaUsage.assetId, createdMediaAssetIds)); + await db.delete(mediaAsset).where(inArray(mediaAsset.id, createdMediaAssetIds)); } redirect(withFlash(redirectPath, { error: message })); } @@ -569,24 +550,17 @@ export async function deleteProjectAction(formData: FormData) { const id = String(formData.get("id") ?? ""); try { - const project = await prisma.portfolioProject.findUnique({ - where: { - id, - }, - select: { - slug: true, - }, - }); + const [project] = await db + .select({ slug: portfolioProject.slug }) + .from(portfolioProject) + .where(eq(portfolioProject.id, id)) + .limit(1); if (!project) { redirect(withFlash(getAdminAppPath("/portfolio"), { error: "Projekt nicht gefunden." })); } - await prisma.portfolioProject.delete({ - where: { - id, - }, - }); + await db.delete(portfolioProject).where(eq(portfolioProject.id, id)); await deleteEntityMediaUsages("portfolio-project", id); await revalidatePortfolioPages(); diff --git a/app/_admin/site-settings/actions.ts b/app/_admin/site-settings/actions.ts index c395f3d..26f90c6 100644 --- a/app/_admin/site-settings/actions.ts +++ b/app/_admin/site-settings/actions.ts @@ -1,6 +1,6 @@ "use server"; -import { MediaUsageType } from "@prisma/client"; +import { inArray } from "drizzle-orm"; import { revalidatePath } from "next/cache"; import { redirect } from "next/navigation"; import { isRedirectError } from "next/dist/client/components/redirect-error"; @@ -30,7 +30,9 @@ import { routing } from "@/i18n/routing"; import { getLocalizedPath } from "@/lib/locale"; import { removeManagedMediaFile } from "@/lib/media-storage"; import { mediaFieldInputSchema } from "@/lib/media-validation"; -import { prisma } from "@/lib/prisma"; +import { db } from "@/lib/db"; +import { mediaAsset } from "@/lib/db/schema"; +import { MediaUsageType } from "@/lib/db/enums"; async function ensureAdmin() { if (!(await isAdminAuthenticated())) { @@ -60,13 +62,7 @@ function parseJsonObject(rawValue: FormDataEntryValue | null, key: string) { async function cleanupCreatedMedia(assetIds: string[], uploadedPaths: string[]) { if (assetIds.length > 0) { - await prisma.mediaAsset.deleteMany({ - where: { - id: { - in: Array.from(new Set(assetIds)), - }, - }, - }); + await db.delete(mediaAsset).where(inArray(mediaAsset.id, Array.from(new Set(assetIds)))); } for (const filePath of Array.from(new Set(uploadedPaths.filter(Boolean)))) { diff --git a/app/_admin/site-settings/brand/page.tsx b/app/_admin/site-settings/brand/page.tsx index c3937b2..c09bbd6 100644 --- a/app/_admin/site-settings/brand/page.tsx +++ b/app/_admin/site-settings/brand/page.tsx @@ -1,4 +1,4 @@ -import { MediaKind } from "@prisma/client"; +import { MediaKind } from "@/lib/db/enums"; import { redirect } from "next/navigation"; import { MotionFade } from "@/components/motion-fade"; diff --git a/app/api/health/route.ts b/app/api/health/route.ts index 6c802de..fba9e74 100644 --- a/app/api/health/route.ts +++ b/app/api/health/route.ts @@ -1,6 +1,7 @@ +import { sql } from "drizzle-orm"; import { NextResponse } from "next/server"; -import { prisma } from "@/lib/prisma"; +import { db } from "@/lib/db"; export const dynamic = "force-dynamic"; @@ -8,7 +9,7 @@ export async function GET() { const timestamp = new Date().toISOString(); try { - await prisma.$queryRaw`SELECT 1`; + await db.execute(sql`SELECT 1`); return NextResponse.json( { diff --git a/components/admin/media-field-picker.tsx b/components/admin/media-field-picker.tsx index 35e2290..63908d9 100644 --- a/components/admin/media-field-picker.tsx +++ b/components/admin/media-field-picker.tsx @@ -2,7 +2,7 @@ /* eslint-disable @next/next/no-img-element */ -import type { MediaKind } from "@prisma/client"; +import type { MediaKind } from "@/lib/db/enums"; import { Check, ImageIcon, Search, Trash2 } from "lucide-react"; import { useEffect, useMemo, useRef, useState } from "react"; diff --git a/components/admin/media-library-manager.tsx b/components/admin/media-library-manager.tsx index 445a6aa..09a718d 100644 --- a/components/admin/media-library-manager.tsx +++ b/components/admin/media-library-manager.tsx @@ -2,7 +2,7 @@ /* eslint-disable @next/next/no-img-element */ -import type { MediaKind } from "@prisma/client"; +import type { MediaKind } from "@/lib/db/enums"; import { FileType2, Grid2x2, ImageIcon, LayoutList, LoaderCircle, Trash2, Upload } from "lucide-react"; import Link from "next/link"; import { useEffect, useMemo, useState } from "react"; diff --git a/components/admin/portfolio-project-form.tsx b/components/admin/portfolio-project-form.tsx index d13d635..9c88fa6 100644 --- a/components/admin/portfolio-project-form.tsx +++ b/components/admin/portfolio-project-form.tsx @@ -1,6 +1,6 @@ "use client"; -import type { PortfolioProjectViewMode, PortfolioSectionType } from "@prisma/client"; +import type { PortfolioProjectViewMode, PortfolioSectionType } from "@/lib/db/enums"; import { ArrowDown, ArrowUp, diff --git a/components/admin/site-settings-form.tsx b/components/admin/site-settings-form.tsx index 134c28f..c85d71d 100644 --- a/components/admin/site-settings-form.tsx +++ b/components/admin/site-settings-form.tsx @@ -2,7 +2,7 @@ /* eslint-disable @next/next/no-img-element */ -import { MediaKind } from "@prisma/client"; +import { MediaKind } from "@/lib/db/enums"; import { Check, FileText, diff --git a/drizzle.config.ts b/drizzle.config.ts new file mode 100644 index 0000000..5785997 --- /dev/null +++ b/drizzle.config.ts @@ -0,0 +1,13 @@ +import type { Config } from "drizzle-kit"; + +const rawConnectionString = + process.env.DATABASE_URL ?? "postgresql://postgres:postgres@localhost:5432/moh_sass"; + +export default { + schema: "./lib/db/schema.ts", + out: "./lib/db/migrations", + dialect: "postgresql", + dbCredentials: { + url: rawConnectionString.split("?")[0], + }, +} satisfies Config; diff --git a/lib/admin-auth.ts b/lib/admin-auth.ts index 4e66165..9f1c790 100644 --- a/lib/admin-auth.ts +++ b/lib/admin-auth.ts @@ -1,8 +1,10 @@ import { createHash, createHmac, timingSafeEqual } from "crypto"; +import { and, eq, like, lt } from "drizzle-orm"; import { cookies, headers } from "next/headers"; import { redirect } from "next/navigation"; -import { prisma } from "./prisma"; +import { db } from "./db"; +import { appConfig } from "./db/schema"; import { getAdminAppPath } from "./admin-routing"; export const ADMIN_SESSION_COOKIE = "moh_admin_session"; @@ -137,11 +139,11 @@ function getLockoutKey(ip: string): string { async function cleanupExpiredLockouts(): Promise { try { const cutoff = new Date(Date.now() - LOCKOUT_SECONDS * 2 * 1000); - await prisma.$executeRaw` - DELETE FROM "AppConfig" - WHERE key LIKE ${`${ADMIN_LOCKOUT_KEY_PREFIX}:%`} - AND "updatedAt" < ${cutoff} - `; + await db + .delete(appConfig) + .where( + and(like(appConfig.key, `${ADMIN_LOCKOUT_KEY_PREFIX}:%`), lt(appConfig.updatedAt, cutoff)), + ); } catch { // Non-critical — ignore cleanup errors. } @@ -216,11 +218,12 @@ export async function getAdminLockState(): Promise<{ locked: boolean; remainingS try { const ip = await getClientIp(); const key = getLockoutKey(ip); - const config = await prisma.appConfig.findUnique({ - where: { key }, - select: { value: true }, - }); - const state = parseFailState(config?.value); + const rows = await db + .select({ value: appConfig.value }) + .from(appConfig) + .where(eq(appConfig.key, key)) + .limit(1); + const state = parseFailState(rows[0]?.value); const now = Date.now(); if (state.lockUntil > now) { @@ -244,23 +247,24 @@ export async function registerFailedAdminAttempt(): Promise<{ locked: boolean; r await cleanupExpiredLockouts(); - const config = await prisma.appConfig.findUnique({ - where: { key }, - select: { value: true }, - }); + const rows = await db + .select({ value: appConfig.value }) + .from(appConfig) + .where(eq(appConfig.key, key)) + .limit(1); - const current = parseFailState(config?.value); + const current = parseFailState(rows[0]?.value); // If a previous lockout has expired, reset the counter. const baseAttempts = current.lockUntil > 0 && current.lockUntil < now ? 0 : current.attempts; const attempts = baseAttempts + 1; const locked = attempts >= MAX_FAILED_ATTEMPTS; const lockUntil = locked ? now + LOCKOUT_SECONDS * 1000 : 0; + const value = JSON.stringify({ attempts, lockUntil }); - await prisma.appConfig.upsert({ - where: { key }, - update: { value: JSON.stringify({ attempts, lockUntil }) }, - create: { key, value: JSON.stringify({ attempts, lockUntil }) }, - }); + await db + .insert(appConfig) + .values({ key, value }) + .onConflictDoUpdate({ target: appConfig.key, set: { value, updatedAt: new Date() } }); return { locked, @@ -276,7 +280,7 @@ export async function resetAdminFailedAttempts(): Promise { try { const ip = await getClientIp(); const key = getLockoutKey(ip); - await prisma.appConfig.deleteMany({ where: { key } }); + await db.delete(appConfig).where(eq(appConfig.key, key)); } catch { // Non-critical — ignore. } diff --git a/lib/app-config.ts b/lib/app-config.ts index 8760aaf..e2e17d3 100644 --- a/lib/app-config.ts +++ b/lib/app-config.ts @@ -1,4 +1,7 @@ -import { prisma } from "./prisma"; +import { and, eq, inArray } from "drizzle-orm"; + +import { db } from "./db"; +import { appConfig, mediaUsage } from "./db/schema"; export const MAINTENANCE_MODE_KEY = "maintenance_mode"; export { SITE_NAME_KEY, @@ -65,45 +68,44 @@ import { type MarqueeSettings, } from "./marquee-settings"; +async function getAppConfigValue(key: string): Promise { + const rows = await db + .select({ value: appConfig.value }) + .from(appConfig) + .where(eq(appConfig.key, key)) + .limit(1); + + return rows[0]?.value; +} + +async function upsertAppConfigValue(key: string, value: string): Promise { + await db + .insert(appConfig) + .values({ key, value }) + .onConflictDoUpdate({ + target: appConfig.key, + set: { value, updatedAt: new Date() }, + }); +} + export async function getMaintenanceMode(): Promise { try { - const config = await prisma.appConfig.findUnique({ - where: { key: MAINTENANCE_MODE_KEY }, - select: { value: true }, - }); - - return config?.value === "true"; + return (await getAppConfigValue(MAINTENANCE_MODE_KEY)) === "true"; } catch { return false; } } export async function setMaintenanceMode(enabled: boolean): Promise { - await prisma.appConfig.upsert({ - where: { key: MAINTENANCE_MODE_KEY }, - update: { - value: enabled ? "true" : "false", - }, - create: { - key: MAINTENANCE_MODE_KEY, - value: enabled ? "true" : "false", - }, - }); + await upsertAppConfigValue(MAINTENANCE_MODE_KEY, enabled ? "true" : "false"); } export async function getSiteSettings(): Promise { try { - const configs = await prisma.appConfig.findMany({ - where: { - key: { - in: [SITE_SETTINGS_KEY, SITE_NAME_KEY], - }, - }, - select: { - key: true, - value: true, - }, - }); + const configs = await db + .select({ key: appConfig.key, value: appConfig.value }) + .from(appConfig) + .where(inArray(appConfig.key, [SITE_SETTINGS_KEY, SITE_NAME_KEY])); const configMap = new Map(configs.map((config) => [config.key, config.value])); const fallbackName = configMap.get(SITE_NAME_KEY) ?? DEFAULT_SITE_NAME; @@ -115,26 +117,12 @@ export async function getSiteSettings(): Promise { } export async function updateSiteSettings(settings: SiteSettings): Promise { - await prisma.appConfig.upsert({ - where: { key: SITE_SETTINGS_KEY }, - update: { - value: JSON.stringify(settings), - }, - create: { - key: SITE_SETTINGS_KEY, - value: JSON.stringify(settings), - }, - }); + await upsertAppConfigValue(SITE_SETTINGS_KEY, JSON.stringify(settings)); } export async function getMailSettings(): Promise { try { - const config = await prisma.appConfig.findUnique({ - where: { key: MAIL_SETTINGS_KEY }, - select: { value: true }, - }); - - return parseMailSettingsValue(config?.value); + return parseMailSettingsValue(await getAppConfigValue(MAIL_SETTINGS_KEY)); } catch { return buildDefaultMailSettings(); } @@ -147,26 +135,12 @@ export async function getMailSettingsFormValues(): Promise { - await prisma.appConfig.upsert({ - where: { key: MAIL_SETTINGS_KEY }, - update: { - value: JSON.stringify(settings), - }, - create: { - key: MAIL_SETTINGS_KEY, - value: JSON.stringify(settings), - }, - }); + await upsertAppConfigValue(MAIL_SETTINGS_KEY, JSON.stringify(settings)); } export async function getMarqueeSettings(): Promise { try { - const config = await prisma.appConfig.findUnique({ - where: { key: MARQUEE_SETTINGS_KEY }, - select: { value: true }, - }); - - return parseMarqueeSettingsValue(config?.value); + return parseMarqueeSettingsValue(await getAppConfigValue(MARQUEE_SETTINGS_KEY)); } catch { return buildDefaultMarqueeSettings(); } @@ -175,30 +149,23 @@ export async function getMarqueeSettings(): Promise { export async function updateMarqueeSettings(settings: MarqueeSettings): Promise { const normalizedSettings = syncMarqueeSettingsToGermanSource(settings); - await prisma.appConfig.upsert({ - where: { key: MARQUEE_SETTINGS_KEY }, - update: { - value: JSON.stringify(normalizedSettings), - }, - create: { - key: MARQUEE_SETTINGS_KEY, - value: JSON.stringify(normalizedSettings), - }, - }); + await upsertAppConfigValue(MARQUEE_SETTINGS_KEY, JSON.stringify(normalizedSettings)); } export async function getSiteSettingsMediaBindings(): Promise { try { - const usages = await prisma.mediaUsage.findMany({ - where: { - entityType: SITE_SETTINGS_ENTITY_TYPE, - entityId: SITE_SETTINGS_ENTITY_ID, - }, - select: { + const usages = await db.query.mediaUsage.findMany({ + where: and( + eq(mediaUsage.entityType, SITE_SETTINGS_ENTITY_TYPE), + eq(mediaUsage.entityId, SITE_SETTINGS_ENTITY_ID), + ), + columns: { fieldKey: true, updatedAt: true, + }, + with: { asset: { - select: { + columns: { id: true, url: true, }, diff --git a/lib/db/enums.ts b/lib/db/enums.ts new file mode 100644 index 0000000..cf19ac5 --- /dev/null +++ b/lib/db/enums.ts @@ -0,0 +1,56 @@ +// Shared enum values + types. NO server/ORM imports here — this file is safe to +// import from client components (replaces the old `@prisma/client` enum imports). +// +// Defined as `const object + union type` (the same shape Prisma generated) rather +// than a TS `enum`, so bare string literals like "IMAGE" stay assignable and +// `z.nativeEnum(...)` keeps working. + +export const PortfolioSectionType = { + RICH_TEXT: "RICH_TEXT", + GALLERY: "GALLERY", + STATS: "STATS", + DELIVERABLES: "DELIVERABLES", + LINK: "LINK", +} as const; +export type PortfolioSectionType = (typeof PortfolioSectionType)[keyof typeof PortfolioSectionType]; + +export const PortfolioAssetKind = { + IMAGE: "IMAGE", + DOCUMENT: "DOCUMENT", +} as const; +export type PortfolioAssetKind = (typeof PortfolioAssetKind)[keyof typeof PortfolioAssetKind]; + +export const PortfolioProjectViewMode = { + GRID: "GRID", + STORY: "STORY", + CASE_STUDY: "CASE_STUDY", +} as const; +export type PortfolioProjectViewMode = + (typeof PortfolioProjectViewMode)[keyof typeof PortfolioProjectViewMode]; + +export const MediaSource = { + UPLOAD: "UPLOAD", + EXTERNAL: "EXTERNAL", +} as const; +export type MediaSource = (typeof MediaSource)[keyof typeof MediaSource]; + +export const MediaKind = { + IMAGE: "IMAGE", + DOCUMENT: "DOCUMENT", +} as const; +export type MediaKind = (typeof MediaKind)[keyof typeof MediaKind]; + +export const MediaUsageType = { + PORTFOLIO_COVER: "PORTFOLIO_COVER", + PORTFOLIO_SECTION: "PORTFOLIO_SECTION", + PORTFOLIO_ASSET: "PORTFOLIO_ASSET", + GENERIC: "GENERIC", +} as const; +export type MediaUsageType = (typeof MediaUsageType)[keyof typeof MediaUsageType]; + +// Helper: enum-object -> tuple of its string values, for Drizzle pgEnum(...). +// Preserves the literal union (not widened to `string`) so pgEnum columns infer +// as the exact union type. +export function enumValues>(e: T): [T[keyof T], ...T[keyof T][]] { + return Object.values(e) as [T[keyof T], ...T[keyof T][]]; +} diff --git a/lib/db/index.ts b/lib/db/index.ts new file mode 100644 index 0000000..b64b3b9 --- /dev/null +++ b/lib/db/index.ts @@ -0,0 +1,24 @@ +import { drizzle } from "drizzle-orm/postgres-js"; +import postgres from "postgres"; + +import * as schema from "./schema"; + +const rawConnectionString = + process.env.DATABASE_URL ?? "postgresql://postgres:postgres@localhost:5432/moh_sass"; + +// Prisma allowed a `?schema=public` query param that postgres.js does not +// understand — strip any unknown query string; `public` is the default schema. +const connectionString = rawConnectionString.split("?")[0]; + +const globalForDb = globalThis as unknown as { + pgClient: ReturnType | undefined; +}; + +const client = globalForDb.pgClient ?? postgres(connectionString, { max: 10 }); + +if (process.env.NODE_ENV !== "production") { + globalForDb.pgClient = client; +} + +export const db = drizzle(client, { schema }); +export { schema }; diff --git a/lib/db/migrations/0000_absurd_rawhide_kid.sql b/lib/db/migrations/0000_absurd_rawhide_kid.sql new file mode 100644 index 0000000..d08286b --- /dev/null +++ b/lib/db/migrations/0000_absurd_rawhide_kid.sql @@ -0,0 +1,125 @@ +CREATE TYPE "public"."MediaKind" AS ENUM('IMAGE', 'DOCUMENT');--> statement-breakpoint +CREATE TYPE "public"."MediaSource" AS ENUM('UPLOAD', 'EXTERNAL');--> statement-breakpoint +CREATE TYPE "public"."MediaUsageType" AS ENUM('PORTFOLIO_COVER', 'PORTFOLIO_SECTION', 'PORTFOLIO_ASSET', 'GENERIC');--> statement-breakpoint +CREATE TYPE "public"."PortfolioAssetKind" AS ENUM('IMAGE', 'DOCUMENT');--> statement-breakpoint +CREATE TYPE "public"."PortfolioProjectViewMode" AS ENUM('GRID', 'STORY', 'CASE_STUDY');--> statement-breakpoint +CREATE TYPE "public"."PortfolioSectionType" AS ENUM('RICH_TEXT', 'GALLERY', 'STATS', 'DELIVERABLES', 'LINK');--> statement-breakpoint +CREATE TABLE "AppConfig" ( + "id" text PRIMARY KEY NOT NULL, + "key" text NOT NULL, + "value" text NOT NULL, + "createdAt" timestamp (3) DEFAULT now() NOT NULL, + "updatedAt" timestamp (3) DEFAULT now() NOT NULL, + CONSTRAINT "AppConfig_key_unique" UNIQUE("key") +); +--> statement-breakpoint +CREATE TABLE "Category" ( + "id" text PRIMARY KEY NOT NULL, + "slug" text NOT NULL, + "nameAr" text NOT NULL, + "nameEn" text NOT NULL, + "nameDe" text NOT NULL, + "descriptionAr" text NOT NULL, + "descriptionEn" text NOT NULL, + "descriptionDe" text NOT NULL, + "sortOrder" integer DEFAULT 0 NOT NULL, + "isActive" boolean DEFAULT true NOT NULL, + "createdAt" timestamp (3) DEFAULT now() NOT NULL, + "updatedAt" timestamp (3) DEFAULT now() NOT NULL, + CONSTRAINT "Category_slug_unique" UNIQUE("slug") +); +--> statement-breakpoint +CREATE TABLE "MediaAsset" ( + "id" text PRIMARY KEY NOT NULL, + "source" "MediaSource" NOT NULL, + "kind" "MediaKind" NOT NULL, + "url" text NOT NULL, + "fileName" text NOT NULL, + "label" text NOT NULL, + "altText" text, + "mimeType" text, + "size" integer, + "createdAt" timestamp (3) DEFAULT now() NOT NULL, + "updatedAt" timestamp (3) DEFAULT now() NOT NULL +); +--> statement-breakpoint +CREATE TABLE "MediaUsage" ( + "id" text PRIMARY KEY NOT NULL, + "assetId" text NOT NULL, + "usageType" "MediaUsageType" NOT NULL, + "entityType" text NOT NULL, + "entityId" text NOT NULL, + "fieldKey" text NOT NULL, + "createdAt" timestamp (3) DEFAULT now() NOT NULL, + "updatedAt" timestamp (3) DEFAULT now() NOT NULL +); +--> statement-breakpoint +CREATE TABLE "PortfolioAsset" ( + "id" text PRIMARY KEY NOT NULL, + "projectId" text NOT NULL, + "kind" "PortfolioAssetKind" NOT NULL, + "filePath" text NOT NULL, + "altAr" text NOT NULL, + "altEn" text NOT NULL, + "altDe" text NOT NULL, + "sortOrder" integer DEFAULT 0 NOT NULL, + "createdAt" timestamp (3) DEFAULT now() NOT NULL, + "updatedAt" timestamp (3) DEFAULT now() NOT NULL +); +--> statement-breakpoint +CREATE TABLE "PortfolioProject" ( + "id" text PRIMARY KEY NOT NULL, + "categoryId" text NOT NULL, + "slug" text NOT NULL, + "viewMode" "PortfolioProjectViewMode" DEFAULT 'GRID' NOT NULL, + "titleAr" text NOT NULL, + "titleEn" text NOT NULL, + "titleDe" text NOT NULL, + "summaryAr" text NOT NULL, + "summaryEn" text NOT NULL, + "summaryDe" text NOT NULL, + "clientName" text NOT NULL, + "projectYear" integer NOT NULL, + "serviceLabelAr" text NOT NULL, + "serviceLabelEn" text NOT NULL, + "serviceLabelDe" text NOT NULL, + "previewUrl" text, + "coverImagePath" text, + "isFeatured" boolean DEFAULT false NOT NULL, + "isPublished" boolean DEFAULT false NOT NULL, + "publishedAt" timestamp (3), + "sortOrder" integer DEFAULT 0 NOT NULL, + "createdAt" timestamp (3) DEFAULT now() NOT NULL, + "updatedAt" timestamp (3) DEFAULT now() NOT NULL, + CONSTRAINT "PortfolioProject_slug_unique" UNIQUE("slug") +); +--> statement-breakpoint +CREATE TABLE "PortfolioSection" ( + "id" text PRIMARY KEY NOT NULL, + "projectId" text NOT NULL, + "type" "PortfolioSectionType" NOT NULL, + "titleAr" text NOT NULL, + "titleEn" text NOT NULL, + "titleDe" text NOT NULL, + "bodyAr" text NOT NULL, + "bodyEn" text NOT NULL, + "bodyDe" text NOT NULL, + "imagePath" text, + "linkUrl" text, + "sortOrder" integer DEFAULT 0 NOT NULL, + "createdAt" timestamp (3) DEFAULT now() NOT NULL, + "updatedAt" timestamp (3) DEFAULT now() NOT NULL +); +--> statement-breakpoint +ALTER TABLE "MediaUsage" ADD CONSTRAINT "MediaUsage_assetId_MediaAsset_id_fk" FOREIGN KEY ("assetId") REFERENCES "public"."MediaAsset"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "PortfolioAsset" ADD CONSTRAINT "PortfolioAsset_projectId_PortfolioProject_id_fk" FOREIGN KEY ("projectId") REFERENCES "public"."PortfolioProject"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "PortfolioProject" ADD CONSTRAINT "PortfolioProject_categoryId_Category_id_fk" FOREIGN KEY ("categoryId") REFERENCES "public"."Category"("id") ON DELETE restrict ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "PortfolioSection" ADD CONSTRAINT "PortfolioSection_projectId_PortfolioProject_id_fk" FOREIGN KEY ("projectId") REFERENCES "public"."PortfolioProject"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +CREATE INDEX "MediaAsset_kind_createdAt_idx" ON "MediaAsset" USING btree ("kind","createdAt");--> statement-breakpoint +CREATE UNIQUE INDEX "MediaUsage_usageType_entityType_entityId_fieldKey_key" ON "MediaUsage" USING btree ("usageType","entityType","entityId","fieldKey");--> statement-breakpoint +CREATE INDEX "MediaUsage_assetId_idx" ON "MediaUsage" USING btree ("assetId");--> statement-breakpoint +CREATE INDEX "MediaUsage_entityType_entityId_idx" ON "MediaUsage" USING btree ("entityType","entityId");--> statement-breakpoint +CREATE INDEX "PortfolioAsset_projectId_sortOrder_idx" ON "PortfolioAsset" USING btree ("projectId","sortOrder");--> statement-breakpoint +CREATE INDEX "PortfolioProject_categoryId_isPublished_sortOrder_idx" ON "PortfolioProject" USING btree ("categoryId","isPublished","sortOrder");--> statement-breakpoint +CREATE INDEX "PortfolioProject_isPublished_sortOrder_idx" ON "PortfolioProject" USING btree ("isPublished","sortOrder");--> statement-breakpoint +CREATE INDEX "PortfolioSection_projectId_sortOrder_idx" ON "PortfolioSection" USING btree ("projectId","sortOrder"); \ No newline at end of file diff --git a/lib/db/migrations/meta/0000_snapshot.json b/lib/db/migrations/meta/0000_snapshot.json new file mode 100644 index 0000000..f848f8d --- /dev/null +++ b/lib/db/migrations/meta/0000_snapshot.json @@ -0,0 +1,956 @@ +{ + "id": "d0f97c12-4a3b-4cf7-8f81-3d00571737f4", + "prevId": "00000000-0000-0000-0000-000000000000", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.AppConfig": { + "name": "AppConfig", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "AppConfig_key_unique": { + "name": "AppConfig_key_unique", + "nullsNotDistinct": false, + "columns": [ + "key" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.Category": { + "name": "Category", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "nameAr": { + "name": "nameAr", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "nameEn": { + "name": "nameEn", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "nameDe": { + "name": "nameDe", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "descriptionAr": { + "name": "descriptionAr", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "descriptionEn": { + "name": "descriptionEn", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "descriptionDe": { + "name": "descriptionDe", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sortOrder": { + "name": "sortOrder", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "isActive": { + "name": "isActive", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "Category_slug_unique": { + "name": "Category_slug_unique", + "nullsNotDistinct": false, + "columns": [ + "slug" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.MediaAsset": { + "name": "MediaAsset", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "source": { + "name": "source", + "type": "MediaSource", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "MediaKind", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "fileName": { + "name": "fileName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "altText": { + "name": "altText", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "mimeType": { + "name": "mimeType", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "size": { + "name": "size", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "MediaAsset_kind_createdAt_idx": { + "name": "MediaAsset_kind_createdAt_idx", + "columns": [ + { + "expression": "kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "createdAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.MediaUsage": { + "name": "MediaUsage", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "assetId": { + "name": "assetId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "usageType": { + "name": "usageType", + "type": "MediaUsageType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "entityType": { + "name": "entityType", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "entityId": { + "name": "entityId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "fieldKey": { + "name": "fieldKey", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "MediaUsage_usageType_entityType_entityId_fieldKey_key": { + "name": "MediaUsage_usageType_entityType_entityId_fieldKey_key", + "columns": [ + { + "expression": "usageType", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entityType", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entityId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "fieldKey", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "MediaUsage_assetId_idx": { + "name": "MediaUsage_assetId_idx", + "columns": [ + { + "expression": "assetId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "MediaUsage_entityType_entityId_idx": { + "name": "MediaUsage_entityType_entityId_idx", + "columns": [ + { + "expression": "entityType", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entityId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "MediaUsage_assetId_MediaAsset_id_fk": { + "name": "MediaUsage_assetId_MediaAsset_id_fk", + "tableFrom": "MediaUsage", + "tableTo": "MediaAsset", + "columnsFrom": [ + "assetId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.PortfolioAsset": { + "name": "PortfolioAsset", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "projectId": { + "name": "projectId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "PortfolioAssetKind", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "filePath": { + "name": "filePath", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "altAr": { + "name": "altAr", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "altEn": { + "name": "altEn", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "altDe": { + "name": "altDe", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sortOrder": { + "name": "sortOrder", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "PortfolioAsset_projectId_sortOrder_idx": { + "name": "PortfolioAsset_projectId_sortOrder_idx", + "columns": [ + { + "expression": "projectId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "sortOrder", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "PortfolioAsset_projectId_PortfolioProject_id_fk": { + "name": "PortfolioAsset_projectId_PortfolioProject_id_fk", + "tableFrom": "PortfolioAsset", + "tableTo": "PortfolioProject", + "columnsFrom": [ + "projectId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.PortfolioProject": { + "name": "PortfolioProject", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "categoryId": { + "name": "categoryId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "viewMode": { + "name": "viewMode", + "type": "PortfolioProjectViewMode", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'GRID'" + }, + "titleAr": { + "name": "titleAr", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "titleEn": { + "name": "titleEn", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "titleDe": { + "name": "titleDe", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "summaryAr": { + "name": "summaryAr", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "summaryEn": { + "name": "summaryEn", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "summaryDe": { + "name": "summaryDe", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "clientName": { + "name": "clientName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "projectYear": { + "name": "projectYear", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "serviceLabelAr": { + "name": "serviceLabelAr", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "serviceLabelEn": { + "name": "serviceLabelEn", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "serviceLabelDe": { + "name": "serviceLabelDe", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "previewUrl": { + "name": "previewUrl", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "coverImagePath": { + "name": "coverImagePath", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "isFeatured": { + "name": "isFeatured", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "isPublished": { + "name": "isPublished", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "publishedAt": { + "name": "publishedAt", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": false + }, + "sortOrder": { + "name": "sortOrder", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "PortfolioProject_categoryId_isPublished_sortOrder_idx": { + "name": "PortfolioProject_categoryId_isPublished_sortOrder_idx", + "columns": [ + { + "expression": "categoryId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "isPublished", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "sortOrder", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "PortfolioProject_isPublished_sortOrder_idx": { + "name": "PortfolioProject_isPublished_sortOrder_idx", + "columns": [ + { + "expression": "isPublished", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "sortOrder", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "PortfolioProject_categoryId_Category_id_fk": { + "name": "PortfolioProject_categoryId_Category_id_fk", + "tableFrom": "PortfolioProject", + "tableTo": "Category", + "columnsFrom": [ + "categoryId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "PortfolioProject_slug_unique": { + "name": "PortfolioProject_slug_unique", + "nullsNotDistinct": false, + "columns": [ + "slug" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.PortfolioSection": { + "name": "PortfolioSection", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "projectId": { + "name": "projectId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "PortfolioSectionType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "titleAr": { + "name": "titleAr", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "titleEn": { + "name": "titleEn", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "titleDe": { + "name": "titleDe", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "bodyAr": { + "name": "bodyAr", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "bodyEn": { + "name": "bodyEn", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "bodyDe": { + "name": "bodyDe", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "imagePath": { + "name": "imagePath", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "linkUrl": { + "name": "linkUrl", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sortOrder": { + "name": "sortOrder", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "PortfolioSection_projectId_sortOrder_idx": { + "name": "PortfolioSection_projectId_sortOrder_idx", + "columns": [ + { + "expression": "projectId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "sortOrder", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "PortfolioSection_projectId_PortfolioProject_id_fk": { + "name": "PortfolioSection_projectId_PortfolioProject_id_fk", + "tableFrom": "PortfolioSection", + "tableTo": "PortfolioProject", + "columnsFrom": [ + "projectId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": { + "public.MediaKind": { + "name": "MediaKind", + "schema": "public", + "values": [ + "IMAGE", + "DOCUMENT" + ] + }, + "public.MediaSource": { + "name": "MediaSource", + "schema": "public", + "values": [ + "UPLOAD", + "EXTERNAL" + ] + }, + "public.MediaUsageType": { + "name": "MediaUsageType", + "schema": "public", + "values": [ + "PORTFOLIO_COVER", + "PORTFOLIO_SECTION", + "PORTFOLIO_ASSET", + "GENERIC" + ] + }, + "public.PortfolioAssetKind": { + "name": "PortfolioAssetKind", + "schema": "public", + "values": [ + "IMAGE", + "DOCUMENT" + ] + }, + "public.PortfolioProjectViewMode": { + "name": "PortfolioProjectViewMode", + "schema": "public", + "values": [ + "GRID", + "STORY", + "CASE_STUDY" + ] + }, + "public.PortfolioSectionType": { + "name": "PortfolioSectionType", + "schema": "public", + "values": [ + "RICH_TEXT", + "GALLERY", + "STATS", + "DELIVERABLES", + "LINK" + ] + } + }, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/lib/db/migrations/meta/_journal.json b/lib/db/migrations/meta/_journal.json new file mode 100644 index 0000000..8b6220a --- /dev/null +++ b/lib/db/migrations/meta/_journal.json @@ -0,0 +1,13 @@ +{ + "version": "7", + "dialect": "postgresql", + "entries": [ + { + "idx": 0, + "version": "7", + "when": 1786146995564, + "tag": "0000_absurd_rawhide_kid", + "breakpoints": true + } + ] +} \ No newline at end of file diff --git a/lib/db/schema.ts b/lib/db/schema.ts new file mode 100644 index 0000000..4bf40e3 --- /dev/null +++ b/lib/db/schema.ts @@ -0,0 +1,248 @@ +import { createId } from "@paralleldrive/cuid2"; +import { relations } from "drizzle-orm"; +import { + boolean, + index, + integer, + pgEnum, + pgTable, + text, + timestamp, + uniqueIndex, +} from "drizzle-orm/pg-core"; + +import { + MediaKind, + MediaSource, + MediaUsageType, + PortfolioAssetKind, + PortfolioProjectViewMode, + PortfolioSectionType, + enumValues, +} from "./enums"; + +// Postgres enum types — names match the ones Prisma created, so no DB migration +// is needed for the ORM swap. +export const portfolioSectionTypeEnum = pgEnum("PortfolioSectionType", enumValues(PortfolioSectionType)); +export const portfolioAssetKindEnum = pgEnum("PortfolioAssetKind", enumValues(PortfolioAssetKind)); +export const portfolioProjectViewModeEnum = pgEnum("PortfolioProjectViewMode", enumValues(PortfolioProjectViewMode)); +export const mediaSourceEnum = pgEnum("MediaSource", enumValues(MediaSource)); +export const mediaKindEnum = pgEnum("MediaKind", enumValues(MediaKind)); +export const mediaUsageTypeEnum = pgEnum("MediaUsageType", enumValues(MediaUsageType)); + +// Shared column builders (Prisma parity): cuid ids, precision-3 timestamps. +const id = () => text("id").primaryKey().$defaultFn(() => createId()); +const createdAt = () => timestamp("createdAt", { precision: 3, mode: "date" }).defaultNow().notNull(); +const updatedAt = () => + timestamp("updatedAt", { precision: 3, mode: "date" }) + .defaultNow() + .notNull() + .$onUpdate(() => new Date()); + +export const appConfig = pgTable("AppConfig", { + id: id(), + key: text("key").notNull().unique(), + value: text("value").notNull(), + createdAt: createdAt(), + updatedAt: updatedAt(), +}); + +export const category = pgTable( + "Category", + { + id: id(), + slug: text("slug").notNull().unique(), + nameAr: text("nameAr").notNull(), + nameEn: text("nameEn").notNull(), + nameDe: text("nameDe").notNull(), + descriptionAr: text("descriptionAr").notNull(), + descriptionEn: text("descriptionEn").notNull(), + descriptionDe: text("descriptionDe").notNull(), + sortOrder: integer("sortOrder").notNull().default(0), + isActive: boolean("isActive").notNull().default(true), + createdAt: createdAt(), + updatedAt: updatedAt(), + }, +); + +export const portfolioProject = pgTable( + "PortfolioProject", + { + id: id(), + categoryId: text("categoryId") + .notNull() + .references(() => category.id, { onDelete: "restrict" }), + slug: text("slug").notNull().unique(), + viewMode: portfolioProjectViewModeEnum("viewMode").notNull().default("GRID"), + titleAr: text("titleAr").notNull(), + titleEn: text("titleEn").notNull(), + titleDe: text("titleDe").notNull(), + summaryAr: text("summaryAr").notNull(), + summaryEn: text("summaryEn").notNull(), + summaryDe: text("summaryDe").notNull(), + clientName: text("clientName").notNull(), + projectYear: integer("projectYear").notNull(), + serviceLabelAr: text("serviceLabelAr").notNull(), + serviceLabelEn: text("serviceLabelEn").notNull(), + serviceLabelDe: text("serviceLabelDe").notNull(), + previewUrl: text("previewUrl"), + coverImagePath: text("coverImagePath"), + isFeatured: boolean("isFeatured").notNull().default(false), + isPublished: boolean("isPublished").notNull().default(false), + publishedAt: timestamp("publishedAt", { precision: 3, mode: "date" }), + sortOrder: integer("sortOrder").notNull().default(0), + createdAt: createdAt(), + updatedAt: updatedAt(), + }, + (table) => [ + index("PortfolioProject_categoryId_isPublished_sortOrder_idx").on( + table.categoryId, + table.isPublished, + table.sortOrder, + ), + index("PortfolioProject_isPublished_sortOrder_idx").on(table.isPublished, table.sortOrder), + ], +); + +export const portfolioSection = pgTable( + "PortfolioSection", + { + id: id(), + projectId: text("projectId") + .notNull() + .references(() => portfolioProject.id, { onDelete: "cascade" }), + type: portfolioSectionTypeEnum("type").notNull(), + titleAr: text("titleAr").notNull(), + titleEn: text("titleEn").notNull(), + titleDe: text("titleDe").notNull(), + bodyAr: text("bodyAr").notNull(), + bodyEn: text("bodyEn").notNull(), + bodyDe: text("bodyDe").notNull(), + imagePath: text("imagePath"), + linkUrl: text("linkUrl"), + sortOrder: integer("sortOrder").notNull().default(0), + createdAt: createdAt(), + updatedAt: updatedAt(), + }, + (table) => [index("PortfolioSection_projectId_sortOrder_idx").on(table.projectId, table.sortOrder)], +); + +export const portfolioAsset = pgTable( + "PortfolioAsset", + { + id: id(), + projectId: text("projectId") + .notNull() + .references(() => portfolioProject.id, { onDelete: "cascade" }), + kind: portfolioAssetKindEnum("kind").notNull(), + filePath: text("filePath").notNull(), + altAr: text("altAr").notNull(), + altEn: text("altEn").notNull(), + altDe: text("altDe").notNull(), + sortOrder: integer("sortOrder").notNull().default(0), + createdAt: createdAt(), + updatedAt: updatedAt(), + }, + (table) => [index("PortfolioAsset_projectId_sortOrder_idx").on(table.projectId, table.sortOrder)], +); + +export const mediaAsset = pgTable( + "MediaAsset", + { + id: id(), + source: mediaSourceEnum("source").notNull(), + kind: mediaKindEnum("kind").notNull(), + url: text("url").notNull(), + fileName: text("fileName").notNull(), + label: text("label").notNull(), + altText: text("altText"), + mimeType: text("mimeType"), + size: integer("size"), + createdAt: createdAt(), + updatedAt: updatedAt(), + }, + (table) => [index("MediaAsset_kind_createdAt_idx").on(table.kind, table.createdAt)], +); + +export const mediaUsage = pgTable( + "MediaUsage", + { + id: id(), + assetId: text("assetId") + .notNull() + .references(() => mediaAsset.id, { onDelete: "cascade" }), + usageType: mediaUsageTypeEnum("usageType").notNull(), + entityType: text("entityType").notNull(), + entityId: text("entityId").notNull(), + fieldKey: text("fieldKey").notNull(), + createdAt: createdAt(), + updatedAt: updatedAt(), + }, + (table) => [ + uniqueIndex("MediaUsage_usageType_entityType_entityId_fieldKey_key").on( + table.usageType, + table.entityType, + table.entityId, + table.fieldKey, + ), + index("MediaUsage_assetId_idx").on(table.assetId), + index("MediaUsage_entityType_entityId_idx").on(table.entityType, table.entityId), + ], +); + +// Relations (enable db.query.* `with:` includes). +export const categoryRelations = relations(category, ({ many }) => ({ + projects: many(portfolioProject), +})); + +export const portfolioProjectRelations = relations(portfolioProject, ({ one, many }) => ({ + category: one(category, { + fields: [portfolioProject.categoryId], + references: [category.id], + }), + sections: many(portfolioSection), + assets: many(portfolioAsset), +})); + +export const portfolioSectionRelations = relations(portfolioSection, ({ one }) => ({ + project: one(portfolioProject, { + fields: [portfolioSection.projectId], + references: [portfolioProject.id], + }), +})); + +export const portfolioAssetRelations = relations(portfolioAsset, ({ one }) => ({ + project: one(portfolioProject, { + fields: [portfolioAsset.projectId], + references: [portfolioProject.id], + }), +})); + +export const mediaAssetRelations = relations(mediaAsset, ({ many }) => ({ + usages: many(mediaUsage), +})); + +export const mediaUsageRelations = relations(mediaUsage, ({ one }) => ({ + asset: one(mediaAsset, { + fields: [mediaUsage.assetId], + references: [mediaAsset.id], + }), +})); + +// Inferred row types (replace the old `@prisma/client` model type imports). +export type AppConfig = typeof appConfig.$inferSelect; +export type Category = typeof category.$inferSelect; +export type PortfolioProject = typeof portfolioProject.$inferSelect; +export type PortfolioSection = typeof portfolioSection.$inferSelect; +export type PortfolioAsset = typeof portfolioAsset.$inferSelect; +export type MediaAsset = typeof mediaAsset.$inferSelect; +export type MediaUsage = typeof mediaUsage.$inferSelect; + +export { + MediaKind, + MediaSource, + MediaUsageType, + PortfolioAssetKind, + PortfolioProjectViewMode, + PortfolioSectionType, +} from "./enums"; diff --git a/lib/db/seed.ts b/lib/db/seed.ts new file mode 100644 index 0000000..4ac6c9a --- /dev/null +++ b/lib/db/seed.ts @@ -0,0 +1,565 @@ +import { and, eq } from "drizzle-orm"; +import { drizzle } from "drizzle-orm/postgres-js"; +import postgres from "postgres"; + +import * as schema from "./schema"; +import { + appConfig, + category, + mediaAsset, + mediaUsage, + portfolioAsset, + portfolioProject, + portfolioSection, +} from "./schema"; + +const connectionString = ( + process.env.DATABASE_URL ?? "postgresql://postgres:postgres@localhost:5432/moh_sass" +).split("?")[0]; + +const client = postgres(connectionString, { max: 1 }); +const db = drizzle(client, { schema }); + +type MediaAssetInput = { + source: "UPLOAD" | "EXTERNAL"; + kind: "IMAGE" | "DOCUMENT"; + url: string; + fileName: string; + label: string; + altText: string | null; + mimeType: string | null; + size: number | null; +}; + +async function upsertMediaAsset(input: MediaAssetInput) { + const [existing] = await db + .select() + .from(mediaAsset) + .where(and(eq(mediaAsset.label, input.label), eq(mediaAsset.url, input.url))) + .limit(1); + + if (existing) { + const [updated] = await db + .update(mediaAsset) + .set({ ...input, updatedAt: new Date() }) + .where(eq(mediaAsset.id, existing.id)) + .returning(); + + return updated; + } + + const [created] = await db.insert(mediaAsset).values(input).returning(); + + return created; +} + +async function upsertAppConfig(key: string, value: string) { + await db + .insert(appConfig) + .values({ key, value }) + .onConflictDoUpdate({ target: appConfig.key, set: { value, updatedAt: new Date() } }); +} + +async function upsertCategory(values: typeof category.$inferInsert) { + const [row] = await db + .insert(category) + .values(values) + .onConflictDoUpdate({ + target: category.slug, + set: { + nameAr: values.nameAr, + nameEn: values.nameEn, + nameDe: values.nameDe, + descriptionAr: values.descriptionAr, + descriptionEn: values.descriptionEn, + descriptionDe: values.descriptionDe, + sortOrder: values.sortOrder, + isActive: values.isActive, + updatedAt: new Date(), + }, + }) + .returning(); + + return row; +} + +async function syncProjectContent( + projectId: string, + sections: Array>, + assets: Array>, +) { + await db.delete(portfolioSection).where(eq(portfolioSection.projectId, projectId)); + await db.delete(portfolioAsset).where(eq(portfolioAsset.projectId, projectId)); + + const createdSections = []; + for (const section of sections) { + const [row] = await db + .insert(portfolioSection) + .values({ projectId, ...section }) + .returning(); + createdSections.push(row); + } + + const createdAssets = []; + for (const asset of assets) { + const [row] = await db + .insert(portfolioAsset) + .values({ projectId, ...asset }) + .returning(); + createdAssets.push(row); + } + + return { createdSections, createdAssets }; +} + +async function syncProjectMediaUsages( + projectId: string, + mediaMap: { + coverAssetId: string | null | undefined; + sectionUsages: Array<{ fieldKey: string; assetId: string | null | undefined }>; + assetUsages: Array<{ fieldKey: string; assetId: string | null | undefined }>; + }, +) { + await db + .delete(mediaUsage) + .where(and(eq(mediaUsage.entityType, "portfolio-project"), eq(mediaUsage.entityId, projectId))); + + const usages: (typeof mediaUsage.$inferInsert)[] = []; + + if (mediaMap.coverAssetId) { + usages.push({ + assetId: mediaMap.coverAssetId, + usageType: "PORTFOLIO_COVER", + entityType: "portfolio-project", + entityId: projectId, + fieldKey: "cover", + }); + } + + for (const sectionUsage of mediaMap.sectionUsages) { + if (!sectionUsage.assetId) continue; + usages.push({ + assetId: sectionUsage.assetId, + usageType: "PORTFOLIO_SECTION", + entityType: "portfolio-project", + entityId: projectId, + fieldKey: sectionUsage.fieldKey, + }); + } + + for (const assetUsage of mediaMap.assetUsages) { + if (!assetUsage.assetId) continue; + usages.push({ + assetId: assetUsage.assetId, + usageType: "PORTFOLIO_ASSET", + entityType: "portfolio-project", + entityId: projectId, + fieldKey: assetUsage.fieldKey, + }); + } + + if (usages.length > 0) { + await db.insert(mediaUsage).values(usages); + } +} + +const SITE_SETTINGS_VALUE = JSON.stringify({ + titleTemplate: "{pageTitle} | moh-sass", + locales: { + ar: { + siteName: "moh-sass", + titleTemplate: "{pageTitle} | {siteName}", + siteDescription: "Multilingual Next.js base project", + subhead: "", + }, + en: { + siteName: "moh-sass", + titleTemplate: "{pageTitle} | {siteName}", + siteDescription: "Multilingual Next.js base project", + subhead: "", + }, + de: { + siteName: "moh-sass", + titleTemplate: "{pageTitle} | {siteName}", + siteDescription: "Multilingual Next.js base project", + subhead: "", + }, + }, +}); + +async function main() { + await db + .delete(mediaUsage) + .where(eq(mediaUsage.entityType, "portfolio-project")); + await db.delete(portfolioSection); + await db.delete(portfolioAsset); + await db.delete(portfolioProject); + await db.delete(category); + + await upsertAppConfig("siteName", "moh-sass"); + await upsertAppConfig("site_settings", SITE_SETTINGS_VALUE); + + const brandCategory = await upsertCategory({ + slug: "branding", + nameAr: "الهوية البصرية", + nameEn: "Branding", + nameDe: "Branding", + descriptionAr: "مشاريع هوية بصرية وشعارات وأنظمة علامة.", + descriptionEn: "Brand identity, logo, and design system work.", + descriptionDe: "Branding, Logos und visuelle Systeme.", + sortOrder: 1, + isActive: true, + }); + + const webCategory = await upsertCategory({ + slug: "web-experiences", + nameAr: "تجارب الويب", + nameEn: "Web Experiences", + nameDe: "Web Experiences", + descriptionAr: "مواقع وصفحات هبوط وتجارب رقمية سريعة.", + descriptionEn: "Websites, landing pages, and digital experiences.", + descriptionDe: "Webseiten, Landingpages und digitale Erlebnisse.", + sortOrder: 2, + isActive: true, + }); + + const commerceCategory = await upsertCategory({ + slug: "commerce", + nameAr: "التجارة الرقمية", + nameEn: "Commerce", + nameDe: "Commerce", + descriptionAr: "متاجر وتجارب شراء رقمية مع تركيز على الوضوح والتحويل.", + descriptionEn: "Commerce experiences with a focus on clarity and conversion.", + descriptionDe: "Commerce-Projekte mit Fokus auf Klarheit und Conversion.", + sortOrder: 3, + isActive: true, + }); + + const gridCover = await upsertMediaAsset({ + source: "UPLOAD", + kind: "IMAGE", + url: "/uploads/portfolio/demo-cover.svg", + fileName: "demo-cover.svg", + label: "Portfolio Grid Cover", + altText: "Portfolio Grid Cover", + mimeType: "image/svg+xml", + size: 1024, + }); + + const storyCover = await upsertMediaAsset({ + source: "UPLOAD", + kind: "IMAGE", + url: "/uploads/portfolio/demo-cover.svg", + fileName: "demo-cover.svg", + label: "Portfolio Story Cover", + altText: "Portfolio Story Cover", + mimeType: "image/svg+xml", + size: 1024, + }); + + const caseStudyCover = await upsertMediaAsset({ + source: "UPLOAD", + kind: "IMAGE", + url: "/uploads/portfolio/demo-cover.svg", + fileName: "demo-cover.svg", + label: "Portfolio Case Study Cover", + altText: "Portfolio Case Study Cover", + mimeType: "image/svg+xml", + size: 1024, + }); + + const projects = [ + { + slug: "grid-product-launch", + categoryId: commerceCategory.id, + viewMode: "GRID" as const, + titleAr: "إطلاق منتج رقمي", + titleEn: "Grid Product Launch", + titleDe: "Grid Product Launch", + summaryAr: "مثال عرض شبكي لمشروع سريع مع أقسام قصيرة وأصول داعمة.", + summaryEn: "Grid view example for a fast product launch page.", + summaryDe: "Grid-Ansicht als Beispiel fuer einen schnellen Produktlaunch.", + clientName: "Launch Studio", + projectYear: 2026, + serviceLabelAr: "تجربة إطلاق", + serviceLabelEn: "Launch Experience", + serviceLabelDe: "Launch Experience", + previewUrl: "https://example.com/preview/grid-product-launch", + coverImagePath: gridCover.url, + isFeatured: true, + isPublished: true, + publishedAt: new Date("2026-01-12T09:00:00.000Z"), + sortOrder: 1, + coverAssetId: gridCover.id, + sections: [ + { + type: "RICH_TEXT" as const, + titleAr: "الفكرة", + titleEn: "Concept", + titleDe: "Konzept", + bodyAr: "واجهة سريعة لعرض المنتج والتركيز على الرسالة الأساسية.", + bodyEn: "A fast modular presentation focused on the main launch message.", + bodyDe: "Eine schnelle modulare Darstellung mit Fokus auf die Hauptbotschaft.", + imagePath: null, + linkUrl: null, + sortOrder: 0, + mediaAssetId: null, + }, + { + type: "GALLERY" as const, + titleAr: "الصورة الرئيسية", + titleEn: "Hero Visual", + titleDe: "Hero Visual", + bodyAr: "", + bodyEn: "", + bodyDe: "", + imagePath: gridCover.url, + linkUrl: null, + sortOrder: 1, + mediaAssetId: gridCover.id, + }, + ], + assets: [ + { + kind: "IMAGE" as const, + filePath: gridCover.url, + altAr: "غلاف مشروع Grid", + altEn: "Grid project cover", + altDe: "Grid Projekt Cover", + sortOrder: 0, + mediaAssetId: gridCover.id, + }, + ], + }, + { + slug: "campaign-site", + categoryId: webCategory.id, + viewMode: "STORY" as const, + titleAr: "موقع حملة", + titleEn: "Campaign Site", + titleDe: "Campaign Site", + summaryAr: "مثال عرض قصصي لمشروع ويب مع تسلسل سردي أوضح.", + summaryEn: "Story view example for a launch campaign website.", + summaryDe: "Story-Ansicht als Beispiel fuer eine Kampagnenseite.", + clientName: "Launch Client", + projectYear: 2024, + serviceLabelAr: "موقع تسويقي", + serviceLabelEn: "Marketing Website", + serviceLabelDe: "Marketing Website", + previewUrl: "https://example.com/preview/campaign-site", + coverImagePath: storyCover.url, + isFeatured: false, + isPublished: true, + publishedAt: new Date("2024-09-05T09:00:00.000Z"), + sortOrder: 2, + coverAssetId: storyCover.id, + sections: [ + { + type: "RICH_TEXT" as const, + titleAr: "السياق", + titleEn: "Context", + titleDe: "Kontext", + bodyAr: "الحملة احتاجت صفحة مرنة وسريعة تتبدل بين أكثر من مرحلة.", + bodyEn: "The campaign needed a flexible page that could adapt across phases.", + bodyDe: "Die Kampagne brauchte eine flexible Seite fuer mehrere Phasen.", + imagePath: null, + linkUrl: null, + sortOrder: 0, + mediaAssetId: null, + }, + { + type: "GALLERY" as const, + titleAr: "العرض البصري", + titleEn: "Visual Flow", + titleDe: "Visueller Ablauf", + bodyAr: "", + bodyEn: "", + bodyDe: "", + imagePath: storyCover.url, + linkUrl: null, + sortOrder: 1, + mediaAssetId: storyCover.id, + }, + { + type: "LINK" as const, + titleAr: "المعاينة", + titleEn: "Preview", + titleDe: "Vorschau", + bodyAr: "رابط العرض المباشر.", + bodyEn: "Direct preview link.", + bodyDe: "Direkter Vorschau-Link.", + imagePath: null, + linkUrl: "https://example.com/preview/campaign-site", + sortOrder: 2, + mediaAssetId: null, + }, + ], + assets: [ + { + kind: "IMAGE" as const, + filePath: storyCover.url, + altAr: "غلاف مشروع Story", + altEn: "Story project cover", + altDe: "Story Projekt Cover", + sortOrder: 0, + mediaAssetId: storyCover.id, + }, + ], + }, + { + slug: "brand-redesign", + categoryId: brandCategory.id, + viewMode: "CASE_STUDY" as const, + titleAr: "إعادة تصميم الهوية", + titleEn: "Brand Redesign", + titleDe: "Brand Redesign", + summaryAr: "مثال عرض دراسة حالة يركز على التحدي والحل والنتيجة.", + summaryEn: "Case study example focused on challenge, solution, and outcome.", + summaryDe: "Case-Study-Ansicht mit Fokus auf Herausforderung, Loesung und Ergebnis.", + clientName: "Studio Client", + projectYear: 2025, + serviceLabelAr: "هوية بصرية", + serviceLabelEn: "Brand Identity", + serviceLabelDe: "Brand Identity", + previewUrl: "https://example.com/preview/brand-redesign", + coverImagePath: caseStudyCover.url, + isFeatured: true, + isPublished: true, + publishedAt: new Date("2025-01-10T09:00:00.000Z"), + sortOrder: 3, + coverAssetId: caseStudyCover.id, + sections: [ + { + type: "RICH_TEXT" as const, + titleAr: "التحدي", + titleEn: "Challenge", + titleDe: "Herausforderung", + bodyAr: "كان المطلوب تحديث الهوية بدون خسارة التعرف البصري الحالي.", + bodyEn: "The brief required a refreshed identity without losing recognition.", + bodyDe: "Die Marke sollte modernisiert werden, ohne die Wiedererkennbarkeit zu verlieren.", + imagePath: null, + linkUrl: null, + sortOrder: 0, + mediaAssetId: null, + }, + { + type: "RICH_TEXT" as const, + titleAr: "الحل", + titleEn: "Solution", + titleDe: "Loesung", + bodyAr: "تم بناء نظام مرئي أوضح مع قواعد استخدام قابلة للتوسع.", + bodyEn: "A clearer visual system with scalable usage rules was created.", + bodyDe: "Es wurde ein klareres visuelles System mit skalierbaren Regeln aufgebaut.", + imagePath: null, + linkUrl: null, + sortOrder: 1, + mediaAssetId: null, + }, + { + type: "GALLERY" as const, + titleAr: "التنفيذ البصري", + titleEn: "Visual Execution", + titleDe: "Visuelle Umsetzung", + bodyAr: "", + bodyEn: "", + bodyDe: "", + imagePath: caseStudyCover.url, + linkUrl: null, + sortOrder: 2, + mediaAssetId: caseStudyCover.id, + }, + ], + assets: [ + { + kind: "IMAGE" as const, + filePath: caseStudyCover.url, + altAr: "غلاف مشروع Case Study", + altEn: "Case study project cover", + altDe: "Case Study Projekt Cover", + sortOrder: 0, + mediaAssetId: caseStudyCover.id, + }, + ], + }, + ]; + + for (const projectConfig of projects) { + const { sections, assets, coverAssetId, ...projectValues } = projectConfig; + + const [project] = await db + .insert(portfolioProject) + .values(projectValues) + .onConflictDoUpdate({ + target: portfolioProject.slug, + set: { + categoryId: projectValues.categoryId, + viewMode: projectValues.viewMode, + titleAr: projectValues.titleAr, + titleEn: projectValues.titleEn, + titleDe: projectValues.titleDe, + summaryAr: projectValues.summaryAr, + summaryEn: projectValues.summaryEn, + summaryDe: projectValues.summaryDe, + clientName: projectValues.clientName, + projectYear: projectValues.projectYear, + serviceLabelAr: projectValues.serviceLabelAr, + serviceLabelEn: projectValues.serviceLabelEn, + serviceLabelDe: projectValues.serviceLabelDe, + previewUrl: projectValues.previewUrl, + coverImagePath: projectValues.coverImagePath, + isFeatured: projectValues.isFeatured, + isPublished: projectValues.isPublished, + publishedAt: projectValues.publishedAt, + sortOrder: projectValues.sortOrder, + updatedAt: new Date(), + }, + }) + .returning(); + + const created = await syncProjectContent( + project.id, + sections.map((section) => ({ + type: section.type, + titleAr: section.titleAr, + titleEn: section.titleEn, + titleDe: section.titleDe, + bodyAr: section.bodyAr, + bodyEn: section.bodyEn, + bodyDe: section.bodyDe, + imagePath: section.imagePath, + linkUrl: section.linkUrl, + sortOrder: section.sortOrder, + })), + assets.map((asset) => ({ + kind: asset.kind, + filePath: asset.filePath, + altAr: asset.altAr, + altEn: asset.altEn, + altDe: asset.altDe, + sortOrder: asset.sortOrder, + })), + ); + + await syncProjectMediaUsages(project.id, { + coverAssetId, + sectionUsages: created.createdSections.map((sectionRow, index) => ({ + fieldKey: sectionRow.id, + assetId: sections[index]?.mediaAssetId, + })), + assetUsages: created.createdAssets.map((assetRow, index) => ({ + fieldKey: assetRow.id, + assetId: assets[index]?.mediaAssetId, + })), + }); + } +} + +main() + .then(async () => { + await client.end(); + }) + .catch(async (error) => { + console.error("Seed failed:", error); + await client.end(); + process.exit(1); + }); diff --git a/lib/media-service.ts b/lib/media-service.ts index e23b386..65eacf1 100644 --- a/lib/media-service.ts +++ b/lib/media-service.ts @@ -1,6 +1,6 @@ import path from "path"; -import { MediaKind, MediaSource } from "@prisma/client"; +import { MediaKind, MediaSource } from "@/lib/db/enums"; import { createMediaAsset, getMediaAssetById } from "@/lib/media"; import { getExtensionForMimeType, removeManagedMediaFile, saveMediaUpload } from "@/lib/media-storage"; diff --git a/lib/media-validation.ts b/lib/media-validation.ts index 974ba29..d5e978e 100644 --- a/lib/media-validation.ts +++ b/lib/media-validation.ts @@ -1,4 +1,4 @@ -import { MediaKind } from "@prisma/client"; +import { MediaKind } from "@/lib/db/enums"; import { z } from "zod"; const mediaModeSchema = z.enum(["library", "external", "upload"]); diff --git a/lib/media.ts b/lib/media.ts index 0a22426..1c58262 100644 --- a/lib/media.ts +++ b/lib/media.ts @@ -1,12 +1,9 @@ -import type { - MediaAsset, - MediaKind, - MediaSource, - MediaUsage, - MediaUsageType, -} from "@prisma/client"; +import { and, count, desc, eq } from "drizzle-orm"; -import { prisma } from "@/lib/prisma"; +import { db } from "@/lib/db"; +import { mediaAsset, mediaUsage } from "@/lib/db/schema"; +import type { MediaAsset, MediaUsage } from "@/lib/db/schema"; +import type { MediaKind, MediaSource, MediaUsageType } from "@/lib/db/enums"; export type MediaAssetView = Pick< MediaAsset, @@ -52,38 +49,38 @@ function mapMediaAsset( } export async function getAdminMediaAssets() { - const assets = await prisma.mediaAsset.findMany({ - include: { + const assets = await db.query.mediaAsset.findMany({ + with: { usages: { - orderBy: [{ createdAt: "desc" }], + orderBy: (usage, { desc: descOrder }) => [descOrder(usage.createdAt)], }, }, - orderBy: [{ createdAt: "desc" }], + orderBy: (asset, { desc: descOrder }) => [descOrder(asset.createdAt)], }); return assets.map(mapMediaAsset); } export async function getMediaOptions(filters?: { kind?: MediaKind }) { - const assets = await prisma.mediaAsset.findMany({ - where: filters?.kind ? { kind: filters.kind } : undefined, - orderBy: [{ createdAt: "desc" }], - select: { - id: true, - kind: true, - url: true, - label: true, - source: true, - }, - }); + const assets = await db + .select({ + id: mediaAsset.id, + kind: mediaAsset.kind, + url: mediaAsset.url, + label: mediaAsset.label, + source: mediaAsset.source, + }) + .from(mediaAsset) + .where(filters?.kind ? eq(mediaAsset.kind, filters.kind) : undefined) + .orderBy(desc(mediaAsset.createdAt)); return assets; } export async function getMediaAssetById(id: string) { - const asset = await prisma.mediaAsset.findUnique({ - where: { id }, - include: { + const asset = await db.query.mediaAsset.findFirst({ + where: eq(mediaAsset.id, id), + with: { usages: true, }, }); @@ -101,8 +98,9 @@ export async function createMediaAsset(input: { mimeType?: string | null; size?: number | null; }) { - return prisma.mediaAsset.create({ - data: { + const [asset] = await db + .insert(mediaAsset) + .values({ source: input.source, kind: input.kind, url: input.url, @@ -111,8 +109,10 @@ export async function createMediaAsset(input: { altText: input.altText ?? null, mimeType: input.mimeType ?? null, size: input.size ?? null, - }, - }); + }) + .returning(); + + return asset; } export async function replaceEntityMediaUsages(input: { @@ -124,51 +124,49 @@ export async function replaceEntityMediaUsages(input: { fieldKey: string; }>; }) { - await prisma.$transaction(async (tx) => { - await tx.mediaUsage.deleteMany({ - where: { - entityType: input.entityType, - entityId: input.entityId, - }, - }); + await db.transaction(async (tx) => { + await tx + .delete(mediaUsage) + .where( + and( + eq(mediaUsage.entityType, input.entityType), + eq(mediaUsage.entityId, input.entityId), + ), + ); if (input.usages.length === 0) { return; } - await tx.mediaUsage.createMany({ - data: input.usages.map((usage) => ({ + await tx.insert(mediaUsage).values( + input.usages.map((usage) => ({ assetId: usage.assetId, usageType: usage.usageType, entityType: input.entityType, entityId: input.entityId, fieldKey: usage.fieldKey, })), - }); + ); }); } export async function deleteEntityMediaUsages(entityType: string, entityId: string) { - await prisma.mediaUsage.deleteMany({ - where: { - entityType, - entityId, - }, - }); + await db + .delete(mediaUsage) + .where(and(eq(mediaUsage.entityType, entityType), eq(mediaUsage.entityId, entityId))); } export async function getPortfolioMediaBindings(projectId: string): Promise { - const usages = await prisma.mediaUsage.findMany({ - where: { - entityType: "portfolio-project", - entityId: projectId, - }, - select: { - assetId: true, - usageType: true, - fieldKey: true, - }, - }); + const usages = await db + .select({ + assetId: mediaUsage.assetId, + usageType: mediaUsage.usageType, + fieldKey: mediaUsage.fieldKey, + }) + .from(mediaUsage) + .where( + and(eq(mediaUsage.entityType, "portfolio-project"), eq(mediaUsage.entityId, projectId)), + ); return usages.reduce( (result, usage) => { @@ -195,9 +193,10 @@ export async function getPortfolioMediaBindings(projectId: string): Promise ({ - ...mapCategory(category), - projectCount: category._count.projects, + return categories.map((record) => ({ + ...mapCategory(record), + projectCount: record.projects.length, })); } export async function getActivePortfolioCategories() { - const categories = await prisma.category.findMany({ - where: { - isActive: true, - }, - orderBy: [{ sortOrder: "asc" }, { createdAt: "asc" }], + const categories = await db.query.category.findMany({ + where: eq(category.isActive, true), + orderBy: [asc(category.sortOrder), asc(category.createdAt)], }); return categories.map(mapCategory); } export async function getActivePortfolioCategoryBySlug(slug: string) { - const category = await prisma.category.findFirst({ - where: { - slug, - isActive: true, - }, + const record = await db.query.category.findFirst({ + where: and(eq(category.slug, slug), eq(category.isActive, true)), }); - return category ? mapCategory(category) : null; + return record ? mapCategory(record) : null; } export async function getAdminPortfolioProjects(filters?: { categoryId?: string; status?: "all" | "draft" | "published"; }) { - const projects = await prisma.portfolioProject.findMany({ - where: { - ...(filters?.categoryId ? { categoryId: filters.categoryId } : {}), - ...(filters?.status === "draft" - ? { isPublished: false } - : filters?.status === "published" - ? { isPublished: true } - : {}), - }, - include: { + const conditions = [ + filters?.categoryId ? eq(portfolioProject.categoryId, filters.categoryId) : undefined, + filters?.status === "draft" + ? eq(portfolioProject.isPublished, false) + : filters?.status === "published" + ? eq(portfolioProject.isPublished, true) + : undefined, + ].filter(Boolean); + + const projects = await db.query.portfolioProject.findMany({ + where: conditions.length ? and(...conditions) : undefined, + with: { category: true, sections: { - orderBy: [{ sortOrder: "asc" }, { createdAt: "asc" }], + orderBy: (section, { asc: ascOrder }) => [ascOrder(section.sortOrder), ascOrder(section.createdAt)], }, assets: { - orderBy: [{ sortOrder: "asc" }, { createdAt: "asc" }], + orderBy: (asset, { asc: ascOrder }) => [ascOrder(asset.sortOrder), ascOrder(asset.createdAt)], }, }, - orderBy: [{ sortOrder: "asc" }, { createdAt: "desc" }], + orderBy: [asc(portfolioProject.sortOrder), desc(portfolioProject.createdAt)], }); return projects.map((project) => mapProject(project)); } export async function getPublishedPortfolioProjects(filters?: { categorySlug?: string }) { - const projects = await prisma.portfolioProject.findMany({ - where: { - isPublished: true, - category: { - isActive: true, - ...(filters?.categorySlug ? { slug: filters.categorySlug } : {}), - }, - }, - include: { + const projects = await db.query.portfolioProject.findMany({ + where: eq(portfolioProject.isPublished, true), + with: { category: true, sections: { - orderBy: [{ sortOrder: "asc" }, { createdAt: "asc" }], + orderBy: (section, { asc: ascOrder }) => [ascOrder(section.sortOrder), ascOrder(section.createdAt)], }, assets: { - orderBy: [{ sortOrder: "asc" }, { createdAt: "asc" }], + orderBy: (asset, { asc: ascOrder }) => [ascOrder(asset.sortOrder), ascOrder(asset.createdAt)], }, }, - orderBy: [{ sortOrder: "asc" }, { publishedAt: "desc" }, { createdAt: "desc" }], + orderBy: [ + asc(portfolioProject.sortOrder), + desc(portfolioProject.publishedAt), + desc(portfolioProject.createdAt), + ], }); - return projects.map((project) => mapProject(project)); + return projects + .filter( + (project) => + project.category.isActive && + (!filters?.categorySlug || project.category.slug === filters.categorySlug), + ) + .map((project) => mapProject(project)); } export const getPublishedPortfolioProjectBySlug = cache(async function (slug: string) { - const project = await prisma.portfolioProject.findFirst({ - where: { - slug, - isPublished: true, - category: { - isActive: true, - }, - }, - include: { + const project = await db.query.portfolioProject.findFirst({ + where: and(eq(portfolioProject.slug, slug), eq(portfolioProject.isPublished, true)), + with: { category: true, sections: { - orderBy: [{ sortOrder: "asc" }, { createdAt: "asc" }], + orderBy: (section, { asc: ascOrder }) => [ascOrder(section.sortOrder), ascOrder(section.createdAt)], }, assets: { - orderBy: [{ sortOrder: "asc" }, { createdAt: "asc" }], + orderBy: (asset, { asc: ascOrder }) => [ascOrder(asset.sortOrder), ascOrder(asset.createdAt)], }, }, }); - return project ? mapProject(project) : null; + if (!project || !project.category.isActive) { + return null; + } + + return mapProject(project); }); export async function getAdminPortfolioProjectById(id: string) { - const project = await prisma.portfolioProject.findUnique({ - where: { - id, - }, - include: { + const project = await db.query.portfolioProject.findFirst({ + where: eq(portfolioProject.id, id), + with: { category: true, sections: { - orderBy: [{ sortOrder: "asc" }, { createdAt: "asc" }], + orderBy: (section, { asc: ascOrder }) => [ascOrder(section.sortOrder), ascOrder(section.createdAt)], }, assets: { - orderBy: [{ sortOrder: "asc" }, { createdAt: "asc" }], + orderBy: (asset, { asc: ascOrder }) => [ascOrder(asset.sortOrder), ascOrder(asset.createdAt)], }, }, }); diff --git a/lib/prisma.ts b/lib/prisma.ts deleted file mode 100644 index 5c4d2bf..0000000 --- a/lib/prisma.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { PrismaPg } from "@prisma/adapter-pg"; -import { PrismaClient } from "@prisma/client"; -import { Pool } from "pg"; - -const globalForPrisma = globalThis as unknown as { - prisma: PrismaClient | undefined; - prismaPool: Pool | undefined; -}; - -const connectionString = - process.env.DATABASE_URL ?? - "postgresql://postgres:postgres@localhost:5432/moh_sass?schema=public"; - -const pool = - globalForPrisma.prismaPool ?? - new Pool({ - connectionString, - }); - -const adapter = new PrismaPg(pool); - -export const prisma = - globalForPrisma.prisma ?? - new PrismaClient({ - adapter, - log: ["warn", "error"], - }); - -if (process.env.NODE_ENV !== "production") { - globalForPrisma.prismaPool = pool; - globalForPrisma.prisma = prisma; -} diff --git a/package-lock.json b/package-lock.json index c88a942..765a180 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,8 +8,7 @@ "name": "moh-sass", "version": "0.1.0", "dependencies": { - "@prisma/adapter-pg": "^7.4.2", - "@prisma/client": "^7.4.2", + "@paralleldrive/cuid2": "^2.2.2", "@radix-ui/react-accordion": "^1.2.12", "@radix-ui/react-checkbox": "^1.3.3", "@radix-ui/react-dialog": "^1.1.15", @@ -19,6 +18,7 @@ "@types/nodemailer": "^7.0.11", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", + "drizzle-orm": "^0.44.5", "framer-motion": "^12.35.0", "gsap": "^3.15.0", "lucide-react": "^0.577.0", @@ -26,7 +26,7 @@ "next-intl": "^4.8.3", "next-themes": "^0.4.6", "nodemailer": "^8.0.1", - "pg": "^8.20.0", + "postgres": "^3.4.5", "react": "^19.2.4", "react-dom": "^19.2.4", "react-hook-form": "^7.71.2", @@ -35,15 +35,15 @@ }, "devDependencies": { "@types/node": "^20", - "@types/pg": "^8.18.0", "@types/react": "^19.2.14", "@types/react-dom": "^19.2.3", + "drizzle-kit": "^0.31.4", "eslint": "^9.39.4", "eslint-config-next": "^16.1.6", "postcss": "^8", - "prisma": "^7.4.2", "tailwindcss": "^3.4.1", "tailwindcss-animate": "^1.0.7", + "tsx": "^4.19.2", "typescript": "^5", "vitest": "^3.2.4" } @@ -334,73 +334,13 @@ "node": ">=6.9.0" } }, - "node_modules/@chevrotain/cst-dts-gen": { - "version": "10.5.0", - "resolved": "https://registry.npmjs.org/@chevrotain/cst-dts-gen/-/cst-dts-gen-10.5.0.tgz", - "integrity": "sha512-lhmC/FyqQ2o7pGK4Om+hzuDrm9rhFYIJ/AXoQBeongmn870Xeb0L6oGEiuR8nohFNL5sMaQEJWCxr1oIVIVXrw==", - "devOptional": true, - "license": "Apache-2.0", - "dependencies": { - "@chevrotain/gast": "10.5.0", - "@chevrotain/types": "10.5.0", - "lodash": "4.17.21" - } - }, - "node_modules/@chevrotain/gast": { - "version": "10.5.0", - "resolved": "https://registry.npmjs.org/@chevrotain/gast/-/gast-10.5.0.tgz", - "integrity": "sha512-pXdMJ9XeDAbgOWKuD1Fldz4ieCs6+nLNmyVhe2gZVqoO7v8HXuHYs5OV2EzUtbuai37TlOAQHrTDvxMnvMJz3A==", - "devOptional": true, - "license": "Apache-2.0", - "dependencies": { - "@chevrotain/types": "10.5.0", - "lodash": "4.17.21" - } - }, - "node_modules/@chevrotain/types": { - "version": "10.5.0", - "resolved": "https://registry.npmjs.org/@chevrotain/types/-/types-10.5.0.tgz", - "integrity": "sha512-f1MAia0x/pAVPWH/T73BJVyO2XU5tI4/iE7cnxb7tqdNTNhQI3Uq3XkqcoteTmD4t1aM0LbHCJOhgIDn07kl2A==", - "devOptional": true, + "node_modules/@drizzle-team/brocli": { + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/@drizzle-team/brocli/-/brocli-0.10.2.tgz", + "integrity": "sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w==", + "dev": true, "license": "Apache-2.0" }, - "node_modules/@chevrotain/utils": { - "version": "10.5.0", - "resolved": "https://registry.npmjs.org/@chevrotain/utils/-/utils-10.5.0.tgz", - "integrity": "sha512-hBzuU5+JjB2cqNZyszkDHZgOSrUUT8V3dhgRl8Q9Gp6dAj/H5+KILGjbhDpc3Iy9qmqlm/akuOI2ut9VUtzJxQ==", - "devOptional": true, - "license": "Apache-2.0" - }, - "node_modules/@electric-sql/pglite": { - "version": "0.3.15", - "resolved": "https://registry.npmjs.org/@electric-sql/pglite/-/pglite-0.3.15.tgz", - "integrity": "sha512-Cj++n1Mekf9ETfdc16TlDi+cDDQF0W7EcbyRHYOAeZdsAe8M/FJg18itDTSwyHfar2WIezawM9o0EKaRGVKygQ==", - "devOptional": true, - "license": "Apache-2.0" - }, - "node_modules/@electric-sql/pglite-socket": { - "version": "0.0.20", - "resolved": "https://registry.npmjs.org/@electric-sql/pglite-socket/-/pglite-socket-0.0.20.tgz", - "integrity": "sha512-J5nLGsicnD9wJHnno9r+DGxfcZWh+YJMCe0q/aCgtG6XOm9Z7fKeite8IZSNXgZeGltSigM9U/vAWZQWdgcSFg==", - "devOptional": true, - "license": "Apache-2.0", - "bin": { - "pglite-server": "dist/scripts/server.js" - }, - "peerDependencies": { - "@electric-sql/pglite": "0.3.15" - } - }, - "node_modules/@electric-sql/pglite-tools": { - "version": "0.2.20", - "resolved": "https://registry.npmjs.org/@electric-sql/pglite-tools/-/pglite-tools-0.2.20.tgz", - "integrity": "sha512-BK50ZnYa3IG7ztXhtgYf0Q7zijV32Iw1cYS8C+ThdQlwx12V5VZ9KRJ42y82Hyb4PkTxZQklVQA9JHyUlex33A==", - "devOptional": true, - "license": "Apache-2.0", - "peerDependencies": { - "@electric-sql/pglite": "0.3.15" - } - }, "node_modules/@emnapi/core": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.9.0.tgz", @@ -434,6 +374,442 @@ "tslib": "^2.4.0" } }, + "node_modules/@esbuild-kit/core-utils": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/@esbuild-kit/core-utils/-/core-utils-3.3.2.tgz", + "integrity": "sha512-sPRAnw9CdSsRmEtnsl2WXWdyquogVpB3yZ3dgwJfe8zrOzTsV7cJvmwrKVa+0ma5BoiGJ+BoqkMvawbayKUsqQ==", + "deprecated": "Merged into tsx: https://tsx.hirok.io", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.18.20", + "source-map-support": "^0.5.21" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/android-arm": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz", + "integrity": "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/android-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz", + "integrity": "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/android-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.20.tgz", + "integrity": "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/darwin-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz", + "integrity": "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/darwin-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz", + "integrity": "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/freebsd-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz", + "integrity": "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/freebsd-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz", + "integrity": "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-arm": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz", + "integrity": "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz", + "integrity": "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-ia32": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz", + "integrity": "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-loong64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz", + "integrity": "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-mips64el": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz", + "integrity": "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-ppc64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz", + "integrity": "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-riscv64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz", + "integrity": "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-s390x": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz", + "integrity": "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz", + "integrity": "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/netbsd-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz", + "integrity": "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/openbsd-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz", + "integrity": "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/sunos-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz", + "integrity": "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/win32-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz", + "integrity": "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/win32-ia32": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz", + "integrity": "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/win32-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz", + "integrity": "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/esbuild": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz", + "integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/android-arm": "0.18.20", + "@esbuild/android-arm64": "0.18.20", + "@esbuild/android-x64": "0.18.20", + "@esbuild/darwin-arm64": "0.18.20", + "@esbuild/darwin-x64": "0.18.20", + "@esbuild/freebsd-arm64": "0.18.20", + "@esbuild/freebsd-x64": "0.18.20", + "@esbuild/linux-arm": "0.18.20", + "@esbuild/linux-arm64": "0.18.20", + "@esbuild/linux-ia32": "0.18.20", + "@esbuild/linux-loong64": "0.18.20", + "@esbuild/linux-mips64el": "0.18.20", + "@esbuild/linux-ppc64": "0.18.20", + "@esbuild/linux-riscv64": "0.18.20", + "@esbuild/linux-s390x": "0.18.20", + "@esbuild/linux-x64": "0.18.20", + "@esbuild/netbsd-x64": "0.18.20", + "@esbuild/openbsd-x64": "0.18.20", + "@esbuild/sunos-x64": "0.18.20", + "@esbuild/win32-arm64": "0.18.20", + "@esbuild/win32-ia32": "0.18.20", + "@esbuild/win32-x64": "0.18.20" + } + }, + "node_modules/@esbuild-kit/esm-loader": { + "version": "2.6.5", + "resolved": "https://registry.npmjs.org/@esbuild-kit/esm-loader/-/esm-loader-2.6.5.tgz", + "integrity": "sha512-FxEMIkJKnodyA1OaCUoEvbYRkoZlLZ4d/eXFu9Fh8CbBBgP5EmZxrfTRyN0qpXZ4vOvqnE5YdRdcrmUUXuU+dA==", + "deprecated": "Merged into tsx: https://tsx.hirok.io", + "dev": true, + "license": "MIT", + "dependencies": { + "@esbuild-kit/core-utils": "^3.3.2", + "get-tsconfig": "^4.7.0" + } + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.27.3", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.3.tgz", @@ -1172,19 +1548,6 @@ "tslib": "^2.8.1" } }, - "node_modules/@hono/node-server": { - "version": "1.19.9", - "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.9.tgz", - "integrity": "sha512-vHL6w3ecZsky+8P5MD+eFfaGTyCeOHUIFYMGpQGbrBTSmNNoxv0if69rEZ5giu36weC5saFuznL411gRX7bJDw==", - "devOptional": true, - "license": "MIT", - "engines": { - "node": ">=18.14.1" - }, - "peerDependencies": { - "hono": "^4" - } - }, "node_modules/@humanfs/core": { "version": "0.19.1", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", @@ -1753,30 +2116,6 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/@mrleebo/prisma-ast": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/@mrleebo/prisma-ast/-/prisma-ast-0.13.1.tgz", - "integrity": "sha512-XyroGQXcHrZdvmrGJvsA9KNeOOgGMg1Vg9OlheUsBOSKznLMDl+YChxbkboRHvtFYJEMRYmlV3uoo/njCw05iw==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "chevrotain": "^10.5.0", - "lilconfig": "^2.1.0" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/@mrleebo/prisma-ast/node_modules/lilconfig": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", - "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==", - "devOptional": true, - "license": "MIT", - "engines": { - "node": ">=10" - } - }, "node_modules/@napi-rs/wasm-runtime": { "version": "0.2.12", "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz", @@ -1964,6 +2303,18 @@ "node": ">= 10" } }, + "node_modules/@noble/hashes": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", + "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -2012,6 +2363,15 @@ "node": ">=12.4.0" } }, + "node_modules/@paralleldrive/cuid2": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@paralleldrive/cuid2/-/cuid2-2.3.1.tgz", + "integrity": "sha512-XO7cAxhnTZl0Yggq6jOgjiOHhbgcO4NqFqwSmQpjK3b6TEE6Uj/jfSk6wzYyemh3+I0sHirKSetjQwn5cZktFw==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "^1.1.5" + } + }, "node_modules/@parcel/watcher": { "version": "2.5.6", "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.6.tgz", @@ -2319,190 +2679,6 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/@prisma/adapter-pg": { - "version": "7.4.2", - "resolved": "https://registry.npmjs.org/@prisma/adapter-pg/-/adapter-pg-7.4.2.tgz", - "integrity": "sha512-oUo2Zhe9Tf6YwVL8kLPuOLTK1Z2pwi/Ua77t2PuGyBan2w7shRKqHvYK+3XXmRH9RWhPJ4SMtHZKpNo6Ax/4bQ==", - "license": "Apache-2.0", - "dependencies": { - "@prisma/driver-adapter-utils": "7.4.2", - "pg": "^8.16.3", - "postgres-array": "3.0.4" - } - }, - "node_modules/@prisma/client": { - "version": "7.4.2", - "resolved": "https://registry.npmjs.org/@prisma/client/-/client-7.4.2.tgz", - "integrity": "sha512-ts2mu+cQHriAhSxngO3StcYubBGTWDtu/4juZhXCUKOwgh26l+s4KD3vT2kMUzFyrYnll9u/3qWrtzRv9CGWzA==", - "license": "Apache-2.0", - "dependencies": { - "@prisma/client-runtime-utils": "7.4.2" - }, - "engines": { - "node": "^20.19 || ^22.12 || >=24.0" - }, - "peerDependencies": { - "prisma": "*", - "typescript": ">=5.4.0" - }, - "peerDependenciesMeta": { - "prisma": { - "optional": true - }, - "typescript": { - "optional": true - } - } - }, - "node_modules/@prisma/client-runtime-utils": { - "version": "7.4.2", - "resolved": "https://registry.npmjs.org/@prisma/client-runtime-utils/-/client-runtime-utils-7.4.2.tgz", - "integrity": "sha512-cID+rzOEb38VyMsx5LwJMEY4NGIrWCNpKu/0ImbeooQ2Px7TI+kOt7cm0NelxUzF2V41UVVXAmYjANZQtCu1/Q==", - "license": "Apache-2.0" - }, - "node_modules/@prisma/config": { - "version": "7.4.2", - "resolved": "https://registry.npmjs.org/@prisma/config/-/config-7.4.2.tgz", - "integrity": "sha512-CftBjWxav99lzY1Z4oDgomdb1gh9BJFAOmWF6P2v1xRfXqQb56DfBub+QKcERRdNoAzCb3HXy3Zii8Vb4AsXhg==", - "devOptional": true, - "license": "Apache-2.0", - "dependencies": { - "c12": "3.1.0", - "deepmerge-ts": "7.1.5", - "effect": "3.18.4", - "empathic": "2.0.0" - } - }, - "node_modules/@prisma/debug": { - "version": "7.4.2", - "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-7.4.2.tgz", - "integrity": "sha512-aP7qzu+g/JnbF6U69LMwHoUkELiserKmWsE2shYuEpNUJ4GrtxBCvZwCyCBHFSH2kLTF2l1goBlBh4wuvRq62w==", - "license": "Apache-2.0" - }, - "node_modules/@prisma/dev": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@prisma/dev/-/dev-0.20.0.tgz", - "integrity": "sha512-ovlBYwWor0OzG+yH4J3Ot+AneD818BttLA+Ii7wjbcLHUrnC4tbUPVGyNd3c/+71KETPKZfjhkTSpdS15dmXNQ==", - "devOptional": true, - "license": "ISC", - "dependencies": { - "@electric-sql/pglite": "0.3.15", - "@electric-sql/pglite-socket": "0.0.20", - "@electric-sql/pglite-tools": "0.2.20", - "@hono/node-server": "1.19.9", - "@mrleebo/prisma-ast": "0.13.1", - "@prisma/get-platform": "7.2.0", - "@prisma/query-plan-executor": "7.2.0", - "foreground-child": "3.3.1", - "get-port-please": "3.2.0", - "hono": "4.11.4", - "http-status-codes": "2.3.0", - "pathe": "2.0.3", - "proper-lockfile": "4.1.2", - "remeda": "2.33.4", - "std-env": "3.10.0", - "valibot": "1.2.0", - "zeptomatch": "2.1.0" - } - }, - "node_modules/@prisma/driver-adapter-utils": { - "version": "7.4.2", - "resolved": "https://registry.npmjs.org/@prisma/driver-adapter-utils/-/driver-adapter-utils-7.4.2.tgz", - "integrity": "sha512-REdjFpT/ye9KdDs+CXAXPIbMQkVLhne9G5Pe97sNY4Ovx4r2DAbWM9hOFvvB1Oq8H8bOCdu0Ri3AoGALquQqVw==", - "license": "Apache-2.0", - "dependencies": { - "@prisma/debug": "7.4.2" - } - }, - "node_modules/@prisma/engines": { - "version": "7.4.2", - "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-7.4.2.tgz", - "integrity": "sha512-B+ZZhI4rXlzjVqRw/93AothEKOU5/x4oVyJFGo9RpHPnBwaPwk4Pi0Q4iGXipKxeXPs/dqljgNBjK0m8nocOJA==", - "devOptional": true, - "hasInstallScript": true, - "license": "Apache-2.0", - "dependencies": { - "@prisma/debug": "7.4.2", - "@prisma/engines-version": "7.5.0-10.94a226be1cf2967af2541cca5529f0f7ba866919", - "@prisma/fetch-engine": "7.4.2", - "@prisma/get-platform": "7.4.2" - } - }, - "node_modules/@prisma/engines-version": { - "version": "7.5.0-10.94a226be1cf2967af2541cca5529f0f7ba866919", - "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-7.5.0-10.94a226be1cf2967af2541cca5529f0f7ba866919.tgz", - "integrity": "sha512-5FIKY3KoYQlBuZC2yc16EXfVRQ8HY+fLqgxkYfWCtKhRb3ajCRzP/rPeoSx11+NueJDANdh4hjY36mdmrTcGSg==", - "devOptional": true, - "license": "Apache-2.0" - }, - "node_modules/@prisma/engines/node_modules/@prisma/get-platform": { - "version": "7.4.2", - "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-7.4.2.tgz", - "integrity": "sha512-UTnChXRwiauzl/8wT4hhe7Xmixja9WE28oCnGpBtRejaHhvekx5kudr3R4Y9mLSA0kqGnAMeyTiKwDVMjaEVsw==", - "devOptional": true, - "license": "Apache-2.0", - "dependencies": { - "@prisma/debug": "7.4.2" - } - }, - "node_modules/@prisma/fetch-engine": { - "version": "7.4.2", - "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-7.4.2.tgz", - "integrity": "sha512-f/c/MwYpdJO7taLETU8rahEstLeXfYgQGlz5fycG7Fbmva3iPdzGmjiSWHeSWIgNnlXnelUdCJqyZnFocurZuA==", - "devOptional": true, - "license": "Apache-2.0", - "dependencies": { - "@prisma/debug": "7.4.2", - "@prisma/engines-version": "7.5.0-10.94a226be1cf2967af2541cca5529f0f7ba866919", - "@prisma/get-platform": "7.4.2" - } - }, - "node_modules/@prisma/fetch-engine/node_modules/@prisma/get-platform": { - "version": "7.4.2", - "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-7.4.2.tgz", - "integrity": "sha512-UTnChXRwiauzl/8wT4hhe7Xmixja9WE28oCnGpBtRejaHhvekx5kudr3R4Y9mLSA0kqGnAMeyTiKwDVMjaEVsw==", - "devOptional": true, - "license": "Apache-2.0", - "dependencies": { - "@prisma/debug": "7.4.2" - } - }, - "node_modules/@prisma/get-platform": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-7.2.0.tgz", - "integrity": "sha512-k1V0l0Td1732EHpAfi2eySTezyllok9dXb6UQanajkJQzPUGi3vO2z7jdkz67SypFTdmbnyGYxvEvYZdZsMAVA==", - "devOptional": true, - "license": "Apache-2.0", - "dependencies": { - "@prisma/debug": "7.2.0" - } - }, - "node_modules/@prisma/get-platform/node_modules/@prisma/debug": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-7.2.0.tgz", - "integrity": "sha512-YSGTiSlBAVJPzX4ONZmMotL+ozJwQjRmZweQNIq/ER0tQJKJynNkRB3kyvt37eOfsbMCXk3gnLF6J9OJ4QWftw==", - "devOptional": true, - "license": "Apache-2.0" - }, - "node_modules/@prisma/query-plan-executor": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@prisma/query-plan-executor/-/query-plan-executor-7.2.0.tgz", - "integrity": "sha512-EOZmNzcV8uJ0mae3DhTsiHgoNCuu1J9mULQpGCh62zN3PxPTd+qI9tJvk5jOst8WHKQNwJWR3b39t0XvfBB0WQ==", - "devOptional": true, - "license": "Apache-2.0" - }, - "node_modules/@prisma/studio-core": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/@prisma/studio-core/-/studio-core-0.13.1.tgz", - "integrity": "sha512-agdqaPEePRHcQ7CexEfkX1RvSH9uWDb6pXrZnhCRykhDFAV0/0P3d07WtfiY8hZWb7oRU4v+NkT4cGFHkQJIPg==", - "devOptional": true, - "license": "Apache-2.0", - "peerDependencies": { - "@types/react": "^18.0.0 || ^19.0.0", - "react": "^18.0.0 || ^19.0.0", - "react-dom": "^18.0.0 || ^19.0.0" - } - }, "node_modules/@radix-ui/number": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.1.tgz", @@ -3703,13 +3879,6 @@ "integrity": "sha512-bXHSaW5jRTmke9Vd0h5P7BtWZG9Znqb8gSDxZnxaGSJnGwPLDPfS+3g0BKzeWqzgZPsIVZkM7m2tbo18cm5HBw==", "license": "MIT" }, - "node_modules/@standard-schema/spec": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", - "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", - "devOptional": true, - "license": "MIT" - }, "node_modules/@swc/core-darwin-arm64": { "version": "1.15.18", "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.15.18.tgz", @@ -3962,18 +4131,6 @@ "@types/node": "*" } }, - "node_modules/@types/pg": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.18.0.tgz", - "integrity": "sha512-gT+oueVQkqnj6ajGJXblFR4iavIXWsGAFCk3dP4Kki5+a9R4NMt0JARdk6s8cUKcfUoqP5dAtDSLU8xYUTFV+Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*", - "pg-protocol": "*", - "pg-types": "^2.2.0" - } - }, "node_modules/@types/react": { "version": "19.2.14", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz", @@ -4937,16 +5094,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/aws-ssl-profiles": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/aws-ssl-profiles/-/aws-ssl-profiles-1.1.2.tgz", - "integrity": "sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g==", - "devOptional": true, - "license": "MIT", - "engines": { - "node": ">= 6.0.0" - } - }, "node_modules/axe-core": { "version": "4.11.1", "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.11.1.tgz", @@ -5062,74 +5209,12 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, - "node_modules/c12": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/c12/-/c12-3.1.0.tgz", - "integrity": "sha512-uWoS8OU1MEIsOv8p/5a82c3H31LsWVR5qiyXVfBNOzfffjUWtPnhAb4BYI2uG2HfGmZmFjCtui5XNWaps+iFuw==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "chokidar": "^4.0.3", - "confbox": "^0.2.2", - "defu": "^6.1.4", - "dotenv": "^16.6.1", - "exsolve": "^1.0.7", - "giget": "^2.0.0", - "jiti": "^2.4.2", - "ohash": "^2.0.11", - "pathe": "^2.0.3", - "perfect-debounce": "^1.0.0", - "pkg-types": "^2.2.0", - "rc9": "^2.1.2" - }, - "peerDependencies": { - "magicast": "^0.3.5" - }, - "peerDependenciesMeta": { - "magicast": { - "optional": true - } - } - }, - "node_modules/c12/node_modules/chokidar": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", - "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "readdirp": "^4.0.1" - }, - "engines": { - "node": ">= 14.16.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/c12/node_modules/jiti": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", - "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", - "devOptional": true, - "license": "MIT", - "bin": { - "jiti": "lib/jiti-cli.mjs" - } - }, - "node_modules/c12/node_modules/readdirp": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", - "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", - "devOptional": true, - "license": "MIT", - "engines": { - "node": ">= 14.18.0" - }, - "funding": { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true, + "license": "MIT" }, "node_modules/cac": { "version": "6.7.14", @@ -5275,21 +5360,6 @@ "node": ">= 16" } }, - "node_modules/chevrotain": { - "version": "10.5.0", - "resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-10.5.0.tgz", - "integrity": "sha512-Pkv5rBY3+CsHOYfV5g/Vs5JY9WTHHDEKOlohI2XeygaZhUeqhAlldZ8Hz9cRmxu709bvS08YzxHdTPHhffc13A==", - "devOptional": true, - "license": "Apache-2.0", - "dependencies": { - "@chevrotain/cst-dts-gen": "10.5.0", - "@chevrotain/gast": "10.5.0", - "@chevrotain/types": "10.5.0", - "@chevrotain/utils": "10.5.0", - "lodash": "4.17.21", - "regexp-to-ast": "0.5.0" - } - }, "node_modules/chokidar": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", @@ -5328,16 +5398,6 @@ "node": ">= 6" } }, - "node_modules/citty": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/citty/-/citty-0.1.6.tgz", - "integrity": "sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "consola": "^3.2.3" - } - }, "node_modules/class-variance-authority": { "version": "0.7.1", "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz", @@ -5402,23 +5462,6 @@ "dev": true, "license": "MIT" }, - "node_modules/confbox": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.4.tgz", - "integrity": "sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ==", - "devOptional": true, - "license": "MIT" - }, - "node_modules/consola": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/consola/-/consola-3.4.2.tgz", - "integrity": "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==", - "devOptional": true, - "license": "MIT", - "engines": { - "node": "^14.18.0 || >=16.10.0" - } - }, "node_modules/convert-source-map": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", @@ -5563,16 +5606,6 @@ "dev": true, "license": "MIT" }, - "node_modules/deepmerge-ts": { - "version": "7.1.5", - "resolved": "https://registry.npmjs.org/deepmerge-ts/-/deepmerge-ts-7.1.5.tgz", - "integrity": "sha512-HOJkrhaYsweh+W+e74Yn7YStZOilkoPb6fycpwNLKzSPtruFs48nYis0zy5yJz1+ktUhHxoRDJ27RQAWLIJVJw==", - "devOptional": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=16.0.0" - } - }, "node_modules/define-data-property": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", @@ -5609,30 +5642,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/defu": { - "version": "6.1.4", - "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz", - "integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==", - "devOptional": true, - "license": "MIT" - }, - "node_modules/denque": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz", - "integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==", - "devOptional": true, - "license": "Apache-2.0", - "engines": { - "node": ">=0.10" - } - }, - "node_modules/destr": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/destr/-/destr-2.0.5.tgz", - "integrity": "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==", - "devOptional": true, - "license": "MIT" - }, "node_modules/detect-libc": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", @@ -5675,17 +5684,629 @@ "node": ">=0.10.0" } }, - "node_modules/dotenv": { - "version": "16.6.1", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", - "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", - "devOptional": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=12" + "node_modules/drizzle-kit": { + "version": "0.31.10", + "resolved": "https://registry.npmjs.org/drizzle-kit/-/drizzle-kit-0.31.10.tgz", + "integrity": "sha512-7OZcmQUrdGI+DUNNsKBn1aW8qSoKuTH7d0mYgSP8bAzdFzKoovxEFnoGQp2dVs82EOJeYycqRtciopszwUf8bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@drizzle-team/brocli": "^0.10.2", + "@esbuild-kit/esm-loader": "^2.5.5", + "esbuild": "^0.25.4", + "tsx": "^4.21.0" }, - "funding": { - "url": "https://dotenvx.com" + "bin": { + "drizzle-kit": "bin.cjs" + } + }, + "node_modules/drizzle-kit/node_modules/@esbuild/aix-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/drizzle-kit/node_modules/@esbuild/android-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/drizzle-kit/node_modules/@esbuild/android-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/drizzle-kit/node_modules/@esbuild/android-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/drizzle-kit/node_modules/@esbuild/darwin-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/drizzle-kit/node_modules/@esbuild/darwin-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/drizzle-kit/node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/drizzle-kit/node_modules/@esbuild/freebsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/drizzle-kit/node_modules/@esbuild/linux-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/drizzle-kit/node_modules/@esbuild/linux-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/drizzle-kit/node_modules/@esbuild/linux-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/drizzle-kit/node_modules/@esbuild/linux-loong64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/drizzle-kit/node_modules/@esbuild/linux-mips64el": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/drizzle-kit/node_modules/@esbuild/linux-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/drizzle-kit/node_modules/@esbuild/linux-riscv64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/drizzle-kit/node_modules/@esbuild/linux-s390x": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/drizzle-kit/node_modules/@esbuild/linux-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/drizzle-kit/node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/drizzle-kit/node_modules/@esbuild/netbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/drizzle-kit/node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/drizzle-kit/node_modules/@esbuild/openbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/drizzle-kit/node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/drizzle-kit/node_modules/@esbuild/sunos-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/drizzle-kit/node_modules/@esbuild/win32-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/drizzle-kit/node_modules/@esbuild/win32-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/drizzle-kit/node_modules/@esbuild/win32-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/drizzle-kit/node_modules/esbuild": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" + } + }, + "node_modules/drizzle-orm": { + "version": "0.44.7", + "resolved": "https://registry.npmjs.org/drizzle-orm/-/drizzle-orm-0.44.7.tgz", + "integrity": "sha512-quIpnYznjU9lHshEOAYLoZ9s3jweleHlZIAWR/jX9gAWNg/JhQ1wj0KGRf7/Zm+obRrYd9GjPVJg790QY9N5AQ==", + "license": "Apache-2.0", + "peerDependencies": { + "@aws-sdk/client-rds-data": ">=3", + "@cloudflare/workers-types": ">=4", + "@electric-sql/pglite": ">=0.2.0", + "@libsql/client": ">=0.10.0", + "@libsql/client-wasm": ">=0.10.0", + "@neondatabase/serverless": ">=0.10.0", + "@op-engineering/op-sqlite": ">=2", + "@opentelemetry/api": "^1.4.1", + "@planetscale/database": ">=1.13", + "@prisma/client": "*", + "@tidbcloud/serverless": "*", + "@types/better-sqlite3": "*", + "@types/pg": "*", + "@types/sql.js": "*", + "@upstash/redis": ">=1.34.7", + "@vercel/postgres": ">=0.8.0", + "@xata.io/client": "*", + "better-sqlite3": ">=7", + "bun-types": "*", + "expo-sqlite": ">=14.0.0", + "gel": ">=2", + "knex": "*", + "kysely": "*", + "mysql2": ">=2", + "pg": ">=8", + "postgres": ">=3", + "sql.js": ">=1", + "sqlite3": ">=5" + }, + "peerDependenciesMeta": { + "@aws-sdk/client-rds-data": { + "optional": true + }, + "@cloudflare/workers-types": { + "optional": true + }, + "@electric-sql/pglite": { + "optional": true + }, + "@libsql/client": { + "optional": true + }, + "@libsql/client-wasm": { + "optional": true + }, + "@neondatabase/serverless": { + "optional": true + }, + "@op-engineering/op-sqlite": { + "optional": true + }, + "@opentelemetry/api": { + "optional": true + }, + "@planetscale/database": { + "optional": true + }, + "@prisma/client": { + "optional": true + }, + "@tidbcloud/serverless": { + "optional": true + }, + "@types/better-sqlite3": { + "optional": true + }, + "@types/pg": { + "optional": true + }, + "@types/sql.js": { + "optional": true + }, + "@upstash/redis": { + "optional": true + }, + "@vercel/postgres": { + "optional": true + }, + "@xata.io/client": { + "optional": true + }, + "better-sqlite3": { + "optional": true + }, + "bun-types": { + "optional": true + }, + "expo-sqlite": { + "optional": true + }, + "gel": { + "optional": true + }, + "knex": { + "optional": true + }, + "kysely": { + "optional": true + }, + "mysql2": { + "optional": true + }, + "pg": { + "optional": true + }, + "postgres": { + "optional": true + }, + "prisma": { + "optional": true + }, + "sql.js": { + "optional": true + }, + "sqlite3": { + "optional": true + } } }, "node_modules/dunder-proto": { @@ -5703,17 +6324,6 @@ "node": ">= 0.4" } }, - "node_modules/effect": { - "version": "3.18.4", - "resolved": "https://registry.npmjs.org/effect/-/effect-3.18.4.tgz", - "integrity": "sha512-b1LXQJLe9D11wfnOKAk3PKxuqYshQ0Heez+y5pnkd3jLj1yx9QhM72zZ9uUrOQyNvrs2GZZd/3maL0ZV18YuDA==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "@standard-schema/spec": "^1.0.0", - "fast-check": "^3.23.1" - } - }, "node_modules/electron-to-chromium": { "version": "1.5.313", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.313.tgz", @@ -5728,16 +6338,6 @@ "dev": true, "license": "MIT" }, - "node_modules/empathic": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/empathic/-/empathic-2.0.0.tgz", - "integrity": "sha512-i6UzDscO/XfAcNYD75CfICkmfLedpyPDdozrLMmQc5ORaQcdMoc21OnlEylMIqI7U8eniKrPMxxtj8k0vhmJhA==", - "devOptional": true, - "license": "MIT", - "engines": { - "node": ">=14" - } - }, "node_modules/es-abstract": { "version": "1.24.1", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.1.tgz", @@ -6523,36 +7123,6 @@ "node": ">=12.0.0" } }, - "node_modules/exsolve": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.8.tgz", - "integrity": "sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==", - "devOptional": true, - "license": "MIT" - }, - "node_modules/fast-check": { - "version": "3.23.2", - "resolved": "https://registry.npmjs.org/fast-check/-/fast-check-3.23.2.tgz", - "integrity": "sha512-h5+1OzzfCC3Ef7VbtKdcv7zsstUQwUDlYpUTvjeUsJAssPgLn7QzbboPtL5ro04Mq0rPOsMzl7q5hIbRs2wD1A==", - "devOptional": true, - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/dubzzz" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fast-check" - } - ], - "license": "MIT", - "dependencies": { - "pure-rand": "^6.1.0" - }, - "engines": { - "node": ">=8.0.0" - } - }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -6694,23 +7264,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/foreground-child": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", - "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", - "devOptional": true, - "license": "ISC", - "dependencies": { - "cross-spawn": "^7.0.6", - "signal-exit": "^4.0.1" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/framer-motion": { "version": "12.35.0", "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.35.0.tgz", @@ -6794,16 +7347,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/generate-function": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz", - "integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "is-property": "^1.0.2" - } - }, "node_modules/generator-function": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/generator-function/-/generator-function-2.0.1.tgz", @@ -6858,13 +7401,6 @@ "node": ">=6" } }, - "node_modules/get-port-please": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/get-port-please/-/get-port-please-3.2.0.tgz", - "integrity": "sha512-I9QVvBw5U/hw3RmWpYKRumUeaDgxTPd401x364rLmWBJcOQ753eov1eTgzDqRG9bqFIfDc7gfzcQEWrUri3o1A==", - "devOptional": true, - "license": "MIT" - }, "node_modules/get-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", @@ -6910,24 +7446,6 @@ "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" } }, - "node_modules/giget": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/giget/-/giget-2.0.0.tgz", - "integrity": "sha512-L5bGsVkxJbJgdnwyuheIunkGatUF/zssUoxxjACCseZYAVbaqdh9Tsmmlkl8vYan09H7sbvKt4pS8GqKLBrEzA==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "citty": "^0.1.6", - "consola": "^3.4.0", - "defu": "^6.1.4", - "node-fetch-native": "^1.6.6", - "nypm": "^0.6.0", - "pathe": "^2.0.3" - }, - "bin": { - "giget": "dist/cli.mjs" - } - }, "node_modules/glob-parent": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", @@ -6984,27 +7502,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "devOptional": true, - "license": "ISC" - }, - "node_modules/grammex": { - "version": "3.1.12", - "resolved": "https://registry.npmjs.org/grammex/-/grammex-3.1.12.tgz", - "integrity": "sha512-6ufJOsSA7LcQehIJNCO7HIBykfM7DXQual0Ny780/DEcJIpBlHRvcqEBWGPYd7hrXL2GJ3oJI1MIhaXjWmLQOQ==", - "devOptional": true, - "license": "MIT" - }, - "node_modules/graphmatch": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/graphmatch/-/graphmatch-1.1.1.tgz", - "integrity": "sha512-5ykVn/EXM1hF0XCaWh05VbYvEiOL2lY1kBxZtaYsyvjp7cmWOU1XsAdfQBwClraEofXDT197lFbXOEVMHpvQOg==", - "devOptional": true, - "license": "MIT" - }, "node_modules/gsap": { "version": "3.15.0", "resolved": "https://registry.npmjs.org/gsap/-/gsap-3.15.0.tgz", @@ -7122,40 +7619,6 @@ "hermes-estree": "0.25.1" } }, - "node_modules/hono": { - "version": "4.11.4", - "resolved": "https://registry.npmjs.org/hono/-/hono-4.11.4.tgz", - "integrity": "sha512-U7tt8JsyrxSRKspfhtLET79pU8K+tInj5QZXs1jSugO1Vq5dFj3kmZsRldo29mTBfcjDRVRXrEZ6LS63Cog9ZA==", - "devOptional": true, - "license": "MIT", - "engines": { - "node": ">=16.9.0" - } - }, - "node_modules/http-status-codes": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/http-status-codes/-/http-status-codes-2.3.0.tgz", - "integrity": "sha512-RJ8XvFvpPM/Dmc5SV+dC4y5PCeOhT3x1Hq0NU3rjGeg5a/CqlhZ7uudknPwZFz4aeAXDcbAyaeP7GAo9lvngtA==", - "devOptional": true, - "license": "MIT" - }, - "node_modules/iconv-lite": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", - "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, "node_modules/icu-minify": { "version": "4.8.3", "resolved": "https://registry.npmjs.org/icu-minify/-/icu-minify-4.8.3.tgz", @@ -7503,13 +7966,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-property": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", - "integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==", - "devOptional": true, - "license": "MIT" - }, "node_modules/is-regex": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", @@ -7860,13 +8316,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "devOptional": true, - "license": "MIT" - }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -7874,13 +8323,6 @@ "dev": true, "license": "MIT" }, - "node_modules/long": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", - "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", - "devOptional": true, - "license": "Apache-2.0" - }, "node_modules/loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", @@ -7911,22 +8353,6 @@ "yallist": "^3.0.2" } }, - "node_modules/lru.min": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/lru.min/-/lru.min-1.1.4.tgz", - "integrity": "sha512-DqC6n3QQ77zdFpCMASA1a3Jlb64Hv2N2DciFGkO/4L9+q/IpIAuRlKOvCXabtRW6cQf8usbmM6BE/TOPysCdIA==", - "devOptional": true, - "license": "MIT", - "engines": { - "bun": ">=1.0.0", - "deno": ">=1.30.0", - "node": ">=8.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wellwelwel" - } - }, "node_modules/lucide-react": { "version": "0.577.0", "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.577.0.tgz", @@ -8028,27 +8454,6 @@ "dev": true, "license": "MIT" }, - "node_modules/mysql2": { - "version": "3.15.3", - "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.15.3.tgz", - "integrity": "sha512-FBrGau0IXmuqg4haEZRBfHNWB5mUARw6hNwPDXXGg0XzVJ50mr/9hb267lvpVMnhZ1FON3qNd4Xfcez1rbFwSg==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "aws-ssl-profiles": "^1.1.1", - "denque": "^2.1.0", - "generate-function": "^2.3.1", - "iconv-lite": "^0.7.0", - "long": "^5.2.1", - "lru.min": "^1.0.0", - "named-placeholders": "^1.1.3", - "seq-queue": "^0.0.5", - "sqlstring": "^2.3.2" - }, - "engines": { - "node": ">= 8.0" - } - }, "node_modules/mz": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", @@ -8061,19 +8466,6 @@ "thenify-all": "^1.0.0" } }, - "node_modules/named-placeholders": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/named-placeholders/-/named-placeholders-1.1.6.tgz", - "integrity": "sha512-Tz09sEL2EEuv5fFowm419c1+a/jSMiBjI9gHxVLrVdbUkkNUUfjsVYs9pVZu5oCon/kmRh9TfLEObFtkVxmY0w==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "lru.min": "^1.1.0" - }, - "engines": { - "node": ">=8.0.0" - } - }, "node_modules/nanoid": { "version": "3.3.11", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", @@ -8253,17 +8645,6 @@ } } }, - "node_modules/next-intl/node_modules/@swc/helpers": { - "version": "0.5.19", - "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.19.tgz", - "integrity": "sha512-QamiFeIK3txNjgUTNppE6MiG3p7TdninpZu0E0PbqVh1a9FNLT2FRhisaa4NcaX52XVhA5l7Pk58Ft7Sqi/2sA==", - "license": "Apache-2.0", - "optional": true, - "peer": true, - "dependencies": { - "tslib": "^2.8.0" - } - }, "node_modules/next-themes": { "version": "0.4.6", "resolved": "https://registry.npmjs.org/next-themes/-/next-themes-0.4.6.tgz", @@ -8337,13 +8718,6 @@ "semver": "bin/semver.js" } }, - "node_modules/node-fetch-native": { - "version": "1.6.7", - "resolved": "https://registry.npmjs.org/node-fetch-native/-/node-fetch-native-1.6.7.tgz", - "integrity": "sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==", - "devOptional": true, - "license": "MIT" - }, "node_modules/node-releases": { "version": "2.0.36", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.36.tgz", @@ -8370,31 +8744,6 @@ "node": ">=0.10.0" } }, - "node_modules/nypm": { - "version": "0.6.5", - "resolved": "https://registry.npmjs.org/nypm/-/nypm-0.6.5.tgz", - "integrity": "sha512-K6AJy1GMVyfyMXRVB88700BJqNUkByijGJM8kEHpLdcAt+vSQAVfkWWHYzuRXHSY6xA2sNc5RjTj0p9rE2izVQ==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "citty": "^0.2.0", - "pathe": "^2.0.3", - "tinyexec": "^1.0.2" - }, - "bin": { - "nypm": "dist/cli.mjs" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/nypm/node_modules/citty": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/citty/-/citty-0.2.1.tgz", - "integrity": "sha512-kEV95lFBhQgtogAPlQfJJ0WGVSokvLr/UEoFPiKKOXF7pl98HfUVUD0ejsuTCld/9xH9vogSywZ5KqHzXrZpqg==", - "devOptional": true, - "license": "MIT" - }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -8528,13 +8877,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/ohash": { - "version": "2.0.11", - "resolved": "https://registry.npmjs.org/ohash/-/ohash-2.0.11.tgz", - "integrity": "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==", - "devOptional": true, - "license": "MIT" - }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -8660,111 +9002,6 @@ "node": ">= 14.16" } }, - "node_modules/perfect-debounce": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz", - "integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==", - "devOptional": true, - "license": "MIT" - }, - "node_modules/pg": { - "version": "8.20.0", - "resolved": "https://registry.npmjs.org/pg/-/pg-8.20.0.tgz", - "integrity": "sha512-ldhMxz2r8fl/6QkXnBD3CR9/xg694oT6DZQ2s6c/RI28OjtSOpxnPrUCGOBJ46RCUxcWdx3p6kw/xnDHjKvaRA==", - "license": "MIT", - "dependencies": { - "pg-connection-string": "^2.12.0", - "pg-pool": "^3.13.0", - "pg-protocol": "^1.13.0", - "pg-types": "2.2.0", - "pgpass": "1.0.5" - }, - "engines": { - "node": ">= 16.0.0" - }, - "optionalDependencies": { - "pg-cloudflare": "^1.3.0" - }, - "peerDependencies": { - "pg-native": ">=3.0.1" - }, - "peerDependenciesMeta": { - "pg-native": { - "optional": true - } - } - }, - "node_modules/pg-cloudflare": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.3.0.tgz", - "integrity": "sha512-6lswVVSztmHiRtD6I8hw4qP/nDm1EJbKMRhf3HCYaqud7frGysPv7FYJ5noZQdhQtN2xJnimfMtvQq21pdbzyQ==", - "license": "MIT", - "optional": true - }, - "node_modules/pg-connection-string": { - "version": "2.12.0", - "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.12.0.tgz", - "integrity": "sha512-U7qg+bpswf3Cs5xLzRqbXbQl85ng0mfSV/J0nnA31MCLgvEaAo7CIhmeyrmJpOr7o+zm0rXK+hNnT5l9RHkCkQ==", - "license": "MIT" - }, - "node_modules/pg-int8": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", - "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==", - "license": "ISC", - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/pg-pool": { - "version": "3.13.0", - "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.13.0.tgz", - "integrity": "sha512-gB+R+Xud1gLFuRD/QgOIgGOBE2KCQPaPwkzBBGC9oG69pHTkhQeIuejVIk3/cnDyX39av2AxomQiyPT13WKHQA==", - "license": "MIT", - "peerDependencies": { - "pg": ">=8.0" - } - }, - "node_modules/pg-protocol": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.13.0.tgz", - "integrity": "sha512-zzdvXfS6v89r6v7OcFCHfHlyG/wvry1ALxZo4LqgUoy7W9xhBDMaqOuMiF3qEV45VqsN6rdlcehHrfDtlCPc8w==", - "license": "MIT" - }, - "node_modules/pg-types": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz", - "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", - "license": "MIT", - "dependencies": { - "pg-int8": "1.0.1", - "postgres-array": "~2.0.0", - "postgres-bytea": "~1.0.0", - "postgres-date": "~1.0.4", - "postgres-interval": "^1.1.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/pg-types/node_modules/postgres-array": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", - "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/pgpass": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz", - "integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==", - "license": "MIT", - "dependencies": { - "split2": "^4.1.0" - } - }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -8804,18 +9041,6 @@ "node": ">= 6" } }, - "node_modules/pkg-types": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.0.tgz", - "integrity": "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "confbox": "^0.2.2", - "exsolve": "^1.0.7", - "pathe": "^2.0.3" - } - }, "node_modules/po-parser": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/po-parser/-/po-parser-2.1.1.tgz", @@ -8999,7 +9224,6 @@ "version": "3.4.7", "resolved": "https://registry.npmjs.org/postgres/-/postgres-3.4.7.tgz", "integrity": "sha512-Jtc2612XINuBjIl/QTWsV5UvE8UHuNblcO3vVADSrKsrc6RqGX6lOW1cEo3CM2v0XG4Nat8nI+YM7/f26VxXLw==", - "devOptional": true, "license": "Unlicense", "engines": { "node": ">=12" @@ -9009,45 +9233,6 @@ "url": "https://github.com/sponsors/porsager" } }, - "node_modules/postgres-array": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-3.0.4.tgz", - "integrity": "sha512-nAUSGfSDGOaOAEGwqsRY27GPOea7CNipJPOA7lPbdEpx5Kg3qzdP0AaWC5MlhTWV9s4hFX39nomVZ+C4tnGOJQ==", - "license": "MIT", - "engines": { - "node": ">=12" - } - }, - "node_modules/postgres-bytea": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.1.tgz", - "integrity": "sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/postgres-date": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz", - "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/postgres-interval": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", - "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", - "license": "MIT", - "dependencies": { - "xtend": "^4.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -9058,40 +9243,6 @@ "node": ">= 0.8.0" } }, - "node_modules/prisma": { - "version": "7.4.2", - "resolved": "https://registry.npmjs.org/prisma/-/prisma-7.4.2.tgz", - "integrity": "sha512-2bP8Ruww3Q95Z2eH4Yqh4KAENRsj/SxbdknIVBfd6DmjPwmpsC4OVFMLOeHt6tM3Amh8ebjvstrUz3V/hOe1dA==", - "devOptional": true, - "hasInstallScript": true, - "license": "Apache-2.0", - "dependencies": { - "@prisma/config": "7.4.2", - "@prisma/dev": "0.20.0", - "@prisma/engines": "7.4.2", - "@prisma/studio-core": "0.13.1", - "mysql2": "3.15.3", - "postgres": "3.4.7" - }, - "bin": { - "prisma": "build/index.js" - }, - "engines": { - "node": "^20.19 || ^22.12 || >=24.0" - }, - "peerDependencies": { - "better-sqlite3": ">=9.0.0", - "typescript": ">=5.4.0" - }, - "peerDependenciesMeta": { - "better-sqlite3": { - "optional": true - }, - "typescript": { - "optional": true - } - } - }, "node_modules/prop-types": { "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", @@ -9104,25 +9255,6 @@ "react-is": "^16.13.1" } }, - "node_modules/proper-lockfile": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/proper-lockfile/-/proper-lockfile-4.1.2.tgz", - "integrity": "sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.4", - "retry": "^0.12.0", - "signal-exit": "^3.0.2" - } - }, - "node_modules/proper-lockfile/node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "devOptional": true, - "license": "ISC" - }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -9133,23 +9265,6 @@ "node": ">=6" } }, - "node_modules/pure-rand": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", - "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==", - "devOptional": true, - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/dubzzz" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fast-check" - } - ], - "license": "MIT" - }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -9171,17 +9286,6 @@ ], "license": "MIT" }, - "node_modules/rc9": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/rc9/-/rc9-2.1.2.tgz", - "integrity": "sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "defu": "^6.1.4", - "destr": "^2.0.3" - } - }, "node_modules/react": { "version": "19.2.4", "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", @@ -9341,13 +9445,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/regexp-to-ast": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/regexp-to-ast/-/regexp-to-ast-0.5.0.tgz", - "integrity": "sha512-tlbJqcMHnPKI9zSrystikWKwHkBqu2a/Sgw01h3zFjvYrMxEDYHzzoMZnUrbIfpTFEsoRnnviOXNCzFiSc54Qw==", - "devOptional": true, - "license": "MIT" - }, "node_modules/regexp.prototype.flags": { "version": "1.5.4", "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", @@ -9369,16 +9466,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/remeda": { - "version": "2.33.4", - "resolved": "https://registry.npmjs.org/remeda/-/remeda-2.33.4.tgz", - "integrity": "sha512-ygHswjlc/opg2VrtiYvUOPLjxjtdKvjGz1/plDhkG66hjNjFr1xmfrs2ClNFo/E6TyUFiwYNh53bKV26oBoMGQ==", - "devOptional": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/remeda" - } - }, "node_modules/resolve": { "version": "1.22.11", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", @@ -9420,16 +9507,6 @@ "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" } }, - "node_modules/retry": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", - "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", - "devOptional": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, "node_modules/reusify": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", @@ -9565,13 +9642,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "devOptional": true, - "license": "MIT" - }, "node_modules/scheduler": { "version": "0.27.0", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", @@ -9591,12 +9661,6 @@ "node": ">=10" } }, - "node_modules/seq-queue": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/seq-queue/-/seq-queue-0.0.5.tgz", - "integrity": "sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q==", - "devOptional": true - }, "node_modules/set-function-length": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", @@ -9797,17 +9861,14 @@ "dev": true, "license": "ISC" }, - "node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "devOptional": true, - "license": "ISC", + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": ">=0.10.0" } }, "node_modules/source-map-js": { @@ -9819,23 +9880,15 @@ "node": ">=0.10.0" } }, - "node_modules/split2": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", - "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", - "license": "ISC", - "engines": { - "node": ">= 10.x" - } - }, - "node_modules/sqlstring": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.3.tgz", - "integrity": "sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==", - "devOptional": true, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, "license": "MIT", - "engines": { - "node": ">= 0.6" + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" } }, "node_modules/stable-hash": { @@ -10189,16 +10242,6 @@ "dev": true, "license": "MIT" }, - "node_modules/tinyexec": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.2.tgz", - "integrity": "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==", - "devOptional": true, - "license": "MIT", - "engines": { - "node": ">=18" - } - }, "node_modules/tinyglobby": { "version": "0.2.15", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", @@ -10329,6 +10372,509 @@ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "license": "0BSD" }, + "node_modules/tsx": { + "version": "4.23.11", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.23.11.tgz", + "integrity": "sha512-Ry2oTEUnhBdeEdWIztY8kf3/nBGnPnjMLVGL0YfdRXMORuPER5NlKmayqxtxRxwB1xBN+RivRaJfe7PM1rtiyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.28.0" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "node_modules/tsx/node_modules/@esbuild/aix-ppc64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.1.tgz", + "integrity": "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/android-arm": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.1.tgz", + "integrity": "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/android-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.1.tgz", + "integrity": "sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/android-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.1.tgz", + "integrity": "sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/darwin-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.1.tgz", + "integrity": "sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/darwin-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.1.tgz", + "integrity": "sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/freebsd-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.1.tgz", + "integrity": "sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/freebsd-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.1.tgz", + "integrity": "sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-arm": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.1.tgz", + "integrity": "sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.1.tgz", + "integrity": "sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-ia32": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.1.tgz", + "integrity": "sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-loong64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.1.tgz", + "integrity": "sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-mips64el": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.1.tgz", + "integrity": "sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-ppc64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.1.tgz", + "integrity": "sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-riscv64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.1.tgz", + "integrity": "sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-s390x": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.1.tgz", + "integrity": "sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.1.tgz", + "integrity": "sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/netbsd-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.1.tgz", + "integrity": "sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/netbsd-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.1.tgz", + "integrity": "sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/openbsd-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.1.tgz", + "integrity": "sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/openbsd-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.1.tgz", + "integrity": "sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/openharmony-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.1.tgz", + "integrity": "sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/sunos-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.1.tgz", + "integrity": "sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/win32-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.1.tgz", + "integrity": "sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/win32-ia32": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.1.tgz", + "integrity": "sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/win32-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.1.tgz", + "integrity": "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/esbuild": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.1.tgz", + "integrity": "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.28.1", + "@esbuild/android-arm": "0.28.1", + "@esbuild/android-arm64": "0.28.1", + "@esbuild/android-x64": "0.28.1", + "@esbuild/darwin-arm64": "0.28.1", + "@esbuild/darwin-x64": "0.28.1", + "@esbuild/freebsd-arm64": "0.28.1", + "@esbuild/freebsd-x64": "0.28.1", + "@esbuild/linux-arm": "0.28.1", + "@esbuild/linux-arm64": "0.28.1", + "@esbuild/linux-ia32": "0.28.1", + "@esbuild/linux-loong64": "0.28.1", + "@esbuild/linux-mips64el": "0.28.1", + "@esbuild/linux-ppc64": "0.28.1", + "@esbuild/linux-riscv64": "0.28.1", + "@esbuild/linux-s390x": "0.28.1", + "@esbuild/linux-x64": "0.28.1", + "@esbuild/netbsd-arm64": "0.28.1", + "@esbuild/netbsd-x64": "0.28.1", + "@esbuild/openbsd-arm64": "0.28.1", + "@esbuild/openbsd-x64": "0.28.1", + "@esbuild/openharmony-arm64": "0.28.1", + "@esbuild/sunos-x64": "0.28.1", + "@esbuild/win32-arm64": "0.28.1", + "@esbuild/win32-ia32": "0.28.1", + "@esbuild/win32-x64": "0.28.1" + } + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -10630,21 +11176,6 @@ "dev": true, "license": "MIT" }, - "node_modules/valibot": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/valibot/-/valibot-1.2.0.tgz", - "integrity": "sha512-mm1rxUsmOxzrwnX5arGS+U4T25RdvpPjPN4yR0u9pUBov9+zGVtO84tif1eY4r6zWxVxu3KzIyknJy3rxfRZZg==", - "devOptional": true, - "license": "MIT", - "peerDependencies": { - "typescript": ">=5" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, "node_modules/vite": { "version": "7.3.1", "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz", @@ -10999,15 +11530,6 @@ "node": ">=0.10.0" } }, - "node_modules/xtend": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", - "license": "MIT", - "engines": { - "node": ">=0.4" - } - }, "node_modules/yallist": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", @@ -11028,17 +11550,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/zeptomatch": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/zeptomatch/-/zeptomatch-2.1.0.tgz", - "integrity": "sha512-KiGErG2J0G82LSpniV0CtIzjlJ10E04j02VOudJsPyPwNZgGnRKQy7I1R7GMyg/QswnE4l7ohSGrQbQbjXPPDA==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "grammex": "^3.1.11", - "graphmatch": "^1.1.0" - } - }, "node_modules/zod": { "version": "4.3.6", "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz", diff --git a/package.json b/package.json index c368843..77243f3 100644 --- a/package.json +++ b/package.json @@ -8,17 +8,14 @@ "start": "next start", "lint": "eslint .", "test": "vitest run", - "prisma:generate": "prisma generate", - "db:migrate": "prisma migrate deploy", - "db:migrate:dev": "prisma migrate dev", - "db:seed": "prisma db seed" - }, - "prisma": { - "seed": "node prisma/seed.js" + "db:generate": "drizzle-kit generate", + "db:migrate": "drizzle-kit migrate", + "db:push": "drizzle-kit push", + "db:studio": "drizzle-kit studio", + "db:seed": "tsx lib/db/seed.ts" }, "dependencies": { - "@prisma/adapter-pg": "^7.4.2", - "@prisma/client": "^7.4.2", + "@paralleldrive/cuid2": "^2.2.2", "@radix-ui/react-accordion": "^1.2.12", "@radix-ui/react-checkbox": "^1.3.3", "@radix-ui/react-dialog": "^1.1.15", @@ -28,6 +25,7 @@ "@types/nodemailer": "^7.0.11", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", + "drizzle-orm": "^0.44.5", "framer-motion": "^12.35.0", "gsap": "^3.15.0", "lucide-react": "^0.577.0", @@ -35,7 +33,7 @@ "next-intl": "^4.8.3", "next-themes": "^0.4.6", "nodemailer": "^8.0.1", - "pg": "^8.20.0", + "postgres": "^3.4.5", "react": "^19.2.4", "react-dom": "^19.2.4", "react-hook-form": "^7.71.2", @@ -44,15 +42,15 @@ }, "devDependencies": { "@types/node": "^20", - "@types/pg": "^8.18.0", "@types/react": "^19.2.14", "@types/react-dom": "^19.2.3", + "drizzle-kit": "^0.31.4", "eslint": "^9.39.4", "eslint-config-next": "^16.1.6", "postcss": "^8", - "prisma": "^7.4.2", "tailwindcss": "^3.4.1", "tailwindcss-animate": "^1.0.7", + "tsx": "^4.19.2", "typescript": "^5", "vitest": "^3.2.4" } diff --git a/prisma.config.ts b/prisma.config.ts deleted file mode 100644 index 970093f..0000000 --- a/prisma.config.ts +++ /dev/null @@ -1,13 +0,0 @@ -import "dotenv/config"; -import { defineConfig } from "prisma/config"; - -export default defineConfig({ - schema: "prisma/schema.prisma", - migrations: { - path: "prisma/migrations", - seed: "node prisma/seed.js", - }, - datasource: { - url: process.env["DATABASE_URL"] ?? "", - }, -}); diff --git a/prisma/migrations/20260306090522_init_app_config/migration.sql b/prisma/migrations/20260306090522_init_app_config/migration.sql deleted file mode 100644 index 31f0801..0000000 --- a/prisma/migrations/20260306090522_init_app_config/migration.sql +++ /dev/null @@ -1,13 +0,0 @@ --- CreateTable -CREATE TABLE "AppConfig" ( - "id" TEXT NOT NULL, - "key" TEXT NOT NULL, - "value" TEXT NOT NULL, - "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updatedAt" TIMESTAMP(3) NOT NULL, - - CONSTRAINT "AppConfig_pkey" PRIMARY KEY ("id") -); - --- CreateIndex -CREATE UNIQUE INDEX "AppConfig_key_key" ON "AppConfig"("key"); diff --git a/prisma/migrations/20260307152159_add_portfolio_management/migration.sql b/prisma/migrations/20260307152159_add_portfolio_management/migration.sql deleted file mode 100644 index 86100f4..0000000 --- a/prisma/migrations/20260307152159_add_portfolio_management/migration.sql +++ /dev/null @@ -1,112 +0,0 @@ -CREATE TYPE "PortfolioSectionType" AS ENUM ( - 'RICH_TEXT', - 'GALLERY', - 'STATS', - 'DELIVERABLES', - 'LINK' -); - -CREATE TYPE "PortfolioAssetKind" AS ENUM ( - 'IMAGE', - 'DOCUMENT' -); - -CREATE TABLE "Category" ( - "id" TEXT NOT NULL, - "slug" TEXT NOT NULL, - "nameAr" TEXT NOT NULL, - "nameEn" TEXT NOT NULL, - "nameDe" TEXT NOT NULL, - "descriptionAr" TEXT NOT NULL, - "descriptionEn" TEXT NOT NULL, - "descriptionDe" TEXT NOT NULL, - "sortOrder" INTEGER NOT NULL DEFAULT 0, - "isActive" BOOLEAN NOT NULL DEFAULT true, - "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updatedAt" TIMESTAMP(3) NOT NULL, - - CONSTRAINT "Category_pkey" PRIMARY KEY ("id") -); - -CREATE TABLE "PortfolioProject" ( - "id" TEXT NOT NULL, - "categoryId" TEXT NOT NULL, - "slug" TEXT NOT NULL, - "titleAr" TEXT NOT NULL, - "titleEn" TEXT NOT NULL, - "titleDe" TEXT NOT NULL, - "summaryAr" TEXT NOT NULL, - "summaryEn" TEXT NOT NULL, - "summaryDe" TEXT NOT NULL, - "clientName" TEXT NOT NULL, - "projectYear" INTEGER NOT NULL, - "serviceLabelAr" TEXT NOT NULL, - "serviceLabelEn" TEXT NOT NULL, - "serviceLabelDe" TEXT NOT NULL, - "previewUrl" TEXT, - "coverImagePath" TEXT, - "isFeatured" BOOLEAN NOT NULL DEFAULT false, - "isPublished" BOOLEAN NOT NULL DEFAULT false, - "publishedAt" TIMESTAMP(3), - "sortOrder" INTEGER NOT NULL DEFAULT 0, - "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updatedAt" TIMESTAMP(3) NOT NULL, - - CONSTRAINT "PortfolioProject_pkey" PRIMARY KEY ("id") -); - -CREATE TABLE "PortfolioSection" ( - "id" TEXT NOT NULL, - "projectId" TEXT NOT NULL, - "type" "PortfolioSectionType" NOT NULL, - "titleAr" TEXT NOT NULL, - "titleEn" TEXT NOT NULL, - "titleDe" TEXT NOT NULL, - "bodyAr" TEXT NOT NULL, - "bodyEn" TEXT NOT NULL, - "bodyDe" TEXT NOT NULL, - "imagePath" TEXT, - "linkUrl" TEXT, - "sortOrder" INTEGER NOT NULL DEFAULT 0, - "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updatedAt" TIMESTAMP(3) NOT NULL, - - CONSTRAINT "PortfolioSection_pkey" PRIMARY KEY ("id") -); - -CREATE TABLE "PortfolioAsset" ( - "id" TEXT NOT NULL, - "projectId" TEXT NOT NULL, - "kind" "PortfolioAssetKind" NOT NULL, - "filePath" TEXT NOT NULL, - "altAr" TEXT NOT NULL, - "altEn" TEXT NOT NULL, - "altDe" TEXT NOT NULL, - "sortOrder" INTEGER NOT NULL DEFAULT 0, - "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updatedAt" TIMESTAMP(3) NOT NULL, - - CONSTRAINT "PortfolioAsset_pkey" PRIMARY KEY ("id") -); - -CREATE UNIQUE INDEX "Category_slug_key" ON "Category"("slug"); -CREATE UNIQUE INDEX "PortfolioProject_slug_key" ON "PortfolioProject"("slug"); -CREATE INDEX "PortfolioProject_categoryId_isPublished_sortOrder_idx" ON "PortfolioProject"("categoryId", "isPublished", "sortOrder"); -CREATE INDEX "PortfolioProject_isPublished_sortOrder_idx" ON "PortfolioProject"("isPublished", "sortOrder"); -CREATE INDEX "PortfolioSection_projectId_sortOrder_idx" ON "PortfolioSection"("projectId", "sortOrder"); -CREATE INDEX "PortfolioAsset_projectId_sortOrder_idx" ON "PortfolioAsset"("projectId", "sortOrder"); - -ALTER TABLE "PortfolioProject" -ADD CONSTRAINT "PortfolioProject_categoryId_fkey" -FOREIGN KEY ("categoryId") REFERENCES "Category"("id") -ON DELETE RESTRICT ON UPDATE CASCADE; - -ALTER TABLE "PortfolioSection" -ADD CONSTRAINT "PortfolioSection_projectId_fkey" -FOREIGN KEY ("projectId") REFERENCES "PortfolioProject"("id") -ON DELETE CASCADE ON UPDATE CASCADE; - -ALTER TABLE "PortfolioAsset" -ADD CONSTRAINT "PortfolioAsset_projectId_fkey" -FOREIGN KEY ("projectId") REFERENCES "PortfolioProject"("id") -ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/prisma/migrations/20260307183000_add_media_library/migration.sql b/prisma/migrations/20260307183000_add_media_library/migration.sql deleted file mode 100644 index ad9139d..0000000 --- a/prisma/migrations/20260307183000_add_media_library/migration.sql +++ /dev/null @@ -1,44 +0,0 @@ -CREATE TYPE "MediaSource" AS ENUM ('UPLOAD', 'EXTERNAL'); - -CREATE TYPE "MediaKind" AS ENUM ('IMAGE', 'DOCUMENT'); - -CREATE TYPE "MediaUsageType" AS ENUM ('PORTFOLIO_COVER', 'PORTFOLIO_SECTION', 'PORTFOLIO_ASSET', 'GENERIC'); - -CREATE TABLE "MediaAsset" ( - "id" TEXT NOT NULL, - "source" "MediaSource" NOT NULL, - "kind" "MediaKind" NOT NULL, - "url" TEXT NOT NULL, - "fileName" TEXT NOT NULL, - "label" TEXT NOT NULL, - "altText" TEXT, - "mimeType" TEXT, - "size" INTEGER, - "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updatedAt" TIMESTAMP(3) NOT NULL, - - CONSTRAINT "MediaAsset_pkey" PRIMARY KEY ("id") -); - -CREATE TABLE "MediaUsage" ( - "id" TEXT NOT NULL, - "assetId" TEXT NOT NULL, - "usageType" "MediaUsageType" NOT NULL, - "entityType" TEXT NOT NULL, - "entityId" TEXT NOT NULL, - "fieldKey" TEXT NOT NULL, - "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updatedAt" TIMESTAMP(3) NOT NULL, - - CONSTRAINT "MediaUsage_pkey" PRIMARY KEY ("id") -); - -CREATE INDEX "MediaAsset_kind_createdAt_idx" ON "MediaAsset"("kind", "createdAt"); - -CREATE INDEX "MediaUsage_assetId_idx" ON "MediaUsage"("assetId"); - -CREATE INDEX "MediaUsage_entityType_entityId_idx" ON "MediaUsage"("entityType", "entityId"); - -CREATE UNIQUE INDEX "MediaUsage_usageType_entityType_entityId_fieldKey_key" ON "MediaUsage"("usageType", "entityType", "entityId", "fieldKey"); - -ALTER TABLE "MediaUsage" ADD CONSTRAINT "MediaUsage_assetId_fkey" FOREIGN KEY ("assetId") REFERENCES "MediaAsset"("id") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/prisma/migrations/20260311120000_add_portfolio_project_view_mode/migration.sql b/prisma/migrations/20260311120000_add_portfolio_project_view_mode/migration.sql deleted file mode 100644 index a4ff63d..0000000 --- a/prisma/migrations/20260311120000_add_portfolio_project_view_mode/migration.sql +++ /dev/null @@ -1,4 +0,0 @@ -CREATE TYPE "PortfolioProjectViewMode" AS ENUM ('GRID', 'STORY', 'CASE_STUDY'); - -ALTER TABLE "PortfolioProject" -ADD COLUMN "viewMode" "PortfolioProjectViewMode" NOT NULL DEFAULT 'GRID'; diff --git a/prisma/migrations/migration_lock.toml b/prisma/migrations/migration_lock.toml deleted file mode 100644 index 044d57c..0000000 --- a/prisma/migrations/migration_lock.toml +++ /dev/null @@ -1,3 +0,0 @@ -# Please do not edit this file manually -# It should be added in your version-control system (e.g., Git) -provider = "postgresql" diff --git a/prisma/schema.prisma b/prisma/schema.prisma deleted file mode 100644 index ddc5624..0000000 --- a/prisma/schema.prisma +++ /dev/null @@ -1,168 +0,0 @@ -generator client { - provider = "prisma-client-js" -} - -datasource db { - provider = "postgresql" -} - -model AppConfig { - id String @id @default(cuid()) - key String @unique - value String - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt -} - -enum PortfolioSectionType { - RICH_TEXT - GALLERY - STATS - DELIVERABLES - LINK -} - -enum PortfolioAssetKind { - IMAGE - DOCUMENT -} - -enum PortfolioProjectViewMode { - GRID - STORY - CASE_STUDY -} - -enum MediaSource { - UPLOAD - EXTERNAL -} - -enum MediaKind { - IMAGE - DOCUMENT -} - -enum MediaUsageType { - PORTFOLIO_COVER - PORTFOLIO_SECTION - PORTFOLIO_ASSET - GENERIC -} - -model Category { - id String @id @default(cuid()) - slug String @unique - nameAr String - nameEn String - nameDe String - descriptionAr String - descriptionEn String - descriptionDe String - sortOrder Int @default(0) - isActive Boolean @default(true) - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - projects PortfolioProject[] -} - -model PortfolioProject { - id String @id @default(cuid()) - categoryId String - slug String @unique - viewMode PortfolioProjectViewMode @default(GRID) - titleAr String - titleEn String - titleDe String - summaryAr String - summaryEn String - summaryDe String - clientName String - projectYear Int - serviceLabelAr String - serviceLabelEn String - serviceLabelDe String - previewUrl String? - coverImagePath String? - isFeatured Boolean @default(false) - isPublished Boolean @default(false) - publishedAt DateTime? - sortOrder Int @default(0) - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - category Category @relation(fields: [categoryId], references: [id], onDelete: Restrict) - sections PortfolioSection[] - assets PortfolioAsset[] - - @@index([categoryId, isPublished, sortOrder]) - @@index([isPublished, sortOrder]) -} - -model PortfolioSection { - id String @id @default(cuid()) - projectId String - type PortfolioSectionType - titleAr String - titleEn String - titleDe String - bodyAr String - bodyEn String - bodyDe String - imagePath String? - linkUrl String? - sortOrder Int @default(0) - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - project PortfolioProject @relation(fields: [projectId], references: [id], onDelete: Cascade) - - @@index([projectId, sortOrder]) -} - -model PortfolioAsset { - id String @id @default(cuid()) - projectId String - kind PortfolioAssetKind - filePath String - altAr String - altEn String - altDe String - sortOrder Int @default(0) - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - project PortfolioProject @relation(fields: [projectId], references: [id], onDelete: Cascade) - - @@index([projectId, sortOrder]) -} - -model MediaAsset { - id String @id @default(cuid()) - source MediaSource - kind MediaKind - url String - fileName String - label String - altText String? - mimeType String? - size Int? - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - usages MediaUsage[] - - @@index([kind, createdAt]) -} - -model MediaUsage { - id String @id @default(cuid()) - assetId String - usageType MediaUsageType - entityType String - entityId String - fieldKey String - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - asset MediaAsset @relation(fields: [assetId], references: [id], onDelete: Cascade) - - @@unique([usageType, entityType, entityId, fieldKey]) - @@index([assetId]) - @@index([entityType, entityId]) -} diff --git a/prisma/seed.js b/prisma/seed.js deleted file mode 100644 index 2115e02..0000000 --- a/prisma/seed.js +++ /dev/null @@ -1,605 +0,0 @@ -const { PrismaPg } = require("@prisma/adapter-pg"); -const { PrismaClient } = require("@prisma/client"); -const { Pool } = require("pg"); - -const connectionString = - process.env.DATABASE_URL || - "postgresql://postgres:postgres@localhost:5432/moh_sass?schema=public"; - -const pool = new Pool({ connectionString }); -const prisma = new PrismaClient({ adapter: new PrismaPg(pool) }); - -async function upsertMediaAsset(input) { - const existing = await prisma.mediaAsset.findFirst({ - where: { - label: input.label, - url: input.url, - }, - }); - - if (existing) { - return prisma.mediaAsset.update({ - where: { id: existing.id }, - data: input, - }); - } - - return prisma.mediaAsset.create({ - data: input, - }); -} - -async function syncProjectContent(projectId, sections, assets) { - await prisma.portfolioSection.deleteMany({ - where: { projectId }, - }); - - await prisma.portfolioAsset.deleteMany({ - where: { projectId }, - }); - - const createdSections = []; - - for (const section of sections) { - const createdSection = await prisma.portfolioSection.create({ - data: { - projectId, - ...section, - }, - }); - - createdSections.push(createdSection); - } - - const createdAssets = []; - - for (const asset of assets) { - const createdAsset = await prisma.portfolioAsset.create({ - data: { - projectId, - ...asset, - }, - }); - - createdAssets.push(createdAsset); - } - - return { createdSections, createdAssets }; -} - -async function syncProjectMediaUsages(projectId, mediaMap) { - await prisma.mediaUsage.deleteMany({ - where: { - entityType: "portfolio-project", - entityId: projectId, - }, - }); - - const usages = []; - - if (mediaMap.coverAssetId) { - usages.push({ - assetId: mediaMap.coverAssetId, - usageType: "PORTFOLIO_COVER", - entityType: "portfolio-project", - entityId: projectId, - fieldKey: "cover", - }); - } - - for (const sectionUsage of mediaMap.sectionUsages) { - usages.push({ - assetId: sectionUsage.assetId, - usageType: "PORTFOLIO_SECTION", - entityType: "portfolio-project", - entityId: projectId, - fieldKey: sectionUsage.fieldKey, - }); - } - - for (const assetUsage of mediaMap.assetUsages) { - usages.push({ - assetId: assetUsage.assetId, - usageType: "PORTFOLIO_ASSET", - entityType: "portfolio-project", - entityId: projectId, - fieldKey: assetUsage.fieldKey, - }); - } - - if (usages.length > 0) { - await prisma.mediaUsage.createMany({ data: usages }); - } -} - -async function main() { - await prisma.mediaUsage.deleteMany({ - where: { - entityType: "portfolio-project", - }, - }); - - await prisma.portfolioSection.deleteMany(); - await prisma.portfolioAsset.deleteMany(); - await prisma.portfolioProject.deleteMany(); - await prisma.category.deleteMany(); - - await prisma.appConfig.upsert({ - where: { key: "siteName" }, - update: { value: "moh-sass" }, - create: { key: "siteName", value: "moh-sass" }, - }); - - await prisma.appConfig.upsert({ - where: { key: "site_settings" }, - update: { - value: JSON.stringify({ - titleTemplate: "{pageTitle} | moh-sass", - locales: { - ar: { - siteName: "moh-sass", - titleTemplate: "{pageTitle} | {siteName}", - siteDescription: "Multilingual Next.js base project", - subhead: "", - }, - en: { - siteName: "moh-sass", - titleTemplate: "{pageTitle} | {siteName}", - siteDescription: "Multilingual Next.js base project", - subhead: "", - }, - de: { - siteName: "moh-sass", - titleTemplate: "{pageTitle} | {siteName}", - siteDescription: "Multilingual Next.js base project", - subhead: "", - }, - }, - }), - }, - create: { - key: "site_settings", - value: JSON.stringify({ - titleTemplate: "{pageTitle} | moh-sass", - locales: { - ar: { - siteName: "moh-sass", - titleTemplate: "{pageTitle} | {siteName}", - siteDescription: "Multilingual Next.js base project", - subhead: "", - }, - en: { - siteName: "moh-sass", - titleTemplate: "{pageTitle} | {siteName}", - siteDescription: "Multilingual Next.js base project", - subhead: "", - }, - de: { - siteName: "moh-sass", - titleTemplate: "{pageTitle} | {siteName}", - siteDescription: "Multilingual Next.js base project", - subhead: "", - }, - }, - }), - }, - }); - - const brandCategory = await prisma.category.upsert({ - where: { slug: "branding" }, - update: { - nameAr: "الهوية البصرية", - nameEn: "Branding", - nameDe: "Branding", - descriptionAr: "مشاريع هوية بصرية وشعارات وأنظمة علامة.", - descriptionEn: "Brand identity, logo, and design system work.", - descriptionDe: "Branding, Logos und visuelle Systeme.", - sortOrder: 1, - isActive: true, - }, - create: { - slug: "branding", - nameAr: "الهوية البصرية", - nameEn: "Branding", - nameDe: "Branding", - descriptionAr: "مشاريع هوية بصرية وشعارات وأنظمة علامة.", - descriptionEn: "Brand identity, logo, and design system work.", - descriptionDe: "Branding, Logos und visuelle Systeme.", - sortOrder: 1, - isActive: true, - }, - }); - - const webCategory = await prisma.category.upsert({ - where: { slug: "web-experiences" }, - update: { - nameAr: "تجارب الويب", - nameEn: "Web Experiences", - nameDe: "Web Experiences", - descriptionAr: "مواقع وصفحات هبوط وتجارب رقمية سريعة.", - descriptionEn: "Websites, landing pages, and digital experiences.", - descriptionDe: "Webseiten, Landingpages und digitale Erlebnisse.", - sortOrder: 2, - isActive: true, - }, - create: { - slug: "web-experiences", - nameAr: "تجارب الويب", - nameEn: "Web Experiences", - nameDe: "Web Experiences", - descriptionAr: "مواقع وصفحات هبوط وتجارب رقمية سريعة.", - descriptionEn: "Websites, landing pages, and digital experiences.", - descriptionDe: "Webseiten, Landingpages und digitale Erlebnisse.", - sortOrder: 2, - isActive: true, - }, - }); - - const commerceCategory = await prisma.category.upsert({ - where: { slug: "commerce" }, - update: { - nameAr: "التجارة الرقمية", - nameEn: "Commerce", - nameDe: "Commerce", - descriptionAr: "متاجر وتجارب شراء رقمية مع تركيز على الوضوح والتحويل.", - descriptionEn: "Commerce experiences with a focus on clarity and conversion.", - descriptionDe: "Commerce-Projekte mit Fokus auf Klarheit und Conversion.", - sortOrder: 3, - isActive: true, - }, - create: { - slug: "commerce", - nameAr: "التجارة الرقمية", - nameEn: "Commerce", - nameDe: "Commerce", - descriptionAr: "متاجر وتجارب شراء رقمية مع تركيز على الوضوح والتحويل.", - descriptionEn: "Commerce experiences with a focus on clarity and conversion.", - descriptionDe: "Commerce-Projekte mit Fokus auf Klarheit und Conversion.", - sortOrder: 3, - isActive: true, - }, - }); - - const gridCover = await upsertMediaAsset({ - source: "UPLOAD", - kind: "IMAGE", - url: "/uploads/portfolio/demo-cover.svg", - fileName: "demo-cover.svg", - label: "Portfolio Grid Cover", - altText: "Portfolio Grid Cover", - mimeType: "image/svg+xml", - size: 1024, - }); - - const storyCover = await upsertMediaAsset({ - source: "UPLOAD", - kind: "IMAGE", - url: "/uploads/portfolio/demo-cover.svg", - fileName: "demo-cover.svg", - label: "Portfolio Story Cover", - altText: "Portfolio Story Cover", - mimeType: "image/svg+xml", - size: 1024, - }); - - const caseStudyCover = await upsertMediaAsset({ - source: "UPLOAD", - kind: "IMAGE", - url: "/uploads/portfolio/demo-cover.svg", - fileName: "demo-cover.svg", - label: "Portfolio Case Study Cover", - altText: "Portfolio Case Study Cover", - mimeType: "image/svg+xml", - size: 1024, - }); - - const projects = [ - { - slug: "grid-product-launch", - categoryId: commerceCategory.id, - viewMode: "GRID", - titleAr: "إطلاق منتج رقمي", - titleEn: "Grid Product Launch", - titleDe: "Grid Product Launch", - summaryAr: "مثال عرض شبكي لمشروع سريع مع أقسام قصيرة وأصول داعمة.", - summaryEn: "Grid view example for a fast product launch page.", - summaryDe: "Grid-Ansicht als Beispiel fuer einen schnellen Produktlaunch.", - clientName: "Launch Studio", - projectYear: 2026, - serviceLabelAr: "تجربة إطلاق", - serviceLabelEn: "Launch Experience", - serviceLabelDe: "Launch Experience", - previewUrl: "https://example.com/preview/grid-product-launch", - coverImagePath: gridCover.url, - isFeatured: true, - isPublished: true, - publishedAt: new Date("2026-01-12T09:00:00.000Z"), - sortOrder: 1, - coverAssetId: gridCover.id, - sections: [ - { - type: "RICH_TEXT", - titleAr: "الفكرة", - titleEn: "Concept", - titleDe: "Konzept", - bodyAr: "واجهة سريعة لعرض المنتج والتركيز على الرسالة الأساسية.", - bodyEn: "A fast modular presentation focused on the main launch message.", - bodyDe: "Eine schnelle modulare Darstellung mit Fokus auf die Hauptbotschaft.", - imagePath: null, - linkUrl: null, - sortOrder: 0, - mediaAssetId: null, - }, - { - type: "GALLERY", - titleAr: "الصورة الرئيسية", - titleEn: "Hero Visual", - titleDe: "Hero Visual", - bodyAr: "", - bodyEn: "", - bodyDe: "", - imagePath: gridCover.url, - linkUrl: null, - sortOrder: 1, - mediaAssetId: gridCover.id, - }, - ], - assets: [ - { - kind: "IMAGE", - filePath: gridCover.url, - altAr: "غلاف مشروع Grid", - altEn: "Grid project cover", - altDe: "Grid Projekt Cover", - sortOrder: 0, - mediaAssetId: gridCover.id, - }, - ], - }, - { - slug: "campaign-site", - categoryId: webCategory.id, - viewMode: "STORY", - titleAr: "موقع حملة", - titleEn: "Campaign Site", - titleDe: "Campaign Site", - summaryAr: "مثال عرض قصصي لمشروع ويب مع تسلسل سردي أوضح.", - summaryEn: "Story view example for a launch campaign website.", - summaryDe: "Story-Ansicht als Beispiel fuer eine Kampagnenseite.", - clientName: "Launch Client", - projectYear: 2024, - serviceLabelAr: "موقع تسويقي", - serviceLabelEn: "Marketing Website", - serviceLabelDe: "Marketing Website", - previewUrl: "https://example.com/preview/campaign-site", - coverImagePath: storyCover.url, - isFeatured: false, - isPublished: true, - publishedAt: new Date("2024-09-05T09:00:00.000Z"), - sortOrder: 2, - coverAssetId: storyCover.id, - sections: [ - { - type: "RICH_TEXT", - titleAr: "السياق", - titleEn: "Context", - titleDe: "Kontext", - bodyAr: "الحملة احتاجت صفحة مرنة وسريعة تتبدل بين أكثر من مرحلة.", - bodyEn: "The campaign needed a flexible page that could adapt across phases.", - bodyDe: "Die Kampagne brauchte eine flexible Seite fuer mehrere Phasen.", - imagePath: null, - linkUrl: null, - sortOrder: 0, - mediaAssetId: null, - }, - { - type: "GALLERY", - titleAr: "العرض البصري", - titleEn: "Visual Flow", - titleDe: "Visueller Ablauf", - bodyAr: "", - bodyEn: "", - bodyDe: "", - imagePath: storyCover.url, - linkUrl: null, - sortOrder: 1, - mediaAssetId: storyCover.id, - }, - { - type: "LINK", - titleAr: "المعاينة", - titleEn: "Preview", - titleDe: "Vorschau", - bodyAr: "رابط العرض المباشر.", - bodyEn: "Direct preview link.", - bodyDe: "Direkter Vorschau-Link.", - imagePath: null, - linkUrl: "https://example.com/preview/campaign-site", - sortOrder: 2, - mediaAssetId: null, - }, - ], - assets: [ - { - kind: "IMAGE", - filePath: storyCover.url, - altAr: "غلاف مشروع Story", - altEn: "Story project cover", - altDe: "Story Projekt Cover", - sortOrder: 0, - mediaAssetId: storyCover.id, - }, - ], - }, - { - slug: "brand-redesign", - categoryId: brandCategory.id, - viewMode: "CASE_STUDY", - titleAr: "إعادة تصميم الهوية", - titleEn: "Brand Redesign", - titleDe: "Brand Redesign", - summaryAr: "مثال عرض دراسة حالة يركز على التحدي والحل والنتيجة.", - summaryEn: "Case study example focused on challenge, solution, and outcome.", - summaryDe: "Case-Study-Ansicht mit Fokus auf Herausforderung, Loesung und Ergebnis.", - clientName: "Studio Client", - projectYear: 2025, - serviceLabelAr: "هوية بصرية", - serviceLabelEn: "Brand Identity", - serviceLabelDe: "Brand Identity", - previewUrl: "https://example.com/preview/brand-redesign", - coverImagePath: caseStudyCover.url, - isFeatured: true, - isPublished: true, - publishedAt: new Date("2025-01-10T09:00:00.000Z"), - sortOrder: 3, - coverAssetId: caseStudyCover.id, - sections: [ - { - type: "RICH_TEXT", - titleAr: "التحدي", - titleEn: "Challenge", - titleDe: "Herausforderung", - bodyAr: "كان المطلوب تحديث الهوية بدون خسارة التعرف البصري الحالي.", - bodyEn: "The brief required a refreshed identity without losing recognition.", - bodyDe: "Die Marke sollte modernisiert werden, ohne die Wiedererkennbarkeit zu verlieren.", - imagePath: null, - linkUrl: null, - sortOrder: 0, - mediaAssetId: null, - }, - { - type: "RICH_TEXT", - titleAr: "الحل", - titleEn: "Solution", - titleDe: "Loesung", - bodyAr: "تم بناء نظام مرئي أوضح مع قواعد استخدام قابلة للتوسع.", - bodyEn: "A clearer visual system with scalable usage rules was created.", - bodyDe: "Es wurde ein klareres visuelles System mit skalierbaren Regeln aufgebaut.", - imagePath: null, - linkUrl: null, - sortOrder: 1, - mediaAssetId: null, - }, - { - type: "GALLERY", - titleAr: "التنفيذ البصري", - titleEn: "Visual Execution", - titleDe: "Visuelle Umsetzung", - bodyAr: "", - bodyEn: "", - bodyDe: "", - imagePath: caseStudyCover.url, - linkUrl: null, - sortOrder: 2, - mediaAssetId: caseStudyCover.id, - }, - ], - assets: [ - { - kind: "IMAGE", - filePath: caseStudyCover.url, - altAr: "غلاف مشروع Case Study", - altEn: "Case study project cover", - altDe: "Case Study Projekt Cover", - sortOrder: 0, - mediaAssetId: caseStudyCover.id, - }, - ], - }, - ]; - - for (const projectConfig of projects) { - const project = await prisma.portfolioProject.upsert({ - where: { slug: projectConfig.slug }, - update: { - categoryId: projectConfig.categoryId, - viewMode: projectConfig.viewMode, - titleAr: projectConfig.titleAr, - titleEn: projectConfig.titleEn, - titleDe: projectConfig.titleDe, - summaryAr: projectConfig.summaryAr, - summaryEn: projectConfig.summaryEn, - summaryDe: projectConfig.summaryDe, - clientName: projectConfig.clientName, - projectYear: projectConfig.projectYear, - serviceLabelAr: projectConfig.serviceLabelAr, - serviceLabelEn: projectConfig.serviceLabelEn, - serviceLabelDe: projectConfig.serviceLabelDe, - previewUrl: projectConfig.previewUrl, - coverImagePath: projectConfig.coverImagePath, - isFeatured: projectConfig.isFeatured, - isPublished: projectConfig.isPublished, - publishedAt: projectConfig.publishedAt, - sortOrder: projectConfig.sortOrder, - }, - create: { - slug: projectConfig.slug, - categoryId: projectConfig.categoryId, - viewMode: projectConfig.viewMode, - titleAr: projectConfig.titleAr, - titleEn: projectConfig.titleEn, - titleDe: projectConfig.titleDe, - summaryAr: projectConfig.summaryAr, - summaryEn: projectConfig.summaryEn, - summaryDe: projectConfig.summaryDe, - clientName: projectConfig.clientName, - projectYear: projectConfig.projectYear, - serviceLabelAr: projectConfig.serviceLabelAr, - serviceLabelEn: projectConfig.serviceLabelEn, - serviceLabelDe: projectConfig.serviceLabelDe, - previewUrl: projectConfig.previewUrl, - coverImagePath: projectConfig.coverImagePath, - isFeatured: projectConfig.isFeatured, - isPublished: projectConfig.isPublished, - publishedAt: projectConfig.publishedAt, - sortOrder: projectConfig.sortOrder, - }, - }); - - const created = await syncProjectContent(project.id, projectConfig.sections.map((section) => ({ - type: section.type, - titleAr: section.titleAr, - titleEn: section.titleEn, - titleDe: section.titleDe, - bodyAr: section.bodyAr, - bodyEn: section.bodyEn, - bodyDe: section.bodyDe, - imagePath: section.imagePath, - linkUrl: section.linkUrl, - sortOrder: section.sortOrder, - })), projectConfig.assets.map((asset) => ({ - kind: asset.kind, - filePath: asset.filePath, - altAr: asset.altAr, - altEn: asset.altEn, - altDe: asset.altDe, - sortOrder: asset.sortOrder, - }))); - - await syncProjectMediaUsages(project.id, { - coverAssetId: projectConfig.coverAssetId, - sectionUsages: created.createdSections - .map((sectionRow, index) => ({ - fieldKey: sectionRow.id, - assetId: projectConfig.sections[index]?.mediaAssetId, - })) - .filter((entry) => entry.assetId), - assetUsages: created.createdAssets - .map((assetRow, index) => ({ - fieldKey: assetRow.id, - assetId: projectConfig.assets[index]?.mediaAssetId, - })) - .filter((entry) => entry.assetId), - }); - } -} - -main() - .catch((error) => { - console.error("Seed failed:", error); - process.exit(1); - }) - .finally(async () => { - await prisma.$disconnect(); - await pool.end(); - });