From 0e32d4004a215b4ade270a9fd1c0801ec0a10344 Mon Sep 17 00:00:00 2001 From: barsa Date: Thu, 11 Dec 2025 14:00:54 +0900 Subject: [PATCH] Remove sharp dependency and related configurations from package.json and pnpm-lock.yaml; update Prisma configuration for database connection handling and streamline database commands in BFF application. Clean up unused migration files and adjust schema.prisma to reflect recent model changes. --- apps/bff/package.json | 8 +- .../20250816070027_init/migration.sql | 166 ------------ .../migration.sql | 17 -- .../migration.sql | 14 - .../20251211045819_init/migration.sql | 195 ++++++++++++++ .../migration.sql | 9 - apps/bff/prisma/prisma.config.ts | 29 +-- apps/bff/prisma/schema.prisma | 106 ++------ apps/bff/src/app/bootstrap.ts | 13 +- apps/portal/next-env.d.ts | 2 +- docs/assets/portal-swimlane.svg | 122 --------- docs/portal/PORTAL-NONTECH-PRESENTATION.md | 9 - package.json | 1 - pnpm-lock.yaml | 242 ------------------ scripts/tools/svg2png.mjs | 41 --- 15 files changed, 232 insertions(+), 742 deletions(-) delete mode 100644 apps/bff/prisma/migrations/20250816070027_init/migration.sql delete mode 100644 apps/bff/prisma/migrations/20250920073101_initial_setup/migration.sql delete mode 100644 apps/bff/prisma/migrations/20251003184411_remove_cached_profile_fields/migration.sql create mode 100644 apps/bff/prisma/migrations/20251211045819_init/migration.sql delete mode 100644 apps/bff/prisma/migrations/20251211113451_force_password_reset_argon2/migration.sql delete mode 100644 docs/assets/portal-swimlane.svg delete mode 100644 scripts/tools/svg2png.mjs diff --git a/apps/bff/package.json b/apps/bff/package.json index 11ecc5c7..e6ce246f 100644 --- a/apps/bff/package.json +++ b/apps/bff/package.json @@ -24,10 +24,10 @@ "type-check": "tsc --project tsconfig.json --noEmit", "type-check:watch": "tsc --project tsconfig.json --noEmit --watch", "clean": "rm -rf dist tsconfig.build.tsbuildinfo", - "db:migrate": "prisma migrate dev --schema=prisma/schema.prisma", - "db:generate": "prisma generate --schema=prisma/schema.prisma", - "db:studio": "prisma studio --schema=prisma/schema.prisma", - "db:reset": "prisma migrate reset --schema=prisma/schema.prisma", + "db:migrate": "prisma migrate dev --config prisma/prisma.config.ts", + "db:generate": "prisma generate --config prisma/prisma.config.ts", + "db:studio": "prisma studio --port 5555 --config prisma/prisma.config.ts", + "db:reset": "prisma migrate reset --config prisma/prisma.config.ts", "db:seed": "tsx prisma/seed.ts" }, "dependencies": { diff --git a/apps/bff/prisma/migrations/20250816070027_init/migration.sql b/apps/bff/prisma/migrations/20250816070027_init/migration.sql deleted file mode 100644 index 0cd4ac7b..00000000 --- a/apps/bff/prisma/migrations/20250816070027_init/migration.sql +++ /dev/null @@ -1,166 +0,0 @@ --- CreateEnum -CREATE TYPE "public"."UserRole" AS ENUM ('USER', 'ADMIN'); - --- CreateEnum -CREATE TYPE "public"."JobStatus" AS ENUM ('PENDING', 'PROCESSING', 'COMPLETED', 'FAILED', 'RETRYING'); - --- CreateEnum -CREATE TYPE "public"."AuditAction" AS ENUM ('LOGIN_SUCCESS', 'LOGIN_FAILED', 'LOGOUT', 'SIGNUP', 'PASSWORD_RESET', 'PASSWORD_CHANGE', 'ACCOUNT_LOCKED', 'ACCOUNT_UNLOCKED', 'PROFILE_UPDATE', 'MFA_ENABLED', 'MFA_DISABLED', 'API_ACCESS'); - --- CreateTable -CREATE TABLE "public"."users" ( - "id" TEXT NOT NULL, - "email" TEXT NOT NULL, - "password_hash" TEXT, - "first_name" TEXT, - "last_name" TEXT, - "company" TEXT, - "phone" TEXT, - "role" "public"."UserRole" NOT NULL DEFAULT 'USER', - "mfa_secret" TEXT, - "email_verified" BOOLEAN NOT NULL DEFAULT false, - "failed_login_attempts" INTEGER NOT NULL DEFAULT 0, - "locked_until" TIMESTAMP(3), - "last_login_at" TIMESTAMP(3), - "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updated_at" TIMESTAMP(3) NOT NULL, - - CONSTRAINT "users_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "public"."id_mappings" ( - "user_id" TEXT NOT NULL, - "whmcs_client_id" INTEGER NOT NULL, - "sf_account_id" TEXT, - "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updated_at" TIMESTAMP(3) NOT NULL, - - CONSTRAINT "id_mappings_pkey" PRIMARY KEY ("user_id") -); - --- CreateTable -CREATE TABLE "public"."invoices_mirror" ( - "invoice_id" INTEGER NOT NULL, - "user_id" TEXT NOT NULL, - "number" TEXT NOT NULL, - "status" TEXT NOT NULL, - "amount_cents" INTEGER NOT NULL, - "currency" CHAR(3) NOT NULL, - "due_date" DATE, - "issued_at" TIMESTAMP(3), - "paid_at" TIMESTAMP(3), - "updated_at" TIMESTAMP(3) NOT NULL, - - CONSTRAINT "invoices_mirror_pkey" PRIMARY KEY ("invoice_id") -); - --- CreateTable -CREATE TABLE "public"."subscriptions_mirror" ( - "service_id" INTEGER NOT NULL, - "user_id" TEXT NOT NULL, - "product_name" TEXT NOT NULL, - "domain" TEXT, - "cycle" TEXT NOT NULL, - "status" TEXT NOT NULL, - "next_due" TIMESTAMP(3), - "amount_cents" INTEGER NOT NULL, - "currency" CHAR(3) NOT NULL, - "registered_at" TIMESTAMP(3) NOT NULL, - "updated_at" TIMESTAMP(3) NOT NULL, - - CONSTRAINT "subscriptions_mirror_pkey" PRIMARY KEY ("service_id") -); - --- CreateTable -CREATE TABLE "public"."idempotency_keys" ( - "key" TEXT NOT NULL, - "user_id" TEXT NOT NULL, - "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - - CONSTRAINT "idempotency_keys_pkey" PRIMARY KEY ("key") -); - --- CreateTable -CREATE TABLE "public"."jobs" ( - "id" TEXT NOT NULL, - "name" TEXT NOT NULL, - "data" JSONB NOT NULL, - "status" "public"."JobStatus" NOT NULL DEFAULT 'PENDING', - "attempts" INTEGER NOT NULL DEFAULT 0, - "max_retries" INTEGER NOT NULL DEFAULT 3, - "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updated_at" TIMESTAMP(3) NOT NULL, - "processed_at" TIMESTAMP(3), - "failed_at" TIMESTAMP(3), - "error" TEXT, - - CONSTRAINT "jobs_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "public"."audit_logs" ( - "id" TEXT NOT NULL, - "user_id" TEXT, - "action" "public"."AuditAction" NOT NULL, - "resource" TEXT, - "details" JSONB, - "ip_address" TEXT, - "user_agent" TEXT, - "success" BOOLEAN NOT NULL DEFAULT true, - "error" TEXT, - "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - - CONSTRAINT "audit_logs_pkey" PRIMARY KEY ("id") -); - --- CreateIndex -CREATE UNIQUE INDEX "users_email_key" ON "public"."users"("email"); - --- CreateIndex -CREATE UNIQUE INDEX "id_mappings_whmcs_client_id_key" ON "public"."id_mappings"("whmcs_client_id"); - --- CreateIndex -CREATE INDEX "invoices_mirror_user_id_status_idx" ON "public"."invoices_mirror"("user_id", "status"); - --- CreateIndex -CREATE INDEX "invoices_mirror_user_id_due_date_idx" ON "public"."invoices_mirror"("user_id", "due_date"); - --- CreateIndex -CREATE INDEX "subscriptions_mirror_user_id_status_idx" ON "public"."subscriptions_mirror"("user_id", "status"); - --- CreateIndex -CREATE INDEX "subscriptions_mirror_user_id_next_due_idx" ON "public"."subscriptions_mirror"("user_id", "next_due"); - --- CreateIndex -CREATE INDEX "idempotency_keys_user_id_idx" ON "public"."idempotency_keys"("user_id"); - --- CreateIndex -CREATE INDEX "idempotency_keys_created_at_idx" ON "public"."idempotency_keys"("created_at"); - --- CreateIndex -CREATE INDEX "jobs_status_created_at_idx" ON "public"."jobs"("status", "created_at"); - --- CreateIndex -CREATE INDEX "audit_logs_user_id_action_idx" ON "public"."audit_logs"("user_id", "action"); - --- CreateIndex -CREATE INDEX "audit_logs_action_created_at_idx" ON "public"."audit_logs"("action", "created_at"); - --- CreateIndex -CREATE INDEX "audit_logs_created_at_idx" ON "public"."audit_logs"("created_at"); - --- AddForeignKey -ALTER TABLE "public"."id_mappings" ADD CONSTRAINT "id_mappings_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE CASCADE ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "public"."invoices_mirror" ADD CONSTRAINT "invoices_mirror_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE CASCADE ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "public"."subscriptions_mirror" ADD CONSTRAINT "subscriptions_mirror_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE CASCADE ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "public"."idempotency_keys" ADD CONSTRAINT "idempotency_keys_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE CASCADE ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "public"."audit_logs" ADD CONSTRAINT "audit_logs_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE SET NULL ON UPDATE CASCADE; diff --git a/apps/bff/prisma/migrations/20250920073101_initial_setup/migration.sql b/apps/bff/prisma/migrations/20250920073101_initial_setup/migration.sql deleted file mode 100644 index cc52c63e..00000000 --- a/apps/bff/prisma/migrations/20250920073101_initial_setup/migration.sql +++ /dev/null @@ -1,17 +0,0 @@ --- CreateTable -CREATE TABLE "public"."sim_usage_daily" ( - "id" SERIAL NOT NULL, - "account" TEXT NOT NULL, - "date" DATE NOT NULL, - "usageMb" DOUBLE PRECISION NOT NULL, - "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updated_at" TIMESTAMP(3) NOT NULL, - - CONSTRAINT "sim_usage_daily_pkey" PRIMARY KEY ("id") -); - --- CreateIndex -CREATE INDEX "sim_usage_daily_account_date_idx" ON "public"."sim_usage_daily"("account", "date"); - --- CreateIndex -CREATE UNIQUE INDEX "sim_usage_daily_account_date_key" ON "public"."sim_usage_daily"("account", "date"); diff --git a/apps/bff/prisma/migrations/20251003184411_remove_cached_profile_fields/migration.sql b/apps/bff/prisma/migrations/20251003184411_remove_cached_profile_fields/migration.sql deleted file mode 100644 index 162af18d..00000000 --- a/apps/bff/prisma/migrations/20251003184411_remove_cached_profile_fields/migration.sql +++ /dev/null @@ -1,14 +0,0 @@ --- Safety migration to ensure cached profile columns stay removed. --- Some environments created an empty migration folder, so deploys failed --- because Prisma could not find migration.sql. This keeps the history intact --- while acting as a no-op when the columns already dropped earlier. - -ALTER TABLE "public"."users" - DROP COLUMN IF EXISTS "first_name", - DROP COLUMN IF EXISTS "last_name", - DROP COLUMN IF EXISTS "company", - DROP COLUMN IF EXISTS "phone"; - -COMMENT ON TABLE "public"."users" - IS 'Portal authentication only. Profile data fetched from WHMCS via IdMapping.'; - diff --git a/apps/bff/prisma/migrations/20251211045819_init/migration.sql b/apps/bff/prisma/migrations/20251211045819_init/migration.sql new file mode 100644 index 00000000..68e0852a --- /dev/null +++ b/apps/bff/prisma/migrations/20251211045819_init/migration.sql @@ -0,0 +1,195 @@ +-- CreateEnum +CREATE TYPE "UserRole" AS ENUM ('USER', 'ADMIN'); + +-- CreateEnum +CREATE TYPE "AuditAction" AS ENUM ('LOGIN_SUCCESS', 'LOGIN_FAILED', 'LOGOUT', 'SIGNUP', 'PASSWORD_RESET', 'PASSWORD_CHANGE', 'ACCOUNT_LOCKED', 'ACCOUNT_UNLOCKED', 'PROFILE_UPDATE', 'MFA_ENABLED', 'MFA_DISABLED', 'API_ACCESS', 'SYSTEM_MAINTENANCE'); + +-- CreateEnum +CREATE TYPE "SmsType" AS ENUM ('DOMESTIC', 'INTERNATIONAL'); + +-- CreateTable +CREATE TABLE "users" ( + "id" TEXT NOT NULL, + "email" TEXT NOT NULL, + "password_hash" TEXT, + "role" "UserRole" NOT NULL DEFAULT 'USER', + "mfa_secret" TEXT, + "email_verified" BOOLEAN NOT NULL DEFAULT false, + "failed_login_attempts" INTEGER NOT NULL DEFAULT 0, + "locked_until" TIMESTAMP(3), + "last_login_at" TIMESTAMP(3), + "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "users_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "id_mappings" ( + "user_id" TEXT NOT NULL, + "whmcs_client_id" INTEGER NOT NULL, + "sf_account_id" TEXT, + "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "id_mappings_pkey" PRIMARY KEY ("user_id") +); + +-- CreateTable +CREATE TABLE "audit_logs" ( + "id" TEXT NOT NULL, + "user_id" TEXT, + "action" "AuditAction" NOT NULL, + "resource" TEXT, + "details" JSONB, + "ip_address" TEXT, + "user_agent" TEXT, + "success" BOOLEAN NOT NULL DEFAULT true, + "error" TEXT, + "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "audit_logs_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "sim_usage_daily" ( + "id" SERIAL NOT NULL, + "account" TEXT NOT NULL, + "date" DATE NOT NULL, + "usageMb" DOUBLE PRECISION NOT NULL, + "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "sim_usage_daily_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "sim_voice_options" ( + "account" TEXT NOT NULL, + "voice_mail_enabled" BOOLEAN NOT NULL DEFAULT false, + "call_waiting_enabled" BOOLEAN NOT NULL DEFAULT false, + "international_roaming_enabled" BOOLEAN NOT NULL DEFAULT false, + "network_type" TEXT NOT NULL DEFAULT '4G', + "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "sim_voice_options_pkey" PRIMARY KEY ("account") +); + +-- CreateTable +CREATE TABLE "sim_call_history_domestic" ( + "id" TEXT NOT NULL, + "account" TEXT NOT NULL, + "call_date" DATE NOT NULL, + "call_time" TEXT NOT NULL, + "called_to" TEXT NOT NULL, + "location" TEXT, + "duration_sec" INTEGER NOT NULL, + "charge_yen" INTEGER NOT NULL, + "month" TEXT NOT NULL, + "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "sim_call_history_domestic_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "sim_call_history_international" ( + "id" TEXT NOT NULL, + "account" TEXT NOT NULL, + "call_date" DATE NOT NULL, + "start_time" TEXT NOT NULL, + "stop_time" TEXT, + "country" TEXT, + "called_to" TEXT NOT NULL, + "duration_sec" INTEGER NOT NULL, + "charge_yen" INTEGER NOT NULL, + "month" TEXT NOT NULL, + "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "sim_call_history_international_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "sim_sms_history" ( + "id" TEXT NOT NULL, + "account" TEXT NOT NULL, + "sms_date" DATE NOT NULL, + "sms_time" TEXT NOT NULL, + "sent_to" TEXT NOT NULL, + "sms_type" "SmsType" NOT NULL DEFAULT 'DOMESTIC', + "month" TEXT NOT NULL, + "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "sim_sms_history_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "sim_history_imports" ( + "id" TEXT NOT NULL, + "month" TEXT NOT NULL, + "talk_file" TEXT, + "sms_file" TEXT, + "talk_records" INTEGER NOT NULL DEFAULT 0, + "sms_records" INTEGER NOT NULL DEFAULT 0, + "imported_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "status" TEXT NOT NULL DEFAULT 'completed', + + CONSTRAINT "sim_history_imports_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE UNIQUE INDEX "users_email_key" ON "users"("email"); + +-- CreateIndex +CREATE UNIQUE INDEX "id_mappings_whmcs_client_id_key" ON "id_mappings"("whmcs_client_id"); + +-- CreateIndex +CREATE INDEX "audit_logs_user_id_action_idx" ON "audit_logs"("user_id", "action"); + +-- CreateIndex +CREATE INDEX "audit_logs_action_created_at_idx" ON "audit_logs"("action", "created_at"); + +-- CreateIndex +CREATE INDEX "audit_logs_created_at_idx" ON "audit_logs"("created_at"); + +-- CreateIndex +CREATE INDEX "sim_usage_daily_account_date_idx" ON "sim_usage_daily"("account", "date"); + +-- CreateIndex +CREATE UNIQUE INDEX "sim_usage_daily_account_date_key" ON "sim_usage_daily"("account", "date"); + +-- CreateIndex +CREATE INDEX "sim_call_history_domestic_account_month_idx" ON "sim_call_history_domestic"("account", "month"); + +-- CreateIndex +CREATE INDEX "sim_call_history_domestic_account_call_date_idx" ON "sim_call_history_domestic"("account", "call_date"); + +-- CreateIndex +CREATE UNIQUE INDEX "sim_call_history_domestic_account_call_date_call_time_calle_key" ON "sim_call_history_domestic"("account", "call_date", "call_time", "called_to"); + +-- CreateIndex +CREATE INDEX "sim_call_history_international_account_month_idx" ON "sim_call_history_international"("account", "month"); + +-- CreateIndex +CREATE INDEX "sim_call_history_international_account_call_date_idx" ON "sim_call_history_international"("account", "call_date"); + +-- CreateIndex +CREATE UNIQUE INDEX "sim_call_history_international_account_call_date_start_time_key" ON "sim_call_history_international"("account", "call_date", "start_time", "called_to"); + +-- CreateIndex +CREATE INDEX "sim_sms_history_account_month_idx" ON "sim_sms_history"("account", "month"); + +-- CreateIndex +CREATE INDEX "sim_sms_history_account_sms_date_idx" ON "sim_sms_history"("account", "sms_date"); + +-- CreateIndex +CREATE UNIQUE INDEX "sim_sms_history_account_sms_date_sms_time_sent_to_key" ON "sim_sms_history"("account", "sms_date", "sms_time", "sent_to"); + +-- CreateIndex +CREATE UNIQUE INDEX "sim_history_imports_month_key" ON "sim_history_imports"("month"); + +-- AddForeignKey +ALTER TABLE "id_mappings" ADD CONSTRAINT "id_mappings_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "audit_logs" ADD CONSTRAINT "audit_logs_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE SET NULL ON UPDATE CASCADE; diff --git a/apps/bff/prisma/migrations/20251211113451_force_password_reset_argon2/migration.sql b/apps/bff/prisma/migrations/20251211113451_force_password_reset_argon2/migration.sql deleted file mode 100644 index 331b3d27..00000000 --- a/apps/bff/prisma/migrations/20251211113451_force_password_reset_argon2/migration.sql +++ /dev/null @@ -1,9 +0,0 @@ --- Force password reset for all users due to migration from bcrypt to argon2 --- bcrypt hashes are incompatible with argon2, so all users must reset their passwords - --- Set all password hashes to NULL, which will require users to go through password reset flow -UPDATE "User" SET password_hash = NULL WHERE password_hash IS NOT NULL; - --- Log the migration for audit purposes (optional - create entry in audit_log if table exists) --- This is a data migration, not a schema change - diff --git a/apps/bff/prisma/prisma.config.ts b/apps/bff/prisma/prisma.config.ts index 4e42188b..cb1cf50c 100644 --- a/apps/bff/prisma/prisma.config.ts +++ b/apps/bff/prisma/prisma.config.ts @@ -1,29 +1,22 @@ -import { defineConfig } from "prisma"; -import { PrismaPg } from "@prisma/adapter-pg"; -import { Pool } from "pg"; +import { defineConfig } from "prisma/config"; + +// Default connection for local development (matches docker/dev/docker-compose.yml) +const DEFAULT_DATABASE_URL = "postgresql://dev:dev@localhost:5432/portal_dev"; /** * Prisma 7 Configuration * - * This configuration file is required for Prisma 7+ where the datasource URL - * is no longer specified in schema.prisma. Instead, connection configuration - * is provided here for migrations and in the PrismaClient constructor for runtime. + * Centralized configuration for Prisma CLI commands (migrate, studio, etc.) + * Runtime adapter is configured in PrismaService, not here. * - * @see https://pris.ly/d/config-datasource - * @see https://pris.ly/d/prisma7-client-config + * @see https://www.prisma.io/docs/orm/reference/prisma-config-reference + * @see https://www.prisma.io/docs/orm/prisma-schema/overview/data-sources */ export default defineConfig({ - earlyAccess: true, schema: "./schema.prisma", - migrate: { - adapter: async () => { - const connectionString = process.env.DATABASE_URL; - if (!connectionString) { - throw new Error("DATABASE_URL environment variable is required for migrations"); - } - const pool = new Pool({ connectionString }); - return new PrismaPg(pool); - }, + // Database connection for CLI commands (migrate, studio, etc.) + datasource: { + url: process.env.DATABASE_URL || DEFAULT_DATABASE_URL, }, }); diff --git a/apps/bff/prisma/schema.prisma b/apps/bff/prisma/schema.prisma index 64afc3f9..9c127bbf 100644 --- a/apps/bff/prisma/schema.prisma +++ b/apps/bff/prisma/schema.prisma @@ -15,30 +15,27 @@ generator client { datasource db { provider = "postgresql" - // Note: Prisma 7+ requires connection URL to be passed via adapter in PrismaClient constructor - // See prisma.config.ts for migration configuration + // Prisma 7+: URL is configured via prisma.config.ts for migrations + // and via --url flag for studio. Runtime uses the adapter in PrismaClient. } model User { - id String @id @default(uuid()) - email String @unique - passwordHash String? @map("password_hash") - role UserRole @default(USER) - + id String @id @default(uuid()) + email String @unique + passwordHash String? @map("password_hash") + role UserRole @default(USER) + // Authentication state only - profile data comes from WHMCS - mfaSecret String? @map("mfa_secret") - emailVerified Boolean @default(false) @map("email_verified") - failedLoginAttempts Int @default(0) @map("failed_login_attempts") - lockedUntil DateTime? @map("locked_until") - lastLoginAt DateTime? @map("last_login_at") - - createdAt DateTime @default(now()) @map("created_at") - updatedAt DateTime @updatedAt @map("updated_at") + mfaSecret String? @map("mfa_secret") + emailVerified Boolean @default(false) @map("email_verified") + failedLoginAttempts Int @default(0) @map("failed_login_attempts") + lockedUntil DateTime? @map("locked_until") + lastLoginAt DateTime? @map("last_login_at") + + createdAt DateTime @default(now()) @map("created_at") + updatedAt DateTime @updatedAt @map("updated_at") auditLogs AuditLog[] idMapping IdMapping? - idempotencyKeys IdempotencyKey[] - invoicesMirror InvoiceMirror[] - subscriptionsMirror SubscriptionMirror[] @@map("users") } @@ -54,71 +51,6 @@ model IdMapping { @@map("id_mappings") } -model InvoiceMirror { - invoiceId Int @id @map("invoice_id") - userId String @map("user_id") - number String - status String - amountCents Int @map("amount_cents") - currency String @db.Char(3) - dueDate DateTime? @map("due_date") @db.Date - issuedAt DateTime? @map("issued_at") - paidAt DateTime? @map("paid_at") - updatedAt DateTime @updatedAt @map("updated_at") - user User @relation(fields: [userId], references: [id], onDelete: Cascade) - - @@index([userId, status]) - @@index([userId, dueDate]) - @@map("invoices_mirror") -} - -model SubscriptionMirror { - serviceId Int @id @map("service_id") - userId String @map("user_id") - productName String @map("product_name") - domain String? - cycle String - status String - nextDue DateTime? @map("next_due") - amountCents Int @map("amount_cents") - currency String @db.Char(3) - registeredAt DateTime @map("registered_at") - updatedAt DateTime @updatedAt @map("updated_at") - user User @relation(fields: [userId], references: [id], onDelete: Cascade) - - @@index([userId, status]) - @@index([userId, nextDue]) - @@map("subscriptions_mirror") -} - -model IdempotencyKey { - key String @id - userId String @map("user_id") - createdAt DateTime @default(now()) @map("created_at") - user User @relation(fields: [userId], references: [id], onDelete: Cascade) - - @@index([userId]) - @@index([createdAt]) - @@map("idempotency_keys") -} - -model Job { - id String @id @default(uuid()) - name String - data Json - status JobStatus @default(PENDING) - attempts Int @default(0) - maxRetries Int @default(3) @map("max_retries") - createdAt DateTime @default(now()) @map("created_at") - updatedAt DateTime @updatedAt @map("updated_at") - processedAt DateTime? @map("processed_at") - failedAt DateTime? @map("failed_at") - error String? - - @@index([status, createdAt]) - @@map("jobs") -} - model AuditLog { id String @id @default(uuid()) userId String? @map("user_id") @@ -143,14 +75,6 @@ enum UserRole { ADMIN } -enum JobStatus { - PENDING - PROCESSING - COMPLETED - FAILED - RETRYING -} - enum AuditAction { LOGIN_SUCCESS LOGIN_FAILED diff --git a/apps/bff/src/app/bootstrap.ts b/apps/bff/src/app/bootstrap.ts index b0b24461..2254f72b 100644 --- a/apps/bff/src/app/bootstrap.ts +++ b/apps/bff/src/app/bootstrap.ts @@ -1,10 +1,10 @@ import type { INestApplication } from "@nestjs/common"; import { ConfigService } from "@nestjs/config"; import { NestFactory } from "@nestjs/core"; +import type { NestExpressApplication } from "@nestjs/platform-express"; import { Logger } from "nestjs-pino"; import helmet from "helmet"; import cookieParser from "cookie-parser"; -import * as express from "express"; import type { CookieOptions, Response, NextFunction, Request } from "express"; /* eslint-disable @typescript-eslint/no-namespace */ @@ -22,12 +22,15 @@ import { UnifiedExceptionFilter } from "../core/http/exception.filter.js"; import { AppModule } from "../app.module.js"; export async function bootstrap(): Promise { - const app = await NestFactory.create(AppModule, { + const app = await NestFactory.create(AppModule, { bufferLogs: true, - // bodyParser is enabled by default in NestJS rawBody: true, // Enable raw body access for debugging }); + // Configure body parser limits via Express adapter + app.useBodyParser("json", { limit: "10mb" }); + app.useBodyParser("urlencoded", { extended: true, limit: "10mb" }); + // Set Pino as the logger app.useLogger(app.get(Logger)); @@ -63,10 +66,6 @@ export async function bootstrap(): Promise { expressInstance.disable("x-powered-by"); } - // Configure JSON body parser with proper limits - app.use(express.json({ limit: "10mb" })); - app.use(express.urlencoded({ extended: true, limit: "10mb" })); - // Enhanced cookie parser with security options app.use(cookieParser()); diff --git a/apps/portal/next-env.d.ts b/apps/portal/next-env.d.ts index 9edff1c7..c4b7818f 100644 --- a/apps/portal/next-env.d.ts +++ b/apps/portal/next-env.d.ts @@ -1,6 +1,6 @@ /// /// -import "./.next/types/routes.d.ts"; +import "./.next/dev/types/routes.d.ts"; // NOTE: This file should not be edited // see https://nextjs.org/docs/app/api-reference/config/typescript for more information. diff --git a/docs/assets/portal-swimlane.svg b/docs/assets/portal-swimlane.svg deleted file mode 100644 index 6562ba8f..00000000 --- a/docs/assets/portal-swimlane.svg +++ /dev/null @@ -1,122 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - Customer - Portal (BFF) - Salesforce - WHMCS - - - - 1) Sign Up & Link Account - Provide Customer Number - - - 2) Add Payment Method - Secure SSO to WHMCS - - - 3) Browse Catalog - Select Plan & Add‑ons - - - 7) View Invoices - Pay via SSO - - - - Validate mapping & address - Create WHMCS client - - - Open WHMCS payment page - (SSO) - - - Create Order in SF - (snapshot address) - - - Provision in WHMCS - after approval - - - Show subs & invoices - - - - Review & Approve - Order - - - - Store Payment Methods - - - Create Services & Invoice - - - - - Customer Number - - - - - SSO Payment Page - - - - Selected items - - - - Order + address - - - - Approved - - - - AddOrder + Accept - - - - Subscriptions + Invoices - - - - - - - Pay Invoice (SSO) - - - Legend: SSO = secure single sign-on; SF = Salesforce - diff --git a/docs/portal/PORTAL-NONTECH-PRESENTATION.md b/docs/portal/PORTAL-NONTECH-PRESENTATION.md index 30becf6f..f7a33731 100644 --- a/docs/portal/PORTAL-NONTECH-PRESENTATION.md +++ b/docs/portal/PORTAL-NONTECH-PRESENTATION.md @@ -150,15 +150,6 @@ Use these slide-ready bullets to explain, in plain language, how our portal work Tip: Pair these slides with a simple swimlane diagram (Customer, Portal, Salesforce, WHMCS) showing the hand‑offs at order and activation. -References for deeper reading (optional for presenters) - -- Address flow details: docs/ADDRESS_SYSTEM.md -- Technical overview: docs/PORTAL-INTEGRATION-OVERVIEW.md - -Diagram (PNG/SVG for slides) - -- Swimlane visual: docs/assets/portal-swimlane.svg - --- ## 11) Migration (Moving Existing WHMCS Users Into the Portal) diff --git a/package.json b/package.json index f69f9c10..0a7763b1 100644 --- a/package.json +++ b/package.json @@ -58,7 +58,6 @@ "globals": "^16.5.0", "husky": "^9.1.7", "prettier": "^3.7.4", - "sharp": "^0.33.5", "tsx": "^4.21.0", "typescript": "^5.9.3", "typescript-eslint": "^8.49.0" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 25f5f1b0..9a797f89 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -43,9 +43,6 @@ importers: prettier: specifier: ^3.7.4 version: 3.7.4 - sharp: - specifier: ^0.33.5 - version: 0.33.5 tsx: specifier: ^4.21.0 version: 4.21.0 @@ -775,65 +772,33 @@ packages: resolution: {integrity: sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw==} engines: {node: '>=18'} - '@img/sharp-darwin-arm64@0.33.5': - resolution: {integrity: sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [arm64] - os: [darwin] - '@img/sharp-darwin-arm64@0.34.5': resolution: {integrity: sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [darwin] - '@img/sharp-darwin-x64@0.33.5': - resolution: {integrity: sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [x64] - os: [darwin] - '@img/sharp-darwin-x64@0.34.5': resolution: {integrity: sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [darwin] - '@img/sharp-libvips-darwin-arm64@1.0.4': - resolution: {integrity: sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==} - cpu: [arm64] - os: [darwin] - '@img/sharp-libvips-darwin-arm64@1.2.4': resolution: {integrity: sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==} cpu: [arm64] os: [darwin] - '@img/sharp-libvips-darwin-x64@1.0.4': - resolution: {integrity: sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==} - cpu: [x64] - os: [darwin] - '@img/sharp-libvips-darwin-x64@1.2.4': resolution: {integrity: sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==} cpu: [x64] os: [darwin] - '@img/sharp-libvips-linux-arm64@1.0.4': - resolution: {integrity: sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==} - cpu: [arm64] - os: [linux] - '@img/sharp-libvips-linux-arm64@1.2.4': resolution: {integrity: sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==} cpu: [arm64] os: [linux] - '@img/sharp-libvips-linux-arm@1.0.5': - resolution: {integrity: sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==} - cpu: [arm] - os: [linux] - '@img/sharp-libvips-linux-arm@1.2.4': resolution: {integrity: sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==} cpu: [arm] @@ -849,64 +814,32 @@ packages: cpu: [riscv64] os: [linux] - '@img/sharp-libvips-linux-s390x@1.0.4': - resolution: {integrity: sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==} - cpu: [s390x] - os: [linux] - '@img/sharp-libvips-linux-s390x@1.2.4': resolution: {integrity: sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==} cpu: [s390x] os: [linux] - '@img/sharp-libvips-linux-x64@1.0.4': - resolution: {integrity: sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==} - cpu: [x64] - os: [linux] - '@img/sharp-libvips-linux-x64@1.2.4': resolution: {integrity: sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==} cpu: [x64] os: [linux] - '@img/sharp-libvips-linuxmusl-arm64@1.0.4': - resolution: {integrity: sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==} - cpu: [arm64] - os: [linux] - '@img/sharp-libvips-linuxmusl-arm64@1.2.4': resolution: {integrity: sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==} cpu: [arm64] os: [linux] - '@img/sharp-libvips-linuxmusl-x64@1.0.4': - resolution: {integrity: sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==} - cpu: [x64] - os: [linux] - '@img/sharp-libvips-linuxmusl-x64@1.2.4': resolution: {integrity: sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==} cpu: [x64] os: [linux] - '@img/sharp-linux-arm64@0.33.5': - resolution: {integrity: sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [arm64] - os: [linux] - '@img/sharp-linux-arm64@0.34.5': resolution: {integrity: sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [linux] - '@img/sharp-linux-arm@0.33.5': - resolution: {integrity: sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [arm] - os: [linux] - '@img/sharp-linux-arm@0.34.5': resolution: {integrity: sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} @@ -925,59 +858,30 @@ packages: cpu: [riscv64] os: [linux] - '@img/sharp-linux-s390x@0.33.5': - resolution: {integrity: sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [s390x] - os: [linux] - '@img/sharp-linux-s390x@0.34.5': resolution: {integrity: sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [s390x] os: [linux] - '@img/sharp-linux-x64@0.33.5': - resolution: {integrity: sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [x64] - os: [linux] - '@img/sharp-linux-x64@0.34.5': resolution: {integrity: sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [linux] - '@img/sharp-linuxmusl-arm64@0.33.5': - resolution: {integrity: sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [arm64] - os: [linux] - '@img/sharp-linuxmusl-arm64@0.34.5': resolution: {integrity: sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [linux] - '@img/sharp-linuxmusl-x64@0.33.5': - resolution: {integrity: sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [x64] - os: [linux] - '@img/sharp-linuxmusl-x64@0.34.5': resolution: {integrity: sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [linux] - '@img/sharp-wasm32@0.33.5': - resolution: {integrity: sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [wasm32] - '@img/sharp-wasm32@0.34.5': resolution: {integrity: sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} @@ -989,24 +893,12 @@ packages: cpu: [arm64] os: [win32] - '@img/sharp-win32-ia32@0.33.5': - resolution: {integrity: sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [ia32] - os: [win32] - '@img/sharp-win32-ia32@0.34.5': resolution: {integrity: sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [ia32] os: [win32] - '@img/sharp-win32-x64@0.33.5': - resolution: {integrity: sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [x64] - os: [win32] - '@img/sharp-win32-x64@0.34.5': resolution: {integrity: sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} @@ -2836,13 +2728,6 @@ packages: color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} - color-string@1.9.1: - resolution: {integrity: sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==} - - color@4.2.3: - resolution: {integrity: sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==} - engines: {node: '>=12.5.0'} - colorette@2.0.20: resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==} @@ -3828,9 +3713,6 @@ packages: is-arrayish@0.2.1: resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} - is-arrayish@0.3.4: - resolution: {integrity: sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA==} - is-async-function@2.1.1: resolution: {integrity: sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==} engines: {node: '>= 0.4'} @@ -5265,10 +5147,6 @@ packages: setprototypeof@1.2.0: resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} - sharp@0.33.5: - resolution: {integrity: sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - sharp@0.34.5: resolution: {integrity: sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} @@ -5304,9 +5182,6 @@ packages: resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} engines: {node: '>=14'} - simple-swizzle@0.2.4: - resolution: {integrity: sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw==} - sirv@2.0.4: resolution: {integrity: sha512-94Bdh3cC2PKrbgSOUqTiGPWVZeSiXfKOVZNJniWoqrWrRkB1CJzBU3NEbiTsPcYy1lDsANA/THzS+9WBiy5nfQ==} engines: {node: '>= 10'} @@ -6449,47 +6324,25 @@ snapshots: '@img/colour@1.0.0': optional: true - '@img/sharp-darwin-arm64@0.33.5': - optionalDependencies: - '@img/sharp-libvips-darwin-arm64': 1.0.4 - optional: true - '@img/sharp-darwin-arm64@0.34.5': optionalDependencies: '@img/sharp-libvips-darwin-arm64': 1.2.4 optional: true - '@img/sharp-darwin-x64@0.33.5': - optionalDependencies: - '@img/sharp-libvips-darwin-x64': 1.0.4 - optional: true - '@img/sharp-darwin-x64@0.34.5': optionalDependencies: '@img/sharp-libvips-darwin-x64': 1.2.4 optional: true - '@img/sharp-libvips-darwin-arm64@1.0.4': - optional: true - '@img/sharp-libvips-darwin-arm64@1.2.4': optional: true - '@img/sharp-libvips-darwin-x64@1.0.4': - optional: true - '@img/sharp-libvips-darwin-x64@1.2.4': optional: true - '@img/sharp-libvips-linux-arm64@1.0.4': - optional: true - '@img/sharp-libvips-linux-arm64@1.2.4': optional: true - '@img/sharp-libvips-linux-arm@1.0.5': - optional: true - '@img/sharp-libvips-linux-arm@1.2.4': optional: true @@ -6499,45 +6352,23 @@ snapshots: '@img/sharp-libvips-linux-riscv64@1.2.4': optional: true - '@img/sharp-libvips-linux-s390x@1.0.4': - optional: true - '@img/sharp-libvips-linux-s390x@1.2.4': optional: true - '@img/sharp-libvips-linux-x64@1.0.4': - optional: true - '@img/sharp-libvips-linux-x64@1.2.4': optional: true - '@img/sharp-libvips-linuxmusl-arm64@1.0.4': - optional: true - '@img/sharp-libvips-linuxmusl-arm64@1.2.4': optional: true - '@img/sharp-libvips-linuxmusl-x64@1.0.4': - optional: true - '@img/sharp-libvips-linuxmusl-x64@1.2.4': optional: true - '@img/sharp-linux-arm64@0.33.5': - optionalDependencies: - '@img/sharp-libvips-linux-arm64': 1.0.4 - optional: true - '@img/sharp-linux-arm64@0.34.5': optionalDependencies: '@img/sharp-libvips-linux-arm64': 1.2.4 optional: true - '@img/sharp-linux-arm@0.33.5': - optionalDependencies: - '@img/sharp-libvips-linux-arm': 1.0.5 - optional: true - '@img/sharp-linux-arm@0.34.5': optionalDependencies: '@img/sharp-libvips-linux-arm': 1.2.4 @@ -6553,51 +6384,26 @@ snapshots: '@img/sharp-libvips-linux-riscv64': 1.2.4 optional: true - '@img/sharp-linux-s390x@0.33.5': - optionalDependencies: - '@img/sharp-libvips-linux-s390x': 1.0.4 - optional: true - '@img/sharp-linux-s390x@0.34.5': optionalDependencies: '@img/sharp-libvips-linux-s390x': 1.2.4 optional: true - '@img/sharp-linux-x64@0.33.5': - optionalDependencies: - '@img/sharp-libvips-linux-x64': 1.0.4 - optional: true - '@img/sharp-linux-x64@0.34.5': optionalDependencies: '@img/sharp-libvips-linux-x64': 1.2.4 optional: true - '@img/sharp-linuxmusl-arm64@0.33.5': - optionalDependencies: - '@img/sharp-libvips-linuxmusl-arm64': 1.0.4 - optional: true - '@img/sharp-linuxmusl-arm64@0.34.5': optionalDependencies: '@img/sharp-libvips-linuxmusl-arm64': 1.2.4 optional: true - '@img/sharp-linuxmusl-x64@0.33.5': - optionalDependencies: - '@img/sharp-libvips-linuxmusl-x64': 1.0.4 - optional: true - '@img/sharp-linuxmusl-x64@0.34.5': optionalDependencies: '@img/sharp-libvips-linuxmusl-x64': 1.2.4 optional: true - '@img/sharp-wasm32@0.33.5': - dependencies: - '@emnapi/runtime': 1.7.1 - optional: true - '@img/sharp-wasm32@0.34.5': dependencies: '@emnapi/runtime': 1.7.1 @@ -6606,15 +6412,9 @@ snapshots: '@img/sharp-win32-arm64@0.34.5': optional: true - '@img/sharp-win32-ia32@0.33.5': - optional: true - '@img/sharp-win32-ia32@0.34.5': optional: true - '@img/sharp-win32-x64@0.33.5': - optional: true - '@img/sharp-win32-x64@0.34.5': optional: true @@ -8687,16 +8487,6 @@ snapshots: color-name@1.1.4: {} - color-string@1.9.1: - dependencies: - color-name: 1.1.4 - simple-swizzle: 0.2.4 - - color@4.2.3: - dependencies: - color-convert: 2.0.1 - color-string: 1.9.1 - colorette@2.0.20: {} combined-stream@1.0.8: @@ -9891,8 +9681,6 @@ snapshots: is-arrayish@0.2.1: {} - is-arrayish@0.3.4: {} - is-async-function@2.1.1: dependencies: async-function: 1.0.0 @@ -11531,32 +11319,6 @@ snapshots: setprototypeof@1.2.0: {} - sharp@0.33.5: - dependencies: - color: 4.2.3 - detect-libc: 2.1.2 - semver: 7.7.3 - optionalDependencies: - '@img/sharp-darwin-arm64': 0.33.5 - '@img/sharp-darwin-x64': 0.33.5 - '@img/sharp-libvips-darwin-arm64': 1.0.4 - '@img/sharp-libvips-darwin-x64': 1.0.4 - '@img/sharp-libvips-linux-arm': 1.0.5 - '@img/sharp-libvips-linux-arm64': 1.0.4 - '@img/sharp-libvips-linux-s390x': 1.0.4 - '@img/sharp-libvips-linux-x64': 1.0.4 - '@img/sharp-libvips-linuxmusl-arm64': 1.0.4 - '@img/sharp-libvips-linuxmusl-x64': 1.0.4 - '@img/sharp-linux-arm': 0.33.5 - '@img/sharp-linux-arm64': 0.33.5 - '@img/sharp-linux-s390x': 0.33.5 - '@img/sharp-linux-x64': 0.33.5 - '@img/sharp-linuxmusl-arm64': 0.33.5 - '@img/sharp-linuxmusl-x64': 0.33.5 - '@img/sharp-wasm32': 0.33.5 - '@img/sharp-win32-ia32': 0.33.5 - '@img/sharp-win32-x64': 0.33.5 - sharp@0.34.5: dependencies: '@img/colour': 1.0.0 @@ -11627,10 +11389,6 @@ snapshots: signal-exit@4.1.0: {} - simple-swizzle@0.2.4: - dependencies: - is-arrayish: 0.3.4 - sirv@2.0.4: dependencies: '@polka/url': 1.0.0-next.29 diff --git a/scripts/tools/svg2png.mjs b/scripts/tools/svg2png.mjs deleted file mode 100644 index 914c5405..00000000 --- a/scripts/tools/svg2png.mjs +++ /dev/null @@ -1,41 +0,0 @@ -#!/usr/bin/env node -/** - * Simple SVG -> PNG converter using sharp. - * Usage: node svg2png.mjs - */ -import fs from "node:fs/promises"; -import path from "node:path"; -import sharp from "sharp"; - -async function main() { - const [, , inPath, outPath, wArg, hArg] = process.argv; - if (!inPath || !outPath || !wArg || !hArg) { - console.error("Usage: node svg2png.mjs "); - process.exit(1); - } - const width = Number(wArg); - const height = Number(hArg); - if (!width || !height) { - console.error("Width and height must be numbers"); - process.exit(1); - } - - const absIn = path.resolve(inPath); - const absOut = path.resolve(outPath); - const svg = await fs.readFile(absIn); - - // Render with background white to avoid transparency issues in slides - const png = await sharp(svg, { density: 300 }) - .resize(width, height, { fit: "contain", background: { r: 255, g: 255, b: 255, alpha: 1 } }) - .png({ compressionLevel: 9 }) - .toBuffer(); - - await fs.mkdir(path.dirname(absOut), { recursive: true }); - await fs.writeFile(absOut, png); - console.log(`Wrote ${absOut} (${width}x${height})`); -} - -main().catch(err => { - console.error("svg2png failed:", err?.message || err); - process.exit(1); -});