From 298898f79d65e2cfaedd4b0981730e11e42e9920 Mon Sep 17 00:00:00 2001 From: Oleg Proskurin Date: Wed, 1 Oct 2025 00:14:14 +0700 Subject: [PATCH] feat: implement apikey auth --- .env.docker | 3 +- apps/api-service/Dockerfile.mono | 26 + apps/api-service/package.json | 1 + apps/api-service/src/app.ts | 22 +- apps/api-service/src/db.ts | 8 + .../src/middleware/auth/rateLimiter.ts | 93 +++ .../src/middleware/auth/requireMasterKey.ts | 31 + .../src/middleware/auth/validateApiKey.ts | 58 ++ apps/api-service/src/routes/admin/keys.ts | 137 ++++ apps/api-service/src/routes/bootstrap.ts | 47 ++ apps/api-service/src/routes/generate.ts | 6 + .../api-service/src/services/ApiKeyService.ts | 164 +++++ docker-compose.yml | 8 +- packages/database/drizzle.config.ts | 10 + .../migrations/0000_gifted_sunfire.sql | 15 + .../migrations/meta/0000_snapshot.json | 117 ++++ .../database/migrations/meta/_journal.json | 13 + packages/database/package.json | 21 + packages/database/src/client.ts | 10 + packages/database/src/index.ts | 3 + packages/database/src/schema/apiKeys.ts | 31 + packages/database/src/schema/index.ts | 1 + packages/database/tsconfig.json | 14 + pnpm-lock.yaml | 656 +++++++++++++++++- pnpm-workspace.yaml | 3 +- 25 files changed, 1486 insertions(+), 12 deletions(-) create mode 100644 apps/api-service/Dockerfile.mono create mode 100644 apps/api-service/src/db.ts create mode 100644 apps/api-service/src/middleware/auth/rateLimiter.ts create mode 100644 apps/api-service/src/middleware/auth/requireMasterKey.ts create mode 100644 apps/api-service/src/middleware/auth/validateApiKey.ts create mode 100644 apps/api-service/src/routes/admin/keys.ts create mode 100644 apps/api-service/src/routes/bootstrap.ts create mode 100644 apps/api-service/src/services/ApiKeyService.ts create mode 100644 packages/database/drizzle.config.ts create mode 100644 packages/database/migrations/0000_gifted_sunfire.sql create mode 100644 packages/database/migrations/meta/0000_snapshot.json create mode 100644 packages/database/migrations/meta/_journal.json create mode 100644 packages/database/package.json create mode 100644 packages/database/src/client.ts create mode 100644 packages/database/src/index.ts create mode 100644 packages/database/src/schema/apiKeys.ts create mode 100644 packages/database/src/schema/index.ts create mode 100644 packages/database/tsconfig.json diff --git a/.env.docker b/.env.docker index 9361ceb..9bf3c3b 100644 --- a/.env.docker +++ b/.env.docker @@ -10,9 +10,10 @@ CORS_ORIGIN=* # Database Configuration DB_HOST=postgres DB_PORT=5432 -DB_NAME=banatie +DB_NAME=banatie_db DB_USER=banatie_user DB_PASSWORD=banatie_secure_password +DATABASE_URL=postgresql://banatie_user:banatie_secure_password@postgres:5432/banatie_db # MinIO Storage Configuration (SNMD) MINIO_ROOT_USER=banatie_admin diff --git a/apps/api-service/Dockerfile.mono b/apps/api-service/Dockerfile.mono new file mode 100644 index 0000000..454f408 --- /dev/null +++ b/apps/api-service/Dockerfile.mono @@ -0,0 +1,26 @@ +# Monorepo-aware Dockerfile for API Service +# Development stage - for docker-compose development with hot reload +FROM node:20-alpine AS development +WORKDIR /app + +# Install pnpm globally +RUN npm install -g pnpm@10.11.0 + +# Copy workspace configuration from root +COPY pnpm-workspace.yaml package.json pnpm-lock.yaml ./ + +# Copy all workspace packages +COPY packages/ ./packages/ +COPY apps/api-service/ ./apps/api-service/ + +# Install all dependencies (workspace-aware) +RUN pnpm install --frozen-lockfile + +# Set working directory to API service +WORKDIR /app/apps/api-service + +# Expose port +EXPOSE 3000 + +# Use development command with hot reload +CMD ["pnpm", "dev"] \ No newline at end of file diff --git a/apps/api-service/package.json b/apps/api-service/package.json index 32aec97..76fef4a 100644 --- a/apps/api-service/package.json +++ b/apps/api-service/package.json @@ -36,6 +36,7 @@ "pnpm": ">=8.0.0" }, "dependencies": { + "@banatie/database": "workspace:*", "@google/genai": "^1.17.0", "cors": "^2.8.5", "dotenv": "^17.2.2", diff --git a/apps/api-service/src/app.ts b/apps/api-service/src/app.ts index 955af40..b30c924 100644 --- a/apps/api-service/src/app.ts +++ b/apps/api-service/src/app.ts @@ -6,6 +6,8 @@ import { generateRouter } from './routes/generate'; import { enhanceRouter } from './routes/enhance'; import { textToImageRouter } from './routes/textToImage'; import { imagesRouter } from './routes/images'; +import bootstrapRoutes from './routes/bootstrap'; +import adminKeysRoutes from './routes/admin/keys'; import { errorHandler, notFoundHandler } from './middleware/errorHandler'; // Load environment variables @@ -25,9 +27,16 @@ export const appConfig: Config = { export const createApp = (): Application => { const app = express(); - // Middleware + // Middleware - CORS configuration + const corsOrigin = process.env['CORS_ORIGIN']?.split(',') || [ + 'http://localhost:3001', // Landing + 'http://localhost:3002', // Studio + 'http://localhost:3003', // Admin + '*' // Allow all for development + ]; + app.use(cors({ - origin: process.env['CORS_ORIGIN'] || '*', + origin: corsOrigin, credentials: true })); @@ -79,7 +88,14 @@ export const createApp = (): Application => { res.json(info); }); - // Mount API routes + // Public routes (no authentication) + // Bootstrap route (no auth, but works only once) + app.use('/api/bootstrap', bootstrapRoutes); + + // Admin routes (require master key) + app.use('/api/admin/keys', adminKeysRoutes); + + // Protected API routes (require valid API key) app.use('/api', generateRouter); app.use('/api', enhanceRouter); app.use('/api', textToImageRouter); diff --git a/apps/api-service/src/db.ts b/apps/api-service/src/db.ts new file mode 100644 index 0000000..e084852 --- /dev/null +++ b/apps/api-service/src/db.ts @@ -0,0 +1,8 @@ +import { createDbClient } from '@banatie/database'; + +const DATABASE_URL = process.env['DATABASE_URL'] || + 'postgresql://banatie_user:banatie_secure_password@localhost:5434/banatie_db'; + +export const db = createDbClient(DATABASE_URL); + +console.log(`[${new Date().toISOString()}] Database client initialized - ${new URL(DATABASE_URL).host}`); \ No newline at end of file diff --git a/apps/api-service/src/middleware/auth/rateLimiter.ts b/apps/api-service/src/middleware/auth/rateLimiter.ts new file mode 100644 index 0000000..c4ecebf --- /dev/null +++ b/apps/api-service/src/middleware/auth/rateLimiter.ts @@ -0,0 +1,93 @@ +import { Request, Response, NextFunction } from 'express'; + +/** + * Simple in-memory rate limiter + * Tracks requests per API key + */ +class RateLimiter { + private requests: Map = new Map(); + private readonly limit: number; + private readonly windowMs: number; + + constructor(limit: number = 100, windowMs: number = 60 * 60 * 1000) { + this.limit = limit; + this.windowMs = windowMs; + + // Cleanup old entries every 5 minutes + setInterval(() => this.cleanup(), 5 * 60 * 1000); + } + + check(keyId: string): { allowed: boolean; remaining: number; resetAt: number } { + const now = Date.now(); + const record = this.requests.get(keyId); + + if (!record || record.resetAt < now) { + // Create new window + const resetAt = now + this.windowMs; + this.requests.set(keyId, { count: 1, resetAt }); + return { allowed: true, remaining: this.limit - 1, resetAt }; + } + + if (record.count >= this.limit) { + return { allowed: false, remaining: 0, resetAt: record.resetAt }; + } + + // Increment counter + record.count++; + return { + allowed: true, + remaining: this.limit - record.count, + resetAt: record.resetAt + }; + } + + private cleanup() { + const now = Date.now(); + for (const [keyId, record] of this.requests.entries()) { + if (record.resetAt < now) { + this.requests.delete(keyId); + } + } + } +} + +const rateLimiter = new RateLimiter(100, 60 * 60 * 1000); // 100 requests per hour + +/** + * Rate limiting middleware + * Must be used AFTER validateApiKey middleware + */ +export function rateLimitByApiKey( + req: Request, + res: Response, + next: NextFunction +): void { + if (!req.apiKey) { + next(); + return; + } + + const result = rateLimiter.check(req.apiKey.id); + + // Add rate limit headers + res.setHeader('X-RateLimit-Limit', '100'); + res.setHeader('X-RateLimit-Remaining', result.remaining.toString()); + res.setHeader('X-RateLimit-Reset', new Date(result.resetAt).toISOString()); + + if (!result.allowed) { + const retryAfter = Math.ceil((result.resetAt - Date.now()) / 1000); + + console.warn(`[${new Date().toISOString()}] Rate limit exceeded: ${req.apiKey.id} (${req.apiKey.keyType}) - reset: ${new Date(result.resetAt).toISOString()}`); + + res.status(429) + .setHeader('Retry-After', retryAfter.toString()) + .json({ + error: 'Rate limit exceeded', + message: `Too many requests. Retry after ${retryAfter} seconds`, + retryAfter, + }); + return; + } + + next(); +} \ No newline at end of file diff --git a/apps/api-service/src/middleware/auth/requireMasterKey.ts b/apps/api-service/src/middleware/auth/requireMasterKey.ts new file mode 100644 index 0000000..f6d7402 --- /dev/null +++ b/apps/api-service/src/middleware/auth/requireMasterKey.ts @@ -0,0 +1,31 @@ +import { Request, Response, NextFunction } from 'express'; + +/** + * Middleware to ensure the API key is a master key + * Must be used AFTER validateApiKey middleware + */ +export function requireMasterKey( + req: Request, + res: Response, + next: NextFunction +): void { + if (!req.apiKey) { + res.status(401).json({ + error: 'Authentication required', + message: 'This endpoint requires authentication', + }); + return; + } + + if (req.apiKey.keyType !== 'master') { + console.warn(`[${new Date().toISOString()}] Non-master key attempted admin action: ${req.apiKey.id} (${req.apiKey.keyType}) - ${req.path}`); + + res.status(403).json({ + error: 'Master key required', + message: 'This endpoint requires a master API key', + }); + return; + } + + next(); +} \ No newline at end of file diff --git a/apps/api-service/src/middleware/auth/validateApiKey.ts b/apps/api-service/src/middleware/auth/validateApiKey.ts new file mode 100644 index 0000000..fb2260c --- /dev/null +++ b/apps/api-service/src/middleware/auth/validateApiKey.ts @@ -0,0 +1,58 @@ +import { Request, Response, NextFunction } from 'express'; +import { ApiKeyService } from '../../services/ApiKeyService'; +import type { ApiKey } from '@banatie/database'; + +// Extend Express Request type to include apiKey +declare global { + namespace Express { + interface Request { + apiKey?: ApiKey; + } + } +} + +const apiKeyService = new ApiKeyService(); + +/** + * Middleware to validate API key from X-API-Key header + */ +export async function validateApiKey( + req: Request, + res: Response, + next: NextFunction +): Promise { + const providedKey = req.headers['x-api-key'] as string; + + if (!providedKey) { + res.status(401).json({ + error: 'Missing API key', + message: 'Provide your API key via X-API-Key header', + }); + return; + } + + try { + const apiKey = await apiKeyService.validateKey(providedKey); + + if (!apiKey) { + res.status(401).json({ + error: 'Invalid API key', + message: 'The provided API key is invalid, expired, or revoked', + }); + return; + } + + // Attach to request for use in routes + req.apiKey = apiKey; + + console.log(`[${new Date().toISOString()}] API key validated: ${apiKey.id} (${apiKey.keyType})`); + + next(); + } catch (error) { + console.error(`[${new Date().toISOString()}] API key validation error:`, error); + res.status(500).json({ + error: 'Authentication failed', + message: 'An error occurred during authentication', + }); + } +} \ No newline at end of file diff --git a/apps/api-service/src/routes/admin/keys.ts b/apps/api-service/src/routes/admin/keys.ts new file mode 100644 index 0000000..cadfd89 --- /dev/null +++ b/apps/api-service/src/routes/admin/keys.ts @@ -0,0 +1,137 @@ +import express from 'express'; +import { ApiKeyService } from '../../services/ApiKeyService'; +import { validateApiKey } from '../../middleware/auth/validateApiKey'; +import { requireMasterKey } from '../../middleware/auth/requireMasterKey'; + +const router = express.Router(); +const apiKeyService = new ApiKeyService(); + +// All admin routes require master key +router.use(validateApiKey); +router.use(requireMasterKey); + +/** + * Create a new API key + * POST /api/admin/keys + */ +router.post('/', async (req, res) => { + try { + const { type, projectId, name, expiresInDays } = req.body; + + // Validation + if (!type || !['master', 'project'].includes(type)) { + return res.status(400).json({ + error: 'Invalid type', + message: 'Type must be either "master" or "project"', + }); + } + + if (type === 'project' && !projectId) { + return res.status(400).json({ + error: 'Missing projectId', + message: 'Project keys require a projectId', + }); + } + + // Create key + const result = type === 'master' + ? await apiKeyService.createMasterKey(name, req.apiKey!.id) + : await apiKeyService.createProjectKey( + projectId, + name, + req.apiKey!.id, + expiresInDays || 90 + ); + + console.log(`[${new Date().toISOString()}] New API key created by admin: ${result.metadata.id} (${result.metadata.keyType}) - by: ${req.apiKey!.id}`); + + res.status(201).json({ + apiKey: result.key, + metadata: { + id: result.metadata.id, + type: result.metadata.keyType, + projectId: result.metadata.projectId, + name: result.metadata.name, + expiresAt: result.metadata.expiresAt, + scopes: result.metadata.scopes, + createdAt: result.metadata.createdAt, + }, + message: 'IMPORTANT: Save this key securely. You will not see it again!', + }); + } catch (error) { + console.error(`[${new Date().toISOString()}] Error creating API key:`, error); + res.status(500).json({ + error: 'Failed to create API key', + message: 'An error occurred while creating the key', + }); + } +}); + +/** + * List all API keys + * GET /api/admin/keys + */ +router.get('/', async (req, res) => { + try { + const keys = await apiKeyService.listKeys(); + + // Don't expose key hashes + const safeKeys = keys.map(key => ({ + id: key.id, + type: key.keyType, + projectId: key.projectId, + name: key.name, + scopes: key.scopes, + isActive: key.isActive, + createdAt: key.createdAt, + expiresAt: key.expiresAt, + lastUsedAt: key.lastUsedAt, + createdBy: key.createdBy, + })); + + res.json({ + keys: safeKeys, + total: safeKeys.length, + }); + } catch (error) { + console.error(`[${new Date().toISOString()}] Error listing API keys:`, error); + res.status(500).json({ + error: 'Failed to list keys', + message: 'An error occurred while fetching keys', + }); + } +}); + +/** + * Revoke an API key + * DELETE /api/admin/keys/:keyId + */ +router.delete('/:keyId', async (req, res) => { + try { + const { keyId } = req.params; + + const success = await apiKeyService.revokeKey(keyId); + + if (!success) { + return res.status(404).json({ + error: 'Key not found', + message: 'The specified API key does not exist', + }); + } + + console.log(`[${new Date().toISOString()}] API key revoked: ${keyId} - by: ${req.apiKey!.id}`); + + res.json({ + message: 'API key revoked successfully', + keyId, + }); + } catch (error) { + console.error(`[${new Date().toISOString()}] Error revoking API key:`, error); + res.status(500).json({ + error: 'Failed to revoke key', + message: 'An error occurred while revoking the key', + }); + } +}); + +export default router; \ No newline at end of file diff --git a/apps/api-service/src/routes/bootstrap.ts b/apps/api-service/src/routes/bootstrap.ts new file mode 100644 index 0000000..73011d7 --- /dev/null +++ b/apps/api-service/src/routes/bootstrap.ts @@ -0,0 +1,47 @@ +import express from 'express'; +import { ApiKeyService } from '../services/ApiKeyService'; + +const router = express.Router(); +const apiKeyService = new ApiKeyService(); + +/** + * Bootstrap endpoint - creates the first master key + * Only works when no keys exist in the database + * + * POST /api/bootstrap/initial-key + */ +router.post('/initial-key', async (req, res) => { + try { + // Check if any keys already exist + const hasKeys = await apiKeyService.hasAnyKeys(); + + if (hasKeys) { + console.warn(`[${new Date().toISOString()}] Bootstrap attempt when keys already exist`); + return res.status(403).json({ + error: 'Bootstrap not allowed', + message: 'API keys already exist. Use /api/admin/keys to create new keys.', + }); + } + + // Create first master key + const { key, metadata } = await apiKeyService.createMasterKey('Initial Master Key'); + + console.log(`[${new Date().toISOString()}] Initial master key created via bootstrap: ${metadata.id}`); + + res.status(201).json({ + apiKey: key, + type: metadata.keyType, + name: metadata.name, + expiresAt: metadata.expiresAt, + message: 'IMPORTANT: Save this key securely. You will not see it again!', + }); + } catch (error) { + console.error(`[${new Date().toISOString()}] Bootstrap error:`, error); + res.status(500).json({ + error: 'Bootstrap failed', + message: 'Failed to create initial API key', + }); + } +}); + +export default router; \ No newline at end of file diff --git a/apps/api-service/src/routes/generate.ts b/apps/api-service/src/routes/generate.ts index b2978a5..1dcdeb3 100644 --- a/apps/api-service/src/routes/generate.ts +++ b/apps/api-service/src/routes/generate.ts @@ -14,6 +14,8 @@ import { logEnhancementResult, } from "../middleware/promptEnhancement"; import { asyncHandler } from "../middleware/errorHandler"; +import { validateApiKey } from "../middleware/auth/validateApiKey"; +import { rateLimitByApiKey } from "../middleware/auth/rateLimiter"; import { GenerateImageResponse } from "../types/api"; // Create router export const generateRouter: RouterType = Router(); @@ -26,6 +28,10 @@ let imageGenService: ImageGenService; */ generateRouter.post( "/generate", + // Authentication middleware + validateApiKey, + rateLimitByApiKey, + // File upload middleware uploadReferenceImages, handleUploadErrors, diff --git a/apps/api-service/src/services/ApiKeyService.ts b/apps/api-service/src/services/ApiKeyService.ts new file mode 100644 index 0000000..b18017a --- /dev/null +++ b/apps/api-service/src/services/ApiKeyService.ts @@ -0,0 +1,164 @@ +import crypto from 'crypto'; +import { db } from '../db'; +import { apiKeys, type ApiKey, type NewApiKey } from '@banatie/database'; +import { eq, and, desc } from 'drizzle-orm'; + +export class ApiKeyService { + /** + * Generate a new API key + * Format: bnt_{64_hex_chars} + */ + private generateKey(): { fullKey: string; keyHash: string; keyPrefix: string } { + const secret = crypto.randomBytes(32).toString('hex'); // 64 chars + const keyPrefix = 'bnt_'; + const fullKey = keyPrefix + secret; + + // Hash for storage (SHA-256) + const keyHash = crypto + .createHash('sha256') + .update(fullKey) + .digest('hex'); + + return { fullKey, keyHash, keyPrefix }; + } + + /** + * Create a master key (admin access, never expires) + */ + async createMasterKey(name?: string, createdBy?: string): Promise<{ key: string; metadata: ApiKey }> { + const { fullKey, keyHash, keyPrefix } = this.generateKey(); + + const [newKey] = await db.insert(apiKeys).values({ + keyHash, + keyPrefix, + keyType: 'master', + projectId: null, + scopes: ['*'], // Full access + name: name || 'Master Key', + expiresAt: null, // Never expires + createdBy: createdBy || null, + }).returning(); + + console.log(`[${new Date().toISOString()}] Master key created: ${newKey?.id} - ${newKey?.name}`); + + return { key: fullKey, metadata: newKey! }; + } + + /** + * Create a project key (expires in 90 days) + */ + async createProjectKey( + projectId: string, + name?: string, + createdBy?: string, + expiresInDays: number = 90 + ): Promise<{ key: string; metadata: ApiKey }> { + const { fullKey, keyHash, keyPrefix } = this.generateKey(); + + const expiresAt = new Date(); + expiresAt.setDate(expiresAt.getDate() + expiresInDays); + + const [newKey] = await db.insert(apiKeys).values({ + keyHash, + keyPrefix, + keyType: 'project', + projectId, + scopes: ['generate', 'read'], + name: name || `Project Key - ${projectId}`, + expiresAt, + createdBy: createdBy || null, + }).returning(); + + console.log(`[${new Date().toISOString()}] Project key created: ${newKey?.id} - ${projectId} - expires: ${expiresAt.toISOString()}`); + + return { key: fullKey, metadata: newKey! }; + } + + /** + * Validate an API key + * Returns null if invalid/expired/revoked + */ + async validateKey(providedKey: string): Promise { + if (!providedKey || !providedKey.startsWith('bnt_')) { + return null; + } + + // Hash the provided key + const keyHash = crypto + .createHash('sha256') + .update(providedKey) + .digest('hex'); + + // Find in database + const [key] = await db + .select() + .from(apiKeys) + .where( + and( + eq(apiKeys.keyHash, keyHash), + eq(apiKeys.isActive, true) + ) + ) + .limit(1); + + if (!key) { + console.warn(`[${new Date().toISOString()}] Invalid API key attempt: ${providedKey.substring(0, 10)}...`); + return null; + } + + // Check expiration + if (key.expiresAt && key.expiresAt < new Date()) { + console.warn(`[${new Date().toISOString()}] Expired API key used: ${key.id} - expired: ${key.expiresAt.toISOString()}`); + return null; + } + + // Update last used timestamp (async, don't wait) + db.update(apiKeys) + .set({ lastUsedAt: new Date() }) + .where(eq(apiKeys.id, key.id)) + .execute() + .catch(err => console.error(`[${new Date().toISOString()}] Failed to update lastUsedAt:`, err)); + + return key; + } + + /** + * Revoke a key (soft delete) + */ + async revokeKey(keyId: string): Promise { + const result = await db + .update(apiKeys) + .set({ isActive: false }) + .where(eq(apiKeys.id, keyId)) + .returning(); + + if (result.length > 0) { + console.log(`[${new Date().toISOString()}] API key revoked: ${keyId}`); + return true; + } + + return false; + } + + /** + * List all keys (for admin) + */ + async listKeys(): Promise { + return db + .select() + .from(apiKeys) + .orderBy(desc(apiKeys.createdAt)); + } + + /** + * Check if any keys exist (for bootstrap) + */ + async hasAnyKeys(): Promise { + const keys = await db + .select({ id: apiKeys.id }) + .from(apiKeys) + .limit(1); + + return keys.length > 0; + } +} \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index 540c530..6ab033d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -2,14 +2,16 @@ services: app: build: - context: ./apps/api-service + context: . + dockerfile: ./apps/api-service/Dockerfile.mono target: development container_name: banatie-app ports: - "3000:3000" volumes: - - ./apps/api-service/src:/app/src - - ./apps/api-service/logs:/app/logs + - ./apps/api-service/src:/app/apps/api-service/src + - ./apps/api-service/logs:/app/apps/api-service/logs + - ./packages:/app/packages networks: - banatie-network depends_on: diff --git a/packages/database/drizzle.config.ts b/packages/database/drizzle.config.ts new file mode 100644 index 0000000..1ab1922 --- /dev/null +++ b/packages/database/drizzle.config.ts @@ -0,0 +1,10 @@ +import type { Config } from 'drizzle-kit'; + +export default { + schema: './src/schema/index.ts', + out: './migrations', + dialect: 'postgresql', + dbCredentials: { + url: process.env.DATABASE_URL || 'postgresql://banatie_user:banatie_secure_password@localhost:5434/banatie_db', + }, +} satisfies Config; \ No newline at end of file diff --git a/packages/database/migrations/0000_gifted_sunfire.sql b/packages/database/migrations/0000_gifted_sunfire.sql new file mode 100644 index 0000000..cc9d65b --- /dev/null +++ b/packages/database/migrations/0000_gifted_sunfire.sql @@ -0,0 +1,15 @@ +CREATE TABLE IF NOT EXISTS "api_keys" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "key_hash" text NOT NULL, + "key_prefix" text DEFAULT 'bnt_' NOT NULL, + "key_type" text NOT NULL, + "project_id" text, + "scopes" jsonb DEFAULT '["generate"]'::jsonb NOT NULL, + "created_at" timestamp DEFAULT now() NOT NULL, + "expires_at" timestamp, + "last_used_at" timestamp, + "is_active" boolean DEFAULT true NOT NULL, + "name" text, + "created_by" uuid, + CONSTRAINT "api_keys_key_hash_unique" UNIQUE("key_hash") +); diff --git a/packages/database/migrations/meta/0000_snapshot.json b/packages/database/migrations/meta/0000_snapshot.json new file mode 100644 index 0000000..663dc41 --- /dev/null +++ b/packages/database/migrations/meta/0000_snapshot.json @@ -0,0 +1,117 @@ +{ + "id": "a0f532c8-8e34-4297-a580-060eb8b49306", + "prevId": "00000000-0000-0000-0000-000000000000", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.api_keys": { + "name": "api_keys", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "key_hash": { + "name": "key_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "key_prefix": { + "name": "key_prefix", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'bnt_'" + }, + "key_type": { + "name": "key_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "scopes": { + "name": "scopes", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[\"generate\"]'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "uuid", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "api_keys_key_hash_unique": { + "name": "api_keys_key_hash_unique", + "nullsNotDistinct": false, + "columns": [ + "key_hash" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": {}, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/packages/database/migrations/meta/_journal.json b/packages/database/migrations/meta/_journal.json new file mode 100644 index 0000000..5fbfbc1 --- /dev/null +++ b/packages/database/migrations/meta/_journal.json @@ -0,0 +1,13 @@ +{ + "version": "7", + "dialect": "postgresql", + "entries": [ + { + "idx": 0, + "version": "7", + "when": 1759250997369, + "tag": "0000_gifted_sunfire", + "breakpoints": true + } + ] +} \ No newline at end of file diff --git a/packages/database/package.json b/packages/database/package.json new file mode 100644 index 0000000..107f125 --- /dev/null +++ b/packages/database/package.json @@ -0,0 +1,21 @@ +{ + "name": "@banatie/database", + "version": "1.0.0", + "private": true, + "main": "./src/index.ts", + "types": "./src/index.ts", + "scripts": { + "db:generate": "drizzle-kit generate", + "db:push": "drizzle-kit push", + "db:studio": "drizzle-kit studio" + }, + "dependencies": { + "drizzle-orm": "^0.36.4", + "postgres": "^3.4.3" + }, + "devDependencies": { + "drizzle-kit": "^0.28.1", + "typescript": "^5.9.2", + "@types/node": "^20.0.0" + } +} \ No newline at end of file diff --git a/packages/database/src/client.ts b/packages/database/src/client.ts new file mode 100644 index 0000000..38d9069 --- /dev/null +++ b/packages/database/src/client.ts @@ -0,0 +1,10 @@ +import { drizzle } from 'drizzle-orm/postgres-js'; +import postgres from 'postgres'; +import * as schema from './schema'; + +export function createDbClient(connectionString: string) { + const queryClient = postgres(connectionString); + return drizzle(queryClient, { schema }); +} + +export type DbClient = ReturnType; \ No newline at end of file diff --git a/packages/database/src/index.ts b/packages/database/src/index.ts new file mode 100644 index 0000000..2cd5d4d --- /dev/null +++ b/packages/database/src/index.ts @@ -0,0 +1,3 @@ +export * from './schema'; +export * from './client'; +export type { DbClient } from './client'; \ No newline at end of file diff --git a/packages/database/src/schema/apiKeys.ts b/packages/database/src/schema/apiKeys.ts new file mode 100644 index 0000000..ef889b7 --- /dev/null +++ b/packages/database/src/schema/apiKeys.ts @@ -0,0 +1,31 @@ +import { pgTable, uuid, text, timestamp, boolean, jsonb } from 'drizzle-orm/pg-core'; + +export const apiKeys = pgTable('api_keys', { + id: uuid('id').primaryKey().defaultRandom(), + + // Key data + keyHash: text('key_hash').notNull().unique(), + keyPrefix: text('key_prefix').notNull().default('bnt_'), + + // Key type: 'master' or 'project' + keyType: text('key_type').notNull().$type<'master' | 'project'>(), + + // For project keys + projectId: text('project_id'), + + // Permissions (for future use) + scopes: jsonb('scopes').$type().notNull().default(['generate']), + + // Lifecycle + createdAt: timestamp('created_at').notNull().defaultNow(), + expiresAt: timestamp('expires_at'), // NULL for master keys, NOW() + 90 days for project + lastUsedAt: timestamp('last_used_at'), + isActive: boolean('is_active').notNull().default(true), + + // Metadata + name: text('name'), // Optional friendly name + createdBy: uuid('created_by'), // Which key created this key (for audit) +}); + +export type ApiKey = typeof apiKeys.$inferSelect; +export type NewApiKey = typeof apiKeys.$inferInsert; \ No newline at end of file diff --git a/packages/database/src/schema/index.ts b/packages/database/src/schema/index.ts new file mode 100644 index 0000000..68cc1ec --- /dev/null +++ b/packages/database/src/schema/index.ts @@ -0,0 +1 @@ +export * from './apiKeys'; \ No newline at end of file diff --git a/packages/database/tsconfig.json b/packages/database/tsconfig.json new file mode 100644 index 0000000..b7866cd --- /dev/null +++ b/packages/database/tsconfig.json @@ -0,0 +1,14 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "moduleResolution": "bundler", + "esModuleInterop": true, + "strict": true, + "skipLibCheck": true, + "declaration": true, + "declarationMap": true, + "outDir": "./dist" + }, + "include": ["src/**/*"] +} \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e414710..09b7789 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -66,6 +66,9 @@ importers: apps/api-service: dependencies: + '@banatie/database': + specifier: workspace:* + version: link:../../packages/database '@google/genai': specifier: ^1.17.0 version: 1.20.0 @@ -256,6 +259,25 @@ importers: specifier: ^3.4.0 version: 3.4.17 + packages/database: + dependencies: + drizzle-orm: + specifier: ^0.36.4 + version: 0.36.4(@types/react@18.3.24)(postgres@3.4.7)(react@18.3.1) + postgres: + specifier: ^3.4.3 + version: 3.4.7 + devDependencies: + '@types/node': + specifier: ^20.0.0 + version: 20.19.17 + drizzle-kit: + specifier: ^0.28.1 + version: 0.28.1 + typescript: + specifier: ^5.9.2 + version: 5.9.2 + packages: '@alloc/quick-lru@5.2.0': @@ -438,6 +460,9 @@ packages: '@dabh/diagnostics@2.0.3': resolution: {integrity: sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA==} + '@drizzle-team/brocli@0.10.2': + resolution: {integrity: sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w==} + '@emnapi/core@1.5.0': resolution: {integrity: sha512-sbP8GzB1WDzacS8fgNPpHlp6C9VZe+SJP3F90W9rLemaQj2PzIuTEl1qDOYQf58YIpyjViI24y9aPWCjEzY2cg==} @@ -447,102 +472,308 @@ packages: '@emnapi/wasi-threads@1.1.0': resolution: {integrity: sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==} + '@esbuild-kit/core-utils@3.3.2': + resolution: {integrity: sha512-sPRAnw9CdSsRmEtnsl2WXWdyquogVpB3yZ3dgwJfe8zrOzTsV7cJvmwrKVa+0ma5BoiGJ+BoqkMvawbayKUsqQ==} + deprecated: 'Merged into tsx: https://tsx.is' + + '@esbuild-kit/esm-loader@2.6.5': + resolution: {integrity: sha512-FxEMIkJKnodyA1OaCUoEvbYRkoZlLZ4d/eXFu9Fh8CbBBgP5EmZxrfTRyN0qpXZ4vOvqnE5YdRdcrmUUXuU+dA==} + deprecated: 'Merged into tsx: https://tsx.is' + + '@esbuild/aix-ppc64@0.19.12': + resolution: {integrity: sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [aix] + '@esbuild/aix-ppc64@0.25.10': resolution: {integrity: sha512-0NFWnA+7l41irNuaSVlLfgNT12caWJVLzp5eAVhZ0z1qpxbockccEt3s+149rE64VUI3Ml2zt8Nv5JVc4QXTsw==} engines: {node: '>=18'} cpu: [ppc64] os: [aix] + '@esbuild/android-arm64@0.18.20': + resolution: {integrity: sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm64@0.19.12': + resolution: {integrity: sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + '@esbuild/android-arm64@0.25.10': resolution: {integrity: sha512-LSQa7eDahypv/VO6WKohZGPSJDq5OVOo3UoFR1E4t4Gj1W7zEQMUhI+lo81H+DtB+kP+tDgBp+M4oNCwp6kffg==} engines: {node: '>=18'} cpu: [arm64] os: [android] + '@esbuild/android-arm@0.18.20': + resolution: {integrity: sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==} + engines: {node: '>=12'} + cpu: [arm] + os: [android] + + '@esbuild/android-arm@0.19.12': + resolution: {integrity: sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w==} + engines: {node: '>=12'} + cpu: [arm] + os: [android] + '@esbuild/android-arm@0.25.10': resolution: {integrity: sha512-dQAxF1dW1C3zpeCDc5KqIYuZ1tgAdRXNoZP7vkBIRtKZPYe2xVr/d3SkirklCHudW1B45tGiUlz2pUWDfbDD4w==} engines: {node: '>=18'} cpu: [arm] os: [android] + '@esbuild/android-x64@0.18.20': + resolution: {integrity: sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + + '@esbuild/android-x64@0.19.12': + resolution: {integrity: sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + '@esbuild/android-x64@0.25.10': resolution: {integrity: sha512-MiC9CWdPrfhibcXwr39p9ha1x0lZJ9KaVfvzA0Wxwz9ETX4v5CHfF09bx935nHlhi+MxhA63dKRRQLiVgSUtEg==} engines: {node: '>=18'} cpu: [x64] os: [android] + '@esbuild/darwin-arm64@0.18.20': + resolution: {integrity: sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-arm64@0.19.12': + resolution: {integrity: sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + '@esbuild/darwin-arm64@0.25.10': resolution: {integrity: sha512-JC74bdXcQEpW9KkV326WpZZjLguSZ3DfS8wrrvPMHgQOIEIG/sPXEN/V8IssoJhbefLRcRqw6RQH2NnpdprtMA==} engines: {node: '>=18'} cpu: [arm64] os: [darwin] + '@esbuild/darwin-x64@0.18.20': + resolution: {integrity: sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + + '@esbuild/darwin-x64@0.19.12': + resolution: {integrity: sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + '@esbuild/darwin-x64@0.25.10': resolution: {integrity: sha512-tguWg1olF6DGqzws97pKZ8G2L7Ig1vjDmGTwcTuYHbuU6TTjJe5FXbgs5C1BBzHbJ2bo1m3WkQDbWO2PvamRcg==} engines: {node: '>=18'} cpu: [x64] os: [darwin] + '@esbuild/freebsd-arm64@0.18.20': + resolution: {integrity: sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-arm64@0.19.12': + resolution: {integrity: sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + '@esbuild/freebsd-arm64@0.25.10': resolution: {integrity: sha512-3ZioSQSg1HT2N05YxeJWYR+Libe3bREVSdWhEEgExWaDtyFbbXWb49QgPvFH8u03vUPX10JhJPcz7s9t9+boWg==} engines: {node: '>=18'} cpu: [arm64] os: [freebsd] + '@esbuild/freebsd-x64@0.18.20': + resolution: {integrity: sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.19.12': + resolution: {integrity: sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + '@esbuild/freebsd-x64@0.25.10': resolution: {integrity: sha512-LLgJfHJk014Aa4anGDbh8bmI5Lk+QidDmGzuC2D+vP7mv/GeSN+H39zOf7pN5N8p059FcOfs2bVlrRr4SK9WxA==} engines: {node: '>=18'} cpu: [x64] os: [freebsd] + '@esbuild/linux-arm64@0.18.20': + resolution: {integrity: sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm64@0.19.12': + resolution: {integrity: sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + '@esbuild/linux-arm64@0.25.10': resolution: {integrity: sha512-5luJWN6YKBsawd5f9i4+c+geYiVEw20FVW5x0v1kEMWNq8UctFjDiMATBxLvmmHA4bf7F6hTRaJgtghFr9iziQ==} engines: {node: '>=18'} cpu: [arm64] os: [linux] + '@esbuild/linux-arm@0.18.20': + resolution: {integrity: sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-arm@0.19.12': + resolution: {integrity: sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + '@esbuild/linux-arm@0.25.10': resolution: {integrity: sha512-oR31GtBTFYCqEBALI9r6WxoU/ZofZl962pouZRTEYECvNF/dtXKku8YXcJkhgK/beU+zedXfIzHijSRapJY3vg==} engines: {node: '>=18'} cpu: [arm] os: [linux] + '@esbuild/linux-ia32@0.18.20': + resolution: {integrity: sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-ia32@0.19.12': + resolution: {integrity: sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + '@esbuild/linux-ia32@0.25.10': resolution: {integrity: sha512-NrSCx2Kim3EnnWgS4Txn0QGt0Xipoumb6z6sUtl5bOEZIVKhzfyp/Lyw4C1DIYvzeW/5mWYPBFJU3a/8Yr75DQ==} engines: {node: '>=18'} cpu: [ia32] os: [linux] + '@esbuild/linux-loong64@0.18.20': + resolution: {integrity: sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-loong64@0.19.12': + resolution: {integrity: sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] + '@esbuild/linux-loong64@0.25.10': resolution: {integrity: sha512-xoSphrd4AZda8+rUDDfD9J6FUMjrkTz8itpTITM4/xgerAZZcFW7Dv+sun7333IfKxGG8gAq+3NbfEMJfiY+Eg==} engines: {node: '>=18'} cpu: [loong64] os: [linux] + '@esbuild/linux-mips64el@0.18.20': + resolution: {integrity: sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-mips64el@0.19.12': + resolution: {integrity: sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + '@esbuild/linux-mips64el@0.25.10': resolution: {integrity: sha512-ab6eiuCwoMmYDyTnyptoKkVS3k8fy/1Uvq7Dj5czXI6DF2GqD2ToInBI0SHOp5/X1BdZ26RKc5+qjQNGRBelRA==} engines: {node: '>=18'} cpu: [mips64el] os: [linux] + '@esbuild/linux-ppc64@0.18.20': + resolution: {integrity: sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-ppc64@0.19.12': + resolution: {integrity: sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + '@esbuild/linux-ppc64@0.25.10': resolution: {integrity: sha512-NLinzzOgZQsGpsTkEbdJTCanwA5/wozN9dSgEl12haXJBzMTpssebuXR42bthOF3z7zXFWH1AmvWunUCkBE4EA==} engines: {node: '>=18'} cpu: [ppc64] os: [linux] + '@esbuild/linux-riscv64@0.18.20': + resolution: {integrity: sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-riscv64@0.19.12': + resolution: {integrity: sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + '@esbuild/linux-riscv64@0.25.10': resolution: {integrity: sha512-FE557XdZDrtX8NMIeA8LBJX3dC2M8VGXwfrQWU7LB5SLOajfJIxmSdyL/gU1m64Zs9CBKvm4UAuBp5aJ8OgnrA==} engines: {node: '>=18'} cpu: [riscv64] os: [linux] + '@esbuild/linux-s390x@0.18.20': + resolution: {integrity: sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-s390x@0.19.12': + resolution: {integrity: sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + '@esbuild/linux-s390x@0.25.10': resolution: {integrity: sha512-3BBSbgzuB9ajLoVZk0mGu+EHlBwkusRmeNYdqmznmMc9zGASFjSsxgkNsqmXugpPk00gJ0JNKh/97nxmjctdew==} engines: {node: '>=18'} cpu: [s390x] os: [linux] + '@esbuild/linux-x64@0.18.20': + resolution: {integrity: sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + + '@esbuild/linux-x64@0.19.12': + resolution: {integrity: sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + '@esbuild/linux-x64@0.25.10': resolution: {integrity: sha512-QSX81KhFoZGwenVyPoberggdW1nrQZSvfVDAIUXr3WqLRZGZqWk/P4T8p2SP+de2Sr5HPcvjhcJzEiulKgnxtA==} engines: {node: '>=18'} @@ -555,6 +786,18 @@ packages: cpu: [arm64] os: [netbsd] + '@esbuild/netbsd-x64@0.18.20': + resolution: {integrity: sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.19.12': + resolution: {integrity: sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + '@esbuild/netbsd-x64@0.25.10': resolution: {integrity: sha512-7RTytDPGU6fek/hWuN9qQpeGPBZFfB4zZgcz2VK2Z5VpdUxEI8JKYsg3JfO0n/Z1E/6l05n0unDCNc4HnhQGig==} engines: {node: '>=18'} @@ -567,6 +810,18 @@ packages: cpu: [arm64] os: [openbsd] + '@esbuild/openbsd-x64@0.18.20': + resolution: {integrity: sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.19.12': + resolution: {integrity: sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + '@esbuild/openbsd-x64@0.25.10': resolution: {integrity: sha512-XkA4frq1TLj4bEMB+2HnI0+4RnjbuGZfet2gs/LNs5Hc7D89ZQBHQ0gL2ND6Lzu1+QVkjp3x1gIcPKzRNP8bXw==} engines: {node: '>=18'} @@ -579,24 +834,72 @@ packages: cpu: [arm64] os: [openharmony] + '@esbuild/sunos-x64@0.18.20': + resolution: {integrity: sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + + '@esbuild/sunos-x64@0.19.12': + resolution: {integrity: sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + '@esbuild/sunos-x64@0.25.10': resolution: {integrity: sha512-fswk3XT0Uf2pGJmOpDB7yknqhVkJQkAQOcW/ccVOtfx05LkbWOaRAtn5SaqXypeKQra1QaEa841PgrSL9ubSPQ==} engines: {node: '>=18'} cpu: [x64] os: [sunos] + '@esbuild/win32-arm64@0.18.20': + resolution: {integrity: sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-arm64@0.19.12': + resolution: {integrity: sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + '@esbuild/win32-arm64@0.25.10': resolution: {integrity: sha512-ah+9b59KDTSfpaCg6VdJoOQvKjI33nTaQr4UluQwW7aEwZQsbMCfTmfEO4VyewOxx4RaDT/xCy9ra2GPWmO7Kw==} engines: {node: '>=18'} cpu: [arm64] os: [win32] + '@esbuild/win32-ia32@0.18.20': + resolution: {integrity: sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-ia32@0.19.12': + resolution: {integrity: sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + '@esbuild/win32-ia32@0.25.10': resolution: {integrity: sha512-QHPDbKkrGO8/cz9LKVnJU22HOi4pxZnZhhA2HYHez5Pz4JeffhDjf85E57Oyco163GnzNCVkZK0b/n4Y0UHcSw==} engines: {node: '>=18'} cpu: [ia32] os: [win32] + '@esbuild/win32-x64@0.18.20': + resolution: {integrity: sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + + '@esbuild/win32-x64@0.19.12': + resolution: {integrity: sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + '@esbuild/win32-x64@0.25.10': resolution: {integrity: sha512-9KpxSVFCu0iK1owoez6aC/s/EdUQLDN3adTxGCqxMVhrPDj6bt5dbrHDXUuq+Bs2vATFBBrQS5vdQ/Ed2P+nbw==} engines: {node: '>=18'} @@ -1850,6 +2153,102 @@ packages: resolution: {integrity: sha512-Sf2LSQP+bOlhKWWyhFsn0UsfdK/kCWRv1iuA2gXAwt3dyNabr6QSj00I2V10pidqz69soatm9ZwZvpQMTIOd5Q==} engines: {node: '>=12'} + drizzle-kit@0.28.1: + resolution: {integrity: sha512-JimOV+ystXTWMgZkLHYHf2w3oS28hxiH1FR0dkmJLc7GHzdGJoJAQtQS5DRppnabsRZwE2U1F6CuezVBgmsBBQ==} + hasBin: true + + drizzle-orm@0.36.4: + resolution: {integrity: sha512-1OZY3PXD7BR00Gl61UUOFihslDldfH4NFRH2MbP54Yxi0G/PKn4HfO65JYZ7c16DeP3SpM3Aw+VXVG9j6CRSXA==} + peerDependencies: + '@aws-sdk/client-rds-data': '>=3' + '@cloudflare/workers-types': '>=3' + '@electric-sql/pglite': '>=0.2.0' + '@libsql/client': '>=0.10.0' + '@libsql/client-wasm': '>=0.10.0' + '@neondatabase/serverless': '>=0.10.0' + '@op-engineering/op-sqlite': '>=2' + '@opentelemetry/api': ^1.4.1 + '@planetscale/database': '>=1' + '@prisma/client': '*' + '@tidbcloud/serverless': '*' + '@types/better-sqlite3': '*' + '@types/pg': '*' + '@types/react': '>=18' + '@types/sql.js': '*' + '@vercel/postgres': '>=0.8.0' + '@xata.io/client': '*' + better-sqlite3: '>=7' + bun-types: '*' + expo-sqlite: '>=14.0.0' + knex: '*' + kysely: '*' + mysql2: '>=2' + pg: '>=8' + postgres: '>=3' + prisma: '*' + react: '>=18' + sql.js: '>=1' + sqlite3: '>=5' + peerDependenciesMeta: + '@aws-sdk/client-rds-data': + optional: true + '@cloudflare/workers-types': + optional: true + '@electric-sql/pglite': + optional: true + '@libsql/client': + optional: true + '@libsql/client-wasm': + optional: true + '@neondatabase/serverless': + optional: true + '@op-engineering/op-sqlite': + optional: true + '@opentelemetry/api': + optional: true + '@planetscale/database': + optional: true + '@prisma/client': + optional: true + '@tidbcloud/serverless': + optional: true + '@types/better-sqlite3': + optional: true + '@types/pg': + optional: true + '@types/react': + optional: true + '@types/sql.js': + optional: true + '@vercel/postgres': + optional: true + '@xata.io/client': + optional: true + better-sqlite3: + optional: true + bun-types: + optional: true + expo-sqlite: + optional: true + knex: + optional: true + kysely: + optional: true + mysql2: + optional: true + pg: + optional: true + postgres: + optional: true + prisma: + optional: true + react: + optional: true + sql.js: + optional: true + sqlite3: + optional: true + dunder-proto@1.0.1: resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} engines: {node: '>= 0.4'} @@ -1918,6 +2317,21 @@ packages: resolution: {integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==} engines: {node: '>= 0.4'} + esbuild-register@3.6.0: + resolution: {integrity: sha512-H2/S7Pm8a9CL1uhp9OvjwrBh5Pvx0H8qVOxNu8Wed9Y7qv56MPtq+GGM8RJpq6glYJn9Wspr8uw7l55uyinNeg==} + peerDependencies: + esbuild: '>=0.12 <1' + + esbuild@0.18.20: + resolution: {integrity: sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==} + engines: {node: '>=12'} + hasBin: true + + esbuild@0.19.12: + resolution: {integrity: sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==} + engines: {node: '>=12'} + hasBin: true + esbuild@0.25.10: resolution: {integrity: sha512-9RiGKvCwaqxO2owP61uQ4BgNborAQskMR6QusfWzQqv7AZOg5oGehdY2pRJMTKuwxd1IDBP4rSbI5lHzU7SMsQ==} engines: {node: '>=18'} @@ -3267,6 +3681,10 @@ packages: resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} engines: {node: ^10 || ^12 || >=14} + 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'} @@ -3543,6 +3961,9 @@ packages: source-map-support@0.5.13: resolution: {integrity: sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==} + source-map-support@0.5.21: + resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} + source-map@0.6.1: resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} engines: {node: '>=0.10.0'} @@ -4239,6 +4660,8 @@ snapshots: enabled: 2.0.0 kuler: 2.0.0 + '@drizzle-team/brocli@0.10.2': {} + '@emnapi/core@1.5.0': dependencies: '@emnapi/wasi-threads': 1.1.0 @@ -4255,81 +4678,226 @@ snapshots: tslib: 2.8.1 optional: true + '@esbuild-kit/core-utils@3.3.2': + dependencies: + esbuild: 0.18.20 + source-map-support: 0.5.21 + + '@esbuild-kit/esm-loader@2.6.5': + dependencies: + '@esbuild-kit/core-utils': 3.3.2 + get-tsconfig: 4.10.1 + + '@esbuild/aix-ppc64@0.19.12': + optional: true + '@esbuild/aix-ppc64@0.25.10': optional: true + '@esbuild/android-arm64@0.18.20': + optional: true + + '@esbuild/android-arm64@0.19.12': + optional: true + '@esbuild/android-arm64@0.25.10': optional: true + '@esbuild/android-arm@0.18.20': + optional: true + + '@esbuild/android-arm@0.19.12': + optional: true + '@esbuild/android-arm@0.25.10': optional: true + '@esbuild/android-x64@0.18.20': + optional: true + + '@esbuild/android-x64@0.19.12': + optional: true + '@esbuild/android-x64@0.25.10': optional: true + '@esbuild/darwin-arm64@0.18.20': + optional: true + + '@esbuild/darwin-arm64@0.19.12': + optional: true + '@esbuild/darwin-arm64@0.25.10': optional: true + '@esbuild/darwin-x64@0.18.20': + optional: true + + '@esbuild/darwin-x64@0.19.12': + optional: true + '@esbuild/darwin-x64@0.25.10': optional: true + '@esbuild/freebsd-arm64@0.18.20': + optional: true + + '@esbuild/freebsd-arm64@0.19.12': + optional: true + '@esbuild/freebsd-arm64@0.25.10': optional: true + '@esbuild/freebsd-x64@0.18.20': + optional: true + + '@esbuild/freebsd-x64@0.19.12': + optional: true + '@esbuild/freebsd-x64@0.25.10': optional: true + '@esbuild/linux-arm64@0.18.20': + optional: true + + '@esbuild/linux-arm64@0.19.12': + optional: true + '@esbuild/linux-arm64@0.25.10': optional: true + '@esbuild/linux-arm@0.18.20': + optional: true + + '@esbuild/linux-arm@0.19.12': + optional: true + '@esbuild/linux-arm@0.25.10': optional: true + '@esbuild/linux-ia32@0.18.20': + optional: true + + '@esbuild/linux-ia32@0.19.12': + optional: true + '@esbuild/linux-ia32@0.25.10': optional: true + '@esbuild/linux-loong64@0.18.20': + optional: true + + '@esbuild/linux-loong64@0.19.12': + optional: true + '@esbuild/linux-loong64@0.25.10': optional: true + '@esbuild/linux-mips64el@0.18.20': + optional: true + + '@esbuild/linux-mips64el@0.19.12': + optional: true + '@esbuild/linux-mips64el@0.25.10': optional: true + '@esbuild/linux-ppc64@0.18.20': + optional: true + + '@esbuild/linux-ppc64@0.19.12': + optional: true + '@esbuild/linux-ppc64@0.25.10': optional: true + '@esbuild/linux-riscv64@0.18.20': + optional: true + + '@esbuild/linux-riscv64@0.19.12': + optional: true + '@esbuild/linux-riscv64@0.25.10': optional: true + '@esbuild/linux-s390x@0.18.20': + optional: true + + '@esbuild/linux-s390x@0.19.12': + optional: true + '@esbuild/linux-s390x@0.25.10': optional: true + '@esbuild/linux-x64@0.18.20': + optional: true + + '@esbuild/linux-x64@0.19.12': + optional: true + '@esbuild/linux-x64@0.25.10': optional: true '@esbuild/netbsd-arm64@0.25.10': optional: true + '@esbuild/netbsd-x64@0.18.20': + optional: true + + '@esbuild/netbsd-x64@0.19.12': + optional: true + '@esbuild/netbsd-x64@0.25.10': optional: true '@esbuild/openbsd-arm64@0.25.10': optional: true + '@esbuild/openbsd-x64@0.18.20': + optional: true + + '@esbuild/openbsd-x64@0.19.12': + optional: true + '@esbuild/openbsd-x64@0.25.10': optional: true '@esbuild/openharmony-arm64@0.25.10': optional: true + '@esbuild/sunos-x64@0.18.20': + optional: true + + '@esbuild/sunos-x64@0.19.12': + optional: true + '@esbuild/sunos-x64@0.25.10': optional: true + '@esbuild/win32-arm64@0.18.20': + optional: true + + '@esbuild/win32-arm64@0.19.12': + optional: true + '@esbuild/win32-arm64@0.25.10': optional: true + '@esbuild/win32-ia32@0.18.20': + optional: true + + '@esbuild/win32-ia32@0.19.12': + optional: true + '@esbuild/win32-ia32@0.25.10': optional: true + '@esbuild/win32-x64@0.18.20': + optional: true + + '@esbuild/win32-x64@0.19.12': + optional: true + '@esbuild/win32-x64@0.25.10': optional: true @@ -5043,7 +5611,7 @@ snapshots: '@types/ws@8.18.1': dependencies: - '@types/node': 20.19.17 + '@types/node': 24.5.2 '@types/yargs-parser@21.0.3': {} @@ -5810,6 +6378,21 @@ snapshots: dotenv@17.2.2: {} + drizzle-kit@0.28.1: + dependencies: + '@drizzle-team/brocli': 0.10.2 + '@esbuild-kit/esm-loader': 2.6.5 + esbuild: 0.19.12 + esbuild-register: 3.6.0(esbuild@0.19.12) + transitivePeerDependencies: + - supports-color + + drizzle-orm@0.36.4(@types/react@18.3.24)(postgres@3.4.7)(react@18.3.1): + optionalDependencies: + '@types/react': 18.3.24 + postgres: 3.4.7 + react: 18.3.1 + dunder-proto@1.0.1: dependencies: call-bind-apply-helpers: 1.0.2 @@ -5941,6 +6524,64 @@ snapshots: is-date-object: 1.1.0 is-symbol: 1.1.1 + esbuild-register@3.6.0(esbuild@0.19.12): + dependencies: + debug: 4.4.3(supports-color@5.5.0) + esbuild: 0.19.12 + transitivePeerDependencies: + - supports-color + + esbuild@0.18.20: + optionalDependencies: + '@esbuild/android-arm': 0.18.20 + '@esbuild/android-arm64': 0.18.20 + '@esbuild/android-x64': 0.18.20 + '@esbuild/darwin-arm64': 0.18.20 + '@esbuild/darwin-x64': 0.18.20 + '@esbuild/freebsd-arm64': 0.18.20 + '@esbuild/freebsd-x64': 0.18.20 + '@esbuild/linux-arm': 0.18.20 + '@esbuild/linux-arm64': 0.18.20 + '@esbuild/linux-ia32': 0.18.20 + '@esbuild/linux-loong64': 0.18.20 + '@esbuild/linux-mips64el': 0.18.20 + '@esbuild/linux-ppc64': 0.18.20 + '@esbuild/linux-riscv64': 0.18.20 + '@esbuild/linux-s390x': 0.18.20 + '@esbuild/linux-x64': 0.18.20 + '@esbuild/netbsd-x64': 0.18.20 + '@esbuild/openbsd-x64': 0.18.20 + '@esbuild/sunos-x64': 0.18.20 + '@esbuild/win32-arm64': 0.18.20 + '@esbuild/win32-ia32': 0.18.20 + '@esbuild/win32-x64': 0.18.20 + + esbuild@0.19.12: + optionalDependencies: + '@esbuild/aix-ppc64': 0.19.12 + '@esbuild/android-arm': 0.19.12 + '@esbuild/android-arm64': 0.19.12 + '@esbuild/android-x64': 0.19.12 + '@esbuild/darwin-arm64': 0.19.12 + '@esbuild/darwin-x64': 0.19.12 + '@esbuild/freebsd-arm64': 0.19.12 + '@esbuild/freebsd-x64': 0.19.12 + '@esbuild/linux-arm': 0.19.12 + '@esbuild/linux-arm64': 0.19.12 + '@esbuild/linux-ia32': 0.19.12 + '@esbuild/linux-loong64': 0.19.12 + '@esbuild/linux-mips64el': 0.19.12 + '@esbuild/linux-ppc64': 0.19.12 + '@esbuild/linux-riscv64': 0.19.12 + '@esbuild/linux-s390x': 0.19.12 + '@esbuild/linux-x64': 0.19.12 + '@esbuild/netbsd-x64': 0.19.12 + '@esbuild/openbsd-x64': 0.19.12 + '@esbuild/sunos-x64': 0.19.12 + '@esbuild/win32-arm64': 0.19.12 + '@esbuild/win32-ia32': 0.19.12 + '@esbuild/win32-x64': 0.19.12 + esbuild@0.25.10: optionalDependencies: '@esbuild/aix-ppc64': 0.25.10 @@ -5987,7 +6628,7 @@ snapshots: eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.44.0(eslint@8.57.1)(typescript@5.9.2))(eslint@8.57.1))(eslint@8.57.1) - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.44.0(eslint@8.57.1)(typescript@5.9.2))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.44.0(eslint@8.57.1)(typescript@5.9.2))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.44.0(eslint@8.57.1)(typescript@5.9.2))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1) eslint-plugin-jsx-a11y: 6.10.2(eslint@8.57.1) eslint-plugin-react: 7.37.5(eslint@8.57.1) eslint-plugin-react-hooks: 5.0.0-canary-7118f5dd7-20230705(eslint@8.57.1) @@ -6021,7 +6662,7 @@ snapshots: tinyglobby: 0.2.15 unrs-resolver: 1.11.1 optionalDependencies: - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.44.0(eslint@8.57.1)(typescript@5.9.2))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.44.0(eslint@8.57.1)(typescript@5.9.2))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.44.0(eslint@8.57.1)(typescript@5.9.2))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1) transitivePeerDependencies: - supports-color @@ -6036,7 +6677,7 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.44.0(eslint@8.57.1)(typescript@5.9.2))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1): + eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.44.0(eslint@8.57.1)(typescript@5.9.2))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.44.0(eslint@8.57.1)(typescript@5.9.2))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.9 @@ -7652,6 +8293,8 @@ snapshots: picocolors: 1.1.1 source-map-js: 1.2.1 + postgres@3.4.7: {} + prelude-ls@1.2.1: {} prettier-linter-helpers@1.0.0: @@ -7981,6 +8624,11 @@ snapshots: buffer-from: 1.1.2 source-map: 0.6.1 + source-map-support@0.5.21: + dependencies: + buffer-from: 1.1.2 + source-map: 0.6.1 + source-map@0.6.1: {} split-on-first@1.1.0: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 7444c2a..c53e539 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,2 +1,3 @@ packages: - - 'apps/*' \ No newline at end of file + - 'apps/*' + - 'packages/*' \ No newline at end of file