diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f8ff609..76114a4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -37,8 +37,5 @@ jobs: - name: Build web run: pnpm --filter @soly/web build - - name: Generate Prisma client - run: pnpm --filter @soly/backend db:generate - - name: Type check (backend) run: pnpm --filter @soly/backend typecheck \ No newline at end of file diff --git a/apps/backend/package.json b/apps/backend/package.json index 4ea15eb..d8ac811 100644 --- a/apps/backend/package.json +++ b/apps/backend/package.json @@ -8,22 +8,16 @@ "build": "tsc", "start": "node dist/index.js", "typecheck": "tsc --noEmit", - "db:migrate": "prisma migrate dev", - "db:generate": "prisma generate", - "db:studio": "prisma studio", "dev:backend": "pnpm --filter @soly/backend dev", "build:backend": "pnpm --filter @soly/backend build" }, "dependencies": { - "@prisma/adapter-pg": "^7.6.0", - "@prisma/client": "^7.6.0", "bcrypt": "^6.0.0", "cors": "^2.8.6", "dotenv": "^17.4.0", "express": "^5.2.1", - "http-status-codes": "^2.3.0", "jsonwebtoken": "^9.0.3", - "pg": "^8.20.0" + "mongodb": "^7.2.0" }, "devDependencies": { "@types/bcrypt": "^6.0.0", @@ -31,8 +25,6 @@ "@types/express": "^5.0.6", "@types/jsonwebtoken": "^9.0.10", "@types/node": "^24.12.2", - "@types/pg": "^8.20.0", - "prisma": "^7.6.0", "tsx": "^4.21.0", "typescript": "~5.9.3" } diff --git a/apps/backend/prisma.config.ts b/apps/backend/prisma.config.ts deleted file mode 100644 index 20471ec..0000000 --- a/apps/backend/prisma.config.ts +++ /dev/null @@ -1,14 +0,0 @@ -// This file was generated by Prisma, and assumes you have installed the following: -// npm install --save-dev prisma dotenv -import 'dotenv/config'; -import { defineConfig } from 'prisma/config'; - -export default defineConfig({ - schema: 'prisma/schema.prisma', - migrations: { - path: 'prisma/migrations', - }, - datasource: { - url: process.env['DATABASE_URL'], - }, -}); diff --git a/apps/backend/prisma/migrations/20260403200037_init/migration.sql b/apps/backend/prisma/migrations/20260403200037_init/migration.sql deleted file mode 100644 index 58d54af..0000000 --- a/apps/backend/prisma/migrations/20260403200037_init/migration.sql +++ /dev/null @@ -1,18 +0,0 @@ --- CreateEnum -CREATE TYPE "Role" AS ENUM ('CUSTOMER', 'PROVIDER', 'ADMIN'); - --- CreateTable -CREATE TABLE "User" ( - "id" TEXT NOT NULL, - "email" TEXT NOT NULL, - "password" TEXT NOT NULL, - "name" TEXT NOT NULL, - "role" "Role" NOT NULL DEFAULT 'CUSTOMER', - "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updatedAt" TIMESTAMP(3) NOT NULL, - - CONSTRAINT "User_pkey" PRIMARY KEY ("id") -); - --- CreateIndex -CREATE UNIQUE INDEX "User_email_key" ON "User"("email"); diff --git a/apps/backend/prisma/migrations/20260403210108_add_business_service_appointment/migration.sql b/apps/backend/prisma/migrations/20260403210108_add_business_service_appointment/migration.sql deleted file mode 100644 index 2cd026f..0000000 --- a/apps/backend/prisma/migrations/20260403210108_add_business_service_appointment/migration.sql +++ /dev/null @@ -1,86 +0,0 @@ -/* - Warnings: - - - You are about to drop the column `email` on the `User` table. All the data in the column will be lost. - - A unique constraint covering the columns `[phoneNumber]` on the table `User` will be added. If there are existing duplicate values, this will fail. - - Added the required column `phoneNumber` to the `User` table without a default value. This is not possible if the table is not empty. - -*/ --- CreateEnum -CREATE TYPE "Status" AS ENUM ('CONFIRMED', 'COMPLETED', 'CANCELED', 'NOSHOW'); - --- DropIndex -DROP INDEX "User_email_key"; - --- AlterTable -ALTER TABLE "User" DROP COLUMN "email", -ADD COLUMN "phoneNumber" TEXT NOT NULL, -ADD COLUMN "worksAtId" TEXT; - --- CreateTable -CREATE TABLE "Business" ( - "id" TEXT NOT NULL, - "name" TEXT NOT NULL, - "phoneNumber" TEXT NOT NULL, - "description" TEXT NOT NULL, - "address" TEXT NOT NULL, - "logo" TEXT, - "ownerId" TEXT NOT NULL, - "instagram" TEXT, - "tiktok" TEXT, - "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updatedAt" TIMESTAMP(3) NOT NULL, - - CONSTRAINT "Business_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "Service" ( - "id" TEXT NOT NULL, - "name" TEXT NOT NULL, - "duration" INTEGER NOT NULL, - "price" INTEGER NOT NULL, - "businessId" TEXT NOT NULL, - "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updatedAt" TIMESTAMP(3) NOT NULL, - - CONSTRAINT "Service_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "Appointment" ( - "id" TEXT NOT NULL, - "customerId" TEXT NOT NULL, - "providerId" TEXT NOT NULL, - "time" TIMESTAMP(3) NOT NULL, - "serviceId" TEXT NOT NULL, - "status" "Status" NOT NULL DEFAULT 'CONFIRMED', - "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updatedAt" TIMESTAMP(3) NOT NULL, - - CONSTRAINT "Appointment_pkey" PRIMARY KEY ("id") -); - --- CreateIndex -CREATE UNIQUE INDEX "Business_ownerId_key" ON "Business"("ownerId"); - --- CreateIndex -CREATE UNIQUE INDEX "User_phoneNumber_key" ON "User"("phoneNumber"); - --- AddForeignKey -ALTER TABLE "User" ADD CONSTRAINT "User_worksAtId_fkey" FOREIGN KEY ("worksAtId") REFERENCES "Business"("id") ON DELETE SET NULL ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "Business" ADD CONSTRAINT "Business_ownerId_fkey" FOREIGN KEY ("ownerId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "Service" ADD CONSTRAINT "Service_businessId_fkey" FOREIGN KEY ("businessId") REFERENCES "Business"("id") ON DELETE RESTRICT ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "Appointment" ADD CONSTRAINT "Appointment_customerId_fkey" FOREIGN KEY ("customerId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "Appointment" ADD CONSTRAINT "Appointment_providerId_fkey" FOREIGN KEY ("providerId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "Appointment" ADD CONSTRAINT "Appointment_serviceId_fkey" FOREIGN KEY ("serviceId") REFERENCES "Service"("id") ON DELETE RESTRICT ON UPDATE CASCADE; diff --git a/apps/backend/prisma/migrations/20260403212016_add_indexes/migration.sql b/apps/backend/prisma/migrations/20260403212016_add_indexes/migration.sql deleted file mode 100644 index 487af01..0000000 --- a/apps/backend/prisma/migrations/20260403212016_add_indexes/migration.sql +++ /dev/null @@ -1,14 +0,0 @@ --- CreateIndex -CREATE INDEX "Appointment_providerId_time_idx" ON "Appointment"("providerId", "time"); - --- CreateIndex -CREATE INDEX "Appointment_customerId_time_idx" ON "Appointment"("customerId", "time"); - --- CreateIndex -CREATE INDEX "Appointment_serviceId_idx" ON "Appointment"("serviceId"); - --- CreateIndex -CREATE INDEX "Service_businessId_idx" ON "Service"("businessId"); - --- CreateIndex -CREATE INDEX "User_worksAtId_idx" ON "User"("worksAtId"); diff --git a/apps/backend/prisma/migrations/20260403212805_snapshot_service_data_on_appointment/migration.sql b/apps/backend/prisma/migrations/20260403212805_snapshot_service_data_on_appointment/migration.sql deleted file mode 100644 index 4d183d1..0000000 --- a/apps/backend/prisma/migrations/20260403212805_snapshot_service_data_on_appointment/migration.sql +++ /dev/null @@ -1,12 +0,0 @@ -/* - Warnings: - - - Added the required column `duration` to the `Appointment` table without a default value. This is not possible if the table is not empty. - - Added the required column `price` to the `Appointment` table without a default value. This is not possible if the table is not empty. - - Added the required column `serviceName` to the `Appointment` table without a default value. This is not possible if the table is not empty. - -*/ --- AlterTable -ALTER TABLE "Appointment" ADD COLUMN "duration" INTEGER NOT NULL, -ADD COLUMN "price" INTEGER NOT NULL, -ADD COLUMN "serviceName" TEXT NOT NULL; diff --git a/apps/backend/prisma/migrations/20260404150637_add_provider_service_relation/migration.sql b/apps/backend/prisma/migrations/20260404150637_add_provider_service_relation/migration.sql deleted file mode 100644 index 8c33911..0000000 --- a/apps/backend/prisma/migrations/20260404150637_add_provider_service_relation/migration.sql +++ /dev/null @@ -1,16 +0,0 @@ --- CreateTable -CREATE TABLE "_ServiceToUser" ( - "A" TEXT NOT NULL, - "B" TEXT NOT NULL, - - CONSTRAINT "_ServiceToUser_AB_pkey" PRIMARY KEY ("A","B") -); - --- CreateIndex -CREATE INDEX "_ServiceToUser_B_index" ON "_ServiceToUser"("B"); - --- AddForeignKey -ALTER TABLE "_ServiceToUser" ADD CONSTRAINT "_ServiceToUser_A_fkey" FOREIGN KEY ("A") REFERENCES "Service"("id") ON DELETE CASCADE ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "_ServiceToUser" ADD CONSTRAINT "_ServiceToUser_B_fkey" FOREIGN KEY ("B") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/apps/backend/prisma/migrations/20260408212027_add_compound_unique_phone_role/migration.sql b/apps/backend/prisma/migrations/20260408212027_add_compound_unique_phone_role/migration.sql deleted file mode 100644 index c062e5f..0000000 --- a/apps/backend/prisma/migrations/20260408212027_add_compound_unique_phone_role/migration.sql +++ /dev/null @@ -1,11 +0,0 @@ -/* - Warnings: - - - A unique constraint covering the columns `[phoneNumber,role]` on the table `User` will be added. If there are existing duplicate values, this will fail. - -*/ --- DropIndex -DROP INDEX "User_phoneNumber_key"; - --- CreateIndex -CREATE UNIQUE INDEX "User_phoneNumber_role_key" ON "User"("phoneNumber", "role"); diff --git a/apps/backend/prisma/migrations/20260413151449_remove_role_from_user/migration.sql b/apps/backend/prisma/migrations/20260413151449_remove_role_from_user/migration.sql deleted file mode 100644 index ee5af0c..0000000 --- a/apps/backend/prisma/migrations/20260413151449_remove_role_from_user/migration.sql +++ /dev/null @@ -1,18 +0,0 @@ -/* - Warnings: - - - You are about to drop the column `role` on the `User` table. All the data in the column will be lost. - - A unique constraint covering the columns `[phoneNumber]` on the table `User` will be added. If there are existing duplicate values, this will fail. - -*/ --- DropIndex -DROP INDEX "User_phoneNumber_role_key"; - --- AlterTable -ALTER TABLE "User" DROP COLUMN "role"; - --- DropEnum -DROP TYPE "Role"; - --- CreateIndex -CREATE UNIQUE INDEX "User_phoneNumber_key" ON "User"("phoneNumber"); diff --git a/apps/backend/prisma/migrations/20260413160000_split_name_into_first_last/migration.sql b/apps/backend/prisma/migrations/20260413160000_split_name_into_first_last/migration.sql deleted file mode 100644 index af78e48..0000000 --- a/apps/backend/prisma/migrations/20260413160000_split_name_into_first_last/migration.sql +++ /dev/null @@ -1,18 +0,0 @@ --- Split name into firstName and lastName -ALTER TABLE "User" ADD COLUMN "firstName" TEXT; -ALTER TABLE "User" ADD COLUMN "lastName" TEXT; - --- Migrate existing data: put everything before first space into firstName, rest into lastName -UPDATE "User" SET - "firstName" = SPLIT_PART("name", ' ', 1), - "lastName" = CASE - WHEN POSITION(' ' IN "name") > 0 THEN SUBSTRING("name" FROM POSITION(' ' IN "name") + 1) - ELSE '' - END; - --- Make columns required -ALTER TABLE "User" ALTER COLUMN "firstName" SET NOT NULL; -ALTER TABLE "User" ALTER COLUMN "lastName" SET NOT NULL; - --- Drop old column -ALTER TABLE "User" DROP COLUMN "name"; diff --git a/apps/backend/prisma/migrations/20260421123511_add_user_avatar/migration.sql b/apps/backend/prisma/migrations/20260421123511_add_user_avatar/migration.sql deleted file mode 100644 index 3766c23..0000000 --- a/apps/backend/prisma/migrations/20260421123511_add_user_avatar/migration.sql +++ /dev/null @@ -1,2 +0,0 @@ --- AlterTable -ALTER TABLE "User" ADD COLUMN "avatar" TEXT; diff --git a/apps/backend/prisma/migrations/migration_lock.toml b/apps/backend/prisma/migrations/migration_lock.toml deleted file mode 100644 index 044d57c..0000000 --- a/apps/backend/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/apps/backend/prisma/schema.prisma b/apps/backend/prisma/schema.prisma deleted file mode 100644 index 18c7879..0000000 --- a/apps/backend/prisma/schema.prisma +++ /dev/null @@ -1,85 +0,0 @@ -generator client { - provider = "prisma-client-js" - previewFeatures = ["driverAdapters"] -} - -datasource db { - provider = "postgresql" -} - -model User { - id String @id @default(cuid()) - phoneNumber String @unique - password String - avatar String? - firstName String - lastName String - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - business Business? @relation("owner") - worksAt Business? @relation("provider", fields: [worksAtId], references: [id]) - worksAtId String? - appointments Appointment[] @relation("customer") - providerAppointments Appointment[] @relation("provider_appointments") - services Service[] - @@index([worksAtId]) -} - -model Business { - id String @id @default(cuid()) - name String - phoneNumber String - description String - address String - logo String? - ownerId String @unique - instagram String? - tiktok String? - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - services Service[] - owner User @relation("owner", fields: [ownerId], references: [id]) - providers User[] @relation("provider") -} - -model Service { - id String @id @default(cuid()) - name String - duration Int - price Int - business Business @relation(fields: [businessId], references: [id]) - businessId String - appointments Appointment[] - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - providers User[] - @@index([businessId]) -} - -model Appointment { - id String @id @default(cuid()) - customer User @relation("customer", fields: [customerId], references: [id]) - customerId String - provider User @relation("provider_appointments", fields: [providerId], references: [id]) - providerId String - time DateTime - serviceName String - service Service @relation(fields: [serviceId], references: [id]) - serviceId String - price Int - duration Int - status Status @default(CONFIRMED) - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - - @@index([providerId, time]) - @@index([customerId, time]) - @@index([serviceId]) -} - -enum Status { - CONFIRMED - COMPLETED - CANCELED - NOSHOW -} \ No newline at end of file diff --git a/apps/backend/src/constants/messages.ts b/apps/backend/src/constants/messages.ts deleted file mode 100644 index c1563eb..0000000 --- a/apps/backend/src/constants/messages.ts +++ /dev/null @@ -1,6 +0,0 @@ -export const AUTH_MESSAGES = { - MISSING_FIELDS: 'Missing required fields', - PHONE_ALREADY_EXISTS: 'A user with this phone number already exists', - INVALID_CREDENTIALS: 'Invalid credentials', - INTERNAL_ERROR: 'Internal server error', -} as const; diff --git a/apps/backend/src/controllers/appointment.controller.ts b/apps/backend/src/controllers/appointment.controller.ts new file mode 100644 index 0000000..215eb23 --- /dev/null +++ b/apps/backend/src/controllers/appointment.controller.ts @@ -0,0 +1,46 @@ +import { Request, Response } from 'express'; +import { AppError } from '../errors/AppError.js'; +import * as appointmentService from '../services/appointment.service.js'; + +export async function create(req: Request, res: Response): Promise { + try { + const result = await appointmentService.create(req.userId!, req.body); + res.status(201).json(result); + } catch (err) { + if (err instanceof AppError) { + res.status(err.status).json({ error: err.message }); + return; + } + res.status(500).json({ error: 'Internal server error' }); + } +} + +export async function getAll(req: Request, res: Response): Promise { + try { + const result = await appointmentService.getByUser(req.userId!); + res.json(result); + } catch (err) { + if (err instanceof AppError) { + res.status(err.status).json({ error: err.message }); + return; + } + res.status(500).json({ error: 'Internal server error' }); + } +} + +export async function update(req: Request, res: Response): Promise { + try { + const result = await appointmentService.update( + req.userId!, + req.params.id as string, + req.body, + ); + res.json(result); + } catch (err) { + if (err instanceof AppError) { + res.status(err.status).json({ error: err.message }); + return; + } + res.status(500).json({ error: 'Internal server error' }); + } +} diff --git a/apps/backend/src/controllers/auth.controller.ts b/apps/backend/src/controllers/auth.controller.ts new file mode 100644 index 0000000..a592005 --- /dev/null +++ b/apps/backend/src/controllers/auth.controller.ts @@ -0,0 +1,29 @@ +import { Request, Response } from 'express'; +import { AppError } from '../errors/AppError.js'; +import * as authService from '../services/auth.service.js'; + +export async function register(req: Request, res: Response): Promise { + try { + const result = await authService.register(req.body); + res.status(201).json(result); + } catch (err) { + if (err instanceof AppError) { + res.status(err.status).json({ error: err.message }); + return; + } + res.status(500).json({ error: 'Internal server error' }); + } +} + +export async function login(req: Request, res: Response): Promise { + try { + const result = await authService.login(req.body); + res.json(result); + } catch (err) { + if (err instanceof AppError) { + res.status(err.status).json({ error: err.message }); + return; + } + res.status(500).json({ error: 'Internal server error' }); + } +} diff --git a/apps/backend/src/controllers/business.controller.ts b/apps/backend/src/controllers/business.controller.ts new file mode 100644 index 0000000..60cee05 --- /dev/null +++ b/apps/backend/src/controllers/business.controller.ts @@ -0,0 +1,59 @@ +import { Request, Response } from 'express'; +import { AppError } from '../errors/AppError.js'; +import * as businessService from '../services/business.service.js'; + +export async function create(req: Request, res: Response): Promise { + try { + const result = await businessService.create(req.userId!, req.body); + res.status(201).json(result); + } catch (err) { + if (err instanceof AppError) { + res.status(err.status).json({ error: err.message }); + return; + } + res.status(500).json({ error: 'Internal server error' }); + } +} + +export async function getById(req: Request, res: Response): Promise { + try { + const result = await businessService.getById(req.params.id as string); + res.json(result); + } catch (err) { + if (err instanceof AppError) { + res.status(err.status).json({ error: err.message }); + return; + } + res.status(500).json({ error: 'Internal server error' }); + } +} + +export async function update(req: Request, res: Response): Promise { + try { + const result = await businessService.update( + req.userId!, + req.params.id as string, + req.body, + ); + res.json(result); + } catch (err) { + if (err instanceof AppError) { + res.status(err.status).json({ error: err.message }); + return; + } + res.status(500).json({ error: 'Internal server error' }); + } +} + +export async function getAll(_req: Request, res: Response): Promise { + try { + const result = await businessService.getAll(); + res.json(result); + } catch (err) { + if (err instanceof AppError) { + res.status(err.status).json({ error: err.message }); + return; + } + res.status(500).json({ error: 'Internal server error' }); + } +} diff --git a/apps/backend/src/controllers/health.controller.ts b/apps/backend/src/controllers/health.controller.ts new file mode 100644 index 0000000..e2b734d --- /dev/null +++ b/apps/backend/src/controllers/health.controller.ts @@ -0,0 +1,11 @@ +import { Request, Response } from 'express'; +import { db } from '../lib/db.js'; + +export async function check(_req: Request, res: Response): Promise { + try { + await db.command({ ping: 1 }); + res.json({ status: 'ok', database: 'connected' }); + } catch { + res.status(500).json({ status: 'error', database: 'disconnected' }); + } +} diff --git a/apps/backend/src/controllers/service.controller.ts b/apps/backend/src/controllers/service.controller.ts new file mode 100644 index 0000000..9cdc55f --- /dev/null +++ b/apps/backend/src/controllers/service.controller.ts @@ -0,0 +1,46 @@ +import { Request, Response } from 'express'; +import { AppError } from '../errors/AppError.js'; +import * as serviceService from '../services/service.service.js'; + +export async function create(req: Request, res: Response): Promise { + try { + const result = await serviceService.create(req.userId!, req.body); + res.status(201).json(result); + } catch (err) { + if (err instanceof AppError) { + res.status(err.status).json({ error: err.message }); + return; + } + res.status(500).json({ error: 'Internal server error' }); + } +} + +export async function remove(req: Request, res: Response): Promise { + try { + await serviceService.remove(req.userId!, req.params.id as string); + res.json({ message: 'Service deleted' }); + } catch (err) { + if (err instanceof AppError) { + res.status(err.status).json({ error: err.message }); + return; + } + res.status(500).json({ error: 'Internal server error' }); + } +} + +export async function update(req: Request, res: Response): Promise { + try { + const result = await serviceService.update( + req.userId!, + req.params.id as string, + req.body, + ); + res.json(result); + } catch (err) { + if (err instanceof AppError) { + res.status(err.status).json({ error: err.message }); + return; + } + res.status(500).json({ error: 'Internal server error' }); + } +} diff --git a/apps/backend/src/errors/AppError.ts b/apps/backend/src/errors/AppError.ts new file mode 100644 index 0000000..3d7f068 --- /dev/null +++ b/apps/backend/src/errors/AppError.ts @@ -0,0 +1,9 @@ +export class AppError extends Error { + constructor( + public status: number, + message: string, + ) { + super(message); + this.name = 'AppError'; + } +} diff --git a/apps/backend/src/index.ts b/apps/backend/src/index.ts index 92d0442..564b3d0 100644 --- a/apps/backend/src/index.ts +++ b/apps/backend/src/index.ts @@ -6,6 +6,7 @@ import authRouter from './routes/auth.js'; import businessRouter from './routes/business.js'; import serviceRouter from './routes/service.js'; import appointmentRouter from './routes/appointment.js'; +import { db } from './lib/db.js'; const app = express(); const PORT = process.env.PORT || 3001; @@ -20,6 +21,30 @@ app.use('/api', businessRouter); app.use('/api', serviceRouter); app.use('/api', appointmentRouter); -app.listen(PORT, () => { - console.log(`Server running on http://localhost:${PORT}`); -}); +async function start() { + try { + await db.command({ ping: 1 }); + await db + .collection('users') + .createIndex({ phoneNumber: 1 }, { unique: true }); + await db + .collection('businesses') + .createIndex({ ownerId: 1 }, { unique: true }); + await db + .collection('appointments') + .createIndex({ providerId: 1, time: 1, status: 1 }); + await db.collection('appointments').createIndex({ customerId: 1 }); + await db.collection('appointments').createIndex({ providerId: 1 }); + await db.collection('appointments').createIndex({ serviceId: 1 }); + console.log('Connected to MongoDB'); + } catch (error) { + console.error('MongoDB initialization failed:', error); + process.exit(1); + } + + app.listen(PORT, () => { + console.log(`Server running on http://localhost:${PORT}`); + }); +} + +start(); diff --git a/apps/backend/src/lib/db.ts b/apps/backend/src/lib/db.ts new file mode 100644 index 0000000..c080f09 --- /dev/null +++ b/apps/backend/src/lib/db.ts @@ -0,0 +1,10 @@ +import { MongoClient } from 'mongodb'; + +const uri = process.env.MONGODB_URI; + +if (!uri) { + throw new Error('MONGODB_URI is not set'); +} + +const client = new MongoClient(uri); +export const db = client.db(process.env.DB_NAME || 'soly_dev'); diff --git a/apps/backend/src/lib/prisma.ts b/apps/backend/src/lib/prisma.ts deleted file mode 100644 index 27b9357..0000000 --- a/apps/backend/src/lib/prisma.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { PrismaClient } from '@prisma/client/index.js'; -import { PrismaPg } from '@prisma/adapter-pg'; - -const adapter = new PrismaPg({ connectionString: process.env.DATABASE_URL }); - -export const prisma = new PrismaClient({ adapter }); diff --git a/apps/backend/src/models/appointment.model.ts b/apps/backend/src/models/appointment.model.ts new file mode 100644 index 0000000..121f848 --- /dev/null +++ b/apps/backend/src/models/appointment.model.ts @@ -0,0 +1,88 @@ +import { ObjectId, WithId, Document } from 'mongodb'; +import { db } from '../lib/db.js'; +import type { Appointment } from '../types/models/index.js'; + +function toAppointment(doc: WithId): Appointment { + const { _id, ...rest } = doc; + return { id: _id.toString(), ...rest } as Appointment; +} + +export async function findAppointmentById( + id: string, +): Promise { + if (!ObjectId.isValid(id)) return null; + const doc = await db + .collection('appointments') + .findOne({ _id: new ObjectId(id) }); + return doc ? toAppointment(doc) : null; +} + +export async function findAppointmentsByUser( + userId: string, +): Promise { + const docs = await db + .collection('appointments') + .find({ $or: [{ customerId: userId }, { providerId: userId }] }) + .toArray(); + return docs.map(toAppointment); +} + +export async function createAppointment( + data: Omit, +): Promise { + const now = new Date(); + const result = await db + .collection('appointments') + .insertOne({ ...data, createdAt: now, updatedAt: now }); + return { + id: result.insertedId.toString(), + ...data, + createdAt: now, + updatedAt: now, + }; +} + +export async function updateAppointment( + id: string, + data: Partial>, +): Promise { + const updated = await db + .collection('appointments') + .findOneAndUpdate( + { _id: new ObjectId(id) }, + { $set: { ...data, updatedAt: new Date() } }, + { returnDocument: 'after' }, + ); + return toAppointment(updated!); +} + +export async function hasTimeConflict( + providerId: string, + time: Date, + duration: number, + excludeAppointmentId?: string, +): Promise { + const dayStart = new Date(time); + dayStart.setHours(0, 0, 0, 0); + const dayEnd = new Date(time); + dayEnd.setHours(23, 59, 59, 999); + + const existing = await db + .collection('appointments') + .find({ + providerId, + status: { $ne: 'CANCELED' }, + time: { $gte: dayStart, $lte: dayEnd }, + ...(excludeAppointmentId + ? { _id: { $ne: new ObjectId(excludeAppointmentId) } } + : {}), + }) + .toArray(); + + return existing.some((apt) => { + const aptStart = new Date(apt.time).getTime(); + const aptEnd = aptStart + apt.duration * 60000; + const newEnd = time.getTime() + duration * 60000; + return time.getTime() < aptEnd && newEnd > aptStart; + }); +} diff --git a/apps/backend/src/models/business.model.ts b/apps/backend/src/models/business.model.ts new file mode 100644 index 0000000..56b9952 --- /dev/null +++ b/apps/backend/src/models/business.model.ts @@ -0,0 +1,70 @@ +import { ObjectId, WithId, Document } from 'mongodb'; +import { db } from '../lib/db.js'; +import type { Business } from '../types/models/index.js'; +import { AppError } from '../errors/AppError.js'; + +function toBusiness(doc: WithId): Business { + const { _id, ...rest } = doc; + return { id: _id.toString(), ...rest } as Business; +} + +export async function findBusinessByOwner( + ownerId: string, +): Promise { + const doc = await db.collection('businesses').findOne({ ownerId }); + return doc ? toBusiness(doc) : null; +} + +export async function findBusinessById(id: string): Promise { + if (!ObjectId.isValid(id)) return null; + const doc = await db + .collection('businesses') + .findOne({ _id: new ObjectId(id) }); + return doc ? toBusiness(doc) : null; +} + +export async function createBusiness( + data: Omit, +): Promise { + const now = new Date(); + try { + const result = await db + .collection('businesses') + .insertOne({ ...data, createdAt: now, updatedAt: now }); + return { + id: result.insertedId.toString(), + ...data, + createdAt: now, + updatedAt: now, + }; + } catch (err: unknown) { + if ( + typeof err === 'object' && + err !== null && + 'code' in err && + (err as { code: number }).code === 11000 + ) { + throw new AppError(409, 'You already own a business'); + } + throw err; + } +} + +export async function updateBusiness( + id: string, + data: Partial>, +): Promise { + const updated = await db + .collection('businesses') + .findOneAndUpdate( + { _id: new ObjectId(id) }, + { $set: { ...data, updatedAt: new Date() } }, + { returnDocument: 'after' }, + ); + return toBusiness(updated!); +} + +export async function findAllBusinesses(): Promise { + const docs = await db.collection('businesses').find().toArray(); + return docs.map(toBusiness); +} diff --git a/apps/backend/src/models/service.model.ts b/apps/backend/src/models/service.model.ts new file mode 100644 index 0000000..97579a0 --- /dev/null +++ b/apps/backend/src/models/service.model.ts @@ -0,0 +1,57 @@ +import { ObjectId, WithId, Document } from 'mongodb'; +import { db } from '../lib/db.js'; +import type { Service } from '../types/models/index.js'; + +function toService(doc: WithId): Service { + const { _id, ...rest } = doc; + return { id: _id.toString(), ...rest } as Service; +} + +export async function findServiceById(id: string): Promise { + if (!ObjectId.isValid(id)) return null; + const doc = await db + .collection('services') + .findOne({ _id: new ObjectId(id) }); + return doc ? toService(doc) : null; +} + +export async function createService( + data: Omit, +): Promise { + const now = new Date(); + const result = await db + .collection('services') + .insertOne({ ...data, createdAt: now, updatedAt: now }); + return { + id: result.insertedId.toString(), + ...data, + createdAt: now, + updatedAt: now, + }; +} + +export async function updateService( + id: string, + data: Partial>, +): Promise { + const updated = await db + .collection('services') + .findOneAndUpdate( + { _id: new ObjectId(id) }, + { $set: { ...data, updatedAt: new Date() } }, + { returnDocument: 'after' }, + ); + return toService(updated!); +} + +export async function deleteService(id: string): Promise { + await db.collection('services').deleteOne({ _id: new ObjectId(id) }); +} + +export async function countActiveAppointmentsByService( + serviceId: string, +): Promise { + return db + .collection('appointments') + .countDocuments({ serviceId, status: { $ne: 'CANCELED' } }); +} diff --git a/apps/backend/src/models/user.model.ts b/apps/backend/src/models/user.model.ts new file mode 100644 index 0000000..ed83bc0 --- /dev/null +++ b/apps/backend/src/models/user.model.ts @@ -0,0 +1,61 @@ +import { ObjectId, WithId, Document } from 'mongodb'; +import { db } from '../lib/db.js'; +import type { User } from '../types/models/index.js'; +import { AppError } from '../errors/AppError.js'; + +function toUser(doc: WithId): User { + const { _id, ...rest } = doc; + return { id: _id.toString(), ...rest } as User; +} + +export async function findUserByPhone( + phoneNumber: string, +): Promise { + const doc = await db.collection('users').findOne({ phoneNumber }); + return doc ? toUser(doc) : null; +} + +export async function findUsersByIds( + ids: string[], + worksAtId?: string, +): Promise { + const validIds = ids.filter((id) => ObjectId.isValid(id)); + const query: Record = { + _id: { $in: validIds.map((id) => new ObjectId(id)) }, + }; + if (worksAtId) query.worksAtId = worksAtId; + const docs = await db.collection('users').find(query).toArray(); + return docs.map(toUser); +} + +export async function createUser(data: { + phoneNumber: string; + firstName: string; + lastName: string; + password: string; + avatar: string | null; +}): Promise { + const now = new Date(); + try { + const result = await db + .collection('users') + .insertOne({ ...data, createdAt: now, updatedAt: now }); + return { + id: result.insertedId.toString(), + ...data, + worksAtId: null, + createdAt: now, + updatedAt: now, + }; + } catch (err: unknown) { + if ( + typeof err === 'object' && + err !== null && + 'code' in err && + (err as { code: number }).code === 11000 + ) { + throw new AppError(409, 'A user with this phone number already exists'); + } + throw err; + } +} diff --git a/apps/backend/src/routes/appointment.ts b/apps/backend/src/routes/appointment.ts index c9ec429..1a9aa8d 100644 --- a/apps/backend/src/routes/appointment.ts +++ b/apps/backend/src/routes/appointment.ts @@ -1,152 +1,15 @@ import { Router, type IRouter } from 'express'; import { authenticate } from '../middleware/auth.js'; -import { prisma } from '../lib/prisma.js'; -import { hasTimeConflict } from '../utils/checkConflicts.js'; +import { + create, + getAll, + update, +} from '../controllers/appointment.controller.js'; const router: IRouter = Router(); -router.post('/appointments', authenticate, async (req, res) => { - try { - const { serviceId, providerId, time } = req.body; - if (!serviceId || !providerId || !time) { - res.status(400).json({ error: 'Missing required fields' }); - return; - } - const service = await prisma.service.findUnique({ - where: { id: serviceId }, - include: { providers: true }, - }); - if (!service) { - res.status(404).json({ error: 'Service not found' }); - return; - } - const isProvider = service.providers.some((p) => p.id === providerId); - if (!isProvider) { - res - .status(404) - .json({ error: 'This provider does not offer this service' }); - return; - } - - const newStart = new Date(time); - if (isNaN(newStart.getTime())) { - res.status(400).json({ error: 'Invalid date format' }); - return; - } - const conflict = await hasTimeConflict( - providerId, - newStart, - service.duration, - ); - if (conflict) { - res.status(409).json({ error: 'Provider is not available at this time' }); - return; - } - - const appointment = await prisma.appointment.create({ - data: { - customerId: req.userId!, - providerId, - serviceId, - time: newStart, - serviceName: service.name, - price: service.price, - duration: service.duration, - }, - }); - - res.status(201).json(appointment); - } catch { - res.status(500).json({ error: 'Internal server error' }); - } -}); - -router.get('/appointments', authenticate, async (req, res) => { - try { - const userId = req.userId; - - const appointments = await prisma.appointment.findMany({ - where: { - OR: [{ customerId: userId }, { providerId: userId }], - }, - }); - - res.json(appointments); - } catch { - res.status(500).json({ error: 'Internal server error' }); - } -}); - -router.put('/appointments/:id', authenticate, async (req, res) => { - try { - const appointmentId = req.params.id as string; - const userId = req.userId; - const { status, time } = req.body; - - const appointment = await prisma.appointment.findUnique({ - where: { id: appointmentId }, - }); - - if (!appointment) { - res.status(404).json({ error: 'Appointment not found' }); - return; - } - - const isCustomer = appointment.customerId === userId; - const isProvider = appointment.providerId === userId; - - if (!isCustomer && !isProvider) { - res.status(403).json({ error: 'Forbidden' }); - return; - } - - if (isCustomer && status && status != 'CANCELED') { - res.status(403).json({ - error: 'Customers can only cancel or reschedule appointments', - }); - return; - } - - if ( - isProvider && - status && - !['CANCELED', 'COMPLETED', 'NOSHOW'].includes(status) - ) { - res.status(400).json({ error: 'Invalid status' }); - return; - } - if (time) { - const newTime = new Date(time); - if (isNaN(newTime.getTime())) { - res.status(400).json({ error: 'Invalid date format' }); - return; - } - const conflict = await hasTimeConflict( - appointment.providerId, - newTime, - appointment.duration, - appointmentId, - ); - if (conflict) { - res - .status(409) - .json({ error: 'Provider is not available at this time' }); - return; - } - } - - const updatedAppointment = await prisma.appointment.update({ - where: { id: appointmentId }, - data: { - ...(status && { status }), - ...(time && { time: new Date(time) }), - }, - }); - - res.json(updatedAppointment); - } catch { - res.status(500).json({ error: 'Internal server error' }); - } -}); +router.post('/appointments', authenticate, create); +router.get('/appointments', authenticate, getAll); +router.put('/appointments/:id', authenticate, update); export default router; diff --git a/apps/backend/src/routes/auth.ts b/apps/backend/src/routes/auth.ts index a6cf815..cf3636a 100644 --- a/apps/backend/src/routes/auth.ts +++ b/apps/backend/src/routes/auth.ts @@ -1,105 +1,9 @@ import { Router, type IRouter } from 'express'; -import bcrypt from 'bcrypt'; -import { prisma } from '../lib/prisma.js'; -import { generateToken } from '../utils/jwt.js'; -import { AUTH_MESSAGES } from '../constants/messages.js'; -import { StatusCodes } from 'http-status-codes'; +import { register, login } from '../controllers/auth.controller.js'; const router: IRouter = Router(); -function sanitizeUser(user: { - id: string; - firstName: string; - lastName: string; - phoneNumber: string; - avatar?: string | null; -}) { - return { - id: user.id, - firstName: user.firstName, - lastName: user.lastName, - phoneNumber: user.phoneNumber, - avatar: user.avatar, - }; -} - -router.post('/register', async (req, res) => { - try { - const { password, phoneNumber, firstName, lastName, avatar } = req.body; - - if (!password || !phoneNumber || !firstName || !lastName) { - res - .status(StatusCodes.BAD_REQUEST) - .json({ error: AUTH_MESSAGES.MISSING_FIELDS }); - return; - } - - const existing = await prisma.user.findUnique({ - where: { phoneNumber }, - }); - if (existing) { - res - .status(StatusCodes.CONFLICT) - .json({ error: AUTH_MESSAGES.PHONE_ALREADY_EXISTS }); - return; - } - - const hashedPassword = await bcrypt.hash(password, 10); - const newUser = await prisma.user.create({ - data: { - phoneNumber, - firstName, - lastName, - password: hashedPassword, - avatar, - }, - }); - - res - .status(StatusCodes.CREATED) - .json({ token: generateToken(newUser.id), user: sanitizeUser(newUser) }); - } catch { - res - .status(StatusCodes.INTERNAL_SERVER_ERROR) - .json({ error: AUTH_MESSAGES.INTERNAL_ERROR }); - } -}); - -router.post('/login', async (req, res) => { - try { - const { phoneNumber, password } = req.body; - - if (!password || !phoneNumber) { - res - .status(StatusCodes.BAD_REQUEST) - .json({ error: AUTH_MESSAGES.MISSING_FIELDS }); - return; - } - - const user = await prisma.user.findUnique({ - where: { phoneNumber }, - }); - if (!user) { - res - .status(StatusCodes.UNAUTHORIZED) - .json({ error: AUTH_MESSAGES.INVALID_CREDENTIALS }); - return; - } - - const isMatch = await bcrypt.compare(password, user.password); - if (!isMatch) { - res - .status(StatusCodes.UNAUTHORIZED) - .json({ error: AUTH_MESSAGES.INVALID_CREDENTIALS }); - return; - } - - res.json({ token: generateToken(user.id), user: sanitizeUser(user) }); - } catch { - res - .status(StatusCodes.INTERNAL_SERVER_ERROR) - .json({ error: AUTH_MESSAGES.INTERNAL_ERROR }); - } -}); +router.post('/register', register); +router.post('/login', login); export default router; diff --git a/apps/backend/src/routes/business.ts b/apps/backend/src/routes/business.ts index 37abcd7..2c450f0 100644 --- a/apps/backend/src/routes/business.ts +++ b/apps/backend/src/routes/business.ts @@ -1,106 +1,17 @@ import { Router, type IRouter } from 'express'; import { authenticate } from '../middleware/auth.js'; -import { prisma } from '../lib/prisma.js'; +import { + create, + getById, + update, + getAll, +} from '../controllers/business.controller.js'; const router: IRouter = Router(); -router.post('/businesses', authenticate, async (req, res) => { - try { - const { name, phoneNumber, description, address, logo, instagram, tiktok } = - req.body; - - if (!name || !phoneNumber || !description || !address) { - res.status(400).json({ error: 'Missing arguments' }); - return; - } - const existing = await prisma.business.findUnique({ - where: { ownerId: req.userId! }, - }); - if (existing) { - res.status(409).json({ error: 'You already own a business' }); - return; - } - - const business = await prisma.business.create({ - data: { - name, - phoneNumber, - description, - address, - logo, - instagram, - tiktok, - ownerId: req.userId!, - }, - }); - res.status(201).json(business); - } catch { - res.status(500).json({ error: 'Internal server error' }); - } -}); - -router.get('/businesses/:id', async (req, res) => { - try { - const businessId = req.params.id; - const business = await prisma.business.findUnique({ - where: { id: businessId }, - }); - if (!business) { - res.status(404).json({ error: 'Business not found' }); - return; - } - res.json(business); - } catch { - res.status(500).json({ error: 'Internal server error' }); - } -}); - -router.put('/businesses/:id', authenticate, async (req, res) => { - try { - const userId = req.userId; - const businessId = req.params.id as string; - const business = await prisma.business.findUnique({ - where: { id: businessId }, - }); - - if (!business) { - res.status(404).json({ error: 'Business not found' }); - return; - } - - if (business.ownerId !== userId) { - res.status(403).json({ error: 'Forbidden' }); - return; - } - - const { name, phoneNumber, description, address, logo, instagram, tiktok } = - req.body; - - const updatedBusiness = await prisma.business.update({ - where: { id: businessId }, - data: { - name, - phoneNumber, - description, - address, - logo, - instagram, - tiktok, - }, - }); - res.json(updatedBusiness); - } catch { - res.status(500).json({ error: 'Internal server error' }); - } -}); - -router.get('/businesses', async (req, res) => { - try { - const businesses = await prisma.business.findMany(); - res.status(200).json(businesses); - } catch { - res.status(500).json({ error: 'Internal server error' }); - } -}); +router.post('/businesses', authenticate, create); +router.get('/businesses', getAll); +router.get('/businesses/:id', getById); +router.put('/businesses/:id', authenticate, update); export default router; diff --git a/apps/backend/src/routes/health.ts b/apps/backend/src/routes/health.ts index 6d1f33a..9c4b0bd 100644 --- a/apps/backend/src/routes/health.ts +++ b/apps/backend/src/routes/health.ts @@ -1,15 +1,8 @@ import { Router, type IRouter } from 'express'; -import { prisma } from '../lib/prisma.js'; +import { check } from '../controllers/health.controller.js'; const router: IRouter = Router(); -router.get('/', async (_req, res) => { - try { - await prisma.$connect(); - res.json({ status: 'ok', database: 'connected' }); - } catch { - res.status(500).json({ status: 'error', database: 'disconnected' }); - } -}); +router.get('/', check); export default router; diff --git a/apps/backend/src/routes/service.ts b/apps/backend/src/routes/service.ts index 8292761..943251d 100644 --- a/apps/backend/src/routes/service.ts +++ b/apps/backend/src/routes/service.ts @@ -1,218 +1,11 @@ import { Router, type IRouter } from 'express'; import { authenticate } from '../middleware/auth.js'; -import { prisma } from '../lib/prisma.js'; +import { create, remove, update } from '../controllers/service.controller.js'; const router: IRouter = Router(); -router.post('/services', authenticate, async (req, res) => { - try { - const userId = req.userId; - const { businessId, name, duration, price, providerIds } = req.body; - - if (!name || !duration || !price || !businessId) { - res.status(400).json({ error: 'Missing required fields' }); - return; - } - - if ( - typeof duration !== 'number' || - duration <= 0 || - typeof price !== 'number' || - price <= 0 - ) { - res - .status(400) - .json({ error: 'Duration and price must be positive numbers' }); - return; - } - - const business = await prisma.business.findUnique({ - where: { id: businessId }, - }); - - if (!business) { - res.status(404).json({ error: 'Business not found' }); - return; - } - - if (business.ownerId !== userId) { - res.status(403).json({ error: 'Forbidden' }); - return; - } - - if ( - providerIds !== undefined && - !( - Array.isArray(providerIds) && - providerIds.every((id: unknown) => typeof id === 'string') - ) - ) { - res - .status(400) - .json({ error: 'providerIds must be an array of strings' }); - return; - } - - if (providerIds !== undefined && providerIds.length > 0) { - const validProviders = await prisma.user.findMany({ - where: { - id: { in: providerIds }, - worksAtId: businessId, - }, - }); - if (validProviders.length !== providerIds.length) { - res.status(400).json({ error: 'One or more provider IDs are invalid' }); - return; - } - } - - const service = await prisma.service.create({ - data: { - name, - duration, - price, - businessId, - providers: - providerIds !== undefined - ? { - connect: providerIds.map((id: string) => ({ id })), - } - : undefined, - }, - }); - - res.status(201).json(service); - } catch { - res.status(500).json({ error: 'Internal server error' }); - } -}); - -router.delete('/services/:id', authenticate, async (req, res) => { - try { - const userId = req.userId; - const serviceId = req.params.id as string; - - const service = await prisma.service.findUnique({ - where: { id: serviceId }, - }); - if (!service) { - res.status(404).json({ error: 'Service not found' }); - return; - } - - const business = await prisma.business.findUnique({ - where: { id: service.businessId }, - }); - - if (!business) { - res.status(404).json({ error: 'Business not found' }); - return; - } - - if (userId !== business.ownerId) { - res.status(403).json({ error: 'Forbidden' }); - return; - } - - const activeCount = await prisma.appointment.count({ - where: { serviceId, status: { not: 'CANCELED' } }, - }); - if (activeCount > 0) { - res - .status(409) - .json({ error: 'Cannot delete service with active appointments' }); - return; - } - - await prisma.service.delete({ where: { id: serviceId } }); - res.json({ message: 'Service deleted' }); - } catch { - res.status(500).json({ error: 'Internal server error' }); - } -}); - -router.put('/services/:id', authenticate, async (req, res) => { - try { - const userId = req.userId; - const serviceId = req.params.id as string; - const { name, duration, price, providerIds } = req.body; - const service = await prisma.service.findUnique({ - where: { id: serviceId }, - }); - if (!service) { - res.status(404).json({ error: 'Service not found' }); - return; - } - const business = await prisma.business.findUnique({ - where: { id: service.businessId }, - }); - - if (!business) { - res.status(404).json({ error: 'Business not found' }); - return; - } - - if (business.ownerId !== userId) { - res.status(403).json({ error: 'Forbidden' }); - return; - } - - if ( - providerIds !== undefined && - !( - Array.isArray(providerIds) && - providerIds.every((id: unknown) => typeof id === 'string') - ) - ) { - res - .status(400) - .json({ error: 'providerIds must be an array of strings' }); - return; - } - - if ( - (duration !== undefined && - (typeof duration !== 'number' || duration <= 0)) || - (price !== undefined && (typeof price !== 'number' || price <= 0)) - ) { - res - .status(400) - .json({ error: 'Duration and price must be positive numbers' }); - return; - } - - if (providerIds !== undefined && providerIds.length > 0) { - const validProviders = await prisma.user.findMany({ - where: { - id: { in: providerIds }, - worksAtId: service.businessId, - }, - }); - if (validProviders.length !== providerIds.length) { - res.status(400).json({ error: 'One or more provider IDs are invalid' }); - return; - } - } - - const updatedService = await prisma.service.update({ - where: { id: serviceId }, - data: { - name, - duration, - price, - providers: - providerIds !== undefined - ? { - set: providerIds.map((id: string) => ({ id })), - } - : undefined, - }, - }); - - res.status(200).json(updatedService); - } catch { - res.status(500).json({ error: 'Internal server error' }); - } -}); +router.post('/services', authenticate, create); +router.delete('/services/:id', authenticate, remove); +router.put('/services/:id', authenticate, update); export default router; diff --git a/apps/backend/src/services/appointment.service.ts b/apps/backend/src/services/appointment.service.ts new file mode 100644 index 0000000..c0056ba --- /dev/null +++ b/apps/backend/src/services/appointment.service.ts @@ -0,0 +1,110 @@ +import { + findAppointmentById, + findAppointmentsByUser, + createAppointment, + updateAppointment, + hasTimeConflict, +} from '../models/appointment.model.js'; +import { findServiceById } from '../models/service.model.js'; +import { AppError } from '../errors/AppError.js'; + +export async function create( + customerId: string, + data: { + serviceId: string; + providerId: string; + time: string; + }, +) { + if (!data.serviceId || !data.providerId || !data.time) { + throw new AppError(400, 'Missing required fields'); + } + + const service = await findServiceById(data.serviceId); + if (!service) throw new AppError(404, 'Service not found'); + + if (!(service.providerIds || []).includes(data.providerId)) { + throw new AppError(404, 'This provider does not offer this service'); + } + + const newStart = new Date(data.time); + if (isNaN(newStart.getTime())) throw new AppError(400, 'Invalid date format'); + + const conflict = await hasTimeConflict( + data.providerId, + newStart, + service.duration, + ); + if (conflict) + throw new AppError(409, 'Provider is not available at this time'); + + return createAppointment({ + customerId, + providerId: data.providerId, + serviceId: data.serviceId, + time: newStart, + serviceName: service.name, + price: service.price, + duration: service.duration, + status: 'CONFIRMED', + }); +} + +export async function getByUser(userId: string) { + return findAppointmentsByUser(userId); +} + +export async function update( + userId: string, + appointmentId: string, + data: { + status?: string; + time?: string; + }, +) { + const appointment = await findAppointmentById(appointmentId); + if (!appointment) throw new AppError(404, 'Appointment not found'); + + const isCustomer = appointment.customerId === userId; + const isProvider = appointment.providerId === userId; + + if (!isCustomer && !isProvider) throw new AppError(403, 'Forbidden'); + + if (isCustomer && data.status && data.status !== 'CANCELED') { + throw new AppError( + 403, + 'Customers can only cancel or reschedule appointments', + ); + } + + if ( + isProvider && + data.status && + !['CONFIRMED', 'CANCELED', 'COMPLETED', 'NOSHOW'].includes(data.status) + ) { + throw new AppError(400, 'Invalid status'); + } + + if (data.time) { + const newTime = new Date(data.time); + if (isNaN(newTime.getTime())) + throw new AppError(400, 'Invalid date format'); + + const conflict = await hasTimeConflict( + appointment.providerId, + newTime, + appointment.duration, + appointmentId, + ); + if (conflict) + throw new AppError(409, 'Provider is not available at this time'); + } + + return updateAppointment(appointmentId, { + ...(data.status && { + status: + data.status as import('../types/models/index.js').AppointmentStatus, + }), + ...(data.time && { time: new Date(data.time) }), + }); +} diff --git a/apps/backend/src/services/auth.service.ts b/apps/backend/src/services/auth.service.ts new file mode 100644 index 0000000..50a5452 --- /dev/null +++ b/apps/backend/src/services/auth.service.ts @@ -0,0 +1,67 @@ +import bcrypt from 'bcrypt'; +import { findUserByPhone, createUser } from '../models/user.model.js'; +import { generateToken } from '../utils/jwt.js'; +import { AppError } from '../errors/AppError.js'; +import type { PublicUser } from '../types/models/index.js'; + +function sanitizeUser(user: { + id: string; + firstName: string; + lastName: string; + phoneNumber: string; + avatar?: string | null; +}): PublicUser { + return { + id: user.id, + firstName: user.firstName, + lastName: user.lastName, + phoneNumber: user.phoneNumber, + avatar: user.avatar ?? null, + }; +} + +export async function register(data: { + phoneNumber: string; + firstName: string; + lastName: string; + password: string; + avatar?: string; +}) { + if ( + !data.password || + !data.phoneNumber || + !data.firstName || + !data.lastName + ) { + throw new AppError(400, 'Missing required fields'); + } + + const existing = await findUserByPhone(data.phoneNumber); + if (existing) + throw new AppError(409, 'A user with this phone number already exists'); + + const hashedPassword = await bcrypt.hash(data.password, 10); + const user = await createUser({ + phoneNumber: data.phoneNumber, + firstName: data.firstName, + lastName: data.lastName, + password: hashedPassword, + avatar: data.avatar || null, + }); + + return { token: generateToken(user.id), user: sanitizeUser(user) }; +} + +export async function login(data: { phoneNumber: string; password: string }) { + if (!data.password || !data.phoneNumber) { + throw new AppError(400, 'Missing required fields'); + } + + const user = await findUserByPhone(data.phoneNumber); + if (!user) throw new AppError(401, 'Invalid credentials'); + + const isMatch = await bcrypt.compare(data.password, user.password); + if (!isMatch) throw new AppError(401, 'Invalid credentials'); + + return { token: generateToken(user.id), user: sanitizeUser(user) }; +} diff --git a/apps/backend/src/services/business.service.ts b/apps/backend/src/services/business.service.ts new file mode 100644 index 0000000..595eff6 --- /dev/null +++ b/apps/backend/src/services/business.service.ts @@ -0,0 +1,61 @@ +import { + findBusinessByOwner, + findBusinessById, + createBusiness, + updateBusiness, + findAllBusinesses, +} from '../models/business.model.js'; +import { AppError } from '../errors/AppError.js'; +import type { Business } from '../types/models/index.js'; + +type BusinessInput = Pick< + Business, + 'name' | 'phoneNumber' | 'description' | 'address' +> & + Partial>; + +export async function create(ownerId: string, data: BusinessInput) { + if (!data.name || !data.phoneNumber || !data.description || !data.address) { + throw new AppError(400, 'Missing arguments'); + } + + const existing = await findBusinessByOwner(ownerId); + if (existing) throw new AppError(409, 'You already own a business'); + + return createBusiness({ + ...data, + logo: data.logo || null, + instagram: data.instagram || null, + tiktok: data.tiktok || null, + ownerId, + }); +} + +export async function getById(id: string) { + const business = await findBusinessById(id); + if (!business) throw new AppError(404, 'Business not found'); + return business; +} + +export async function update( + userId: string, + id: string, + data: Partial, +) { + const business = await findBusinessById(id); + if (!business) throw new AppError(404, 'Business not found'); + if (business.ownerId !== userId) throw new AppError(403, 'Forbidden'); + return updateBusiness(id, { + ...(data.name !== undefined && { name: data.name }), + ...(data.phoneNumber !== undefined && { phoneNumber: data.phoneNumber }), + ...(data.description !== undefined && { description: data.description }), + ...(data.address !== undefined && { address: data.address }), + ...(data.logo !== undefined && { logo: data.logo }), + ...(data.instagram !== undefined && { instagram: data.instagram }), + ...(data.tiktok !== undefined && { tiktok: data.tiktok }), + }); +} + +export async function getAll() { + return findAllBusinesses(); +} diff --git a/apps/backend/src/services/service.service.ts b/apps/backend/src/services/service.service.ts new file mode 100644 index 0000000..4f20468 --- /dev/null +++ b/apps/backend/src/services/service.service.ts @@ -0,0 +1,129 @@ +import { findBusinessById } from '../models/business.model.js'; +import { + findServiceById, + createService, + updateService, + deleteService, + countActiveAppointmentsByService, +} from '../models/service.model.js'; +import { findUsersByIds } from '../models/user.model.js'; +import { AppError } from '../errors/AppError.js'; + +async function validateProviders( + providerIds: unknown, + businessId: string, +): Promise { + if (providerIds === undefined) return []; + + if ( + !( + Array.isArray(providerIds) && + providerIds.every((id) => typeof id === 'string') + ) + ) { + throw new AppError(400, 'providerIds must be an array of strings'); + } + + if (providerIds.length > 0) { + const valid = await findUsersByIds(providerIds, businessId); + if (valid.length !== providerIds.length) { + throw new AppError(400, 'One or more provider IDs are invalid'); + } + } + + return providerIds; +} + +export async function create( + userId: string, + data: { + businessId: string; + name: string; + duration: number; + price: number; + providerIds?: string[]; + }, +) { + if (!data.name || !data.duration || !data.price || !data.businessId) { + throw new AppError(400, 'Missing required fields'); + } + + if ( + typeof data.duration !== 'number' || + data.duration <= 0 || + typeof data.price !== 'number' || + data.price <= 0 + ) { + throw new AppError(400, 'Duration and price must be positive numbers'); + } + + const business = await findBusinessById(data.businessId); + if (!business) throw new AppError(404, 'Business not found'); + if (business.ownerId !== userId) throw new AppError(403, 'Forbidden'); + + const providerIds = await validateProviders( + data.providerIds, + data.businessId, + ); + return createService({ + name: data.name, + duration: data.duration, + price: data.price, + businessId: data.businessId, + providerIds, + }); +} + +export async function remove(userId: string, serviceId: string) { + const service = await findServiceById(serviceId); + if (!service) throw new AppError(404, 'Service not found'); + + const business = await findBusinessById(service.businessId); + if (!business) throw new AppError(404, 'Business not found'); + if (userId !== business.ownerId) throw new AppError(403, 'Forbidden'); + + const activeCount = await countActiveAppointmentsByService(serviceId); + if (activeCount > 0) + throw new AppError(409, 'Cannot delete service with active appointments'); + + await deleteService(serviceId); +} + +export async function update( + userId: string, + serviceId: string, + data: { + name?: string; + duration?: number; + price?: number; + providerIds?: string[]; + }, +) { + const service = await findServiceById(serviceId); + if (!service) throw new AppError(404, 'Service not found'); + + const business = await findBusinessById(service.businessId); + if (!business) throw new AppError(404, 'Business not found'); + if (business.ownerId !== userId) throw new AppError(403, 'Forbidden'); + + if ( + (data.duration !== undefined && + (typeof data.duration !== 'number' || data.duration <= 0)) || + (data.price !== undefined && + (typeof data.price !== 'number' || data.price <= 0)) + ) { + throw new AppError(400, 'Duration and price must be positive numbers'); + } + + const providerIds = + data.providerIds !== undefined + ? await validateProviders(data.providerIds, service.businessId) + : undefined; + + return updateService(serviceId, { + ...(data.name !== undefined && { name: data.name }), + ...(data.duration !== undefined && { duration: data.duration }), + ...(data.price !== undefined && { price: data.price }), + ...(providerIds !== undefined && { providerIds }), + }); +} diff --git a/apps/backend/src/types/models/appointment.ts b/apps/backend/src/types/models/appointment.ts new file mode 100644 index 0000000..29c84ee --- /dev/null +++ b/apps/backend/src/types/models/appointment.ts @@ -0,0 +1,19 @@ +export type AppointmentStatus = + | 'CONFIRMED' + | 'COMPLETED' + | 'CANCELED' + | 'NOSHOW'; + +export interface Appointment { + id: string; + customerId: string; + providerId: string; + serviceId: string; + time: Date; + serviceName: string; + price: number; + duration: number; + status: AppointmentStatus; + createdAt: Date; + updatedAt: Date; +} diff --git a/apps/backend/src/types/models/business.ts b/apps/backend/src/types/models/business.ts new file mode 100644 index 0000000..006bdb4 --- /dev/null +++ b/apps/backend/src/types/models/business.ts @@ -0,0 +1,13 @@ +export interface Business { + id: string; + name: string; + phoneNumber: string; + description: string; + address: string; + logo: string | null; + instagram: string | null; + tiktok: string | null; + ownerId: string; + createdAt: Date; + updatedAt: Date; +} diff --git a/apps/backend/src/types/models/index.ts b/apps/backend/src/types/models/index.ts new file mode 100644 index 0000000..1c26d2a --- /dev/null +++ b/apps/backend/src/types/models/index.ts @@ -0,0 +1,4 @@ +export type { User, PublicUser } from './user.js'; +export type { Business } from './business.js'; +export type { Service } from './service.js'; +export type { Appointment, AppointmentStatus } from './appointment.js'; diff --git a/apps/backend/src/types/models/service.ts b/apps/backend/src/types/models/service.ts new file mode 100644 index 0000000..9a511b9 --- /dev/null +++ b/apps/backend/src/types/models/service.ts @@ -0,0 +1,10 @@ +export interface Service { + id: string; + name: string; + duration: number; + price: number; + businessId: string; + providerIds: string[]; + createdAt: Date; + updatedAt: Date; +} diff --git a/apps/backend/src/types/models/user.ts b/apps/backend/src/types/models/user.ts new file mode 100644 index 0000000..d032372 --- /dev/null +++ b/apps/backend/src/types/models/user.ts @@ -0,0 +1,16 @@ +export interface User { + id: string; + phoneNumber: string; + firstName: string; + lastName: string; + password: string; + avatar: string | null; + worksAtId?: string | null; + createdAt: Date; + updatedAt: Date; +} + +export type PublicUser = Omit< + User, + 'password' | 'worksAtId' | 'createdAt' | 'updatedAt' +>; diff --git a/apps/backend/src/utils/checkConflicts.ts b/apps/backend/src/utils/checkConflicts.ts deleted file mode 100644 index ff43fea..0000000 --- a/apps/backend/src/utils/checkConflicts.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { prisma } from '../lib/prisma.js'; - -export async function hasTimeConflict( - providerId: string, - time: Date, - duration: number, - excludeAppointmentId?: string, -) { - const dayStart = new Date(time); - dayStart.setHours(0, 0, 0, 0); - const dayEnd = new Date(time); - dayEnd.setHours(23, 59, 59, 999); - - const existingAppointments = await prisma.appointment.findMany({ - where: { - providerId, - status: { not: 'CANCELED' }, - time: { gte: dayStart, lte: dayEnd }, - ...(excludeAppointmentId ? { id: { not: excludeAppointmentId } } : {}), - }, - }); - - return existingAppointments.some((apt) => { - const aptStart = apt.time.getTime(); - const aptEnd = aptStart + apt.duration * 60000; - const newEnd = time.getTime() + duration * 60000; - return time.getTime() < aptEnd && newEnd > aptStart; - }); -} diff --git a/package.json b/package.json index f4a2124..6cb4956 100644 --- a/package.json +++ b/package.json @@ -4,9 +4,8 @@ "version": "0.0.0", "type": "module", "scripts": { - "dev": "pnpm --filter @soly/web dev", + "dev": "pnpm -r --parallel --filter @soly/web --filter @soly/backend dev", "build": "pnpm --filter @soly/web build", - "dev:all": "pnpm -r --parallel dev", "build:all": "pnpm -r build", "lint": "eslint . --fix", "format": "prettier --write \"apps/**/*.{ts,tsx,css,json}\" \"packages/**/*.{ts,tsx,css,json}\"", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6e5bb4e..6623f92 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -44,12 +44,6 @@ importers: apps/backend: dependencies: - '@prisma/adapter-pg': - specifier: ^7.6.0 - version: 7.6.0 - '@prisma/client': - specifier: ^7.6.0 - version: 7.6.0(prisma@7.6.0(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3))(typescript@5.9.3) bcrypt: specifier: ^6.0.0 version: 6.0.0 @@ -62,15 +56,12 @@ importers: express: specifier: ^5.2.1 version: 5.2.1 - http-status-codes: - specifier: ^2.3.0 - version: 2.3.0 jsonwebtoken: specifier: ^9.0.3 version: 9.0.3 - pg: - specifier: ^8.20.0 - version: 8.20.0 + mongodb: + specifier: ^7.2.0 + version: 7.2.0 devDependencies: '@types/bcrypt': specifier: ^6.0.0 @@ -87,12 +78,6 @@ importers: '@types/node': specifier: ^24.12.2 version: 24.12.2 - '@types/pg': - specifier: ^8.20.0 - version: 8.20.0 - prisma: - specifier: ^7.6.0 - version: 7.6.0(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3) tsx: specifier: ^4.21.0 version: 4.21.0 @@ -352,26 +337,6 @@ packages: peerDependencies: '@capacitor/core': ^8.3.0 - '@clack/core@0.5.0': - resolution: {integrity: sha512-p3y0FIOwaYRUPRcMO7+dlmLh8PSRcrjuTndsiA0WAFbWES0mLZlrjVoBRZ9DzkPFJZG6KGkJmoEAY0ZcVWTkow==} - - '@clack/prompts@0.11.0': - resolution: {integrity: sha512-pMN5FcrEw9hUkZA4f+zLlzivQSeQf5dRGJjSUbvVYDLvpKCdQx5OaknvKzgbtXOizhP+SJJJjqEbOe55uKKfAw==} - - '@electric-sql/pglite-socket@0.1.1': - resolution: {integrity: sha512-p2hoXw3Z3LQHwTeikdZNsFBOvXGqKY2hk51BBw+8NKND8eoH+8LFOtW9Z8CQKmTJ2qqGYu82ipqiyFZOTTXNfw==} - hasBin: true - peerDependencies: - '@electric-sql/pglite': 0.4.1 - - '@electric-sql/pglite-tools@0.3.1': - resolution: {integrity: sha512-C+T3oivmy9bpQvSxVqXA1UDY8cB9Eb9vZHL9zxWwEUfDixbXv4G3r2LjoTdR33LD8aomR3O9ZXEO3XEwr/cUCA==} - peerDependencies: - '@electric-sql/pglite': 0.4.1 - - '@electric-sql/pglite@0.4.1': - resolution: {integrity: sha512-mZ9NzzUSYPOCnxHH1oAHPRzoMFJHY472raDKwXl/+6oPbpdJ7g8LsCN4FSaIIfkiCKHhb3iF/Zqo3NYxaIhU7Q==} - '@emnapi/core@1.9.2': resolution: {integrity: sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA==} @@ -581,12 +546,6 @@ packages: resolution: {integrity: sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@hono/node-server@1.19.11': - resolution: {integrity: sha512-dr8/3zEaB+p0D2n/IUrlPF1HZm586qgJNXK1a9fhg/PzdtkK7Ksd5l312tJX2yBuALqDYBlG20QEbayqPyxn+g==} - engines: {node: '>=18.14.1'} - peerDependencies: - hono: ^4 - '@humanfs/core@0.19.1': resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} engines: {node: '>=18.18.0'} @@ -655,8 +614,8 @@ packages: '@jridgewell/trace-mapping@0.3.31': resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} - '@kurkle/color@0.3.4': - resolution: {integrity: sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==} + '@mongodb-js/saslprep@1.4.11': + resolution: {integrity: sha512-o9rAHc0IpIjuPSxRutWpE1F62x7n+4mVS4rCNHkzhIUMQcc18bb6xEq5wd2NdN0WjepIyXIppRshYI2kQDOZVA==} '@napi-rs/wasm-runtime@1.1.2': resolution: {integrity: sha512-sNXv5oLJ7ob93xkZ1XnxisYhGYXfaG9f65/ZgYuAu3qt7b3NadcOEhLvx28hv31PgX8SZJRYrAIPQilQmFpLVw==} @@ -683,143 +642,6 @@ packages: resolution: {integrity: sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==} engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} - '@prisma/adapter-pg@7.6.0': - resolution: {integrity: sha512-BjHNmJqqa42NqJSDPnXUfwUofWo8LJY7Ui2gqxN4DmAOb+H/gGKv+hln2Xq/1kSJXPW5AXMXuNiPDMpywvyIOw==} - - '@prisma/client-runtime-utils@7.6.0': - resolution: {integrity: sha512-fD7jlqubsZvVODKvsp9lOpXVecx2aWGxC2l35Ioz2t+teUJ5CfR0SAMsi7UkU1VvaZmmm+DS6BdujF622nY7tQ==} - - '@prisma/client@7.6.0': - resolution: {integrity: sha512-7Pe/1ayh3GgWPEg4mmT4ax77LJ1wC+XlnIFvQ94bLP2DsUnOpnruQQR3Jw7r+Frthk94QqDNxo3FjSg8h9PXeQ==} - engines: {node: ^20.19 || ^22.12 || >=24.0} - peerDependencies: - prisma: '*' - typescript: '>=5.4.0' - peerDependenciesMeta: - prisma: - optional: true - typescript: - optional: true - - '@prisma/config@7.6.0': - resolution: {integrity: sha512-MuAz1MK4PeG5/03YzfzX3CnFVHQ6qePGwUpQRzPzX5tT0ffJ3Tzi9zJZbBc+VzEGFCM8ghW/gTVDR85Syjt+Yw==} - - '@prisma/debug@7.2.0': - resolution: {integrity: sha512-YSGTiSlBAVJPzX4ONZmMotL+ozJwQjRmZweQNIq/ER0tQJKJynNkRB3kyvt37eOfsbMCXk3gnLF6J9OJ4QWftw==} - - '@prisma/debug@7.6.0': - resolution: {integrity: sha512-LpHr3qos4lQZ6sxwjStf59YBht7m9/QF7NSQsMH6qGENWZu2w3UkQUGn1h5iRkDjnWRj3VHykOu9qFhps4ADvA==} - - '@prisma/dev@0.24.3': - resolution: {integrity: sha512-ffHlQuKXZiaDt9Go0OnCTdJZrHxK0k7omJKNV86/VjpsXu5EIHZLK0T7JSWgvNlJwh56kW9JFu9v0qJciFzepg==} - - '@prisma/driver-adapter-utils@7.6.0': - resolution: {integrity: sha512-D8j3p0RnhLuufMaRLX6QqtGgPC5Ao3l5oFP6Q5AL0rTHi4vna+NzGEipwCsfvcSvaGFCbsH3lsTMbb4WvY+ovA==} - - '@prisma/engines-version@7.6.0-1.75cbdc1eb7150937890ad5465d861175c6624711': - resolution: {integrity: sha512-r51DLcJ8bDRSrBEJF3J4cinoWyGA7rfP2mG6lD90VqIbGNOkbfcLcXalSVjq5Y6brQS3vcjrq4GbyUb1Cb7vkw==} - - '@prisma/engines@7.6.0': - resolution: {integrity: sha512-Sn5edRzhHqgRV2M+A0eIbY442B4mReWWf3pKs/LKreYgW7oa/up8JtK/s4iv/EQA097cyboZ08mmkpbLp+tZ3w==} - - '@prisma/fetch-engine@7.6.0': - resolution: {integrity: sha512-N575Ni95c3FkduWY/eKTHqNYgNbceZ1tQaSknVtJjpKmiiBXmniESn/GTxsDvICC4ZeiNrXxioGInzQrCdx16w==} - - '@prisma/get-platform@7.2.0': - resolution: {integrity: sha512-k1V0l0Td1732EHpAfi2eySTezyllok9dXb6UQanajkJQzPUGi3vO2z7jdkz67SypFTdmbnyGYxvEvYZdZsMAVA==} - - '@prisma/get-platform@7.6.0': - resolution: {integrity: sha512-ohZDwXvtmnbzOcutR2D13lDWpZP1wQjmPyztmt0AwXLzQI7q95EE7NYCvS+M6N6SivT+BM0NOqLmTH3wms4L3A==} - - '@prisma/query-plan-executor@7.2.0': - resolution: {integrity: sha512-EOZmNzcV8uJ0mae3DhTsiHgoNCuu1J9mULQpGCh62zN3PxPTd+qI9tJvk5jOst8WHKQNwJWR3b39t0XvfBB0WQ==} - - '@prisma/streams-local@0.1.2': - resolution: {integrity: sha512-l49yTxKKF2odFxaAXTmwmkBKL3+bVQ1tFOooGifu4xkdb9NMNLxHj27XAhTylWZod8I+ISGM5erU1xcl/oBCtg==} - engines: {bun: '>=1.3.6', node: '>=22.0.0'} - - '@prisma/studio-core@0.27.3': - resolution: {integrity: sha512-AADjNFPdsrglxHQVTmHFqv6DuKQZ5WY4p5/gVFY017twvNrSwpLJ9lqUbYYxEu2W7nbvVxTZA8deJ8LseNALsw==} - engines: {node: ^20.19 || ^22.12 || >=24.0, pnpm: '8'} - peerDependencies: - '@types/react': ^18.0.0 || ^19.0.0 - react: ^18.0.0 || ^19.0.0 - react-dom: ^18.0.0 || ^19.0.0 - - '@radix-ui/primitive@1.1.3': - resolution: {integrity: sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==} - - '@radix-ui/react-compose-refs@1.1.2': - resolution: {integrity: sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - '@radix-ui/react-primitive@2.1.3': - resolution: {integrity: sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-slot@1.2.3': - resolution: {integrity: sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - '@radix-ui/react-toggle@1.1.10': - resolution: {integrity: sha512-lS1odchhFTeZv3xwHH31YPObmJn8gOg7Lq12inrr0+BH/l3Tsq32VfjqH1oh80ARM3mlkfMic15n0kg4sD1poQ==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-use-controllable-state@1.2.2': - resolution: {integrity: sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - '@radix-ui/react-use-effect-event@0.0.2': - resolution: {integrity: sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - '@radix-ui/react-use-layout-effect@1.1.1': - resolution: {integrity: sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@rc-component/async-validator@5.1.0': resolution: {integrity: sha512-n4HcR5siNUXRX23nDizbZBQPO0ZM/5oTtmKZ6/eqL0L2bo747cklFdZGRN2f+c9qWGICwDzrhW0H7tE9PptdcA==} engines: {node: '>=14.x'} @@ -1204,9 +1026,6 @@ packages: '@rolldown/pluginutils@1.0.0-rc.7': resolution: {integrity: sha512-qujRfC8sFVInYSPPMLQByRh7zhwkGFS4+tyMQ83srV1qrxL4g8E2tyxVVyxd0+8QeBM1mIk9KbWxkegRr76XzA==} - '@standard-schema/spec@1.1.0': - resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} - '@tybys/wasm-util@0.10.1': resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==} @@ -1252,9 +1071,6 @@ packages: '@types/node@24.12.2': resolution: {integrity: sha512-A1sre26ke7HDIuY/M23nd9gfB+nrmhtYyMINbjI1zHJxYteKR6qSMX56FsmjMcDb3SMcjJg5BiRRgOCC/yBD0g==} - '@types/pg@8.20.0': - resolution: {integrity: sha512-bEPFOaMAHTEP1EzpvHTbmwR8UsFyHSKsRisLIHVMXnpNefSbGA1bD6CVy+qKjGSqmZqNqBDV2azOBo8TgkcVow==} - '@types/qs@6.15.0': resolution: {integrity: sha512-JawvT8iBVWpzTrz3EGw9BTQFg3BQNmwERdKE22vlTxawwtbyUSlMppvZYKLZzB5zgACXdXxbD3m1bXaMqP/9ow==} @@ -1278,6 +1094,12 @@ packages: '@types/slice-ansi@4.0.0': resolution: {integrity: sha512-+OpjSaq85gvlZAYINyzKpLeiFkSC4EsC6IIiT6v6TLSU5k5U83fHGj9Lel8oKEXM0HqgrMVCjXPDPVICtxF7EQ==} + '@types/webidl-conversions@7.0.3': + resolution: {integrity: sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA==} + + '@types/whatwg-url@13.0.0': + resolution: {integrity: sha512-N8WXpbE6Wgri7KUSvrmQcqrMllKZ9uxkYWMt+mCSGwNc0Hsw9VQTW7ApqI4XNrx6/SaM2QQJCzMPDEXE058s+Q==} + '@typescript-eslint/eslint-plugin@8.58.0': resolution: {integrity: sha512-RLkVSiNuUP1C2ROIWfqX+YcUfLaSnxGE/8M+Y57lopVwg9VTYYfhuz15Yf1IzCKgZj6/rIbYTmJCUSqr76r0Wg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -1371,9 +1193,6 @@ packages: ajv@6.14.0: resolution: {integrity: sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==} - ajv@8.18.0: - resolution: {integrity: sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==} - ansi-escapes@7.3.0: resolution: {integrity: sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg==} engines: {node: '>=18'} @@ -1421,10 +1240,6 @@ packages: resolution: {integrity: sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==} engines: {node: '>= 4.0.0'} - aws-ssl-profiles@1.1.2: - resolution: {integrity: sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g==} - engines: {node: '>= 6.0.0'} - axios@1.14.0: resolution: {integrity: sha512-3Y8yrqLSwjuzpXuZ0oIYZ/XGgLwUIBU3uLvbcpb0pidD9ctpShJd43KSlEEkVQg6DS0G9NKyzOvBfUtDKEyHvQ==} @@ -1447,10 +1262,6 @@ packages: resolution: {integrity: sha512-cU8v/EGSrnH+HnxV2z0J7/blxH8gq7Xh2JFT6Aroax7UohdmiJJlxApMxtKfuI7z68NvvVcmR78k2LbT6efhRg==} engines: {node: '>= 18'} - better-result@2.7.0: - resolution: {integrity: sha512-7zrmXjAK8u8Z6SOe4R65XObOR5X+Y2I/VVku3t5cPOGQ8/WsBcfFmfnIPiEl5EBMDOzPHRwbiPbMtQBKYdw7RA==} - hasBin: true - big-integer@1.6.52: resolution: {integrity: sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==} engines: {node: '>=0.6'} @@ -1479,6 +1290,10 @@ packages: engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true + bson@7.2.0: + resolution: {integrity: sha512-YCEo7KjMlbNlyHhz7zAZNDpIpQbd+wOEHJYezv0nMYTn4x31eIUM2yomNNubclAt63dObUzKHWsBLJ9QcZNSnQ==} + engines: {node: '>=20.19.0'} + buffer-crc32@0.2.13: resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==} @@ -1489,14 +1304,6 @@ packages: resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} engines: {node: '>= 0.8'} - c12@3.1.0: - resolution: {integrity: sha512-uWoS8OU1MEIsOv8p/5a82c3H31LsWVR5qiyXVfBNOzfffjUWtPnhAb4BYI2uG2HfGmZmFjCtui5XNWaps+iFuw==} - peerDependencies: - magicast: ^0.3.5 - peerDependenciesMeta: - magicast: - optional: true - call-bind-apply-helpers@1.0.2: resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} engines: {node: '>= 0.4'} @@ -1519,24 +1326,10 @@ packages: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} - chart.js@4.5.1: - resolution: {integrity: sha512-GIjfiT9dbmHRiYi6Nl2yFCq7kkwdkp1W/lp2J99rX0yo9tgJGn3lKQATztIjb5tVtevcBtIdICNWqlq5+E8/Pw==} - engines: {pnpm: '>=8'} - - chokidar@4.0.3: - resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} - engines: {node: '>= 14.16.0'} - chownr@3.0.0: resolution: {integrity: sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==} engines: {node: '>=18'} - citty@0.1.6: - resolution: {integrity: sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==} - - citty@0.2.2: - resolution: {integrity: sha512-+6vJA3L98yv+IdfKGZHBNiGW5KHn22e/JwID0Strsz8h4S/csAu/OuICwxrg44k5MRiZHWIo8XXuJgQTriRP4w==} - cli-cursor@5.0.0: resolution: {integrity: sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==} engines: {node: '>=18'} @@ -1584,13 +1377,6 @@ packages: concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} - confbox@0.2.4: - resolution: {integrity: sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ==} - - consola@3.4.2: - resolution: {integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==} - engines: {node: ^14.18.0 || >=16.10.0} - content-disposition@1.0.1: resolution: {integrity: sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==} engines: {node: '>=18'} @@ -1640,32 +1426,18 @@ packages: deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} - deepmerge-ts@7.1.5: - resolution: {integrity: sha512-HOJkrhaYsweh+W+e74Yn7YStZOilkoPb6fycpwNLKzSPtruFs48nYis0zy5yJz1+ktUhHxoRDJ27RQAWLIJVJw==} - engines: {node: '>=16.0.0'} - define-lazy-prop@2.0.0: resolution: {integrity: sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==} engines: {node: '>=8'} - defu@6.1.6: - resolution: {integrity: sha512-f8mefEW4WIVg4LckePx3mALjQSPQgFlg9U8yaPdlsbdYcHQyj9n2zL2LJEA52smeYxOvmd/nB7TpMtHGMTHcug==} - delayed-stream@1.0.0: resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} engines: {node: '>=0.4.0'} - denque@2.1.0: - resolution: {integrity: sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==} - engines: {node: '>=0.10'} - depd@2.0.0: resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} engines: {node: '>= 0.8'} - destr@2.0.5: - resolution: {integrity: sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==} - detect-libc@2.1.2: resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} engines: {node: '>=8'} @@ -1674,10 +1446,6 @@ packages: resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} engines: {node: '>=8'} - dotenv@16.6.1: - resolution: {integrity: sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==} - engines: {node: '>=12'} - dotenv@17.4.0: resolution: {integrity: sha512-kCKF62fwtzwYm0IGBNjRUjtJgMfGapII+FslMHIjMR5KTnwEmBmWLDRSnc3XSNP8bNy34tekgQyDT0hr7pERRQ==} engines: {node: '>=12'} @@ -1692,9 +1460,6 @@ packages: ee-first@1.1.1: resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} - effect@3.20.0: - resolution: {integrity: sha512-qMLfDJscrNG8p/aw+IkT9W7fgj50Z4wG5bLBy0Txsxz8iUHjDIkOgO3SV0WZfnQbNG2VJYb0b+rDLMrhM4+Krw==} - electron-to-chromium@1.5.331: resolution: {integrity: sha512-IbxXrsTlD3hRodkLnbxAPP4OuJYdWCeM3IOdT+CpcMoIwIoDfCmRpEtSPfwBXxVkg9xmBeY7Lz2Eo2TDn/HC3Q==} @@ -1711,10 +1476,6 @@ packages: emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} - empathic@2.0.0: - resolution: {integrity: sha512-i6UzDscO/XfAcNYD75CfICkmfLedpyPDdozrLMmQc5ORaQcdMoc21OnlEylMIqI7U8eniKrPMxxtj8k0vhmJhA==} - engines: {node: '>=14'} - encodeurl@2.0.0: resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} engines: {node: '>= 0.8'} @@ -1723,10 +1484,6 @@ packages: resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==} engines: {node: '>=6'} - env-paths@3.0.0: - resolution: {integrity: sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - environment@1.1.0: resolution: {integrity: sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==} engines: {node: '>=18'} @@ -1855,13 +1612,6 @@ packages: resolution: {integrity: sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==} engines: {node: '>= 18'} - exsolve@1.0.8: - resolution: {integrity: sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==} - - fast-check@3.23.2: - resolution: {integrity: sha512-h5+1OzzfCC3Ef7VbtKdcv7zsstUQwUDlYpUTvjeUsJAssPgLn7QzbboPtL5ro04Mq0rPOsMzl7q5hIbRs2wD1A==} - engines: {node: '>=8.0.0'} - fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} @@ -1878,9 +1628,6 @@ packages: fast-levenshtein@2.0.6: resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} - fast-uri@3.1.0: - resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==} - fastq@1.20.1: resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==} @@ -1944,10 +1691,6 @@ packages: debug: optional: true - foreground-child@3.3.1: - resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} - engines: {node: '>=14'} - form-data@4.0.5: resolution: {integrity: sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==} engines: {node: '>= 6'} @@ -1976,9 +1719,6 @@ packages: function-bind@1.1.2: resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} - generate-function@2.3.1: - resolution: {integrity: sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==} - gensync@1.0.0-beta.2: resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} engines: {node: '>=6.9.0'} @@ -1991,9 +1731,6 @@ packages: resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} engines: {node: '>= 0.4'} - get-port-please@3.2.0: - resolution: {integrity: sha512-I9QVvBw5U/hw3RmWpYKRumUeaDgxTPd401x364rLmWBJcOQ753eov1eTgzDqRG9bqFIfDc7gfzcQEWrUri3o1A==} - get-proto@1.0.1: resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} engines: {node: '>= 0.4'} @@ -2006,10 +1743,6 @@ packages: engines: {node: '>=10'} hasBin: true - giget@2.0.0: - resolution: {integrity: sha512-L5bGsVkxJbJgdnwyuheIunkGatUF/zssUoxxjACCseZYAVbaqdh9Tsmmlkl8vYan09H7sbvKt4pS8GqKLBrEzA==} - hasBin: true - glob-parent@5.1.2: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} engines: {node: '>= 6'} @@ -2041,12 +1774,6 @@ packages: graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} - grammex@3.1.12: - resolution: {integrity: sha512-6ufJOsSA7LcQehIJNCO7HIBykfM7DXQual0Ny780/DEcJIpBlHRvcqEBWGPYd7hrXL2GJ3oJI1MIhaXjWmLQOQ==} - - graphmatch@1.1.1: - resolution: {integrity: sha512-5ykVn/EXM1hF0XCaWh05VbYvEiOL2lY1kBxZtaYsyvjp7cmWOU1XsAdfQBwClraEofXDT197lFbXOEVMHpvQOg==} - has-flag@4.0.0: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} @@ -2069,17 +1796,10 @@ packages: hermes-parser@0.25.1: resolution: {integrity: sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==} - hono@4.12.10: - resolution: {integrity: sha512-mx/p18PLy5og9ufies2GOSUqep98Td9q4i/EF6X7yJgAiIopxqdfIO3jbqsi3jRgTgw88jMDEzVKi+V2EF+27w==} - engines: {node: '>=16.9.0'} - http-errors@2.0.1: resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==} engines: {node: '>= 0.8'} - http-status-codes@2.3.0: - resolution: {integrity: sha512-RJ8XvFvpPM/Dmc5SV+dC4y5PCeOhT3x1Hq0NU3rjGeg5a/CqlhZ7uudknPwZFz4aeAXDcbAyaeP7GAo9lvngtA==} - husky@9.1.7: resolution: {integrity: sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==} engines: {node: '>=18'} @@ -2147,9 +1867,6 @@ packages: is-promise@4.0.0: resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==} - is-property@1.0.2: - resolution: {integrity: sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==} - is-wsl@2.2.0: resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==} engines: {node: '>=8'} @@ -2179,9 +1896,6 @@ packages: json-schema-traverse@0.4.1: resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} - json-schema-traverse@1.0.0: - resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} - json-stable-stringify-without-jsonify@1.0.1: resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} @@ -2340,9 +2054,6 @@ packages: resolution: {integrity: sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==} engines: {node: '>=18'} - long@5.3.2: - resolution: {integrity: sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==} - lru-cache@11.2.7: resolution: {integrity: sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA==} engines: {node: 20 || >=22} @@ -2350,10 +2061,6 @@ packages: lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} - lru.min@1.1.4: - resolution: {integrity: sha512-DqC6n3QQ77zdFpCMASA1a3Jlb64Hv2N2DciFGkO/4L9+q/IpIAuRlKOvCXabtRW6cQf8usbmM6BE/TOPysCdIA==} - engines: {bun: '>=1.0.0', deno: '>=1.30.0', node: '>=8.0.0'} - lucide-react@1.7.0: resolution: {integrity: sha512-yI7BeItCLZJTXikmK4KNUGCKoGzSvbKlfCvw44bU4fXAL6v3gYS4uHD1jzsLkfwODYwI6Drw5Tu9Z5ulDe0TSg==} peerDependencies: @@ -2371,6 +2078,9 @@ packages: resolution: {integrity: sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==} engines: {node: '>= 0.8'} + memory-pager@1.5.0: + resolution: {integrity: sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==} + merge-descriptors@2.0.0: resolution: {integrity: sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==} engines: {node: '>=18'} @@ -2418,16 +2128,39 @@ packages: resolution: {integrity: sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==} engines: {node: '>= 18'} - ms@2.1.3: - resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + mongodb-connection-string-url@7.0.1: + resolution: {integrity: sha512-h0AZ9A7IDVwwHyMxmdMXKy+9oNlF0zFoahHiX3vQ8e3KFcSP3VmsmfvtRSuLPxmyv2vjIDxqty8smTgie/SNRQ==} + engines: {node: '>=20.19.0'} - mysql2@3.15.3: - resolution: {integrity: sha512-FBrGau0IXmuqg4haEZRBfHNWB5mUARw6hNwPDXXGg0XzVJ50mr/9hb267lvpVMnhZ1FON3qNd4Xfcez1rbFwSg==} - engines: {node: '>= 8.0'} + mongodb@7.2.0: + resolution: {integrity: sha512-F/2+BMZtLVhY30ioZp0dAmZ+IRZMBqI+nrv6t5+9/1AIwCa8sMRC3jBf81lpxMhnZgqq8CoUD503Z1oZWq1/sw==} + engines: {node: '>=20.19.0'} + peerDependencies: + '@aws-sdk/credential-providers': ^3.806.0 + '@mongodb-js/zstd': ^7.0.0 + gcp-metadata: ^7.0.1 + kerberos: ^7.0.0 + mongodb-client-encryption: '>=7.0.0 <7.1.0' + snappy: ^7.3.2 + socks: ^2.8.6 + peerDependenciesMeta: + '@aws-sdk/credential-providers': + optional: true + '@mongodb-js/zstd': + optional: true + gcp-metadata: + optional: true + kerberos: + optional: true + mongodb-client-encryption: + optional: true + snappy: + optional: true + socks: + optional: true - named-placeholders@1.1.6: - resolution: {integrity: sha512-Tz09sEL2EEuv5fFowm419c1+a/jSMiBjI9gHxVLrVdbUkkNUUfjsVYs9pVZu5oCon/kmRh9TfLEObFtkVxmY0w==} - engines: {node: '>=8.0.0'} + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} nanoid@3.3.11: resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} @@ -2450,9 +2183,6 @@ packages: resolution: {integrity: sha512-9MdFxmkKaOYVTV+XVRG8ArDwwQ77XIgIPyKASB1k3JPq3M8fGQQQE3YpMOrKm6g//Ktx8ivZr8xo1Qmtqub+GA==} engines: {node: ^18 || ^20 || >= 21} - node-fetch-native@1.6.7: - resolution: {integrity: sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==} - node-gyp-build@4.8.4: resolution: {integrity: sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==} hasBin: true @@ -2460,11 +2190,6 @@ packages: node-releases@2.0.37: resolution: {integrity: sha512-1h5gKZCF+pO/o3Iqt5Jp7wc9rH3eJJ0+nh/CIoiRwjRxde/hAHyLPXYN4V3CqKAbiZPSeJFSWHmJsbkicta0Eg==} - nypm@0.6.5: - resolution: {integrity: sha512-K6AJy1GMVyfyMXRVB88700BJqNUkByijGJM8kEHpLdcAt+vSQAVfkWWHYzuRXHSY6xA2sNc5RjTj0p9rE2izVQ==} - engines: {node: '>=18'} - hasBin: true - object-assign@4.1.1: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} @@ -2473,9 +2198,6 @@ packages: resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} engines: {node: '>= 0.4'} - ohash@2.0.11: - resolution: {integrity: sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==} - on-finished@2.4.1: resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} engines: {node: '>= 0.8'} @@ -2545,49 +2267,9 @@ packages: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} engines: {node: '>=8'} - pathe@2.0.3: - resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} - pend@1.2.0: resolution: {integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==} - perfect-debounce@1.0.0: - resolution: {integrity: sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==} - - pg-cloudflare@1.3.0: - resolution: {integrity: sha512-6lswVVSztmHiRtD6I8hw4qP/nDm1EJbKMRhf3HCYaqud7frGysPv7FYJ5noZQdhQtN2xJnimfMtvQq21pdbzyQ==} - - pg-connection-string@2.12.0: - resolution: {integrity: sha512-U7qg+bpswf3Cs5xLzRqbXbQl85ng0mfSV/J0nnA31MCLgvEaAo7CIhmeyrmJpOr7o+zm0rXK+hNnT5l9RHkCkQ==} - - pg-int8@1.0.1: - resolution: {integrity: sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==} - engines: {node: '>=4.0.0'} - - pg-pool@3.13.0: - resolution: {integrity: sha512-gB+R+Xud1gLFuRD/QgOIgGOBE2KCQPaPwkzBBGC9oG69pHTkhQeIuejVIk3/cnDyX39av2AxomQiyPT13WKHQA==} - peerDependencies: - pg: '>=8.0' - - pg-protocol@1.13.0: - resolution: {integrity: sha512-zzdvXfS6v89r6v7OcFCHfHlyG/wvry1ALxZo4LqgUoy7W9xhBDMaqOuMiF3qEV45VqsN6rdlcehHrfDtlCPc8w==} - - pg-types@2.2.0: - resolution: {integrity: sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==} - engines: {node: '>=4'} - - pg@8.20.0: - resolution: {integrity: sha512-ldhMxz2r8fl/6QkXnBD3CR9/xg694oT6DZQ2s6c/RI28OjtSOpxnPrUCGOBJ46RCUxcWdx3p6kw/xnDHjKvaRA==} - engines: {node: '>= 16.0.0'} - peerDependencies: - pg-native: '>=3.0.1' - peerDependenciesMeta: - pg-native: - optional: true - - pgpass@1.0.5: - resolution: {integrity: sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==} - picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} @@ -2603,9 +2285,6 @@ packages: resolution: {integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==} engines: {node: '>=8'} - pkg-types@2.3.0: - resolution: {integrity: sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==} - plist@3.1.0: resolution: {integrity: sha512-uysumyrvkUX0rX/dEVqt8gC3sTBzd4zoWfLeS29nb53imdaXVvLINYXTI2GNqzaMuvacNx4uJQ8+b3zXR0pkgQ==} engines: {node: '>=10.4.0'} @@ -2614,30 +2293,6 @@ packages: resolution: {integrity: sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==} engines: {node: ^10 || ^12 || >=14} - postgres-array@2.0.0: - resolution: {integrity: sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==} - engines: {node: '>=4'} - - postgres-array@3.0.4: - resolution: {integrity: sha512-nAUSGfSDGOaOAEGwqsRY27GPOea7CNipJPOA7lPbdEpx5Kg3qzdP0AaWC5MlhTWV9s4hFX39nomVZ+C4tnGOJQ==} - engines: {node: '>=12'} - - postgres-bytea@1.0.1: - resolution: {integrity: sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ==} - engines: {node: '>=0.10.0'} - - postgres-date@1.0.7: - resolution: {integrity: sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==} - engines: {node: '>=0.10.0'} - - postgres-interval@1.2.0: - resolution: {integrity: sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==} - engines: {node: '>=0.10.0'} - - postgres@3.4.7: - resolution: {integrity: sha512-Jtc2612XINuBjIl/QTWsV5UvE8UHuNblcO3vVADSrKsrc6RqGX6lOW1cEo3CM2v0XG4Nat8nI+YM7/f26VxXLw==} - engines: {node: '>=12'} - prelude-ls@1.2.1: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} @@ -2651,26 +2306,10 @@ packages: engines: {node: '>=14'} hasBin: true - prisma@7.6.0: - resolution: {integrity: sha512-OKJIPT81K3+F+AayIkY/Y3mkF2NWoFh7lZApaaqPYy7EHILKdO0VsmGkP+hDKYTySHsFSyLWXm/JgcR1B8fY1Q==} - engines: {node: ^20.19 || ^22.12 || >=24.0} - hasBin: true - peerDependencies: - better-sqlite3: '>=9.0.0' - typescript: '>=5.4.0' - peerDependenciesMeta: - better-sqlite3: - optional: true - typescript: - optional: true - prompts@2.4.2: resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} engines: {node: '>= 6'} - proper-lockfile@4.1.2: - resolution: {integrity: sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==} - proxy-addr@2.0.7: resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} engines: {node: '>= 0.10'} @@ -2683,9 +2322,6 @@ packages: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} - pure-rand@6.1.0: - resolution: {integrity: sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==} - qs@6.15.0: resolution: {integrity: sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ==} engines: {node: '>=0.6'} @@ -2701,9 +2337,6 @@ packages: resolution: {integrity: sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==} engines: {node: '>= 0.10'} - rc9@2.1.2: - resolution: {integrity: sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg==} - react-dom@19.2.4: resolution: {integrity: sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==} peerDependencies: @@ -2737,17 +2370,6 @@ packages: resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} engines: {node: '>= 6'} - readdirp@4.1.2: - resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} - engines: {node: '>= 14.18.0'} - - remeda@2.33.4: - resolution: {integrity: sha512-ygHswjlc/opg2VrtiYvUOPLjxjtdKvjGz1/plDhkG66hjNjFr1xmfrs2ClNFo/E6TyUFiwYNh53bKV26oBoMGQ==} - - require-from-string@2.0.2: - resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} - engines: {node: '>=0.10.0'} - resolve-from@4.0.0: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} @@ -2759,10 +2381,6 @@ packages: resolution: {integrity: sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==} engines: {node: '>=18'} - retry@0.12.0: - resolution: {integrity: sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==} - engines: {node: '>= 4'} - reusify@1.1.0: resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} @@ -2819,9 +2437,6 @@ packages: resolution: {integrity: sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==} engines: {node: '>= 18'} - seq-queue@0.0.5: - resolution: {integrity: sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q==} - serve-static@2.2.1: resolution: {integrity: sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==} engines: {node: '>= 18'} @@ -2886,21 +2501,17 @@ packages: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} + sparse-bitfield@3.0.3: + resolution: {integrity: sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==} + split2@4.2.0: resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} engines: {node: '>= 10.x'} - sqlstring@2.3.3: - resolution: {integrity: sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==} - engines: {node: '>= 0.6'} - statuses@2.0.2: resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} engines: {node: '>= 0.8'} - std-env@3.10.0: - resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==} - string-argv@0.3.2: resolution: {integrity: sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==} engines: {node: '>=0.6.19'} @@ -2977,6 +2588,10 @@ packages: resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} engines: {node: '>=0.6'} + tr46@5.1.1: + resolution: {integrity: sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==} + engines: {node: '>=18'} + tree-kill@1.2.2: resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} hasBin: true @@ -3046,14 +2661,6 @@ packages: util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} - valibot@1.2.0: - resolution: {integrity: sha512-mm1rxUsmOxzrwnX5arGS+U4T25RdvpPjPN4yR0u9pUBov9+zGVtO84tif1eY4r6zWxVxu3KzIyknJy3rxfRZZg==} - peerDependencies: - typescript: '>=5' - peerDependenciesMeta: - typescript: - optional: true - vary@1.1.2: resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} engines: {node: '>= 0.8'} @@ -3101,6 +2708,14 @@ packages: yaml: optional: true + webidl-conversions@7.0.0: + resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==} + engines: {node: '>=12'} + + whatwg-url@14.2.0: + resolution: {integrity: sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==} + engines: {node: '>=18'} + which@2.0.2: resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} engines: {node: '>= 8'} @@ -3133,10 +2748,6 @@ packages: resolution: {integrity: sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg==} engines: {node: '>=8.0'} - xtend@4.0.2: - resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} - engines: {node: '>=0.4'} - yallist@3.1.1: resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} @@ -3156,9 +2767,6 @@ packages: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} - zeptomatch@2.1.0: - resolution: {integrity: sha512-KiGErG2J0G82LSpniV0CtIzjlJ10E04j02VOudJsPyPwNZgGnRKQy7I1R7GMyg/QswnE4l7ohSGrQbQbjXPPDA==} - zod-validation-error@4.0.2: resolution: {integrity: sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==} engines: {node: '>=18.0.0'} @@ -3370,27 +2978,6 @@ snapshots: dependencies: '@capacitor/core': 8.3.0 - '@clack/core@0.5.0': - dependencies: - picocolors: 1.1.1 - sisteransi: 1.0.5 - - '@clack/prompts@0.11.0': - dependencies: - '@clack/core': 0.5.0 - picocolors: 1.1.1 - sisteransi: 1.0.5 - - '@electric-sql/pglite-socket@0.1.1(@electric-sql/pglite@0.4.1)': - dependencies: - '@electric-sql/pglite': 0.4.1 - - '@electric-sql/pglite-tools@0.3.1(@electric-sql/pglite@0.4.1)': - dependencies: - '@electric-sql/pglite': 0.4.1 - - '@electric-sql/pglite@0.4.1': {} - '@emnapi/core@1.9.2': dependencies: '@emnapi/wasi-threads': 1.2.1 @@ -3535,10 +3122,6 @@ snapshots: '@eslint/core': 0.17.0 levn: 0.4.1 - '@hono/node-server@1.19.11(hono@4.12.10)': - dependencies: - hono: 4.12.10 - '@humanfs/core@0.19.1': {} '@humanfs/node@0.16.7': @@ -3649,7 +3232,9 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.5 - '@kurkle/color@0.3.4': {} + '@mongodb-js/saslprep@1.4.11': + dependencies: + sparse-bitfield: 3.0.3 '@napi-rs/wasm-runtime@1.1.2(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)': dependencies: @@ -3674,161 +3259,6 @@ snapshots: '@pkgr/core@0.2.9': {} - '@prisma/adapter-pg@7.6.0': - dependencies: - '@prisma/driver-adapter-utils': 7.6.0 - '@types/pg': 8.20.0 - pg: 8.20.0 - postgres-array: 3.0.4 - transitivePeerDependencies: - - pg-native - - '@prisma/client-runtime-utils@7.6.0': {} - - '@prisma/client@7.6.0(prisma@7.6.0(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3))(typescript@5.9.3)': - dependencies: - '@prisma/client-runtime-utils': 7.6.0 - optionalDependencies: - prisma: 7.6.0(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3) - typescript: 5.9.3 - - '@prisma/config@7.6.0': - dependencies: - c12: 3.1.0 - deepmerge-ts: 7.1.5 - effect: 3.20.0 - empathic: 2.0.0 - transitivePeerDependencies: - - magicast - - '@prisma/debug@7.2.0': {} - - '@prisma/debug@7.6.0': {} - - '@prisma/dev@0.24.3(typescript@5.9.3)': - dependencies: - '@electric-sql/pglite': 0.4.1 - '@electric-sql/pglite-socket': 0.1.1(@electric-sql/pglite@0.4.1) - '@electric-sql/pglite-tools': 0.3.1(@electric-sql/pglite@0.4.1) - '@hono/node-server': 1.19.11(hono@4.12.10) - '@prisma/get-platform': 7.2.0 - '@prisma/query-plan-executor': 7.2.0 - '@prisma/streams-local': 0.1.2 - foreground-child: 3.3.1 - get-port-please: 3.2.0 - hono: 4.12.10 - 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(typescript@5.9.3) - zeptomatch: 2.1.0 - transitivePeerDependencies: - - typescript - - '@prisma/driver-adapter-utils@7.6.0': - dependencies: - '@prisma/debug': 7.6.0 - - '@prisma/engines-version@7.6.0-1.75cbdc1eb7150937890ad5465d861175c6624711': {} - - '@prisma/engines@7.6.0': - dependencies: - '@prisma/debug': 7.6.0 - '@prisma/engines-version': 7.6.0-1.75cbdc1eb7150937890ad5465d861175c6624711 - '@prisma/fetch-engine': 7.6.0 - '@prisma/get-platform': 7.6.0 - - '@prisma/fetch-engine@7.6.0': - dependencies: - '@prisma/debug': 7.6.0 - '@prisma/engines-version': 7.6.0-1.75cbdc1eb7150937890ad5465d861175c6624711 - '@prisma/get-platform': 7.6.0 - - '@prisma/get-platform@7.2.0': - dependencies: - '@prisma/debug': 7.2.0 - - '@prisma/get-platform@7.6.0': - dependencies: - '@prisma/debug': 7.6.0 - - '@prisma/query-plan-executor@7.2.0': {} - - '@prisma/streams-local@0.1.2': - dependencies: - ajv: 8.18.0 - better-result: 2.7.0 - env-paths: 3.0.0 - proper-lockfile: 4.1.2 - - '@prisma/studio-core@0.27.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': - dependencies: - '@radix-ui/react-toggle': 1.1.10(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@types/react': 19.2.14 - chart.js: 4.5.1 - react: 19.2.4 - react-dom: 19.2.4(react@19.2.4) - transitivePeerDependencies: - - '@types/react-dom' - - '@radix-ui/primitive@1.1.3': {} - - '@radix-ui/react-compose-refs@1.1.2(@types/react@19.2.14)(react@19.2.4)': - dependencies: - react: 19.2.4 - optionalDependencies: - '@types/react': 19.2.14 - - '@radix-ui/react-primitive@2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': - dependencies: - '@radix-ui/react-slot': 1.2.3(@types/react@19.2.14)(react@19.2.4) - react: 19.2.4 - react-dom: 19.2.4(react@19.2.4) - optionalDependencies: - '@types/react': 19.2.14 - '@types/react-dom': 19.2.3(@types/react@19.2.14) - - '@radix-ui/react-slot@1.2.3(@types/react@19.2.14)(react@19.2.4)': - dependencies: - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.4) - react: 19.2.4 - optionalDependencies: - '@types/react': 19.2.14 - - '@radix-ui/react-toggle@1.1.10(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': - dependencies: - '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.4) - react: 19.2.4 - react-dom: 19.2.4(react@19.2.4) - optionalDependencies: - '@types/react': 19.2.14 - '@types/react-dom': 19.2.3(@types/react@19.2.14) - - '@radix-ui/react-use-controllable-state@1.2.2(@types/react@19.2.14)(react@19.2.4)': - dependencies: - '@radix-ui/react-use-effect-event': 0.0.2(@types/react@19.2.14)(react@19.2.4) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.4) - react: 19.2.4 - optionalDependencies: - '@types/react': 19.2.14 - - '@radix-ui/react-use-effect-event@0.0.2(@types/react@19.2.14)(react@19.2.4)': - dependencies: - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.4) - react: 19.2.4 - optionalDependencies: - '@types/react': 19.2.14 - - '@radix-ui/react-use-layout-effect@1.1.1(@types/react@19.2.14)(react@19.2.4)': - dependencies: - react: 19.2.4 - optionalDependencies: - '@types/react': 19.2.14 - '@rc-component/async-validator@5.1.0': dependencies: '@babel/runtime': 7.29.2 @@ -4229,8 +3659,6 @@ snapshots: '@rolldown/pluginutils@1.0.0-rc.7': {} - '@standard-schema/spec@1.1.0': {} - '@tybys/wasm-util@0.10.1': dependencies: tslib: 2.8.1 @@ -4289,12 +3717,6 @@ snapshots: dependencies: undici-types: 7.16.0 - '@types/pg@8.20.0': - dependencies: - '@types/node': 24.12.2 - pg-protocol: 1.13.0 - pg-types: 2.2.0 - '@types/qs@6.15.0': {} '@types/range-parser@1.2.7': {} @@ -4318,6 +3740,12 @@ snapshots: '@types/slice-ansi@4.0.0': {} + '@types/webidl-conversions@7.0.3': {} + + '@types/whatwg-url@13.0.0': + dependencies: + '@types/webidl-conversions': 7.0.3 + '@typescript-eslint/eslint-plugin@8.58.0(@typescript-eslint/parser@8.58.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)': dependencies: '@eslint-community/regexpp': 4.12.2 @@ -4434,13 +3862,6 @@ snapshots: json-schema-traverse: 0.4.1 uri-js: 4.4.1 - ajv@8.18.0: - dependencies: - fast-deep-equal: 3.1.3 - fast-uri: 3.1.0 - json-schema-traverse: 1.0.0 - require-from-string: 2.0.2 - ansi-escapes@7.3.0: dependencies: environment: 1.1.0 @@ -4524,8 +3945,6 @@ snapshots: at-least-node@1.0.0: {} - aws-ssl-profiles@1.1.2: {} - axios@1.14.0: dependencies: follow-redirects: 1.15.11 @@ -4547,10 +3966,6 @@ snapshots: node-addon-api: 8.7.0 node-gyp-build: 4.8.4 - better-result@2.7.0: - dependencies: - '@clack/prompts': 0.11.0 - big-integer@1.6.52: {} body-parser@2.2.2: @@ -4592,27 +4007,14 @@ snapshots: node-releases: 2.0.37 update-browserslist-db: 1.2.3(browserslist@4.28.2) + bson@7.2.0: {} + buffer-crc32@0.2.13: {} buffer-equal-constant-time@1.0.1: {} bytes@3.1.2: {} - c12@3.1.0: - dependencies: - chokidar: 4.0.3 - confbox: 0.2.4 - defu: 6.1.6 - dotenv: 16.6.1 - exsolve: 1.0.8 - giget: 2.0.0 - jiti: 2.6.1 - ohash: 2.0.11 - pathe: 2.0.3 - perfect-debounce: 1.0.0 - pkg-types: 2.3.0 - rc9: 2.1.2 - call-bind-apply-helpers@1.0.2: dependencies: es-errors: 1.3.0 @@ -4634,22 +4036,8 @@ snapshots: ansi-styles: 4.3.0 supports-color: 7.2.0 - chart.js@4.5.1: - dependencies: - '@kurkle/color': 0.3.4 - - chokidar@4.0.3: - dependencies: - readdirp: 4.1.2 - chownr@3.0.0: {} - citty@0.1.6: - dependencies: - consola: 3.4.2 - - citty@0.2.2: {} - cli-cursor@5.0.0: dependencies: restore-cursor: 5.1.0 @@ -4685,10 +4073,6 @@ snapshots: concat-map@0.0.1: {} - confbox@0.2.4: {} - - consola@3.4.2: {} - content-disposition@1.0.1: {} content-type@1.0.5: {} @@ -4722,28 +4106,18 @@ snapshots: deep-is@0.1.4: {} - deepmerge-ts@7.1.5: {} - define-lazy-prop@2.0.0: {} - defu@6.1.6: {} - delayed-stream@1.0.0: {} - denque@2.1.0: {} - depd@2.0.0: {} - destr@2.0.5: {} - detect-libc@2.1.2: {} dir-glob@3.0.1: dependencies: path-type: 4.0.0 - dotenv@16.6.1: {} - dotenv@17.4.0: {} dunder-proto@1.0.1: @@ -4758,11 +4132,6 @@ snapshots: ee-first@1.1.1: {} - effect@3.20.0: - dependencies: - '@standard-schema/spec': 1.1.0 - fast-check: 3.23.2 - electron-to-chromium@1.5.331: {} elementtree@0.1.7: @@ -4775,14 +4144,10 @@ snapshots: emoji-regex@8.0.0: {} - empathic@2.0.0: {} - encodeurl@2.0.0: {} env-paths@2.2.1: {} - env-paths@3.0.0: {} - environment@1.1.0: {} es-define-property@1.0.1: {} @@ -4972,12 +4337,6 @@ snapshots: transitivePeerDependencies: - supports-color - exsolve@1.0.8: {} - - fast-check@3.23.2: - dependencies: - pure-rand: 6.1.0 - fast-deep-equal@3.1.3: {} fast-diff@1.3.0: {} @@ -4994,8 +4353,6 @@ snapshots: fast-levenshtein@2.0.6: {} - fast-uri@3.1.0: {} - fastq@1.20.1: dependencies: reusify: 1.1.0 @@ -5060,11 +4417,6 @@ snapshots: follow-redirects@1.15.11: {} - foreground-child@3.3.1: - dependencies: - cross-spawn: 7.0.6 - signal-exit: 4.1.0 - form-data@4.0.5: dependencies: asynckit: 0.4.0 @@ -5095,10 +4447,6 @@ snapshots: function-bind@1.1.2: {} - generate-function@2.3.1: - dependencies: - is-property: 1.0.2 - gensync@1.0.0-beta.2: {} get-east-asian-width@1.5.0: {} @@ -5116,8 +4464,6 @@ snapshots: hasown: 2.0.2 math-intrinsics: 1.1.0 - get-port-please@3.2.0: {} - get-proto@1.0.1: dependencies: dunder-proto: 1.0.1 @@ -5137,15 +4483,6 @@ snapshots: fs-extra: 11.3.4 globby: 11.1.0 - giget@2.0.0: - dependencies: - citty: 0.1.6 - consola: 3.4.2 - defu: 6.1.6 - node-fetch-native: 1.6.7 - nypm: 0.6.5 - pathe: 2.0.3 - glob-parent@5.1.2: dependencies: is-glob: 4.0.3 @@ -5177,10 +4514,6 @@ snapshots: graceful-fs@4.2.11: {} - grammex@3.1.12: {} - - graphmatch@1.1.1: {} - has-flag@4.0.0: {} has-symbols@1.1.0: {} @@ -5199,8 +4532,6 @@ snapshots: dependencies: hermes-estree: 0.25.1 - hono@4.12.10: {} - http-errors@2.0.1: dependencies: depd: 2.0.0 @@ -5209,8 +4540,6 @@ snapshots: statuses: 2.0.2 toidentifier: 1.0.1 - http-status-codes@2.3.0: {} - husky@9.1.7: {} iconv-lite@0.7.2: @@ -5254,15 +4583,14 @@ snapshots: is-promise@4.0.0: {} - is-property@1.0.2: {} - is-wsl@2.2.0: dependencies: is-docker: 2.2.1 isexe@2.0.0: {} - jiti@2.6.1: {} + jiti@2.6.1: + optional: true js-tokens@4.0.0: {} @@ -5276,8 +4604,6 @@ snapshots: json-schema-traverse@0.4.1: {} - json-schema-traverse@1.0.0: {} - json-stable-stringify-without-jsonify@1.0.1: {} json2mq@0.2.0: @@ -5428,16 +4754,12 @@ snapshots: strip-ansi: 7.2.0 wrap-ansi: 9.0.2 - long@5.3.2: {} - lru-cache@11.2.7: {} lru-cache@5.1.1: dependencies: yallist: 3.1.1 - lru.min@1.1.4: {} - lucide-react@1.7.0(react@19.2.4): dependencies: react: 19.2.4 @@ -5450,6 +4772,8 @@ snapshots: media-typer@1.1.0: {} + memory-pager@1.5.0: {} + merge-descriptors@2.0.0: {} merge2@1.4.1: {} @@ -5487,23 +4811,18 @@ snapshots: dependencies: minipass: 7.1.3 - ms@2.1.3: {} - - mysql2@3.15.3: + mongodb-connection-string-url@7.0.1: dependencies: - aws-ssl-profiles: 1.1.2 - denque: 2.1.0 - generate-function: 2.3.1 - iconv-lite: 0.7.2 - long: 5.3.2 - lru.min: 1.1.4 - named-placeholders: 1.1.6 - seq-queue: 0.0.5 - sqlstring: 2.3.3 + '@types/whatwg-url': 13.0.0 + whatwg-url: 14.2.0 - named-placeholders@1.1.6: + mongodb@7.2.0: dependencies: - lru.min: 1.1.4 + '@mongodb-js/saslprep': 1.4.11 + bson: 7.2.0 + mongodb-connection-string-url: 7.0.1 + + ms@2.1.3: {} nanoid@3.3.11: {} @@ -5529,24 +4848,14 @@ snapshots: node-addon-api@8.7.0: {} - node-fetch-native@1.6.7: {} - node-gyp-build@4.8.4: {} node-releases@2.0.37: {} - nypm@0.6.5: - dependencies: - citty: 0.2.2 - pathe: 2.0.3 - tinyexec: 1.0.4 - object-assign@4.1.1: {} object-inspect@1.13.4: {} - ohash@2.0.11: {} - on-finished@2.4.1: dependencies: ee-first: 1.1.1 @@ -5613,47 +4922,8 @@ snapshots: path-type@4.0.0: {} - pathe@2.0.3: {} - pend@1.2.0: {} - perfect-debounce@1.0.0: {} - - pg-cloudflare@1.3.0: - optional: true - - pg-connection-string@2.12.0: {} - - pg-int8@1.0.1: {} - - pg-pool@3.13.0(pg@8.20.0): - dependencies: - pg: 8.20.0 - - pg-protocol@1.13.0: {} - - pg-types@2.2.0: - dependencies: - pg-int8: 1.0.1 - postgres-array: 2.0.0 - postgres-bytea: 1.0.1 - postgres-date: 1.0.7 - postgres-interval: 1.2.0 - - pg@8.20.0: - dependencies: - pg-connection-string: 2.12.0 - pg-pool: 3.13.0(pg@8.20.0) - pg-protocol: 1.13.0 - pg-types: 2.2.0 - pgpass: 1.0.5 - optionalDependencies: - pg-cloudflare: 1.3.0 - - pgpass@1.0.5: - dependencies: - split2: 4.2.0 - picocolors@1.1.1: {} picomatch@2.3.2: {} @@ -5664,12 +4934,6 @@ snapshots: dependencies: find-up: 4.1.0 - pkg-types@2.3.0: - dependencies: - confbox: 0.2.4 - exsolve: 1.0.8 - pathe: 2.0.3 - plist@3.1.0: dependencies: '@xmldom/xmldom': 0.8.12 @@ -5682,20 +4946,6 @@ snapshots: picocolors: 1.1.1 source-map-js: 1.2.1 - postgres-array@2.0.0: {} - - postgres-array@3.0.4: {} - - postgres-bytea@1.0.1: {} - - postgres-date@1.0.7: {} - - postgres-interval@1.2.0: - dependencies: - xtend: 4.0.2 - - postgres@3.4.7: {} - prelude-ls@1.2.1: {} prettier-linter-helpers@1.0.1: @@ -5704,34 +4954,11 @@ snapshots: prettier@3.8.1: {} - prisma@7.6.0(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3): - dependencies: - '@prisma/config': 7.6.0 - '@prisma/dev': 0.24.3(typescript@5.9.3) - '@prisma/engines': 7.6.0 - '@prisma/studio-core': 0.27.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - mysql2: 3.15.3 - postgres: 3.4.7 - optionalDependencies: - typescript: 5.9.3 - transitivePeerDependencies: - - '@types/react' - - '@types/react-dom' - - magicast - - react - - react-dom - prompts@2.4.2: dependencies: kleur: 3.0.3 sisteransi: 1.0.5 - proper-lockfile@4.1.2: - dependencies: - graceful-fs: 4.2.11 - retry: 0.12.0 - signal-exit: 3.0.7 - proxy-addr@2.0.7: dependencies: forwarded: 0.2.0 @@ -5741,8 +4968,6 @@ snapshots: punycode@2.3.1: {} - pure-rand@6.1.0: {} - qs@6.15.0: dependencies: side-channel: 1.1.0 @@ -5758,11 +4983,6 @@ snapshots: iconv-lite: 0.7.2 unpipe: 1.0.0 - rc9@2.1.2: - dependencies: - defu: 6.1.6 - destr: 2.0.5 - react-dom@19.2.4(react@19.2.4): dependencies: react: 19.2.4 @@ -5792,12 +5012,6 @@ snapshots: string_decoder: 1.3.0 util-deprecate: 1.0.2 - readdirp@4.1.2: {} - - remeda@2.33.4: {} - - require-from-string@2.0.2: {} - resolve-from@4.0.0: {} resolve-pkg-maps@1.0.0: {} @@ -5807,8 +5021,6 @@ snapshots: onetime: 7.0.0 signal-exit: 4.1.0 - retry@0.12.0: {} - reusify@1.1.0: {} rfdc@1.4.1: {} @@ -5890,8 +5102,6 @@ snapshots: transitivePeerDependencies: - supports-color - seq-queue@0.0.5: {} - serve-static@2.2.1: dependencies: encodeurl: 2.0.0 @@ -5965,14 +5175,14 @@ snapshots: source-map-js@1.2.1: {} - split2@4.2.0: {} + sparse-bitfield@3.0.3: + dependencies: + memory-pager: 1.5.0 - sqlstring@2.3.3: {} + split2@4.2.0: {} statuses@2.0.2: {} - std-env@3.10.0: {} - string-argv@0.3.2: {} string-convert@0.2.1: {} @@ -6049,6 +5259,10 @@ snapshots: toidentifier@1.0.1: {} + tr46@5.1.1: + dependencies: + punycode: 2.3.1 + tree-kill@1.2.2: {} trim-repeated@1.0.0: @@ -6111,10 +5325,6 @@ snapshots: util-deprecate@1.0.2: {} - valibot@1.2.0(typescript@5.9.3): - optionalDependencies: - typescript: 5.9.3 - vary@1.1.2: {} vite@8.0.3(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)(@types/node@24.12.2)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3): @@ -6135,6 +5345,13 @@ snapshots: - '@emnapi/core' - '@emnapi/runtime' + webidl-conversions@7.0.0: {} + + whatwg-url@14.2.0: + dependencies: + tr46: 5.1.1 + webidl-conversions: 7.0.0 + which@2.0.2: dependencies: isexe: 2.0.0 @@ -6164,8 +5381,6 @@ snapshots: xmlbuilder@15.1.1: {} - xtend@4.0.2: {} - yallist@3.1.1: {} yallist@5.0.0: {} @@ -6179,11 +5394,6 @@ snapshots: yocto-queue@0.1.0: {} - zeptomatch@2.1.0: - dependencies: - grammex: 3.1.12 - graphmatch: 1.1.1 - zod-validation-error@4.0.2(zod@4.3.6): dependencies: zod: 4.3.6