commit 93cd6872a9ed1382825d6e24155e982eb522e3f0 Author: Jean Jacques Avril Date: Mon Oct 6 18:11:03 2025 +0200 Initial commit diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..6783665 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,2 @@ +.env +.env.example \ No newline at end of file diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..986902a --- /dev/null +++ b/.env.example @@ -0,0 +1,14 @@ +# --- Database Configuration --- +DB_USER=idp +DB_PASSWORD=secure-password # WICHTIG: Ändere dieses Passwort! +DB_NAME=ipd_db +DB_PORT=5433 + +# --- APP Configuration --- +DATABASE_URL="postgresql://idp:secure-password@localhost:5433/idp_db" +JWT_PRIVATE_KEY_PATH=./data/jwt_private.pem +JWT_PUBLIC_KEY_PATH=./data/jwt_public.pem +APP_CONFIG_PATH=./data/app.json + +# --- CSRF Configuration --- +CSRF_TRUSTED_DOMAINS=http://localhost:5173,http://localhost:3000 \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..171f629 --- /dev/null +++ b/.gitignore @@ -0,0 +1,26 @@ +node_modules + +# Output +.output +.vercel +.netlify +.wrangler +/.svelte-kit +/build + +# OS +.DS_Store +Thumbs.db + +# Env +.env +.env.* +!.env.example +!.env.test + +# Vite +vite.config.js.timestamp-* +vite.config.ts.timestamp-* + +# SQLite +*.db diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..b6f27f1 --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +engine-strict=true diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..0243576 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,10 @@ +# Package Managers +package-lock.json +pnpm-lock.yaml +yarn.lock +bun.lock +bun.lockb + +# Miscellaneous +/static/ +/drizzle/ diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..8103a0b --- /dev/null +++ b/.prettierrc @@ -0,0 +1,16 @@ +{ + "useTabs": true, + "singleQuote": true, + "trailingComma": "none", + "printWidth": 100, + "plugins": ["prettier-plugin-svelte", "prettier-plugin-tailwindcss"], + "overrides": [ + { + "files": "*.svelte", + "options": { + "parser": "svelte" + } + } + ], + "tailwindStylesheet": "./src/app.css" +} diff --git a/.roo/mcp.json b/.roo/mcp.json new file mode 100644 index 0000000..114c56e --- /dev/null +++ b/.roo/mcp.json @@ -0,0 +1,15 @@ +{ + "mcpServers": { + "context7": { + "command": "npx", + "args": ["-y", "@upstash/context7-mcp"], + "env": { + "DEFAULT_MINIMUM_TOKENS": "" + } + }, + "filesystem": { + "command": "npx", + "args": ["-y", "@modelcontextprotocol/server-filesystem", "/"] + } + } +} diff --git a/.roo/prompts/svelte.txt b/.roo/prompts/svelte.txt new file mode 100644 index 0000000..6c98f59 --- /dev/null +++ b/.roo/prompts/svelte.txt @@ -0,0 +1,215 @@ + +You are an expert in Svelte 5, SvelteKit, TypeScript, and modern web development. + +Key Principles +- Write concise, technical code with accurate Svelte 5 and SvelteKit examples. +- Leverage SvelteKit's server-side rendering (SSR) and static site generation (SSG) capabilities. +- Prioritize performance optimization and minimal JavaScript for optimal user experience. +- Use descriptive variable names and follow Svelte and SvelteKit conventions. +- Organize files using SvelteKit's file-based routing system. + +Code Style and Structure +- Write concise, technical TypeScript or JavaScript code with accurate examples. +- Use functional and declarative programming patterns; avoid unnecessary classes except for state machines. +- Prefer iteration and modularization over code duplication. +- Structure files: component logic, markup, styles, helpers, types. +- Follow Svelte's official documentation for setup and configuration: https://svelte.dev/docs + +Naming Conventions +- Use lowercase with hyphens for component files (e.g., `components/auth-form.svelte`). +- Use PascalCase for component names in imports and usage. +- Use camelCase for variables, functions, and props. + +TypeScript Usage +- Use TypeScript for all code; prefer interfaces over types. +- Avoid enums; use const objects instead. +- Use functional components with TypeScript interfaces for props. +- Enable strict mode in TypeScript for better type safety. + +Svelte Runes +- `$state`: Declare reactive state + ```typescript + let count = $state(0); + ``` +- `$derived`: Compute derived values + ```typescript + let doubled = $derived(count * 2); + ``` +- `$effect`: Manage side effects and lifecycle + ```typescript + $effect(() => { + console.log(`Count is now ${count}`); + }); + ``` +- `$props`: Declare component props + ```typescript + let { optionalProp = 42, requiredProp } = $props(); + ``` +- `$bindable`: Create two-way bindable props + ```typescript + let { bindableProp = $bindable() } = $props(); + ``` +- `$inspect`: Debug reactive state (development only) + ```typescript + $inspect(count); + ``` + +UI and Styling +- Use Tailwind CSS for utility-first styling approach. +- Leverage Shadcn components for pre-built, customizable UI elements. +- Import Shadcn components from `$lib/components/ui`. +- Organize Tailwind classes using the `cn()` utility from `$lib/utils`. +- Use Svelte's built-in transition and animation features. + +Shadcn Color Conventions +- Use `background` and `foreground` convention for colors. +- Define CSS variables without color space function: + ```css + --primary: 222.2 47.4% 11.2%; + --primary-foreground: 210 40% 98%; + ``` +- Usage example: + ```svelte +
Hello
+ ``` +- Key color variables: + - `--background`, `--foreground`: Default body colors + - `--muted`, `--muted-foreground`: Muted backgrounds + - `--card`, `--card-foreground`: Card backgrounds + - `--popover`, `--popover-foreground`: Popover backgrounds + - `--border`: Default border color + - `--input`: Input border color + - `--primary`, `--primary-foreground`: Primary button colors + - `--secondary`, `--secondary-foreground`: Secondary button colors + - `--accent`, `--accent-foreground`: Accent colors + - `--destructive`, `--destructive-foreground`: Destructive action colors + - `--ring`: Focus ring color + - `--radius`: Border radius for components + +SvelteKit Project Structure +- Use the recommended SvelteKit project structure: + ``` + - src/ + - lib/ + - routes/ + - app.html + - static/ + - svelte.config.js + - vite.config.js + ``` + +Component Development +- Create .svelte files for Svelte components. +- Use .svelte.ts files for component logic and state machines. +- Implement proper component composition and reusability. +- Use Svelte's props for data passing. +- Leverage Svelte's reactive declarations for local state management. + +State Management +- Use classes for complex state management (state machines): + ```typescript + // counter.svelte.ts + class Counter { + count = $state(0); + incrementor = $state(1); + + increment() { + this.count += this.incrementor; + } + + resetCount() { + this.count = 0; + } + + resetIncrementor() { + this.incrementor = 1; + } + } + + export const counter = new Counter(); + ``` +- Use in components: + ```svelte + + + + ``` + +Routing and Pages +- Utilize SvelteKit's file-based routing system in the src/routes/ directory. +- Implement dynamic routes using [slug] syntax. +- Use load functions for server-side data fetching and pre-rendering. +- Implement proper error handling with +error.svelte pages. + +Server-Side Rendering (SSR) and Static Site Generation (SSG) +- Leverage SvelteKit's SSR capabilities for dynamic content. +- Implement SSG for static pages using prerender option. +- Use the adapter-auto for automatic deployment configuration. + +Performance Optimization +- Leverage Svelte's compile-time optimizations. +- Use `{#key}` blocks to force re-rendering of components when needed. +- Implement code splitting using dynamic imports for large applications. +- Profile and monitor performance using browser developer tools. +- Use `$effect.tracking()` to optimize effect dependencies. +- Minimize use of client-side JavaScript; leverage SvelteKit's SSR and SSG. +- Implement proper lazy loading for images and other assets. + +Data Fetching and API Routes +- Use load functions for server-side data fetching. +- Implement proper error handling for data fetching operations. +- Create API routes in the src/routes/api/ directory. +- Implement proper request handling and response formatting in API routes. +- Use SvelteKit's hooks for global API middleware. + +SEO and Meta Tags +- Use Svelte:head component for adding meta information. +- Implement canonical URLs for proper SEO. +- Create reusable SEO components for consistent meta tag management. + +Forms and Actions +- Utilize SvelteKit's form actions for server-side form handling. +- Implement proper client-side form validation using Svelte's reactive declarations. +- Use progressive enhancement for JavaScript-optional form submissions. + +Internationalization (i18n) with Paraglide.js +- Use Paraglide.js for internationalization: https://inlang.com/m/gerre34r/library-inlang-paraglideJs +- Install Paraglide.js: `npm install @inlang/paraglide-js` +- Set up language files in the `languages` directory. +- Use the `t` function to translate strings: + ```svelte + + +

{t('welcome_message')}

+ ``` +- Support multiple languages and RTL layouts. +- Ensure text scaling and font adjustments for accessibility. + +Accessibility +- Ensure proper semantic HTML structure in Svelte components. +- Implement ARIA attributes where necessary. +- Ensure keyboard navigation support for interactive elements. +- Use Svelte's bind:this for managing focus programmatically. + +Key Conventions +1. Embrace Svelte's simplicity and avoid over-engineering solutions. +2. Use SvelteKit for full-stack applications with SSR and API routes. +3. Prioritize Web Vitals (LCP, FID, CLS) for performance optimization. +4. Use environment variables for configuration management. +5. Follow Svelte's best practices for component composition and state management. +6. Ensure cross-browser compatibility by testing on multiple platforms. +7. Keep your Svelte and SvelteKit versions up to date. + +Documentation +- Svelte 5 Runes: https://svelte-5-preview.vercel.app/docs/runes +- Svelte Documentation: https://svelte.dev/docs +- SvelteKit Documentation: https://kit.svelte.dev/docs +- Paraglide.js Documentation: https://inlang.com/m/gerre34r/library-inlang-paraglideJs/usage + +Refer to Svelte, SvelteKit, and Paraglide.js documentation for detailed information on components, internationalization, and best practices. diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..629f094 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,3 @@ +{ + "recommendations": ["inlang.vs-code-extension"] +} diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..9947b4a --- /dev/null +++ b/Dockerfile @@ -0,0 +1,44 @@ +# Build stage +FROM node:20-alpine AS build + +WORKDIR /app + +# Copy package files +COPY package*.json ./ + +# Install all dependencies (including dev dependencies for build) +RUN npm ci + +# Copy source code +COPY . . + +# Create a dummy .env file for build +RUN echo 'DATABASE_URL="postgresql://dummy:dummy@localhost:5432/dummy"' > .env && \ + echo 'JWT_PRIVATE_KEY_PATH=./data/jwt_private.pem' >> .env && \ + echo 'JWT_PUBLIC_KEY_PATH=./data/jwt_public.pem' >> .env && \ + echo 'APP_CONFIG_PATH=./data/app.json' >> .env + +# Build the application +RUN npm run build +#RUN npx drizzle-kit generate + +# Runtime stage +FROM node:20-alpine AS runtime + +WORKDIR /app + +# Copy package files +COPY --from=build /app/package*.json ./ +COPY --from=build /app/drizzle ./drizzle + +# Install only production dependencies +RUN npm ci --only=production + +# Copy built application +COPY --from=build /app/build ./build + +# Expose the port the app runs on +EXPOSE 3000 + +# Start the application +CMD ["node", "build/index.js"] \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..42fa94b --- /dev/null +++ b/README.md @@ -0,0 +1,163 @@ +# RFID-CP - RFID Card Management System + +A modern web application for managing RFID cards and member access control built with Svelte 5, PostgreSQL, and Drizzle ORM. + +## Features + +- 🔐 **Admin Authentication**: Secure login system for administrators +- 👥 **Member Management**: Create and manage member profiles +- 🆔 **RFID Card Management**: Track and assign RFID cards to members +- 📱 **Web NFC Integration**: Scan RFID cards directly from browsers that support Web NFC +- 🎨 **Modern UI**: Built with Shadcn/ui components and Tailwind CSS +- 🌍 **Internationalization**: Multi-language support with Paraglide JS +- 📊 **Dashboard**: Overview of system statistics and recent activity + +## Quick Start + +### Prerequisites + +- Node.js 18+ +- PostgreSQL database +- npm or yarn + +### Installation + +1. Clone the repository: + +```bash +git clone +cd rfid-cp +``` + +2. Install dependencies: + +```bash +npm install +``` + +3. Set up environment variables: + +```bash +cp .env.example .env +``` + +Edit `.env` and configure your database connection: + +```env +DATABASE_URL="postgresql://username:password@localhost:5432/rfid_cp" +``` + +4. Set up the database: + +```bash +# Push schema to database +npm run db:push + +# Run migrations (if needed) +npm run db:migrate +``` + +5. Seed the database with initial data: + +```bash +npm run db:seed +``` + +This will create: + +- An admin user with credentials: `admin` / `admin123` +- Sample members, RFID cards, and assignments +- Sample devices + +6. Start the development server: + +```bash +npm run dev +``` + +The application will be available at `http://localhost:5173`. + +## Database Seeding + +The seeder script creates initial data for development and testing: + +```bash +npm run db:seed +``` + +### What gets seeded: + +- **Admin User**: `admin` / `admin123` +- **Sample Members**: Max Mustermann, Anna Schmidt, Peter Müller +- **RFID Cards**: Various cards with different statuses (NEW, ENGRAVED) +- **Assignments**: Cards assigned to members +- **Devices**: RFID scanner and lock system devices + +## Available Scripts + +- `npm run dev` - Start development server +- `npm run build` - Build for production +- `npm run preview` - Preview production build +- `npm run check` - Type checking +- `npm run lint` - Lint code +- `npm run format` - Format code +- `npm run db:push` - Push schema to database +- `npm run db:migrate` - Run migrations +- `npm run db:studio` - Open Drizzle Studio +- `npm run db:seed` - Seed database with initial data + +## Project Structure + +``` +src/ +├── lib/ +│ ├── components/ # Reusable UI components +│ ├── server/ # Server-side utilities +│ └── utils/ # Utility functions +├── routes/ # SvelteKit routes +│ ├── admin/ # Admin panel routes +│ └── api/ # API routes +├── schemas/ # Database schemas and validation +└── rpc-routes/ # RPC route handlers + +scripts/ +└── seed.ts # Database seeding script + +drizzle/ # Database migrations +``` + +## Technology Stack + +- **Frontend**: Svelte 5, SvelteKit +- **Styling**: Tailwind CSS, Shadcn/ui +- **Database**: PostgreSQL with Drizzle ORM +- **Authentication**: Argon2 password hashing +- **Internationalization**: Paraglide JS +- **Forms**: SvelteKit Superforms +- **Icons**: Lucide Svelte + +## Development + +### Database Schema + +The database schema is defined in `src/schemas/database/schema.ts` using Drizzle ORM. Key entities: + +- `managementUsers` - Admin users +- `members` - System members +- `rfidCards` - RFID card inventory +- `memberRfidCards` - Card-to-member assignments +- `devices` - RFID scanners and lock systems +- `accessLogs` - Access event logging +- `systemLogs` - System event logging + +### Web NFC Support + +The application includes Web NFC integration for scanning RFID cards directly in supported browsers. The feature gracefully degrades when Web NFC is not available. + +### Internationalization + +The app supports multiple languages using Paraglide JS. Translation files are located in the `messages/` directory. + +## License + +[Add your license here] diff --git a/components.json b/components.json new file mode 100644 index 0000000..c5d91b4 --- /dev/null +++ b/components.json @@ -0,0 +1,16 @@ +{ + "$schema": "https://shadcn-svelte.com/schema.json", + "tailwind": { + "css": "src/app.css", + "baseColor": "slate" + }, + "aliases": { + "components": "$lib/components", + "utils": "$lib/utils", + "ui": "$lib/components/ui", + "hooks": "$lib/hooks", + "lib": "$lib" + }, + "typescript": true, + "registry": "https://shadcn-svelte.com/registry" +} diff --git a/compose.dev.yml b/compose.dev.yml new file mode 100644 index 0000000..ff84b41 --- /dev/null +++ b/compose.dev.yml @@ -0,0 +1,41 @@ +services: + # Service name for our PostgreSQL database + db: + # We use a specific version of PostgreSQL for stability. + # The 'alpine' tag means it's a smaller image size. + image: postgres:16-alpine + + # This ensures the database container restarts automatically if it crashes or the server reboots. + restart: always + + # Environment variables are loaded from the .env file. + # These are used by the PostgreSQL image to create the user and database on first run. + environment: + POSTGRES_USER: ${DB_USER} + POSTGRES_PASSWORD: ${DB_PASSWORD} + POSTGRES_DB: ${DB_NAME} + + # Port mapping: : + # - We expose the internal container port 5432 (PostgreSQL's default) + # - to our custom port ${DB_PORT} (5433 from our .env) on our local machine. + ports: + - '${DB_PORT}:5432' + + # Volumes are essential to persist data. + # - We create a named volume 'postgres_data'. + # - All database files inside the container (/var/lib/postgresql/data) are stored in this volume. + # - This means your data is safe even if you remove the container with 'docker-compose down'. + volumes: + - postgres_data:/var/lib/postgresql/data + + # A healthcheck to ensure the database is ready to accept connections + # before other services (if any) might try to connect. + healthcheck: + test: ['CMD-SHELL', 'pg_isready -U ${DB_USER} -d ${DB_NAME}'] + interval: 10s + timeout: 5s + retries: 5 + +# Top-level volumes definition. This is where Docker manages the named volume. +volumes: + postgres_data: diff --git a/compose.yml b/compose.yml new file mode 100644 index 0000000..8102836 --- /dev/null +++ b/compose.yml @@ -0,0 +1,42 @@ +services: + app: + build: . + restart: always + ports: + - '3000:3000' + environment: + - DATABASE_URL=postgresql://idp:secure-password@db:5432/idp_db + - JWT_PRIVATE_KEY_PATH=./data/jwt_private.pem + - JWT_PUBLIC_KEY_PATH=./data/jwt_public.pem + - APP_CONFIG_PATH=./data/app.json + depends_on: + db: + condition: service_healthy + volumes: + - ./data:/app/data + networks: + - rfid-network + + db: + image: postgres:16-alpine + restart: always + environment: + POSTGRES_USER: idp + POSTGRES_PASSWORD: secure-password + POSTGRES_DB: idp_db + volumes: + - postgres_data:/var/lib/postgresql/data + healthcheck: + test: ['CMD-SHELL', 'pg_isready -U idp -d idp_db'] + interval: 10s + timeout: 5s + retries: 5 + networks: + - rfid-network + +volumes: + postgres_data: + +networks: + rfid-network: + driver: bridge diff --git a/data/app.json b/data/app.json new file mode 100644 index 0000000..0f839b9 --- /dev/null +++ b/data/app.json @@ -0,0 +1,23 @@ +{ + "app": { + "name": "My SvelteKit1323", + "version": "1.0.013", + "supportEmail": "support@example.com", + "footerText": "© 2024 RFID Control Panel. All rights reserved." + }, + "features": { + "enableDeviceApi": true, + "enableRFID": true, + "enableLogging": false, + "maxLoginAttempts": 5, + "selfServicePortal": { + "enabled": false, + "allowProfileUpdate": false, + "allowPasswordChange": false + }, + "logging": { + "save": ["ACTION", "INFO", "WARNING", "ERROR"], + "console": ["ACTION", "INFO", "WARNING", "ERROR"] + } + } +} diff --git a/data/jwt_private.pem b/data/jwt_private.pem new file mode 100644 index 0000000..b8935ce --- /dev/null +++ b/data/jwt_private.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDKIbHlA6W5GTZP +wt6X4+0xpfdMs1LyTuOAUBzZYtZ04eLlSjn05mkDxAhgzY2zI67ZPRcP1NYJvhtS +iSdF/4+DB2Q/8+InWFytAdOMWZyPRggoP9W2V7B6/Qmfco94GwsGXBzH/yzqhVDf +RB7yZ5c9+BdsWcPSQMR5r48enWOkzPZnwnN52TVvQPW9trf4CF0NBvk9jTTJyf1I +6TZJ/biNQylWoK62xuv8ONe+ewSqc0I+gNyyb7cPow9KQ0QbdSa5i8AI3v6pYvHU +7+tBBp1U5HrIaxAtrjxkcO1lB7ZrKBeJK2dmgljOn9za4BmcQrwFSjlrP37qBLkO +J/WD3nupAgMBAAECggEAQx6BNezKIPawRDgPNhynbK8RuVRqnK95d+giTEU3tUQ0 +1bKaICpKgMediJd5EB0vOE/C0qz4DfAknnmQovvDqdzQezP+ytXaoFBU8OvowuKc +XtEjpBLmNVDE2z9tSJKiTJBVtkuTnKR8QEcch53CaP9yt7XvAmgjVczoF2Bn8BbQ +NqIfEuTuWlh25wQrh+ghxxnrIAXABSkNPBO3qBMIOuHz7VLiPNQYjdBdoLptyTEG +luawWpvzaBkoJxkdiswS2KaauzohlQ/c7SE353l1Of1eB39bB+wkf3nzh7CGKW+M +8/jg/Eb0fQflsAeGlV4NNpcE488rRfVoNHTxem+NqQKBgQDoyvK9lbvhU/9xyb9w +6C/hLLk3TZI5BPC2YVpYwF916q0dnhP74+pjVYg1w9vZwiKOEnIaK6N+Plrnn6l9 +rhFoSzfR5n9kv3TPG+4ZiEkv42z8M0Alj4XFc9T22QStxY267s0xLtTQip9CO6wK +AAVJnZxHCHwy7f5Nzh4HN0hNTwKBgQDeSEAgjTp20VLdJqGJsK6P4iR27ulQYsBD +SqTFuv5pnunpMP/kol4tG5AByuhm4gYf/5N8YGXOwY8cNKTHQHUEXnQTYUBrCJG4 +gk32+wwYKdfEiuS6fVLhGWx5JqHeNEaNl6QNjH6KE9BjYXokNa1h+bXbrBi8LDNs +td+PHX8ZhwKBgAs0CS6akCot8rM1NGNoqTU7A8bnksvvsu30DXcL+wwfuSkdvHR4 +6YTSTvXXcTMvpp4TwS4FP58JvTI8etmzkN7mD8+oOiVNGYAGJhVQe8U0OsCAbuvf +1l5ETtF6bEE4qrN+Xp2pVVCb+0IiwQrSKW77iNPaUq/YyE5SRxuty2r9AoGAQBwq +krjpAdgBxFMeCC5zSoB47+ycUlkJBt+Cgp0aP7Bb2qwNQg4qh2wJrtqtCO9rwNLf +4OGUu3tMIWB1nhpTJb1wUR6di8Fe9g5vGiryJA39c2xz5+25d77zcEXaLdJ5/uCb +qmS5Im3wjplQtxzcMwPolcEfKTa+Zj5WilqBjAUCgYBmRZjkc+jZ1S5byQ0TcoIz +tbjE8kxKXCN0TrauMYPfEpNGZPUic+gOEVUD9D367Lr+foTBLA88Nal9UoK+/vxZ +kooGy7N3oPHpkErxTEMzKirAiJwDwvy8G8Mz5tle/P2vdhqX0jdeq3CUyXypSKZP +izPns90mdDLKqb7jlIgJMA== +-----END PRIVATE KEY----- \ No newline at end of file diff --git a/data/jwt_public.pem b/data/jwt_public.pem new file mode 100644 index 0000000..090ca84 --- /dev/null +++ b/data/jwt_public.pem @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyiGx5QOluRk2T8Lel+Pt +MaX3TLNS8k7jgFAc2WLWdOHi5Uo59OZpA8QIYM2NsyOu2T0XD9TWCb4bUoknRf+P +gwdkP/PiJ1hcrQHTjFmcj0YIKD/Vtlewev0Jn3KPeBsLBlwcx/8s6oVQ30Qe8meX +PfgXbFnD0kDEea+PHp1jpMz2Z8Jzedk1b0D1vba3+AhdDQb5PY00ycn9SOk2Sf24 +jUMpVqCutsbr/DjXvnsEqnNCPoDcsm+3D6MPSkNEG3UmuYvACN7+qWLx1O/rQQad +VOR6yGsQLa48ZHDtZQe2aygXiStnZoJYzp/c2uAZnEK8BUo5az9+6gS5Dif1g957 +qQIDAQAB +-----END PUBLIC KEY----- \ No newline at end of file diff --git a/drizzle.config.ts b/drizzle.config.ts new file mode 100644 index 0000000..f7c270d --- /dev/null +++ b/drizzle.config.ts @@ -0,0 +1,13 @@ +import { defineConfig } from 'drizzle-kit'; + +// if (!process.env.DATABASE_URL) throw new Error('DATABASE_URL is not set'); + +export default defineConfig({ + schema: './src/schemas/database/schema.ts', + dialect: 'postgresql', + dbCredentials: { + url: process.env.DATABASE_URL || 'postgresql://dummy:dummy@localhost:5432/dummy' + }, + verbose: true, + strict: true +}); diff --git a/drizzle/0000_peaceful_susan_delgado.sql b/drizzle/0000_peaceful_susan_delgado.sql new file mode 100644 index 0000000..5fd0df4 --- /dev/null +++ b/drizzle/0000_peaceful_susan_delgado.sql @@ -0,0 +1,121 @@ +CREATE TYPE "public"."card_assignment_status" AS ENUM('ASSIGNED', 'RETURNED');--> statement-breakpoint +CREATE TYPE "public"."device_type" AS ENUM('RFID_SCANNER', 'LOCK_SYSTEM');--> statement-breakpoint +CREATE TYPE "public"."management_user_role" AS ENUM('ADMIN');--> statement-breakpoint +CREATE TYPE "public"."rfid_card_status" AS ENUM('NEW', 'ENGRAVED', 'LOST', 'DISPOSED');--> statement-breakpoint +CREATE TYPE "public"."system_log_type" AS ENUM('ACTION', 'INFO', 'WARNING', 'ERROR');--> statement-breakpoint +CREATE TABLE "access_logs" ( + "member_id" uuid NOT NULL, + "device_id" uuid NOT NULL, + "accessed_at" timestamp NOT NULL, + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "created_at" timestamp DEFAULT now() NOT NULL, + "updated_at" timestamp DEFAULT now() NOT NULL +); +--> statement-breakpoint +CREATE TABLE "devices" ( + "name" varchar(200) NOT NULL, + "api_key" varchar(255) NOT NULL, + "type" "device_type" DEFAULT 'RFID_SCANNER' NOT NULL, + "last_seen_at" timestamp, + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "created_at" timestamp DEFAULT now() NOT NULL, + "updated_at" timestamp DEFAULT now() NOT NULL, + CONSTRAINT "devices_api_key_unique" UNIQUE("api_key") +); +--> statement-breakpoint +CREATE TABLE "management_users" ( + "first_name" varchar(100) NOT NULL, + "last_name" varchar(100) NOT NULL, + "username" varchar(50) NOT NULL, + "password_hash" varchar(255) NOT NULL, + "role" "management_user_role" DEFAULT 'ADMIN' NOT NULL, + "enabled" boolean DEFAULT true NOT NULL, + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "created_at" timestamp DEFAULT now() NOT NULL, + "updated_at" timestamp DEFAULT now() NOT NULL, + CONSTRAINT "management_users_username_unique" UNIQUE("username") +); +--> statement-breakpoint +CREATE TABLE "member_rfid_cards" ( + "member_id" uuid NOT NULL, + "card_id" uuid NOT NULL, + "status" "card_assignment_status" DEFAULT 'ASSIGNED' NOT NULL, + "issued_at" timestamp, + "returned_at" timestamp, + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "created_at" timestamp DEFAULT now() NOT NULL, + "updated_at" timestamp DEFAULT now() NOT NULL +); +--> statement-breakpoint +CREATE TABLE "members" ( + "first_name" varchar(100) NOT NULL, + "last_name" varchar(100) NOT NULL, + "title" varchar(20), + "birth_date" timestamp, + "membership_number" varchar(50), + "occupation" varchar(200), + "street" varchar(200), + "house_number" varchar(10), + "postal_code" varchar(10), + "city" varchar(100), + "phone_home" varchar(20), + "phone_work" varchar(20), + "phone_mobile" varchar(20), + "email" varchar(255), + "guest_account" boolean DEFAULT false NOT NULL, + "joined_at" timestamp, + "free_text_function" text, + "free_text_comment" text, + "password_hash" varchar(255), + "profile_image" varchar(500), + "allow_self_service" boolean DEFAULT false NOT NULL, + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "created_at" timestamp DEFAULT now() NOT NULL, + "updated_at" timestamp DEFAULT now() NOT NULL, + CONSTRAINT "members_membership_number_unique" UNIQUE("membership_number") +); +--> statement-breakpoint +CREATE TABLE "rfid_cards" ( + "rfid_id" varchar(50) NOT NULL, + "status" "rfid_card_status" DEFAULT 'NEW' NOT NULL, + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "created_at" timestamp DEFAULT now() NOT NULL, + "updated_at" timestamp DEFAULT now() NOT NULL, + CONSTRAINT "rfid_cards_rfid_id_unique" UNIQUE("rfid_id") +); +--> statement-breakpoint +CREATE TABLE "system_logs" ( + "type" "system_log_type" NOT NULL, + "name" varchar(255), + "user_id" uuid, + "payload" text, + "created_at" timestamp DEFAULT now() NOT NULL +); +--> statement-breakpoint +ALTER TABLE "access_logs" ADD CONSTRAINT "access_logs_member_id_members_id_fk" FOREIGN KEY ("member_id") REFERENCES "public"."members"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "access_logs" ADD CONSTRAINT "access_logs_device_id_devices_id_fk" FOREIGN KEY ("device_id") REFERENCES "public"."devices"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "member_rfid_cards" ADD CONSTRAINT "member_rfid_cards_member_id_members_id_fk" FOREIGN KEY ("member_id") REFERENCES "public"."members"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "member_rfid_cards" ADD CONSTRAINT "member_rfid_cards_card_id_rfid_cards_id_fk" FOREIGN KEY ("card_id") REFERENCES "public"."rfid_cards"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "system_logs" ADD CONSTRAINT "system_logs_user_id_management_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."management_users"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint +CREATE INDEX "access_logs_member_id_idx" ON "access_logs" USING btree ("member_id");--> statement-breakpoint +CREATE INDEX "access_logs_device_id_idx" ON "access_logs" USING btree ("device_id");--> statement-breakpoint +CREATE INDEX "access_logs_accessed_at_idx" ON "access_logs" USING btree ("accessed_at");--> statement-breakpoint +CREATE UNIQUE INDEX "devices_api_key_idx" ON "devices" USING btree ("api_key");--> statement-breakpoint +CREATE INDEX "devices_type_idx" ON "devices" USING btree ("type");--> statement-breakpoint +CREATE INDEX "devices_name_idx" ON "devices" USING btree ("name");--> statement-breakpoint +CREATE UNIQUE INDEX "management_users_username_idx" ON "management_users" USING btree ("username");--> statement-breakpoint +CREATE INDEX "management_users_enabled_idx" ON "management_users" USING btree ("enabled");--> statement-breakpoint +CREATE INDEX "member_rfid_cards_member_id_idx" ON "member_rfid_cards" USING btree ("member_id");--> statement-breakpoint +CREATE INDEX "member_rfid_cards_card_id_idx" ON "member_rfid_cards" USING btree ("card_id");--> statement-breakpoint +CREATE INDEX "member_rfid_cards_status_idx" ON "member_rfid_cards" USING btree ("status");--> statement-breakpoint +CREATE INDEX "member_rfid_cards_member_card_status_idx" ON "member_rfid_cards" USING btree ("member_id","card_id","status");--> statement-breakpoint +CREATE UNIQUE INDEX "members_membership_number_idx" ON "members" USING btree ("membership_number");--> statement-breakpoint +CREATE INDEX "members_email_idx" ON "members" USING btree ("email");--> statement-breakpoint +CREATE INDEX "members_guest_account_idx" ON "members" USING btree ("guest_account");--> statement-breakpoint +CREATE INDEX "members_name_idx" ON "members" USING btree ("first_name","last_name");--> statement-breakpoint +CREATE UNIQUE INDEX "rfid_cards_rfid_id_idx" ON "rfid_cards" USING btree ("rfid_id");--> statement-breakpoint +CREATE INDEX "rfid_cards_status_idx" ON "rfid_cards" USING btree ("status");--> statement-breakpoint +CREATE INDEX "system_logs_type_idx" ON "system_logs" USING btree ("type");--> statement-breakpoint +CREATE INDEX "system_logs_user_id_idx" ON "system_logs" USING btree ("user_id");--> statement-breakpoint +CREATE INDEX "system_logs_created_at_idx" ON "system_logs" USING btree ("created_at");--> statement-breakpoint +CREATE INDEX "system_logs_name_idx" ON "system_logs" USING btree ("name"); \ No newline at end of file diff --git a/drizzle/meta/0000_snapshot.json b/drizzle/meta/0000_snapshot.json new file mode 100644 index 0000000..ffa09a6 --- /dev/null +++ b/drizzle/meta/0000_snapshot.json @@ -0,0 +1,1024 @@ +{ + "id": "a0101695-385c-4a06-8311-608e5c56ecd0", + "prevId": "00000000-0000-0000-0000-000000000000", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.access_logs": { + "name": "access_logs", + "schema": "", + "columns": { + "member_id": { + "name": "member_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "device_id": { + "name": "device_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "accessed_at": { + "name": "accessed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "access_logs_member_id_idx": { + "name": "access_logs_member_id_idx", + "columns": [ + { + "expression": "member_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "access_logs_device_id_idx": { + "name": "access_logs_device_id_idx", + "columns": [ + { + "expression": "device_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "access_logs_accessed_at_idx": { + "name": "access_logs_accessed_at_idx", + "columns": [ + { + "expression": "accessed_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "access_logs_member_id_members_id_fk": { + "name": "access_logs_member_id_members_id_fk", + "tableFrom": "access_logs", + "tableTo": "members", + "columnsFrom": [ + "member_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "access_logs_device_id_devices_id_fk": { + "name": "access_logs_device_id_devices_id_fk", + "tableFrom": "access_logs", + "tableTo": "devices", + "columnsFrom": [ + "device_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.devices": { + "name": "devices", + "schema": "", + "columns": { + "name": { + "name": "name", + "type": "varchar(200)", + "primaryKey": false, + "notNull": true + }, + "api_key": { + "name": "api_key", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "device_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'RFID_SCANNER'" + }, + "last_seen_at": { + "name": "last_seen_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "devices_api_key_idx": { + "name": "devices_api_key_idx", + "columns": [ + { + "expression": "api_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "devices_type_idx": { + "name": "devices_type_idx", + "columns": [ + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "devices_name_idx": { + "name": "devices_name_idx", + "columns": [ + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "devices_api_key_unique": { + "name": "devices_api_key_unique", + "nullsNotDistinct": false, + "columns": [ + "api_key" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.management_users": { + "name": "management_users", + "schema": "", + "columns": { + "first_name": { + "name": "first_name", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "last_name": { + "name": "last_name", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "username": { + "name": "username", + "type": "varchar(50)", + "primaryKey": false, + "notNull": true + }, + "password_hash": { + "name": "password_hash", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "management_user_role", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'ADMIN'" + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "management_users_username_idx": { + "name": "management_users_username_idx", + "columns": [ + { + "expression": "username", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "management_users_enabled_idx": { + "name": "management_users_enabled_idx", + "columns": [ + { + "expression": "enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "management_users_username_unique": { + "name": "management_users_username_unique", + "nullsNotDistinct": false, + "columns": [ + "username" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.member_rfid_cards": { + "name": "member_rfid_cards", + "schema": "", + "columns": { + "member_id": { + "name": "member_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "card_id": { + "name": "card_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "card_assignment_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'ASSIGNED'" + }, + "issued_at": { + "name": "issued_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "returned_at": { + "name": "returned_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "member_rfid_cards_member_id_idx": { + "name": "member_rfid_cards_member_id_idx", + "columns": [ + { + "expression": "member_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "member_rfid_cards_card_id_idx": { + "name": "member_rfid_cards_card_id_idx", + "columns": [ + { + "expression": "card_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "member_rfid_cards_status_idx": { + "name": "member_rfid_cards_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "member_rfid_cards_member_card_status_idx": { + "name": "member_rfid_cards_member_card_status_idx", + "columns": [ + { + "expression": "member_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "card_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "member_rfid_cards_member_id_members_id_fk": { + "name": "member_rfid_cards_member_id_members_id_fk", + "tableFrom": "member_rfid_cards", + "tableTo": "members", + "columnsFrom": [ + "member_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "member_rfid_cards_card_id_rfid_cards_id_fk": { + "name": "member_rfid_cards_card_id_rfid_cards_id_fk", + "tableFrom": "member_rfid_cards", + "tableTo": "rfid_cards", + "columnsFrom": [ + "card_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.members": { + "name": "members", + "schema": "", + "columns": { + "first_name": { + "name": "first_name", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "last_name": { + "name": "last_name", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "birth_date": { + "name": "birth_date", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "membership_number": { + "name": "membership_number", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "occupation": { + "name": "occupation", + "type": "varchar(200)", + "primaryKey": false, + "notNull": false + }, + "street": { + "name": "street", + "type": "varchar(200)", + "primaryKey": false, + "notNull": false + }, + "house_number": { + "name": "house_number", + "type": "varchar(10)", + "primaryKey": false, + "notNull": false + }, + "postal_code": { + "name": "postal_code", + "type": "varchar(10)", + "primaryKey": false, + "notNull": false + }, + "city": { + "name": "city", + "type": "varchar(100)", + "primaryKey": false, + "notNull": false + }, + "phone_home": { + "name": "phone_home", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "phone_work": { + "name": "phone_work", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "phone_mobile": { + "name": "phone_mobile", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "email": { + "name": "email", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "guest_account": { + "name": "guest_account", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "joined_at": { + "name": "joined_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "free_text_function": { + "name": "free_text_function", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "free_text_comment": { + "name": "free_text_comment", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "password_hash": { + "name": "password_hash", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "profile_image": { + "name": "profile_image", + "type": "varchar(500)", + "primaryKey": false, + "notNull": false + }, + "allow_self_service": { + "name": "allow_self_service", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "members_membership_number_idx": { + "name": "members_membership_number_idx", + "columns": [ + { + "expression": "membership_number", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "members_email_idx": { + "name": "members_email_idx", + "columns": [ + { + "expression": "email", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "members_guest_account_idx": { + "name": "members_guest_account_idx", + "columns": [ + { + "expression": "guest_account", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "members_name_idx": { + "name": "members_name_idx", + "columns": [ + { + "expression": "first_name", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "last_name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "members_membership_number_unique": { + "name": "members_membership_number_unique", + "nullsNotDistinct": false, + "columns": [ + "membership_number" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.rfid_cards": { + "name": "rfid_cards", + "schema": "", + "columns": { + "rfid_id": { + "name": "rfid_id", + "type": "varchar(50)", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "rfid_card_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'NEW'" + }, + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "rfid_cards_rfid_id_idx": { + "name": "rfid_cards_rfid_id_idx", + "columns": [ + { + "expression": "rfid_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "rfid_cards_status_idx": { + "name": "rfid_cards_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "rfid_cards_rfid_id_unique": { + "name": "rfid_cards_rfid_id_unique", + "nullsNotDistinct": false, + "columns": [ + "rfid_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.system_logs": { + "name": "system_logs", + "schema": "", + "columns": { + "type": { + "name": "type", + "type": "system_log_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "payload": { + "name": "payload", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "system_logs_type_idx": { + "name": "system_logs_type_idx", + "columns": [ + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "system_logs_user_id_idx": { + "name": "system_logs_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "system_logs_created_at_idx": { + "name": "system_logs_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "system_logs_name_idx": { + "name": "system_logs_name_idx", + "columns": [ + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "system_logs_user_id_management_users_id_fk": { + "name": "system_logs_user_id_management_users_id_fk", + "tableFrom": "system_logs", + "tableTo": "management_users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": { + "public.card_assignment_status": { + "name": "card_assignment_status", + "schema": "public", + "values": [ + "ASSIGNED", + "RETURNED" + ] + }, + "public.device_type": { + "name": "device_type", + "schema": "public", + "values": [ + "RFID_SCANNER", + "LOCK_SYSTEM" + ] + }, + "public.management_user_role": { + "name": "management_user_role", + "schema": "public", + "values": [ + "ADMIN" + ] + }, + "public.rfid_card_status": { + "name": "rfid_card_status", + "schema": "public", + "values": [ + "NEW", + "ENGRAVED", + "LOST", + "DISPOSED" + ] + }, + "public.system_log_type": { + "name": "system_log_type", + "schema": "public", + "values": [ + "ACTION", + "INFO", + "WARNING", + "ERROR" + ] + } + }, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/drizzle/meta/_journal.json b/drizzle/meta/_journal.json new file mode 100644 index 0000000..137abbe --- /dev/null +++ b/drizzle/meta/_journal.json @@ -0,0 +1,13 @@ +{ + "version": "7", + "dialect": "postgresql", + "entries": [ + { + "idx": 0, + "version": "7", + "when": 1758824141426, + "tag": "0000_peaceful_susan_delgado", + "breakpoints": true + } + ] +} \ No newline at end of file diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 0000000..9c3f05a --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,62 @@ +import prettier from 'eslint-config-prettier'; +import { fileURLToPath } from 'node:url'; +import { includeIgnoreFile } from '@eslint/compat'; +import js from '@eslint/js'; +import svelte from 'eslint-plugin-svelte'; +import { defineConfig } from 'eslint/config'; +import globals from 'globals'; +import ts from 'typescript-eslint'; +// highlight-start +// 1. Plugin importieren +import unusedImports from 'eslint-plugin-unused-imports'; +// highlight-end +import svelteConfig from './svelte.config.js'; + +const gitignorePath = fileURLToPath(new URL('./.gitignore', import.meta.url)); + +export default defineConfig( + includeIgnoreFile(gitignorePath), + js.configs.recommended, + ...ts.configs.recommended, + ...svelte.configs.recommended, + prettier, + ...svelte.configs.prettier, + { + languageOptions: { + globals: { ...globals.browser, ...globals.node } + }, + // highlight-start + // 2. Plugin hier hinzufügen + plugins: { + 'unused-imports': unusedImports + }, + // 3. Regeln für das Plugin definieren + rules: { + 'no-undef': 'off', + 'no-unused-vars': 'off', // Wichtig: Die Standard-ESLint-Regel deaktivieren + 'unused-imports/no-unused-imports': 'error', // Regel des Plugins aktivieren + 'unused-imports/no-unused-vars': [ + // Sicherstellen, dass keine benutzten Variablen fälschlich entfernt werden + 'warn', + { + vars: 'all', + varsIgnorePattern: '^_', + args: 'after-used', + argsIgnorePattern: '^_' + } + ] + } + // highlight-end + }, + { + files: ['**/*.svelte', '**/*.svelte.ts', '**/*.svelte.js'], + languageOptions: { + parserOptions: { + projectService: true, + extraFileExtensions: ['.svelte'], + parser: ts.parser, + svelteConfig + } + } + } +); diff --git a/messages/de.json b/messages/de.json new file mode 100644 index 0000000..9e5afaa --- /dev/null +++ b/messages/de.json @@ -0,0 +1,545 @@ +{ + "$schema": "https://inlang.com/schema/inlang-message-format", + "example_message": "Guten Tag {username}", + "user_list_updated": "Benutzerliste wurde mit Ihren Filtern aktualisiert.", + "filters_cleared": "Alle Filter wurden gelöscht.", + "delete_user_failed": "Benutzer konnte nicht gelöscht werden. Bitte versuchen Sie es erneut.", + "user_deleted_success": "Benutzer erfolgreich gelöscht.", + "user_deleted_named": "{firstName} {lastName} wurde gelöscht.", + "user_created_success": "Benutzer erfolgreich erstellt.", + "user_updated_success": "Benutzer erfolgreich aktualisiert.", + "user_create_failed": "Benutzer konnte nicht erstellt werden. Bitte versuchen Sie es erneut.", + "user_update_failed": "Benutzer konnte nicht aktualisiert werden. Bitte versuchen Sie es erneut.", + "user_delete_failed_retry": "Benutzer konnte nicht gelöscht werden. Bitte versuchen Sie es erneut.", + "user_id_required": "Benutzer-ID ist erforderlich", + "user_not_found": "Benutzer nicht gefunden", + "users_title": "Benutzer", + "users_description": "Benutzerkonten und Berechtigungen verwalten.", + "filter_button": "Filtern", + "add_user_button": "Benutzer hinzufügen", + "user_management_title": "Benutzerverwaltung", + "user_management_description": "Alle Benutzerkonten anzeigen und verwalten", + "search_users_placeholder": "Benutzer suchen...", + "select_label": "Auswählen", + "user_column": "Benutzer", + "role_column": "Rolle", + "status_column": "Status", + "created_column": "Erstellt", + "actions_column": "Aktionen", + "enabled_status": "Aktiviert", + "disabled_status": "Deaktiviert", + "view_details": "Details anzeigen", + "edit_user": "Benutzer bearbeiten", + "delete_user": "Benutzer löschen", + "no_users_found": "Keine Benutzer gefunden.", + "add_user_title": "Benutzer hinzufügen", + "edit_user_title": "Benutzer bearbeiten", + "create_user_description": "Einen neuen Verwaltungsbenutzer erstellen", + "modify_user_description": "Benutzerdetails ändern", + "first_name_label": "Vorname", + "last_name_label": "Nachname", + "username_label": "Benutzername", + "password_label": "Passwort", + "leave_blank_to_keep": "(leer lassen zum Beibehalten)", + "enabled_label": "Aktiviert", + "cancel_button": "Abbrechen", + "create_button": "Erstellen", + "save_button": "Speichern", + "advanced_filters_title": "Erweiterte Filter", + "advanced_filters_description": "Benutzer filtern und sortieren, um das Gewünschte zu finden", + "account_status_label": "Kontostatus", + "all_status": "Alle Status", + "enabled": "Aktiviert", + "disabled": "Deaktiviert", + "role_label": "Rolle", + "all_roles": "Alle Rollen", + "admin": "Admin", + "user": "Benutzer", + "created_date_range_label": "Erstellungsdatumsbereich", + "from_label": "Von", + "to_label": "Bis", + "sort_by_label": "Sortieren nach", + "first_name": "Vorname", + "last_name": "Nachname", + "created_date": "Erstellungsdatum", + "ascending": "Aufsteigend", + "descending": "Absteigend", + "reset_button": "Zurücksetzen", + "apply_filters_button": "Filter anwenden", + "dashboard_title": "Dashboard", + "dashboard_welcome": "Willkommen zurück! Hier ist, was in Ihrer Anwendung passiert.", + "more_button": "Mehr", + "add_new_button": "Neu hinzufügen", + "total_users": "Gesamtbenutzer", + "revenue": "Umsatz", + "growth_rate": "Wachstumsrate", + "active_sessions": "Aktive Sitzungen", + "active_users_this_month": "Aktive Benutzer diesen Monat", + "total_revenue_this_month": "Gesamtumsatz diesen Monat", + "monthly_growth_rate": "Monatliche Wachstumsrate", + "current_active_sessions": "Aktuelle aktive Sitzungen", + "recent_activity": "Kürzliche Aktivität", + "created_new_post": "Neuen Beitrag erstellt", + "updated_profile": "Profil aktualisiert", + "deleted_comment": "Kommentar gelöscht", + "uploaded_image": "Bild hochgeladen", + "changed_settings": "Einstellungen geändert", + "welcome_to_sveltekit": "Willkommen bei SvelteKit", + "visit_docs": "Besuchen Sie svelte.dev/docs/kit, um die Dokumentation zu lesen", + "analytics_overview": "Analyse-Übersicht", + "monthly_performance_metrics": "Monatliche Leistungsmetriken und Trends", + "chart_visualization_placeholder": "Diagramm-Visualisierung würde hier stehen", + "chart_integration_needed": "Integration mit Diagramm-Bibliothek erforderlich", + "latest_user_actions": "Neueste Benutzeraktionen und Systemereignisse", + "view_all_activity": "Alle Aktivitäten anzeigen", + "quick_actions": "Schnellaktionen", + "frequently_used_tasks": "Häufig verwendete Verwaltungsaufgaben", + "manage_users": "Benutzer verwalten", + "view_reports": "Berichte anzeigen", + "system_status": "Systemstatus", + "add_content": "Inhalt hinzufügen", + "more_options": "Weitere Optionen", + "username_required": "Der Benutzername ist erforderlich.", + "first_name_required": "Der Vorname ist erforderlich.", + "last_name_required": "Der Nachname ist erforderlich.", + "password_required": "Passwort ist erforderlich.", + "password_min_length": "Das Passwort muss mindestens 8 Zeichen lang sein.", + "sidebar_dashboard": "Dashboard", + "sidebar_users": "Benutzer", + "sidebar_members": "Mitglieder", + "sidebar_devices": "Geräte", + "sidebar_analytics": "Analyse", + "sidebar_content": "Inhalt", + "sidebar_posts": "Beiträge", + "sidebar_pages": "Seiten", + "sidebar_media": "Medien", + "sidebar_database": "Datenbank", + "sidebar_settings": "Einstellungen", + "sidebar_security": "Sicherheit", + "sidebar_notifications": "Benachrichtigungen", + "sidebar_navigation": "Navigation", + "sidebar_system": "System", + "sidebar_admin_panel": "Admin Panel", + "sidebar_management": "Verwaltung", + "sidebar_system_online": "System Online", + "settings_title": "Einstellungen", + "settings_description": "Konfigurieren Sie Ihre Anwendungseinstellungen", + "general_settings": "Allgemeine Einstellungen", + "site_name": "Seitenname", + "site_name_placeholder": "Geben Sie den Seitennamen ein", + "site_description": "Seitenbeschreibung", + "site_description_placeholder": "Beschreiben Sie Ihre Anwendung", + "admin_email": "Admin-E-Mail", + "admin_email_placeholder": "admin@example.com", + "timezone": "Zeitzone", + "timezone_placeholder": "UTC", + "language": "Sprache", + "language_placeholder": "de", + "security_settings": "Sicherheitseinstellungen", + "two_factor_auth": "Zwei-Faktor-Authentifizierung", + "session_timeout": "Sitzungs-Timeout (Minuten)", + "password_complexity": "Passwort-Komplexität", + "login_attempts": "Maximale Anmeldeversuche", + "ip_whitelist": "IP-Whitelist", + "notification_settings": "Benachrichtigungseinstellungen", + "email_notifications": "E-Mail-Benachrichtigungen", + "push_notifications": "Push-Benachrichtigungen", + "weekly_reports": "Wöchentliche Berichte", + "system_alerts": "Systemwarnungen", + "user_registrations": "Benutzerregistrierungen", + "system_settings": "Systemeinstellungen", + "maintenance_mode": "Wartungsmodus", + "backup_frequency": "Sicherungshäufigkeit", + "backup_frequency_placeholder": "täglich", + "save_settings": "Einstellungen speichern", + "settings_saved": "Einstellungen erfolgreich gespeichert", + "settings_save_error": "Fehler beim Speichern der Einstellungen", + "confirm_delete": "Löschen bestätigen", + "confirm_delete_description": "Sind Sie sicher, dass Sie {firstName} {lastName} löschen möchten? Diese Aktion kann nicht rückgängig gemacht werden.", + "confirm_delete_generic": "Sind Sie sicher, dass Sie diesen Benutzer löschen möchten? Diese Aktion kann nicht rückgängig gemacht werden.", + "user_details_title": "Benutzerdetails", + "user_information": "Benutzerinformationen", + "no_user_selected": "Kein Benutzer ausgewählt", + "edit_user_button": "Benutzer bearbeiten", + "close_button": "Schließen", + "delete_user_button": "Benutzer löschen", + "user_role": "Rolle", + "user_status": "Status", + "created_label": "Erstellt", + "last_updated": "Zuletzt aktualisiert", + "user_id_label": "Benutzer-ID", + "select_all_users": "Alle Benutzer auswählen", + "selected_count": "{count} von {total} ausgewählt", + "select_users_bulk": "Benutzer für Massenaktionen auswählen", + "bulk_actions": "Massenaktionen", + "actions_for_users": "Aktionen für {count} Benutzer", + "enable_users": "Benutzer aktivieren", + "disable_users": "Benutzer deaktivieren", + "send_email": "E-Mail senden", + "delete_users": "Benutzer löschen", + "total_users_stat": "Gesamtbenutzer", + "all_registered_users": "Alle registrierten Benutzer", + "currently_enabled": "Derzeit aktiviert", + "accounts_disabled": "Konten deaktiviert", + "admins_stat": "Administratoren", + "administrator_accounts": "Administrator-Konten", + "device_list_updated": "Geräteliste wurde mit Ihren Filtern aktualisiert.", + "delete_device_failed": "Gerät konnte nicht gelöscht werden. Bitte versuchen Sie es erneut.", + "device_deleted_success": "Gerät erfolgreich gelöscht.", + "device_deleted_named": "{name} wurde gelöscht.", + "device_created_success": "Gerät erfolgreich erstellt.", + "device_updated_success": "Gerät erfolgreich aktualisiert.", + "device_create_failed": "Gerät konnte nicht erstellt werden. Bitte versuchen Sie es erneut.", + "device_update_failed": "Gerät konnte nicht aktualisiert werden. Bitte versuchen Sie es erneut.", + "device_delete_failed_retry": "Gerät konnte nicht gelöscht werden. Bitte versuchen Sie es erneut.", + "device_id_required": "Geräte-ID ist erforderlich", + "device_not_found": "Gerät nicht gefunden", + "devices_title": "Geräte", + "devices_description": "RFID-Geräte und Schließsysteme verwalten.", + "add_device_button": "Gerät hinzufügen", + "total_devices_stat": "Gesamtgeräte", + "all_registered_devices": "Alle registrierten Geräte", + "rfid_scanners_stat": "RFID-Scanner", + "rfid_scanner_devices": "RFID-Scanner-Geräte", + "lock_systems_stat": "Schließsysteme", + "lock_system_devices": "Schließsystem-Geräte", + "device_management_title": "Geräteverwaltung", + "device_management_description": "Alle registrierten Geräte anzeigen und verwalten", + "search_devices_placeholder": "Geräte suchen...", + "device_column": "Gerät", + "type_column": "Typ", + "api_key_column": "API-Schlüssel", + "last_seen_column": "Zuletzt gesehen", + "rfid_scanner": "RFID-Scanner", + "lock_system": "Schließsystem", + "edit_device": "Gerät bearbeiten", + "delete_device": "Gerät löschen", + "no_devices_found": "Keine Geräte gefunden.", + "add_device_title": "Gerät hinzufügen", + "edit_device_title": "Gerät bearbeiten", + "create_device_description": "Ein neues Gerät erstellen", + "modify_device_description": "Gerätedetails ändern", + "device_name_label": "Gerätename", + "device_type_label": "Gerätetyp", + "api_key_label": "API-Schlüssel (optional)", + "leave_blank_for_auto_generation": "Leer lassen für automatische Generierung", + "api_key_auto_generation_description": "Falls nicht angegeben, wird ein API-Schlüssel automatisch generiert", + "confirm_delete_device_description": "Sind Sie sicher, dass Sie \"{name}\" löschen möchten? Diese Aktion kann nicht rückgängig gemacht werden.", + "delete_device_button": "Gerät löschen", + "device_details_title": "Gerätedetails", + "device_information": "Geräteinformationen anzeigen", + "name_label": "Name", + "type_label": "Typ", + "last_seen_label": "Zuletzt gesehen", + "updated_label": "Aktualisiert", + "no_device_selected": "Kein Gerät ausgewählt", + "edit_device_button": "Gerät bearbeiten", + "advanced_filters_device_description": "Geräte nach Typ und anderen Kriterien filtern", + "all_types": "Alle Typen", + "member_list_updated": "Mitgliederliste wurde mit Ihren Filtern aktualisiert.", + "delete_member_failed": "Mitglied konnte nicht gelöscht werden. Bitte versuchen Sie es erneut.", + "member_deleted_success": "Mitglied erfolgreich gelöscht.", + "member_deleted_named": "{name} wurde gelöscht.", + "member_created_success": "Mitglied erfolgreich erstellt.", + "member_updated_success": "Mitglied erfolgreich aktualisiert.", + "member_create_failed": "Mitglied konnte nicht erstellt werden. Bitte versuchen Sie es erneut.", + "member_update_failed": "Mitglied konnte nicht aktualisiert werden. Bitte versuchen Sie es erneut.", + "member_delete_failed_retry": "Mitglied konnte nicht gelöscht werden. Bitte versuchen Sie es erneut.", + "member_id_required": "Mitglieds-ID ist erforderlich", + "member_not_found": "Mitglied nicht gefunden", + "form_check_errors": "Bitte überprüfen Sie das Formular auf Fehler", + "members_title": "Mitglieder", + "members_description": "Vereinsmitglieder und deren Informationen verwalten.", + "add_member_button": "Mitglied hinzufügen", + "total_members_stat": "Gesamtmitglieder", + "all_registered_members": "Alle registrierten Mitglieder", + "guest_accounts_stat": "Gastkonten", + "guest_member_accounts": "Gastmitglieder-Konten", + "regular_members_stat": "Reguläre Mitglieder", + "regular_member_accounts": "Reguläre Mitglieder-Konten", + "member_management_title": "Mitgliederverwaltung", + "member_management_description": "Alle Vereinsmitglieder anzeigen und verwalten", + "search_members_placeholder": "Mitglieder suchen...", + "membership_number_column": "Mitgliedsnr.", + "email_column": "E-Mail", + "phone_column": "Telefon", + "edit_member": "Mitglied bearbeiten", + "delete_member": "Mitglied löschen", + "no_members_found": "Keine Mitglieder gefunden.", + "add_member_title": "Mitglied hinzufügen", + "edit_member_title": "Mitglied bearbeiten", + "create_member_description": "Ein neues Mitglied erstellen", + "modify_member_description": "Mitgliederdetails ändern", + "membership_number_label": "Mitgliedsnummer", + "title_label": "Titel", + "birth_date_label": "Geburtsdatum", + "occupation_label": "Beruf", + "street_label": "Straße", + "house_number_label": "Hausnummer", + "postal_code_label": "Postleitzahl", + "city_label": "Stadt", + "phone_home_label": "Telefon (Privat)", + "phone_work_label": "Telefon (Arbeit)", + "phone_mobile_label": "Telefon (Mobil)", + "guest_account_label": "Gastkonto", + "joined_at_label": "Beigetreten am", + "free_text_function_label": "Funktion/Rolle", + "free_text_comment_label": "Kommentare", + "confirm_delete_member_description": "Sind Sie sicher, dass Sie \"{name}\" löschen möchten? Diese Aktion kann nicht rückgängig gemacht werden.", + "delete_member_button": "Mitglied löschen", + "member_details_title": "Mitgliederdetails", + "member_information": "Mitgliederinformationen anzeigen", + "no_member_selected": "Kein Mitglied ausgewählt", + "edit_member_button": "Mitglied bearbeiten", + "advanced_filters_member_description": "Mitglieder nach verschiedenen Kriterien filtern", + "account_type_label": "Kontotyp", + "all_account_types": "Alle Kontotypen", + "guest_account": "Gastkonto", + "member_account": "Mitgliedskonto", + "joined_date_range_label": "Beitrittsdatumsbereich", + "birth_date_range_label": "Geburtsdatumsbereich", + "email": "E-Mail", + "membership_number": "Mitgliedsnummer", + "all_members": "Alle Mitglieder", + "rfid_cards_title": "RFID-Karten", + "rfid_cards_description": "RFID-Karten und Zugangskontrolle verwalten.", + "add_rfid_card_button": "RFID-Karte hinzufügen", + "rfid_card_management_title": "RFID-Karten-Verwaltung", + "rfid_card_management_description": "Alle RFID-Karten anzeigen und verwalten", + "search_rfid_cards_placeholder": "RFID-Karten suchen...", + "rfid_id_column": "RFID-ID", + "assigned_member_column": "Zugewiesenes Mitglied", + "active_status": "Aktiv", + "inactive_status": "Inaktiv", + "edit_rfid_card": "RFID-Karte bearbeiten", + "delete_rfid_card": "RFID-Karte löschen", + "no_rfid_cards_found": "Keine RFID-Karten gefunden.", + "add_rfid_card_title": "RFID-Karte hinzufügen", + "edit_rfid_card_title": "RFID-Karte bearbeiten", + "create_rfid_card_description": "Eine neue RFID-Karte erstellen", + "modify_rfid_card_description": "RFID-Kartendetails ändern", + "rfid_id_label": "RFID-ID", + "rfid_card_status_label": "Status", + "rfid_card_status_new": "Neu", + "rfid_card_status_engraved": "Beschriftet", + "rfid_card_status_lost": "Verloren", + "rfid_card_status_disposed": "Entsorgt", + "assign_rfid_card_title": "RFID-Karte zuweisen", + "assign_rfid_card_description": "Diese RFID-Karte einem Mitglied zuweisen", + "unassign_rfid_card_title": "RFID-Karte-Zuweisung aufheben", + "unassign_rfid_card_description": "Die Zuweisung dieser RFID-Karte von ihrem aktuellen Mitglied entfernen", + "assign_card_button": "Karte zuweisen", + "unassign_card_button": "Zuweisung aufheben", + "select_member_label": "Mitglied auswählen", + "no_members_available": "Keine Mitglieder verfügbar", + "rfid_card_assigned_success": "RFID-Karte erfolgreich zugewiesen", + "rfid_card_unassigned_success": "RFID-Karte-Zuweisung erfolgreich aufgehoben", + "rfid_card_assign_failed": "RFID-Karte konnte nicht zugewiesen werden", + "rfid_card_unassign_failed": "RFID-Karte-Zuweisung konnte nicht aufgehoben werden", + "assigned_member_label": "Zugewiesenes Mitglied", + "select_member_placeholder": "Ein Mitglied auswählen", + "unassigned": "Nicht zugewiesen", + "confirm_delete_rfid_card_description": "Sind Sie sicher, dass Sie die RFID-Karte \"{rfidId}\" löschen möchten? Diese Aktion kann nicht rückgängig gemacht werden.", + "delete_rfid_card_button": "RFID-Karte löschen", + "rfid_card_details_title": "RFID-Kartendetails", + "rfid_card_information": "RFID-Karteninformationen anzeigen", + "no_rfid_card_selected": "Keine RFID-Karte ausgewählt", + "edit_rfid_card_button": "RFID-Karte bearbeiten", + "advanced_filters_rfid_card_description": "RFID-Karten nach Status und anderen Kriterien filtern", + "all_statuses": "Alle Status", + "rfid_card_list_updated": "RFID-Kartenliste wurde mit Ihren Filtern aktualisiert.", + "delete_rfid_card_failed": "RFID-Karte konnte nicht gelöscht werden. Bitte versuchen Sie es erneut.", + "rfid_card_deleted_success": "RFID-Karte erfolgreich gelöscht.", + "rfid_card_deleted_named": "RFID-Karte {rfidId} wurde gelöscht.", + "rfid_card_created_success": "RFID-Karte erfolgreich erstellt.", + "rfid_card_updated_success": "RFID-Karte erfolgreich aktualisiert.", + "rfid_card_create_failed": "RFID-Karte konnte nicht erstellt werden. Bitte versuchen Sie es erneut.", + "rfid_card_update_failed": "RFID-Karte konnte nicht aktualisiert werden. Bitte versuchen Sie es erneut.", + "rfid_card_delete_failed_retry": "RFID-Karte konnte nicht gelöscht werden. Bitte versuchen Sie es erneut.", + "rfid_card_id_required": "RFID-Karten-ID ist erforderlich", + "rfid_card_not_found": "RFID-Karte nicht gefunden", + "total_rfid_cards_stat": "Gesamt-RFID-Karten", + "all_registered_rfid_cards": "Alle registrierten RFID-Karten", + "active_rfid_cards_stat": "Aktive RFID-Karten", + "active_rfid_card_count": "Anzahl aktiver RFID-Karten", + "inactive_rfid_cards_stat": "Inaktive RFID-Karten", + "inactive_rfid_card_count": "Anzahl inaktiver RFID-Karten", + "assigned_rfid_cards_stat": "Zugewiesene RFID-Karten", + "assigned_rfid_card_count": "Anzahl zugewiesener RFID-Karten", + "unassigned_rfid_cards_stat": "Nicht zugewiesene RFID-Karten", + "unassigned_rfid_card_count": "Anzahl nicht zugewiesener RFID-Karten", + "sidebar_rfid_cards": "RFID-Karten", + "sidebar_rfid_card_assignments": "Zuweisungen", + "sidebar_logs": "Protokolle", + "sidebar_system_logs": "Systemprotokoll", + "sidebar_access_logs": "Zutrittsprotokolle", + "rfid_card_assignments_title": "RFID-Karten-Zuweisungen", + "access_logs_title": "Zutrittsprotokolle", + "access_logs_description": "Zutrittsprotokolle für RFID-System anzeigen", + "accessed_at_column": "Zugang um", + "search_access_logs_placeholder": "Zutrittsprotokolle suchen...", + "no_access_logs_found": "Keine Zutrittsprotokolle gefunden.", + "advanced_filters_access_logs_description": "Zutrittsprotokolle nach verschiedenen Kriterien filtern", + "from_date_label": "Von Datum", + "to_date_label": "Bis Datum", + "rfid_card_assignments_description": "RFID-Karten-Zuweisungen zu Mitgliedern verwalten", + "rfid_card_assignments_table_title": "RFID-Karten-Zuweisungen", + "rfid_card_assignments_table_description": "RFID-Karten-Zuweisungen zu Mitgliedern anzeigen und verwalten", + "rfid_card_column": "RFID-Karte", + "assignment_status_column": "Status", + "issued_at_column": "Ausgegeben am", + "returned_at_column": "Zurückgegeben am", + "assigned_status": "Zugewiesen", + "returned_status": "Zurückgegeben", + "unknown_member": "Unbekanntes Mitglied", + "unknown_card": "Unbekannt", + "no_assignments_found": "Keine Zuweisungen gefunden", + "admin_login_title": "Admin-Anmeldung", + "admin_login_subtitle": "Zugriff auf die RFID-Control-Panel-Verwaltung", + "sign_in": "Anmelden", + "enter_credentials": "Geben Sie Ihre Anmeldedaten ein, um auf das Admin-Panel zuzugreifen", + "username": "Benutzername", + "password": "Passwort", + "enter_username": "Geben Sie Ihren Benutzernamen ein", + "enter_password": "Geben Sie Ihr Passwort ein", + "signing_in": "Anmeldung läuft", + "secure_admin_access": "Sicherer Admin-Zugang erforderlich", + "all_rights_reserved": "Alle Rechte vorbehalten", + "zod": { + "duplicate_membership_number": "Mitgliedsnummer ist bereits vergeben.", + "duplicate_email": "E-Mail-Adresse ist bereits registriert.", + "field_required": "Dieses Feld ist erforderlich", + "username_required": "Benutzername ist erforderlich", + "first_name_required": "Vorname ist erforderlich", + "last_name_required": "Nachname ist erforderlich", + "password_required": "Passwort ist erforderlich", + "email_required": "E-Mail ist erforderlich", + "invalid_field": "Ungültiger Wert", + "invalid_email": "Ungültige E-Mail-Adresse", + "invalid_username": "Ungültiger Benutzername", + "password_min_length": "Passwort muss mindestens 8 Zeichen lang sein", + "string_too_short": "Text ist zu kurz", + "string_too_long": "Text ist zu lang", + "number_too_small": "Zahl ist zu klein", + "number_too_big": "Zahl ist zu groß", + "invalid_date": "Ungültiges Datum", + "invalid_string": "Ungültiger Zeichenkettenwert", + "array_too_short": "Array muss mindestens {min} Elemente enthalten", + "array_too_long": "Array darf höchstens {max} Elemente enthalten", + "value_too_small": "Wert ist zu klein", + "value_too_big": "Wert ist zu groß", + "invalid_url": "Ungültiges URL-Format", + "invalid_uuid": "Ungültiges UUID-Format", + "invalid_cuid": "Ungültiges CUID-Format", + "invalid_ulid": "Ungültiges ULID-Format", + "invalid_format": "Ungültiges Format", + "not_multiple_of": "Wert muss ein Vielfaches von {multiple} sein", + "unrecognized_keys": "Unbekannte Schlüssel: {keys}", + "invalid_union": "Ungültiger Union-Wert", + "invalid_key": "Ungültiger Schlüssel: {key}", + "invalid_element": "Ungültiges Array-Element", + "invalid_value": "Ungültiger Wert", + "passwords_do_not_match": "Passwörter stimmen nicht überein", + "password_mismatch": "Passwörter stimmen nicht überein", + "username_already_exists": "Benutzername ist bereits vergeben", + "email_already_exists": "E-Mail ist bereits registriert", + "identifier_required": "E-Mail oder Mitgliedsnummer ist erforderlich", + "token_required": "Reset-Token ist erforderlich", + "current_password_required": "Aktuelles Passwort ist erforderlich", + "invalid_credentials": "Ungültige E-Mail/Mitgliedsnummer oder Passwort", + "account_disabled": "Konto ist deaktiviert", + "self_service_disabled": "Selbstbedienungszugang ist für dieses Konto nicht aktiviert", + "birth_date_in_future" : "Geburtsdatum kann nicht in der Zukunft liegen" + }, + "selfservice": { + "member_portal": "Mitgliederportal", + "dashboard": "Dashboard", + "my_profile": "Mein Profil", + "security": "Sicherheit", + "profile_title": "Mein Profil", + "profile_description": "Verwalten Sie Ihre persönlichen Informationen und Kontaktdaten", + "profile_updated_success": "Profil erfolgreich aktualisiert!", + "profile_update_failed": "Profil konnte nicht aktualisiert werden. Bitte versuchen Sie es erneut.", + "account_information": "Kontoinformationen", + "account_info_description": "Informationen, die nicht über den Selbstbedienungsbereich geändert werden können", + "status": "Status", + "guest_member": "Gastmitglied", + "full_member": "Vollmitglied", + "membership_number": "Mitgliedsnummer", + "date_of_birth": "Geburtsdatum", + "title": "Titel", + "occupation": "Beruf", + "member_since": "Mitglied seit", + "admin_only_fields": "Diese Felder können nur von einem Administrator aktualisiert werden", + "personal_information": "Persönliche Informationen", + "personal_info_description": "Aktualisieren Sie Ihre persönlichen Daten und Kontaktinformationen", + "personal_details": "Persönliche Daten", + "first_name": "Vorname", + "last_name": "Nachname", + "enter_first_name": "Geben Sie Ihren Vornamen ein", + "enter_last_name": "Geben Sie Ihren Nachnamen ein", + "contact_information": "Kontaktinformationen", + "email_address": "E-Mail-Adresse", + "enter_email": "Geben Sie Ihre E-Mail-Adresse ein", + "home_phone": "Festnetz", + "work_phone": "Arbeitstelefon", + "mobile_phone": "Mobiltelefon", + "address": "Adresse", + "street": "Straße", + "street_name": "Straßenname", + "number": "Nummer", + "house_number": "Hausnr.", + "postal_code": "Postleitzahl", + "city": "Stadt", + "cancel": "Abbrechen", + "save_changes": "Änderungen speichern", + "saving": "Speichern...", + "welcome_back": "Willkommen zurück", + "sign_in_description": "Melden Sie sich mit Ihrer E-Mail oder Mitgliedsnummer an", + "email_or_membership": "E-Mail oder Mitgliedsnummer", + "enter_email_or_membership": "Geben Sie Ihre E-Mail oder Mitgliedsnummer ein", + "password": "Passwort", + "enter_password": "Geben Sie Ihr Passwort ein", + "forgot_password": "Passwort vergessen?", + "signing_in": "Anmeldung läuft...", + "sign_in": "Anmelden", + "no_selfservice_access": "Haben Sie keinen Zugang zum Selbstbedienungsportal?", + "contact_admin": "Wenden Sie sich an Ihren Organisationsadministrator.", + "admin_login": "Admin-Anmeldung", + "access_selfservice_portal": "Greifen Sie auf Ihr Selbstbedienungsportal zu", + "hide_password": "Passwort ausblenden", + "show_password": "Passwort anzeigen", + "profile_update": "Profilaktualisierung", + "not_set": "Nicht festgelegt", + "dashboard_title": "Dashboard", + "manage_profile_description": "Verwalten Sie Ihr Profil und greifen Sie auf Selbstbedienungsfunktionen zu", + "member_profile": "Mitgliederprofil", + "current_member_info": "Ihre aktuellen Mitgliederinformationen", + "no_title_set": "Kein Titel festgelegt", + "edit_profile": "Profil bearbeiten", + "quick_actions": "Schnellaktionen", + "common_tasks_shortcuts": "Häufige Aufgaben und Verknüpfungen", + "update_personal_info": "Persönliche Informationen aktualisieren", + "change_password": "Passwort ändern", + "account_status": "Kontostatus", + "your_account_info": "Ihre Kontoinformationen", + "account_type": "Kontotyp", + "self_service_access": "Selbstbedienungszugang", + "enabled": "Aktiviert", + "need_help": "Brauchen Sie Hilfe?", + "contact_support_assistance": "Support kontaktieren oder Hilfe erhalten", + "need_assistance_text": "Wenn Sie Hilfe bei Ihrem Konto benötigen oder Fragen haben, wenden Sie sich bitte an Ihren Organisationsadministrator.", + "contact_support": "Support kontaktieren", + "security_settings": "Sicherheitseinstellungen", + "manage_security_password": "Verwalten Sie die Sicherheit Ihres Kontos und Ihr Passwort", + "update_password_secure": "Aktualisieren Sie Ihr Passwort, um Ihr Konto sicher zu halten", + "current_password": "Aktuelles Passwort", + "enter_current_password": "Aktuelles Passwort eingeben", + "new_password": "Neues Passwort", + "enter_new_password": "Neues Passwort eingeben", + "confirm_new_password": "Neues Passwort bestätigen", + "confirm_new_password_placeholder": "Neues Passwort bestätigen", + "updating": "Aktualisierung läuft...", + "update_password": "Passwort aktualisieren" + } +} diff --git a/messages/en.json b/messages/en.json new file mode 100644 index 0000000..925b7a1 --- /dev/null +++ b/messages/en.json @@ -0,0 +1,546 @@ +{ + "$schema": "https://inlang.com/schema/inlang-message-format", + "example_message": "Hello world {username}", + "user_list_updated": "User list has been updated with your filters.", + "filters_cleared": "All filters have been cleared.", + "delete_user_failed": "Failed to delete user. Please try again.", + "user_deleted_success": "User deleted successfully.", + "user_deleted_named": "{firstName} {lastName} has been deleted.", + "user_created_success": "User created successfully.", + "user_updated_success": "User updated successfully.", + "user_create_failed": "Failed to create user. Please try again.", + "user_update_failed": "Failed to update user. Please try again.", + "user_delete_failed_retry": "Failed to delete user. Please try again.", + "user_id_required": "User ID is required", + "user_not_found": "User not found", + "users_title": "Users", + "users_description": "Manage user accounts and permissions.", + "filter_button": "Filter", + "add_user_button": "Add User", + "user_management_title": "User Management", + "user_management_description": "View and manage all user accounts", + "search_users_placeholder": "Search users...", + "select_label": "Select", + "user_column": "User", + "role_column": "Role", + "status_column": "Status", + "created_column": "Created", + "actions_column": "Actions", + "enabled_status": "Enabled", + "disabled_status": "Disabled", + "view_details": "View details", + "edit_user": "Edit user", + "delete_user": "Delete user", + "no_users_found": "No users found.", + "add_user_title": "Add User", + "edit_user_title": "Edit User", + "create_user_description": "Create a new management user", + "modify_user_description": "Modify user details", + "first_name_label": "First name", + "last_name_label": "Last name", + "username_label": "Username", + "password_label": "Password", + "leave_blank_to_keep": "(leave blank to keep)", + "enabled_label": "Enabled", + "cancel_button": "Cancel", + "create_button": "Create", + "save_button": "Save", + "advanced_filters_title": "Advanced Filters", + "advanced_filters_description": "Filter and sort users to find what you need", + "account_status_label": "Account Status", + "all_status": "All Status", + "enabled": "Enabled", + "disabled": "Disabled", + "role_label": "Role", + "all_roles": "All Roles", + "admin": "Admin", + "user": "User", + "created_date_range_label": "Created Date Range", + "from_label": "From", + "to_label": "To", + "sort_by_label": "Sort By", + "first_name": "First Name", + "last_name": "Last Name", + "created_date": "Created Date", + "ascending": "Ascending", + "descending": "Descending", + "reset_button": "Reset", + "apply_filters_button": "Apply Filters", + "dashboard_title": "Dashboard", + "dashboard_welcome": "Welcome back! Here's what's happening with your application.", + "more_button": "More", + "add_new_button": "Add New", + "total_users": "Total Users", + "revenue": "Revenue", + "growth_rate": "Growth Rate", + "active_sessions": "Active Sessions", + "active_users_this_month": "Active users this month", + "total_revenue_this_month": "Total revenue this month", + "monthly_growth_rate": "Monthly growth rate", + "current_active_sessions": "Current active sessions", + "recent_activity": "Recent Activity", + "created_new_post": "Created new post", + "updated_profile": "Updated profile", + "deleted_comment": "Deleted comment", + "uploaded_image": "Uploaded image", + "changed_settings": "Changed settings", + "welcome_to_sveltekit": "Welcome to SvelteKit", + "visit_docs": "Visit svelte.dev/docs/kit to read the documentation", + "analytics_overview": "Analytics Overview", + "monthly_performance_metrics": "Monthly performance metrics and trends", + "chart_visualization_placeholder": "Chart visualization would go here", + "chart_integration_needed": "Integration with charting library needed", + "latest_user_actions": "Latest user actions and system events", + "view_all_activity": "View All Activity", + "quick_actions": "Quick Actions", + "frequently_used_tasks": "Frequently used administrative tasks", + "manage_users": "Manage Users", + "view_reports": "View Reports", + "system_status": "System Status", + "add_content": "Add Content", + "more_options": "More Options", + "username_required": "Username is required.", + "first_name_required": "First name is required.", + "last_name_required": "Last name is required.", + "password_required": "Password is required.", + "password_min_length": "Password must be at least 8 characters long.", + "sidebar_dashboard": "Dashboard", + "sidebar_users": "Users", + "sidebar_members": "Members", + "sidebar_devices": "Devices", + "sidebar_analytics": "Analytics", + "sidebar_content": "Content", + "sidebar_posts": "Posts", + "sidebar_pages": "Pages", + "sidebar_media": "Media", + "sidebar_database": "Database", + "sidebar_settings": "Settings", + "sidebar_security": "Security", + "sidebar_notifications": "Notifications", + "sidebar_navigation": "Navigation", + "sidebar_system": "System", + "sidebar_admin_panel": "Admin Panel", + "sidebar_management": "Management", + "sidebar_system_online": "System Online", + "invalid_credentials": "Invalid username or password", + "account_disabled": "Account is disabled", + "login_failed": "Login failed", + "settings_title": "Settings", + "settings_description": "Configure your application settings", + "general_settings": "General Settings", + "site_name": "Site Name", + "site_name_placeholder": "Enter site name", + "site_description": "Site Description", + "site_description_placeholder": "Describe your application", + "admin_email": "Admin Email", + "admin_email_placeholder": "admin@example.com", + "timezone": "Timezone", + "timezone_placeholder": "UTC", + "language": "Language", + "language_placeholder": "en", + "security_settings": "Security Settings", + "two_factor_auth": "Two-Factor Authentication", + "session_timeout": "Session Timeout (Minutes)", + "password_complexity": "Password Complexity", + "login_attempts": "Maximum Login Attempts", + "ip_whitelist": "IP Whitelist", + "notification_settings": "Notification Settings", + "email_notifications": "Email Notifications", + "push_notifications": "Push Notifications", + "weekly_reports": "Weekly Reports", + "system_alerts": "System Alerts", + "user_registrations": "User Registrations", + "system_settings": "System Settings", + "maintenance_mode": "Maintenance Mode", + "backup_frequency": "Backup Frequency", + "backup_frequency_placeholder": "daily", + "save_settings": "Save Settings", + "settings_saved": "Settings saved successfully", + "settings_save_error": "Error saving settings", + "confirm_delete": "Confirm Delete", + "confirm_delete_description": "Are you sure you want to delete {firstName} {lastName}? This action cannot be undone.", + "confirm_delete_generic": "Are you sure you want to delete this user? This action cannot be undone.", + "user_details_title": "User Details", + "user_information": "User information", + "no_user_selected": "No user selected", + "edit_user_button": "Edit User", + "close_button": "Close", + "delete_user_button": "Delete User", + "user_role": "Role", + "user_status": "Status", + "created_label": "Created", + "last_updated": "Last Updated", + "user_id_label": "User ID", + "select_all_users": "Select all users", + "selected_count": "{count} of {total} selected", + "select_users_bulk": "Select users for bulk actions", + "bulk_actions": "Bulk Actions", + "actions_for_users": "Actions for {count} users", + "enable_users": "Enable Users", + "disable_users": "Disable Users", + "send_email": "Send Email", + "delete_users": "Delete Users", + "total_users_stat": "Total Users", + "all_registered_users": "All registered users", + "currently_enabled": "Currently enabled", + "accounts_disabled": "Accounts disabled", + "admins_stat": "Admins", + "administrator_accounts": "Administrator accounts", + "device_list_updated": "Device list has been updated with your filters.", + "delete_device_failed": "Failed to delete device. Please try again.", + "device_deleted_success": "Device deleted successfully.", + "device_deleted_named": "{name} has been deleted.", + "device_created_success": "Device created successfully.", + "device_updated_success": "Device updated successfully.", + "device_create_failed": "Failed to create device. Please try again.", + "device_update_failed": "Failed to update device. Please try again.", + "device_delete_failed_retry": "Failed to delete device. Please try again.", + "device_id_required": "Device ID is required", + "device_not_found": "Device not found", + "devices_title": "Devices", + "devices_description": "Manage RFID devices and lock systems.", + "add_device_button": "Add Device", + "total_devices_stat": "Total Devices", + "all_registered_devices": "All registered devices", + "rfid_scanners_stat": "RFID Scanners", + "rfid_scanner_devices": "RFID scanner devices", + "lock_systems_stat": "Lock Systems", + "lock_system_devices": "Lock system devices", + "device_management_title": "Device Management", + "device_management_description": "View and manage all registered devices", + "search_devices_placeholder": "Search devices...", + "device_column": "Device", + "type_column": "Type", + "api_key_column": "API Key", + "last_seen_column": "Last Seen", + "rfid_scanner": "RFID Scanner", + "lock_system": "Lock System", + "edit_device": "Edit device", + "delete_device": "Delete device", + "no_devices_found": "No devices found.", + "add_device_title": "Add Device", + "edit_device_title": "Edit Device", + "create_device_description": "Create a new device", + "modify_device_description": "Modify device details", + "device_name_label": "Device Name", + "device_type_label": "Device Type", + "api_key_label": "API Key (optional)", + "leave_blank_for_auto_generation": "Leave blank for auto-generation", + "api_key_auto_generation_description": "If not provided, an API key will be generated automatically", + "confirm_delete_device_description": "Are you sure you want to delete \"{name}\"? This action cannot be undone.", + "delete_device_button": "Delete Device", + "device_details_title": "Device Details", + "device_information": "View device information", + "name_label": "Name", + "type_label": "Type", + "last_seen_label": "Last Seen", + "updated_label": "Updated", + "no_device_selected": "No device selected", + "edit_device_button": "Edit Device", + "advanced_filters_device_description": "Filter devices by type and other criteria", + "all_types": "All Types", + "rfid_card_list_updated": "RFID card list has been updated with your filters.", + "delete_rfid_card_failed": "Failed to delete RFID card. Please try again.", + "rfid_card_deleted_success": "RFID card deleted successfully.", + "rfid_card_deleted_named": "RFID card {rfidId} has been deleted.", + "rfid_card_created_success": "RFID card created successfully.", + "rfid_card_updated_success": "RFID card updated successfully.", + "rfid_card_create_failed": "Failed to create RFID card. Please try again.", + "rfid_card_update_failed": "Failed to update RFID card. Please try again.", + "rfid_card_delete_failed_retry": "Failed to delete RFID card. Please try again.", + "rfid_card_id_required": "RFID card ID is required", + "rfid_card_not_found": "RFID card not found", + "rfid_cards_title": "RFID Cards", + "rfid_cards_description": "Manage RFID cards and their assignments.", + "add_rfid_card_button": "Add RFID Card", + "total_rfid_cards_stat": "Total RFID Cards", + "all_registered_rfid_cards": "All registered RFID cards", + "new_cards_stat": "New Cards", + "new_rfid_cards": "New RFID cards", + "engraved_cards_stat": "Engraved Cards", + "engraved_rfid_cards": "Engraved RFID cards", + "rfid_card_management_title": "RFID Card Management", + "rfid_card_management_description": "View and manage all RFID cards", + "search_rfid_cards_placeholder": "Search RFID cards...", + "rfid_id_column": "RFID ID", + "assigned_member_column": "Assigned Member", + "active_status": "Active", + "inactive_status": "Inactive", + "rfid_card_status_label": "Status", + "rfid_card_status_new": "New", + "rfid_card_status_engraved": "Engraved", + "rfid_card_status_lost": "Lost", + "rfid_card_status_disposed": "Disposed", + "assign_rfid_card_title": "Assign RFID Card", + "assign_rfid_card_description": "Assign this RFID card to a member", + "unassign_rfid_card_title": "Unassign RFID Card", + "unassign_rfid_card_description": "Remove the assignment of this RFID card from its current member", + "assign_card_button": "Assign Card", + "unassign_card_button": "Unassign Card", + "select_member_label": "Select Member", + "no_members_available": "No members available", + "rfid_card_assigned_success": "RFID card assigned successfully", + "rfid_card_unassigned_success": "RFID card unassigned successfully", + "rfid_card_assign_failed": "Failed to assign RFID card", + "rfid_card_unassign_failed": "Failed to unassign RFID card", + "assigned_member_label": "Assigned Member", + "select_member_placeholder": "Select a member", + "unassigned": "Unassigned", + "active_rfid_cards_stat": "Active RFID Cards", + "active_rfid_card_count": "Active RFID card count", + "inactive_rfid_cards_stat": "Inactive RFID Cards", + "inactive_rfid_card_count": "Inactive RFID card count", + "assigned_rfid_cards_stat": "Assigned RFID Cards", + "assigned_rfid_card_count": "Assigned RFID card count", + "unassigned_rfid_cards_stat": "Unassigned RFID Cards", + "unassigned_rfid_card_count": "Unassigned RFID card count", + "sidebar_rfid_cards": "RFID Cards", + "sidebar_rfid_card_assignments": "Assignments", + "sidebar_logs": "Logs", + "sidebar_system_logs": "System Logs", + "sidebar_access_logs": "Access Logs", + "rfid_card_assignments_title": "RFID Card Assignments", + "access_logs_title": "Access Logs", + "access_logs_description": "View access logs for RFID system", + "accessed_at_column": "Accessed At", + "search_access_logs_placeholder": "Search access logs...", + "no_access_logs_found": "No access logs found.", + "advanced_filters_access_logs_description": "Filter access logs by various criteria", + "from_date_label": "From Date", + "to_date_label": "To Date", + "rfid_card_assignments_description": "Manage RFID card assignments to members", + "rfid_card_assignments_table_title": "RFID Card Assignments", + "rfid_card_assignments_table_description": "View and manage RFID card assignments to members", + "rfid_card_column": "RFID Card", + "assignment_status_column": "Status", + "issued_at_column": "Issued At", + "returned_at_column": "Returned At", + "assigned_status": "Assigned", + "returned_status": "Returned", + "unknown_member": "Unknown Member", + "unknown_card": "Unknown", + "no_assignments_found": "No assignments found", + "edit_rfid_card": "Edit RFID card", + "delete_rfid_card": "Delete RFID card", + "no_rfid_cards_found": "No RFID cards found.", + "add_rfid_card_title": "Add RFID Card", + "edit_rfid_card_title": "Edit RFID Card", + "create_rfid_card_description": "Create a new RFID card", + "modify_rfid_card_description": "Modify RFID card details", + "rfid_id_label": "RFID ID", + "card_status_label": "Status", + "confirm_delete_rfid_card_description": "Are you sure you want to delete RFID card \"{rfidId}\"? This action cannot be undone.", + "delete_rfid_card_button": "Delete RFID Card", + "rfid_card_details_title": "RFID Card Details", + "rfid_card_information": "View RFID card information", + "no_rfid_card_selected": "No RFID card selected", + "edit_rfid_card_button": "Edit RFID Card", + "advanced_filters_rfid_card_description": "Filter RFID cards by status and other criteria", + "all_statuses": "All Statuses", + "member_list_updated": "Member list has been updated with your filters.", + "delete_member_failed": "Failed to delete member. Please try again.", + "member_deleted_success": "Member deleted successfully.", + "member_deleted_named": "{name} has been deleted.", + "member_created_success": "Member created successfully.", + "member_updated_success": "Member updated successfully.", + "member_create_failed": "Failed to create member. Please try again.", + "member_update_failed": "Failed to update member. Please try again.", + "member_delete_failed_retry": "Failed to delete member. Please try again.", + "member_id_required": "Member ID is required", + "member_not_found": "Member not found", + "form_check_errors": "Please check the form for errors", + "members_title": "Members", + "members_description": "Manage club members and their information.", + "add_member_button": "Add Member", + "total_members_stat": "Total Members", + "all_registered_members": "All registered members", + "guest_accounts_stat": "Guest Accounts", + "guest_member_accounts": "Guest member accounts", + "regular_members_stat": "Regular Members", + "regular_member_accounts": "Regular member accounts", + "member_management_title": "Member Management", + "member_management_description": "View and manage all club members", + "search_members_placeholder": "Search members...", + "member_column": "Member", + "membership_number_column": "Membership #", + "email_column": "Email", + "phone_column": "Phone", + "edit_member": "Edit member", + "delete_member": "Delete member", + "no_members_found": "No members found.", + "add_member_title": "Add Member", + "edit_member_title": "Edit Member", + "create_member_description": "Create a new member", + "modify_member_description": "Modify member details", + "membership_number_label": "Membership Number", + "title_label": "Title", + "birth_date_label": "Birth Date", + "occupation_label": "Occupation", + "street_label": "Street", + "house_number_label": "House Number", + "postal_code_label": "Postal Code", + "city_label": "City", + "phone_home_label": "Phone (Home)", + "phone_work_label": "Phone (Work)", + "phone_mobile_label": "Phone (Mobile)", + "guest_account_label": "Guest Account", + "joined_at_label": "Joined At", + "free_text_function_label": "Function/Role", + "free_text_comment_label": "Comments", + "confirm_delete_member_description": "Are you sure you want to delete \"{name}\"? This action cannot be undone.", + "delete_member_button": "Delete Member", + "member_details_title": "Member Details", + "member_information": "View member information", + "no_member_selected": "No member selected", + "edit_member_button": "Edit Member", + "advanced_filters_member_description": "Filter members by various criteria", + "all_members": "All Members", + "admin_login_title": "Admin Login", + "admin_login_subtitle": "Access the RFID Control Panel administration", + "sign_in": "Sign In", + "enter_credentials": "Enter your credentials to access the admin panel", + "username": "Username", + "password": "Password", + "enter_username": "Enter your username", + "enter_password": "Enter your password", + "signing_in": "Signing in", + "secure_admin_access": "Secure admin access required", + "all_rights_reserved": "All rights reserved", + "zod": { + "duplicate_membership_number": "Membership number is already in use.", + "duplicate_email": "Email address is already registered.", + "field_required": "This field is required.", + "username_required": "Username is required.", + "first_name_required": "First name is required.", + "last_name_required": "Last name is required.", + "password_required": "Password is required.", + "email_required": "Email is required.", + "invalid_field": "Invalid value.", + "invalid_email": "Invalid email address.", + "invalid_username": "Invalid username.", + "password_min_length": "Password must be at least 8 characters long.", + "string_too_short": "Text is too short (minimum {min} characters).", + "string_too_long": "Text is too long (maximum {max} characters).", + "number_too_small": "Number is too small (minimum {min}).", + "number_too_big": "Number is too big (maximum {max}).", + "invalid_date": "Invalid date.", + "invalid_string": "Invalid string value.", + "array_too_short": "Array must contain at least {min} items.", + "array_too_long": "Array must contain at most {max} items.", + "value_too_small": "Value is too small.", + "value_too_big": "Value is too big.", + "invalid_url": "Invalid URL format.", + "invalid_uuid": "Invalid UUID format.", + "invalid_cuid": "Invalid CUID format.", + "invalid_ulid": "Invalid ULID format.", + "invalid_format": "Invalid format.", + "not_multiple_of": "Value must be a multiple of {multiple}.", + "unrecognized_keys": "Unrecognized keys: {keys}.", + "invalid_union": "Invalid union value.", + "invalid_key": "Invalid key: {key}.", + "invalid_element": "Invalid array element.", + "invalid_value": "Invalid value.", + "passwords_do_not_match": "Passwords do not match.", + "password_mismatch": "Passwords do not match.", + "username_already_exists": "This username is already taken.", + "email_already_exists": "This email address is already registered.", + "identifier_required": "Email or membership number is required.", + "token_required": "Reset token is required.", + "current_password_required": "Current password is required.", + "invalid_credentials": "Invalid email/membership number or password.", + "account_disabled": "Account is disabled.", + "self_service_disabled": "Self-service access is not enabled for this account.", + "birth_date_in_future": "Date of birth cannot be in the future." + }, + "selfservice": { + "member_portal": "Member Portal", + "dashboard": "Dashboard", + "my_profile": "My Profile", + "security": "Security", + "profile_title": "My Profile", + "profile_description": "Manage your personal information and contact details", + "profile_updated_success": "Profile updated successfully!", + "profile_update_failed": "Failed to update profile. Please try again.", + "account_information": "Account Information", + "account_info_description": "Information that cannot be changed through self-service", + "status": "Status", + "guest_member": "Guest Member", + "full_member": "Full Member", + "membership_number": "Membership Number", + "date_of_birth": "Date of Birth", + "title": "Title", + "occupation": "Occupation", + "member_since": "Member Since", + "admin_only_fields": "These fields can only be updated by an administrator", + "personal_information": "Personal Information", + "personal_info_description": "Update your personal details and contact information", + "personal_details": "Personal Details", + "first_name": "First Name", + "last_name": "Last Name", + "enter_first_name": "Enter your first name", + "enter_last_name": "Enter your last name", + "contact_information": "Contact Information", + "email_address": "Email Address", + "enter_email": "Enter your email address", + "home_phone": "Home Phone", + "work_phone": "Work Phone", + "mobile_phone": "Mobile Phone", + "address": "Address", + "street": "Street", + "street_name": "Street name", + "number": "Number", + "house_number": "House #", + "postal_code": "Postal Code", + "city": "City", + "cancel": "Cancel", + "save_changes": "Save Changes", + "saving": "Saving...", + "welcome_back": "Welcome Back", + "sign_in_description": "Sign in with your email or membership number", + "email_or_membership": "Email or Membership Number", + "enter_email_or_membership": "Enter your email or membership number", + "password": "Password", + "enter_password": "Enter your password", + "forgot_password": "Forgot your password?", + "signing_in": "Signing in...", + "sign_in": "Sign In", + "no_selfservice_access": "Don't have access to the self-service portal?", + "contact_admin": "Contact your organization administrator.", + "admin_login": "Admin Login", + "access_selfservice_portal": "Access your self-service portal", + "hide_password": "Hide password", + "show_password": "Show password", + "profile_update": "Profile Update", + "not_set": "Not set", + "dashboard_title": "Dashboard", + "manage_profile_description": "Manage your profile and access self-service features", + "member_profile": "Member Profile", + "current_member_info": "Your current member information", + "no_title_set": "No title set", + "edit_profile": "Edit Profile", + "quick_actions": "Quick Actions", + "common_tasks_shortcuts": "Common tasks and shortcuts", + "update_personal_info": "Update Personal Info", + "change_password": "Change Password", + "account_status": "Account Status", + "your_account_info": "Your account information", + "account_type": "Account Type", + "self_service_access": "Self-Service Access", + "enabled": "Enabled", + "need_help": "Need Help?", + "contact_support_assistance": "Contact support or get assistance", + "need_assistance_text": "If you need assistance with your account or have questions, please contact your organization administrator.", + "contact_support": "Contact Support", + "security_settings": "Security Settings", + "manage_security_password": "Manage your account security and password", + "update_password_secure": "Update your password to keep your account secure", + "current_password": "Current Password", + "enter_current_password": "Enter current password", + "new_password": "New Password", + "enter_new_password": "Enter new password", + "confirm_new_password": "Confirm New Password", + "confirm_new_password_placeholder": "Confirm new password", + "updating": "Updating...", + "update_password": "Update Password" + } +} diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..7b8c4d8 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,8458 @@ +{ + "name": "rfid-cp", + "version": "0.0.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "rfid-cp", + "version": "0.0.1", + "dependencies": { + "@orpc/client": "^1.8.9", + "@orpc/json-schema": "^1.8.9", + "@orpc/openapi": "^1.8.9", + "@orpc/server": "^1.8.9", + "@orpc/tanstack-query": "^1.8.9", + "@orpc/zod": "^1.8.9", + "@tanstack/svelte-query": "^5.89.0", + "argon2": "^0.44.0", + "d3-scale": "^4.0.2", + "drizzle-zod": "^0.8.3", + "jose": "^6.1.0", + "postgres": "^3.4.7", + "sveltekit-superforms": "^2.27.1", + "ts-proto": "^2.7.7", + "uuid": "^13.0.0", + "zod": "4.0.10" + }, + "devDependencies": { + "@eslint/compat": "^1.2.5", + "@eslint/js": "^9.22.0", + "@inlang/cli": "^3.0.0", + "@inlang/paraglide-js": "2.3.2", + "@internationalized/date": "^3.9.0", + "@lucide/svelte": "^0.515.0", + "@sveltejs/adapter-node": "^5.2.12", + "@sveltejs/kit": "^2.22.0", + "@sveltejs/vite-plugin-svelte": "^6.0.0", + "@tailwindcss/forms": "^0.5.9", + "@tailwindcss/typography": "^0.5.15", + "@tailwindcss/vite": "^4.0.0", + "@tanstack/table-core": "^8.21.3", + "@types/argon2": "^0.14.1", + "@types/better-sqlite3": "^7.6.12", + "@types/d3-scale": "^4.0.9", + "@types/node": "^22", + "bits-ui": "^2.11.0", + "clsx": "^2.1.1", + "dotenv": "^17.2.2", + "drizzle-kit": "^0.30.2", + "drizzle-orm": "^0.40.0", + "eslint": "^9.36.0", + "eslint-config-prettier": "^10.0.1", + "eslint-plugin-svelte": "^3.12.4", + "eslint-plugin-unused-imports": "^4.2.0", + "formsnap": "^2.0.1", + "globals": "^16.0.0", + "layerchart": "^2.0.0-next.39", + "mdsvex": "^0.12.3", + "mode-watcher": "^1.1.0", + "prettier": "^3.6.2", + "prettier-plugin-svelte": "^3.4.0", + "prettier-plugin-tailwindcss": "^0.6.11", + "svelte": "^5.0.0", + "svelte-check": "^4.0.0", + "svelte-sonner": "^1.0.5", + "tailwind-merge": "^3.3.1", + "tailwind-variants": "^1.0.0", + "tailwindcss": "^4.0.0", + "tsx": "^4.20.5", + "tw-animate-css": "^1.3.8", + "typescript": "^5.0.0", + "typescript-eslint": "^8.20.0", + "vite": "^7.0.4" + } + }, + "node_modules/@ark/schema": { + "version": "0.49.0", + "resolved": "https://registry.npmjs.org/@ark/schema/-/schema-0.49.0.tgz", + "integrity": "sha512-GphZBLpW72iS0v4YkeUtV3YIno35Gimd7+ezbPO9GwEi9kzdUrPVjvf6aXSBAfHikaFc/9pqZOpv3pOXnC71tw==", + "license": "MIT", + "optional": true, + "dependencies": { + "@ark/util": "0.49.0" + } + }, + "node_modules/@ark/util": { + "version": "0.49.0", + "resolved": "https://registry.npmjs.org/@ark/util/-/util-0.49.0.tgz", + "integrity": "sha512-/BtnX7oCjNkxi2vi6y1399b+9xd1jnCrDYhZ61f0a+3X8x8DxlK52VgEEzyuC2UQMPACIfYrmHkhD3lGt2GaMA==", + "license": "MIT", + "optional": true + }, + "node_modules/@babel/runtime": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz", + "integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bufbuild/protobuf": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-2.9.0.tgz", + "integrity": "sha512-rnJenoStJ8nvmt9Gzye8nkYd6V22xUAnu4086ER7h1zJ508vStko4pMvDeQ446ilDTFpV5wnoc5YS7XvMwwMqA==", + "license": "(Apache-2.0 AND BSD-3-Clause)" + }, + "node_modules/@dagrejs/dagre": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@dagrejs/dagre/-/dagre-1.1.5.tgz", + "integrity": "sha512-Ghgrh08s12DCL5SeiR6AoyE80mQELTWhJBRmXfFoqDiFkR458vPEdgTbbjA0T+9ETNxUblnD0QW55tfdvi5pjQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@dagrejs/graphlib": "2.2.4" + } + }, + "node_modules/@dagrejs/graphlib": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/@dagrejs/graphlib/-/graphlib-2.2.4.tgz", + "integrity": "sha512-mepCf/e9+SKYy1d02/UkvSy6+6MoyXhVxP8lLDfA7BPE1X1d4dR0sZznmbM8/XVJ1GPM+Svnx7Xj6ZweByWUkw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">17.0.0" + } + }, + "node_modules/@drizzle-team/brocli": { + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/@drizzle-team/brocli/-/brocli-0.10.2.tgz", + "integrity": "sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/@epic-web/invariant": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@epic-web/invariant/-/invariant-1.0.0.tgz", + "integrity": "sha512-lrTPqgvfFQtR/eY/qkIzp98OGdNJu0m5ji3q/nJI8v3SXkRKEnWiOxMmbvcSoAIzv/cGiuvRy57k4suKQSAdwA==", + "license": "MIT" + }, + "node_modules/@esbuild-kit/core-utils": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/@esbuild-kit/core-utils/-/core-utils-3.3.2.tgz", + "integrity": "sha512-sPRAnw9CdSsRmEtnsl2WXWdyquogVpB3yZ3dgwJfe8zrOzTsV7cJvmwrKVa+0ma5BoiGJ+BoqkMvawbayKUsqQ==", + "deprecated": "Merged into tsx: https://tsx.is", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.18.20", + "source-map-support": "^0.5.21" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/android-arm": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz", + "integrity": "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/android-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz", + "integrity": "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/android-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.20.tgz", + "integrity": "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/darwin-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz", + "integrity": "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/darwin-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz", + "integrity": "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/freebsd-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz", + "integrity": "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/freebsd-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz", + "integrity": "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-arm": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz", + "integrity": "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz", + "integrity": "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-ia32": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz", + "integrity": "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-loong64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz", + "integrity": "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-mips64el": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz", + "integrity": "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-ppc64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz", + "integrity": "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-riscv64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz", + "integrity": "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-s390x": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz", + "integrity": "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz", + "integrity": "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/netbsd-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz", + "integrity": "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/openbsd-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz", + "integrity": "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/sunos-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz", + "integrity": "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/win32-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz", + "integrity": "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/win32-ia32": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz", + "integrity": "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/win32-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz", + "integrity": "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/esbuild": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz", + "integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/android-arm": "0.18.20", + "@esbuild/android-arm64": "0.18.20", + "@esbuild/android-x64": "0.18.20", + "@esbuild/darwin-arm64": "0.18.20", + "@esbuild/darwin-x64": "0.18.20", + "@esbuild/freebsd-arm64": "0.18.20", + "@esbuild/freebsd-x64": "0.18.20", + "@esbuild/linux-arm": "0.18.20", + "@esbuild/linux-arm64": "0.18.20", + "@esbuild/linux-ia32": "0.18.20", + "@esbuild/linux-loong64": "0.18.20", + "@esbuild/linux-mips64el": "0.18.20", + "@esbuild/linux-ppc64": "0.18.20", + "@esbuild/linux-riscv64": "0.18.20", + "@esbuild/linux-s390x": "0.18.20", + "@esbuild/linux-x64": "0.18.20", + "@esbuild/netbsd-x64": "0.18.20", + "@esbuild/openbsd-x64": "0.18.20", + "@esbuild/sunos-x64": "0.18.20", + "@esbuild/win32-arm64": "0.18.20", + "@esbuild/win32-ia32": "0.18.20", + "@esbuild/win32-x64": "0.18.20" + } + }, + "node_modules/@esbuild-kit/esm-loader": { + "version": "2.6.5", + "resolved": "https://registry.npmjs.org/@esbuild-kit/esm-loader/-/esm-loader-2.6.5.tgz", + "integrity": "sha512-FxEMIkJKnodyA1OaCUoEvbYRkoZlLZ4d/eXFu9Fh8CbBBgP5EmZxrfTRyN0qpXZ4vOvqnE5YdRdcrmUUXuU+dA==", + "deprecated": "Merged into tsx: https://tsx.is", + "dev": true, + "license": "MIT", + "dependencies": { + "@esbuild-kit/core-utils": "^3.3.2", + "get-tsconfig": "^4.7.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.19.12.tgz", + "integrity": "sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.12.tgz", + "integrity": "sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.12.tgz", + "integrity": "sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.12.tgz", + "integrity": "sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.12.tgz", + "integrity": "sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.12.tgz", + "integrity": "sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.12.tgz", + "integrity": "sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.12.tgz", + "integrity": "sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.12.tgz", + "integrity": "sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.12.tgz", + "integrity": "sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.12.tgz", + "integrity": "sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.12.tgz", + "integrity": "sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.12.tgz", + "integrity": "sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w==", + "cpu": [ + "mips64el" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.12.tgz", + "integrity": "sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.12.tgz", + "integrity": "sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.12.tgz", + "integrity": "sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.12.tgz", + "integrity": "sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.10.tgz", + "integrity": "sha512-AKQM3gfYfSW8XRk8DdMCzaLUFB15dTrZfnX8WXQoOUpUBQ+NaAFCP1kPS/ykbbGYz7rxn0WS48/81l9hFl3u4A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.12.tgz", + "integrity": "sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.10.tgz", + "integrity": "sha512-5Se0VM9Wtq797YFn+dLimf2Zx6McttsH2olUBsDml+lm0GOCRVebRWUvDtkY4BWYv/3NgzS8b/UM3jQNh5hYyw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.12.tgz", + "integrity": "sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.10.tgz", + "integrity": "sha512-AVTSBhTX8Y/Fz6OmIVBip9tJzZEUcY8WLh7I59+upa5/GPhh2/aM6bvOMQySspnCCHvFi79kMtdJS1w0DXAeag==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.12.tgz", + "integrity": "sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.12.tgz", + "integrity": "sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.12.tgz", + "integrity": "sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.12.tgz", + "integrity": "sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz", + "integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", + "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/compat": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@eslint/compat/-/compat-1.4.0.tgz", + "integrity": "sha512-DEzm5dKeDBPm3r08Ixli/0cmxr8LkRdwxMRUIJBlSCpAwSrvFEJpVBzV+66JhDxiaqKxnRzCXhtiMiczF7Hglg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.16.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "peerDependencies": { + "eslint": "^8.40 || 9" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/@eslint/config-array": { + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.0.tgz", + "integrity": "sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.6", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.3.1.tgz", + "integrity": "sha512-xR93k9WhrDYpXHORXpxVL5oHj3Era7wo6k/Wd8/IsQNnZUTzkGS29lyn3nAT05v6ltUuTFVCCYDEGfy2Or/sPA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.16.0.tgz", + "integrity": "sha512-nmC8/totwobIiFcGkDza3GIKfAw1+hLiYVrh3I1nIomQ8PEr5cxg34jnkmGawul/ep52wGRAcyeDCNtWKSOj4Q==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", + "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/js": { + "version": "9.36.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.36.0.tgz", + "integrity": "sha512-uhCbYtYynH30iZErszX78U+nR3pJU3RHGQ57NXy5QupD4SBVwDeU8TNBy+MjMngc1UyIW9noKqsRqfjQTBU2dw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz", + "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.5.tgz", + "integrity": "sha512-Z5kJ+wU3oA7MMIqVR9tyZRtjYPr4OC004Q4Rw7pgOKUOKkJfZ3O24nz3WYfGRpMDNmcOi3TwQOmgm7B7Tpii0w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.15.2", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit/node_modules/@eslint/core": { + "version": "0.15.2", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.2.tgz", + "integrity": "sha512-78Md3/Rrxh83gCxoUc0EiciuOHsIITzLy53m3d9UyiW8y9Dj2D29FeETqyKA+BRK76tnTp6RXWb3pCay8Oyomg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@exodus/schemasafe": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@exodus/schemasafe/-/schemasafe-1.3.0.tgz", + "integrity": "sha512-5Aap/GaRupgNx/feGBwLLTVv8OQFfv3pq2lPRzPg9R+IOBnDgghTGW7l7EuVXOvg5cc/xSAlRW8rBrjIC3Nvqw==", + "license": "MIT", + "optional": true + }, + "node_modules/@floating-ui/core": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.3.tgz", + "integrity": "sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@floating-ui/utils": "^0.2.10" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.4.tgz", + "integrity": "sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@floating-ui/core": "^1.7.3", + "@floating-ui/utils": "^0.2.10" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.10", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.10.tgz", + "integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@gcornut/valibot-json-schema": { + "version": "0.42.0", + "resolved": "https://registry.npmjs.org/@gcornut/valibot-json-schema/-/valibot-json-schema-0.42.0.tgz", + "integrity": "sha512-4Et4AN6wmqeA0PfU5Clkv/IS27wiefsWf6TemAZrb75uzkClYEFavim7SboeKwbll9Nbsn2Iv0LT/HS5H7orZg==", + "optional": true, + "dependencies": { + "valibot": "~0.42.0" + }, + "bin": { + "valibot-json-schema": "bin/index.js" + }, + "optionalDependencies": { + "@types/json-schema": ">= 7.0.14", + "esbuild-runner": ">= 2.2.2" + } + }, + "node_modules/@gcornut/valibot-json-schema/node_modules/valibot": { + "version": "0.42.1", + "resolved": "https://registry.npmjs.org/valibot/-/valibot-0.42.1.tgz", + "integrity": "sha512-3keXV29Ar5b//Hqi4MbSdV7lfVp6zuYLZuA9V1PvQUsXqogr+u5lvLPLk3A4f74VUXDnf/JfWMN6sB+koJ/FFw==", + "license": "MIT", + "optional": true, + "peerDependencies": { + "typescript": ">=5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@hapi/hoek": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", + "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==", + "license": "BSD-3-Clause", + "optional": true + }, + "node_modules/@hapi/topo": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.1.0.tgz", + "integrity": "sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==", + "license": "BSD-3-Clause", + "optional": true, + "dependencies": { + "@hapi/hoek": "^9.0.0" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.7", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", + "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@inlang/cli": { + "version": "3.0.12", + "resolved": "https://registry.npmjs.org/@inlang/cli/-/cli-3.0.12.tgz", + "integrity": "sha512-0FZJtgrt1Ol4iwKtA0VICrsHcA3stWTSP2jq8mpTgjTlFU63gr5JcyFljUT8Dp5nDIJYmdh3kJ0a8PhW0X8clQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@inlang/sdk": "2.4.9", + "esbuild-wasm": "^0.19.2" + }, + "bin": { + "inlang": "bin/run.js" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@inlang/paraglide-js": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/@inlang/paraglide-js/-/paraglide-js-2.3.2.tgz", + "integrity": "sha512-mF7ku1AaXQxa6fnbBczXiEAM7lxhYzaAH7FnDVvAbpzNtgRJESi11KN0bzpCH0YhsKLdimFrQlzjaSVlo9Uh+Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inlang/recommend-sherlock": "0.2.1", + "@inlang/sdk": "2.4.9", + "commander": "11.1.0", + "consola": "3.4.0", + "json5": "2.2.3", + "unplugin": "^2.1.2", + "urlpattern-polyfill": "^10.0.0" + }, + "bin": { + "paraglide-js": "bin/run.js" + } + }, + "node_modules/@inlang/recommend-sherlock": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@inlang/recommend-sherlock/-/recommend-sherlock-0.2.1.tgz", + "integrity": "sha512-ckv8HvHy/iTqaVAEKrr+gnl+p3XFNwe5D2+6w6wJk2ORV2XkcRkKOJ/XsTUJbPSiyi4PI+p+T3bqbmNx/rDUlg==", + "dev": true, + "license": "MIT", + "dependencies": { + "comment-json": "^4.2.3" + } + }, + "node_modules/@inlang/sdk": { + "version": "2.4.9", + "resolved": "https://registry.npmjs.org/@inlang/sdk/-/sdk-2.4.9.tgz", + "integrity": "sha512-cvz/C1rF5WBxzHbEoiBoI6Sz6q6M+TdxfWkEGBYTD77opY8i8WN01prUWXEM87GPF4SZcyIySez9U0Ccm12oFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@lix-js/sdk": "0.4.7", + "@sinclair/typebox": "^0.31.17", + "kysely": "^0.27.4", + "sqlite-wasm-kysely": "0.3.0", + "uuid": "^10.0.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@inlang/sdk/node_modules/uuid": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz", + "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==", + "dev": true, + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/@internationalized/date": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/@internationalized/date/-/date-3.9.0.tgz", + "integrity": "sha512-yaN3brAnHRD+4KyyOsJyk49XUvj2wtbNACSqg0bz3u8t2VuzhC8Q5dfRnrSxjnnbDb+ienBnkn1TzQfE154vyg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@swc/helpers": "^0.5.0" + } + }, + "node_modules/@isaacs/fs-minipass": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", + "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.4" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@layerstack/svelte-actions": { + "version": "1.0.1-next.14", + "resolved": "https://registry.npmjs.org/@layerstack/svelte-actions/-/svelte-actions-1.0.1-next.14.tgz", + "integrity": "sha512-MPBmVaB+GfNHvBkg5nJkPG18smoXKvsvJRpsdWnrUBfca+TieZLoaEzNxDH+9LG11dIXP9gghsXt1mUqbbyAsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@floating-ui/dom": "^1.7.0", + "@layerstack/utils": "2.0.0-next.14", + "d3-scale": "^4.0.2" + } + }, + "node_modules/@layerstack/svelte-state": { + "version": "0.1.0-next.19", + "resolved": "https://registry.npmjs.org/@layerstack/svelte-state/-/svelte-state-0.1.0-next.19.tgz", + "integrity": "sha512-yCYoQAIbeP8y1xmOB/r0+UundgP4JFnpNURgMki+26TotzoqrZ5oLpHvhPSVm60ks+buR3ebDBTeUFdHzxwzQQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@layerstack/utils": "2.0.0-next.14" + } + }, + "node_modules/@layerstack/tailwind": { + "version": "2.0.0-next.17", + "resolved": "https://registry.npmjs.org/@layerstack/tailwind/-/tailwind-2.0.0-next.17.tgz", + "integrity": "sha512-ZSn6ouqpnzB6DKzSKLVwrUBOQsrzpDA/By2/ba9ApxgTGnaD1nyqNwrvmZ+kswdAwB4YnrGEAE4VZkKrB2+DaQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@layerstack/utils": "^2.0.0-next.14", + "clsx": "^2.1.1", + "d3-array": "^3.2.4", + "lodash-es": "^4.17.21", + "tailwind-merge": "^3.2.0" + } + }, + "node_modules/@layerstack/utils": { + "version": "2.0.0-next.14", + "resolved": "https://registry.npmjs.org/@layerstack/utils/-/utils-2.0.0-next.14.tgz", + "integrity": "sha512-1I2CS0Cwgs53W35qVg1eBdYhB/CiPvL3s0XE61b8jWkTHxgjBF65yYNgXjW74kv7WI7GsJcWMNBufPd0rnu9kA==", + "dev": true, + "license": "MIT", + "dependencies": { + "d3-array": "^3.2.4", + "d3-time": "^3.1.0", + "d3-time-format": "^4.1.0", + "lodash-es": "^4.17.21" + } + }, + "node_modules/@lix-js/sdk": { + "version": "0.4.7", + "resolved": "https://registry.npmjs.org/@lix-js/sdk/-/sdk-0.4.7.tgz", + "integrity": "sha512-pRbW+joG12L0ULfMiWYosIW0plmW4AsUdiPCp+Z8rAsElJ+wJ6in58zhD3UwUcd4BNcpldEGjg6PdA7e0RgsDQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@lix-js/server-protocol-schema": "0.1.1", + "dedent": "1.5.1", + "human-id": "^4.1.1", + "js-sha256": "^0.11.0", + "kysely": "^0.27.4", + "sqlite-wasm-kysely": "0.3.0", + "uuid": "^10.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@lix-js/sdk/node_modules/uuid": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz", + "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==", + "dev": true, + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/@lix-js/server-protocol-schema": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@lix-js/server-protocol-schema/-/server-protocol-schema-0.1.1.tgz", + "integrity": "sha512-jBeALB6prAbtr5q4vTuxnRZZv1M2rKe8iNqRQhFJ4Tv7150unEa0vKyz0hs8Gl3fUGsWaNJBh3J8++fpbrpRBQ==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/@lucide/svelte": { + "version": "0.515.0", + "resolved": "https://registry.npmjs.org/@lucide/svelte/-/svelte-0.515.0.tgz", + "integrity": "sha512-CEAyqcZmNBfYzVgaRmK2RFJP5tnbXxekRyDk0XX/eZQRfsJmkDvmQwXNX8C869BgNeryzmrRyjHhUL6g9ZOHNA==", + "dev": true, + "license": "ISC", + "peerDependencies": { + "svelte": "^5" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@orpc/client": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@orpc/client/-/client-1.9.0.tgz", + "integrity": "sha512-EkSzVSmXAUZWDCsT/n+lmD1ghf+/rnb3sPR8+Lr42d1YPt7OL7JciSMGt0pTOaVYjX1VLUMZ6i/FtnPDG1A2og==", + "license": "MIT", + "dependencies": { + "@orpc/shared": "1.9.0", + "@orpc/standard-server": "1.9.0", + "@orpc/standard-server-fetch": "1.9.0", + "@orpc/standard-server-peer": "1.9.0" + } + }, + "node_modules/@orpc/contract": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@orpc/contract/-/contract-1.9.0.tgz", + "integrity": "sha512-MfhQMWEZtHUI/Xz0AdOjAUHz2uH8qxS+XhOvCaPScLw7oyezhxDwQrXE30PTMKrb5IQQvMrzKPWvIvxgs7bQWQ==", + "license": "MIT", + "dependencies": { + "@orpc/client": "1.9.0", + "@orpc/shared": "1.9.0", + "@standard-schema/spec": "^1.0.0", + "openapi-types": "^12.1.3" + } + }, + "node_modules/@orpc/interop": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@orpc/interop/-/interop-1.9.0.tgz", + "integrity": "sha512-BDOJggDMWOIqHi0r4YUH4TFUfad6zreP0cFgsjkLcMwYaIzgHINaA2Tzf21lLims/gksL8qsnkX8NCYZeGknuA==", + "license": "MIT" + }, + "node_modules/@orpc/json-schema": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@orpc/json-schema/-/json-schema-1.9.0.tgz", + "integrity": "sha512-d3QCbmMPSWjjt9FMAKHVVD7PHqQwNZ8z6rMTFep7+hA48zNfHEnexdzj1ee/rIg+yU/u/ihauVqEMl7eOZ8V6Q==", + "license": "MIT", + "dependencies": { + "@orpc/contract": "1.9.0", + "@orpc/interop": "1.9.0", + "@orpc/openapi": "1.9.0", + "@orpc/server": "1.9.0", + "@orpc/shared": "1.9.0" + } + }, + "node_modules/@orpc/openapi": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@orpc/openapi/-/openapi-1.9.0.tgz", + "integrity": "sha512-mdIyE6ciHymBigsNrSu+OHODa8GCK4ZKObMqe/p9hghMuf877eG9t7i2qNK//jiBZQX8aZdyoyPNRO+ecke3DQ==", + "license": "MIT", + "dependencies": { + "@orpc/client": "1.9.0", + "@orpc/contract": "1.9.0", + "@orpc/interop": "1.9.0", + "@orpc/openapi-client": "1.9.0", + "@orpc/server": "1.9.0", + "@orpc/shared": "1.9.0", + "@orpc/standard-server": "1.9.0", + "rou3": "^0.7.3" + } + }, + "node_modules/@orpc/openapi-client": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@orpc/openapi-client/-/openapi-client-1.9.0.tgz", + "integrity": "sha512-X0JQN1P1iHAGTS7vGnYT4AK5Uc4xwBxN7bifIYXhnIGYlEMR6i1DJOxlGHrS6dD5D58QZsfZ+TiFP77z8LoJag==", + "license": "MIT", + "dependencies": { + "@orpc/client": "1.9.0", + "@orpc/contract": "1.9.0", + "@orpc/shared": "1.9.0", + "@orpc/standard-server": "1.9.0" + } + }, + "node_modules/@orpc/server": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@orpc/server/-/server-1.9.0.tgz", + "integrity": "sha512-Y00wK3RuMq5d4AC+Qj6yZBRgnpw0y/sq3kML3MGyxCbNOzh9DYAuAC3aFUUDVj20PhASAOckgXnquNmqBaeKxA==", + "license": "MIT", + "dependencies": { + "@orpc/client": "1.9.0", + "@orpc/contract": "1.9.0", + "@orpc/interop": "1.9.0", + "@orpc/shared": "1.9.0", + "@orpc/standard-server": "1.9.0", + "@orpc/standard-server-aws-lambda": "1.9.0", + "@orpc/standard-server-fetch": "1.9.0", + "@orpc/standard-server-node": "1.9.0", + "@orpc/standard-server-peer": "1.9.0", + "cookie": "^1.0.2" + }, + "peerDependencies": { + "crossws": ">=0.3.4", + "ws": ">=8.18.1" + }, + "peerDependenciesMeta": { + "crossws": { + "optional": true + }, + "ws": { + "optional": true + } + } + }, + "node_modules/@orpc/shared": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@orpc/shared/-/shared-1.9.0.tgz", + "integrity": "sha512-Aa67z0TzppHksEnzZuaIlXdx202OMfLZsFBmnjzej8ObwZ7RLVF1B5L11IcQJSiO4IU7RGa5fW2J0BSAbMZOkQ==", + "license": "MIT", + "dependencies": { + "radash": "^12.1.1", + "type-fest": "^5.0.1" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.9.0" + }, + "peerDependenciesMeta": { + "@opentelemetry/api": { + "optional": true + } + } + }, + "node_modules/@orpc/standard-server": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@orpc/standard-server/-/standard-server-1.9.0.tgz", + "integrity": "sha512-qgeznH8eiiVld5mHEHkdliXgoXAdaDdJXCNMItNqk2Y/8uknsyONPvbgh5KVVPLWNtosO4EqNHWPozDi+BdzPw==", + "license": "MIT", + "dependencies": { + "@orpc/shared": "1.9.0" + } + }, + "node_modules/@orpc/standard-server-aws-lambda": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@orpc/standard-server-aws-lambda/-/standard-server-aws-lambda-1.9.0.tgz", + "integrity": "sha512-LMq9Yd8143FNwbnLNarm8Ze/ECR40HaMLTFhs5OH5f1zZuVtLUBQFpXFpFnYekPtzbzxKgpQ16VRXsHJ02Z2KA==", + "license": "MIT", + "dependencies": { + "@orpc/shared": "1.9.0", + "@orpc/standard-server": "1.9.0", + "@orpc/standard-server-fetch": "1.9.0", + "@orpc/standard-server-node": "1.9.0" + } + }, + "node_modules/@orpc/standard-server-fetch": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@orpc/standard-server-fetch/-/standard-server-fetch-1.9.0.tgz", + "integrity": "sha512-ZT7GhCnRD4T3FCpT7zihte1Nkl4JxO68snEe1Ld2UF5pp5ktqZr2yq9BTDU0jHDBcseB/4YIfSEMWIJPr6TAkw==", + "license": "MIT", + "dependencies": { + "@orpc/shared": "1.9.0", + "@orpc/standard-server": "1.9.0" + } + }, + "node_modules/@orpc/standard-server-node": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@orpc/standard-server-node/-/standard-server-node-1.9.0.tgz", + "integrity": "sha512-WwFVGZ8kDxLuR/1Zp9V4qp+umOGyRnqeN4PUkyDJlY7sU4p+W9aVusIrf4lfMCKzCaOoQ9uO9eW6yY1FwyorhQ==", + "license": "MIT", + "dependencies": { + "@orpc/shared": "1.9.0", + "@orpc/standard-server": "1.9.0", + "@orpc/standard-server-fetch": "1.9.0" + } + }, + "node_modules/@orpc/standard-server-peer": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@orpc/standard-server-peer/-/standard-server-peer-1.9.0.tgz", + "integrity": "sha512-Y4cxNN3JDFywuUHPqEAqHAh5HQj2bY5Gc4LmFTH0MViIiKgTKBg0grAagPDexzHHREZMz2XYlReJMDVCOLiqOg==", + "license": "MIT", + "dependencies": { + "@orpc/shared": "1.9.0", + "@orpc/standard-server": "1.9.0" + } + }, + "node_modules/@orpc/tanstack-query": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@orpc/tanstack-query/-/tanstack-query-1.9.0.tgz", + "integrity": "sha512-qN/wJqu0wLPnbjEmZxRRQYOkaDV/NppARrRW5OMc6RLQGZqmOYdiotMc0XKLn1BRS/YXJqm3o3czpW8kKKtaGw==", + "license": "MIT", + "dependencies": { + "@orpc/shared": "1.9.0" + }, + "peerDependencies": { + "@orpc/client": "1.9.0", + "@tanstack/query-core": ">=5.80.2" + } + }, + "node_modules/@orpc/zod": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@orpc/zod/-/zod-1.9.0.tgz", + "integrity": "sha512-vCB1bsCdB1xhw8Jf/X4IAZQAJcXl7xIu8+0hV5Kiibs8hrfSwdNYhaooW2SXq7xar4MaTfrVvtQXaIve9D1m5w==", + "license": "MIT", + "dependencies": { + "@orpc/json-schema": "1.9.0", + "@orpc/openapi": "1.9.0", + "@orpc/shared": "1.9.0", + "escape-string-regexp": "^5.0.0", + "wildcard-match": "^5.1.3" + }, + "peerDependencies": { + "@orpc/contract": "1.9.0", + "@orpc/server": "1.9.0", + "zod": ">=3.25.0" + } + }, + "node_modules/@petamoriken/float16": { + "version": "3.9.2", + "resolved": "https://registry.npmjs.org/@petamoriken/float16/-/float16-3.9.2.tgz", + "integrity": "sha512-VgffxawQde93xKxT3qap3OH+meZf7VaSB5Sqd4Rqc+FP5alWbpOyan/7tRbOAvynjpG3GpdtAuGU/NdhQpmrog==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/@phc/format": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@phc/format/-/format-1.0.0.tgz", + "integrity": "sha512-m7X9U6BG2+J+R1lSOdCiITLLrxm+cWlNI3HUFA92oLO77ObGNzaKdh8pMLqdZcshtkKuV84olNNXDfMc4FezBQ==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/@polka/url": { + "version": "1.0.0-next.29", + "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.29.tgz", + "integrity": "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==", + "license": "MIT" + }, + "node_modules/@poppinss/macroable": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@poppinss/macroable/-/macroable-1.1.0.tgz", + "integrity": "sha512-y/YKzZDuG8XrpXpM7Z1RdQpiIc0MAKyva24Ux1PB4aI7RiSI/79K8JVDcdyubriTm7vJ1LhFs8CrZpmPnx/8Pw==", + "license": "MIT", + "optional": true + }, + "node_modules/@rollup/plugin-commonjs": { + "version": "28.0.6", + "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-28.0.6.tgz", + "integrity": "sha512-XSQB1K7FUU5QP+3lOQmVCE3I0FcbbNvmNT4VJSj93iUjayaARrTQeoRdiYQoftAJBLrR9t2agwAd3ekaTgHNlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "^5.0.1", + "commondir": "^1.0.1", + "estree-walker": "^2.0.2", + "fdir": "^6.2.0", + "is-reference": "1.2.1", + "magic-string": "^0.30.3", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=16.0.0 || 14 >= 14.17" + }, + "peerDependencies": { + "rollup": "^2.68.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-json": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@rollup/plugin-json/-/plugin-json-6.1.0.tgz", + "integrity": "sha512-EGI2te5ENk1coGeADSIwZ7G2Q8CJS2sF120T7jLw4xFw9n7wIOXHo+kIYRAoVpJAN+kmqZSoO3Fp4JtoNF4ReA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "^5.1.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-node-resolve": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-16.0.1.tgz", + "integrity": "sha512-tk5YCxJWIG81umIvNkSod2qK5KyQW19qcBF/B78n1bjtOON6gzKoVeSzAE8yHCZEDmqkHKkxplExA8KzdJLJpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "^5.0.1", + "@types/resolve": "1.20.2", + "deepmerge": "^4.2.2", + "is-module": "^1.0.0", + "resolve": "^1.22.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^2.78.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/pluginutils": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.3.0.tgz", + "integrity": "sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.52.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.52.2.tgz", + "integrity": "sha512-o3pcKzJgSGt4d74lSZ+OCnHwkKBeAbFDmbEm5gg70eA8VkyCuC/zV9TwBnmw6VjDlRdF4Pshfb+WE9E6XY1PoQ==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.52.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.52.2.tgz", + "integrity": "sha512-cqFSWO5tX2vhC9hJTK8WAiPIm4Q8q/cU8j2HQA0L3E1uXvBYbOZMhE2oFL8n2pKB5sOCHY6bBuHaRwG7TkfJyw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.52.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.52.2.tgz", + "integrity": "sha512-vngduywkkv8Fkh3wIZf5nFPXzWsNsVu1kvtLETWxTFf/5opZmflgVSeLgdHR56RQh71xhPhWoOkEBvbehwTlVA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.52.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.52.2.tgz", + "integrity": "sha512-h11KikYrUCYTrDj6h939hhMNlqU2fo/X4NB0OZcys3fya49o1hmFaczAiJWVAFgrM1NCP6RrO7lQKeVYSKBPSQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.52.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.52.2.tgz", + "integrity": "sha512-/eg4CI61ZUkLXxMHyVlmlGrSQZ34xqWlZNW43IAU4RmdzWEx0mQJ2mN/Cx4IHLVZFL6UBGAh+/GXhgvGb+nVxw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.52.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.52.2.tgz", + "integrity": "sha512-QOWgFH5X9+p+S1NAfOqc0z8qEpJIoUHf7OWjNUGOeW18Mx22lAUOiA9b6r2/vpzLdfxi/f+VWsYjUOMCcYh0Ng==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.52.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.52.2.tgz", + "integrity": "sha512-kDWSPafToDd8LcBYd1t5jw7bD5Ojcu12S3uT372e5HKPzQt532vW+rGFFOaiR0opxePyUkHrwz8iWYEyH1IIQA==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.52.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.52.2.tgz", + "integrity": "sha512-gKm7Mk9wCv6/rkzwCiUC4KnevYhlf8ztBrDRT9g/u//1fZLapSRc+eDZj2Eu2wpJ+0RzUKgtNijnVIB4ZxyL+w==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.52.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.52.2.tgz", + "integrity": "sha512-66lA8vnj5mB/rtDNwPgrrKUOtCLVQypkyDa2gMfOefXK6rcZAxKLO9Fy3GkW8VkPnENv9hBkNOFfGLf6rNKGUg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.52.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.52.2.tgz", + "integrity": "sha512-s+OPucLNdJHvuZHuIz2WwncJ+SfWHFEmlC5nKMUgAelUeBUnlB4wt7rXWiyG4Zn07uY2Dd+SGyVa9oyLkVGOjA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.52.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.52.2.tgz", + "integrity": "sha512-8wTRM3+gVMDLLDdaT6tKmOE3lJyRy9NpJUS/ZRWmLCmOPIJhVyXwjBo+XbrrwtV33Em1/eCTd5TuGJm4+DmYjw==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.52.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.52.2.tgz", + "integrity": "sha512-6yqEfgJ1anIeuP2P/zhtfBlDpXUb80t8DpbYwXQ3bQd95JMvUaqiX+fKqYqUwZXqdJDd8xdilNtsHM2N0cFm6A==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.52.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.52.2.tgz", + "integrity": "sha512-sshYUiYVSEI2B6dp4jMncwxbrUqRdNApF2c3bhtLAU0qA8Lrri0p0NauOsTWh3yCCCDyBOjESHMExonp7Nzc0w==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.52.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.52.2.tgz", + "integrity": "sha512-duBLgd+3pqC4MMwBrKkFxaZerUxZcYApQVC5SdbF5/e/589GwVvlRUnyqMFbM8iUSb1BaoX/3fRL7hB9m2Pj8Q==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.52.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.52.2.tgz", + "integrity": "sha512-tzhYJJidDUVGMgVyE+PmxENPHlvvqm1KILjjZhB8/xHYqAGeizh3GBGf9u6WdJpZrz1aCpIIHG0LgJgH9rVjHQ==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.52.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.52.2.tgz", + "integrity": "sha512-opH8GSUuVcCSSyHHcl5hELrmnk4waZoVpgn/4FDao9iyE4WpQhyWJ5ryl5M3ocp4qkRuHfyXnGqg8M9oKCEKRA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.52.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.52.2.tgz", + "integrity": "sha512-LSeBHnGli1pPKVJ79ZVJgeZWWZXkEe/5o8kcn23M8eMKCUANejchJbF/JqzM4RRjOJfNRhKJk8FuqL1GKjF5oQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.52.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.52.2.tgz", + "integrity": "sha512-uPj7MQ6/s+/GOpolavm6BPo+6CbhbKYyZHUDvZ/SmJM7pfDBgdGisFX3bY/CBDMg2ZO4utfhlApkSfZ92yXw7Q==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.52.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.52.2.tgz", + "integrity": "sha512-Z9MUCrSgIaUeeHAiNkm3cQyst2UhzjPraR3gYYfOjAuZI7tcFRTOD+4cHLPoS/3qinchth+V56vtqz1Tv+6KPA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.52.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.52.2.tgz", + "integrity": "sha512-+GnYBmpjldD3XQd+HMejo+0gJGwYIOfFeoBQv32xF/RUIvccUz20/V6Otdv+57NE70D5pa8W/jVGDoGq0oON4A==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.52.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.52.2.tgz", + "integrity": "sha512-ApXFKluSB6kDQkAqZOKXBjiaqdF1BlKi+/eqnYe9Ee7U2K3pUDKsIyr8EYm/QDHTJIM+4X+lI0gJc3TTRhd+dA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.52.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.52.2.tgz", + "integrity": "sha512-ARz+Bs8kY6FtitYM96PqPEVvPXqEZmPZsSkXvyX19YzDqkCaIlhCieLLMI5hxO9SRZ2XtCtm8wxhy0iJ2jxNfw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@sideway/address": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz", + "integrity": "sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q==", + "license": "BSD-3-Clause", + "optional": true, + "dependencies": { + "@hapi/hoek": "^9.0.0" + } + }, + "node_modules/@sideway/formula": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.1.tgz", + "integrity": "sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==", + "license": "BSD-3-Clause", + "optional": true + }, + "node_modules/@sideway/pinpoint": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz", + "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==", + "license": "BSD-3-Clause", + "optional": true + }, + "node_modules/@sinclair/typebox": { + "version": "0.31.28", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.31.28.tgz", + "integrity": "sha512-/s55Jujywdw/Jpan+vsy6JZs1z2ZTGxTmbZTPiuSL2wz9mfzA2gN1zzaqmvfi4pq+uOt7Du85fkiwv5ymW84aQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@sqlite.org/sqlite-wasm": { + "version": "3.48.0-build4", + "resolved": "https://registry.npmjs.org/@sqlite.org/sqlite-wasm/-/sqlite-wasm-3.48.0-build4.tgz", + "integrity": "sha512-hI6twvUkzOmyGZhQMza1gpfqErZxXRw6JEsiVjUbo7tFanVD+8Oil0Ih3l2nGzHdxPI41zFmfUQG7GHqhciKZQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "sqlite-wasm": "bin/index.js" + } + }, + "node_modules/@standard-schema/spec": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.0.0.tgz", + "integrity": "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==", + "license": "MIT" + }, + "node_modules/@sveltejs/acorn-typescript": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@sveltejs/acorn-typescript/-/acorn-typescript-1.0.5.tgz", + "integrity": "sha512-IwQk4yfwLdibDlrXVE04jTZYlLnwsTT2PIOQQGNLWfjavGifnk1JD1LcZjZaBTRcxZu2FfPfNLOE04DSu9lqtQ==", + "license": "MIT", + "peerDependencies": { + "acorn": "^8.9.0" + } + }, + "node_modules/@sveltejs/adapter-node": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/@sveltejs/adapter-node/-/adapter-node-5.3.2.tgz", + "integrity": "sha512-nBJSipMb1KLjnAM7uzb+YpnA1VWKb+WdR+0mXEnXI6K1A3XYWbjkcjnW20ubg07sicK8XaGY/FAX3PItw39qBQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rollup/plugin-commonjs": "^28.0.1", + "@rollup/plugin-json": "^6.1.0", + "@rollup/plugin-node-resolve": "^16.0.0", + "rollup": "^4.9.5" + }, + "peerDependencies": { + "@sveltejs/kit": "^2.4.0" + } + }, + "node_modules/@sveltejs/kit": { + "version": "2.43.4", + "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.43.4.tgz", + "integrity": "sha512-GfvOq3A/qMRhj2L9eKjxaI8FLqZDh5SY74YzhRKT//u2AvQw96ksEfjuHviC4jg9U08mBVB0Y47EwEJHO4BB4Q==", + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.0.0", + "@sveltejs/acorn-typescript": "^1.0.5", + "@types/cookie": "^0.6.0", + "acorn": "^8.14.1", + "cookie": "^0.6.0", + "devalue": "^5.3.2", + "esm-env": "^1.2.2", + "kleur": "^4.1.5", + "magic-string": "^0.30.5", + "mrmime": "^2.0.0", + "sade": "^1.8.1", + "set-cookie-parser": "^2.6.0", + "sirv": "^3.0.0" + }, + "bin": { + "svelte-kit": "svelte-kit.js" + }, + "engines": { + "node": ">=18.13" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.0.0", + "@sveltejs/vite-plugin-svelte": "^3.0.0 || ^4.0.0-next.1 || ^5.0.0 || ^6.0.0-next.0", + "svelte": "^4.0.0 || ^5.0.0-next.0", + "vite": "^5.0.3 || ^6.0.0 || ^7.0.0-beta.0" + }, + "peerDependenciesMeta": { + "@opentelemetry/api": { + "optional": true + } + } + }, + "node_modules/@sveltejs/kit/node_modules/cookie": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@sveltejs/vite-plugin-svelte": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-6.2.1.tgz", + "integrity": "sha512-YZs/OSKOQAQCnJvM/P+F1URotNnYNeU3P2s4oIpzm1uFaqUEqRxUB0g5ejMjEb5Gjb9/PiBI5Ktrq4rUUF8UVQ==", + "license": "MIT", + "dependencies": { + "@sveltejs/vite-plugin-svelte-inspector": "^5.0.0", + "debug": "^4.4.1", + "deepmerge": "^4.3.1", + "magic-string": "^0.30.17", + "vitefu": "^1.1.1" + }, + "engines": { + "node": "^20.19 || ^22.12 || >=24" + }, + "peerDependencies": { + "svelte": "^5.0.0", + "vite": "^6.3.0 || ^7.0.0" + } + }, + "node_modules/@sveltejs/vite-plugin-svelte-inspector": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte-inspector/-/vite-plugin-svelte-inspector-5.0.1.tgz", + "integrity": "sha512-ubWshlMk4bc8mkwWbg6vNvCeT7lGQojE3ijDh3QTR6Zr/R+GXxsGbyH4PExEPpiFmqPhYiVSVmHBjUcVc1JIrA==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.1" + }, + "engines": { + "node": "^20.19 || ^22.12 || >=24" + }, + "peerDependencies": { + "@sveltejs/vite-plugin-svelte": "^6.0.0-next.0", + "svelte": "^5.0.0", + "vite": "^6.3.0 || ^7.0.0" + } + }, + "node_modules/@swc/helpers": { + "version": "0.5.17", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.17.tgz", + "integrity": "sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.8.0" + } + }, + "node_modules/@tailwindcss/forms": { + "version": "0.5.10", + "resolved": "https://registry.npmjs.org/@tailwindcss/forms/-/forms-0.5.10.tgz", + "integrity": "sha512-utI1ONF6uf/pPNO68kmN1b8rEwNXv3czukalo8VtJH8ksIkZXr3Q3VYudZLkCsDd4Wku120uF02hYK25XGPorw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mini-svg-data-uri": "^1.2.3" + }, + "peerDependencies": { + "tailwindcss": ">=3.0.0 || >= 3.0.0-alpha.1 || >= 4.0.0-alpha.20 || >= 4.0.0-beta.1" + } + }, + "node_modules/@tailwindcss/node": { + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.13.tgz", + "integrity": "sha512-eq3ouolC1oEFOAvOMOBAmfCIqZBJuvWvvYWh5h5iOYfe1HFC6+GZ6EIL0JdM3/niGRJmnrOc+8gl9/HGUaaptw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.4", + "enhanced-resolve": "^5.18.3", + "jiti": "^2.5.1", + "lightningcss": "1.30.1", + "magic-string": "^0.30.18", + "source-map-js": "^1.2.1", + "tailwindcss": "4.1.13" + } + }, + "node_modules/@tailwindcss/oxide": { + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.13.tgz", + "integrity": "sha512-CPgsM1IpGRa880sMbYmG1s4xhAy3xEt1QULgTJGQmZUeNgXFR7s1YxYygmJyBGtou4SyEosGAGEeYqY7R53bIA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "detect-libc": "^2.0.4", + "tar": "^7.4.3" + }, + "engines": { + "node": ">= 10" + }, + "optionalDependencies": { + "@tailwindcss/oxide-android-arm64": "4.1.13", + "@tailwindcss/oxide-darwin-arm64": "4.1.13", + "@tailwindcss/oxide-darwin-x64": "4.1.13", + "@tailwindcss/oxide-freebsd-x64": "4.1.13", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.13", + "@tailwindcss/oxide-linux-arm64-gnu": "4.1.13", + "@tailwindcss/oxide-linux-arm64-musl": "4.1.13", + "@tailwindcss/oxide-linux-x64-gnu": "4.1.13", + "@tailwindcss/oxide-linux-x64-musl": "4.1.13", + "@tailwindcss/oxide-wasm32-wasi": "4.1.13", + "@tailwindcss/oxide-win32-arm64-msvc": "4.1.13", + "@tailwindcss/oxide-win32-x64-msvc": "4.1.13" + } + }, + "node_modules/@tailwindcss/oxide-android-arm64": { + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.13.tgz", + "integrity": "sha512-BrpTrVYyejbgGo57yc8ieE+D6VT9GOgnNdmh5Sac6+t0m+v+sKQevpFVpwX3pBrM2qKrQwJ0c5eDbtjouY/+ew==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-darwin-arm64": { + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.13.tgz", + "integrity": "sha512-YP+Jksc4U0KHcu76UhRDHq9bx4qtBftp9ShK/7UGfq0wpaP96YVnnjFnj3ZFrUAjc5iECzODl/Ts0AN7ZPOANQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-darwin-x64": { + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.13.tgz", + "integrity": "sha512-aAJ3bbwrn/PQHDxCto9sxwQfT30PzyYJFG0u/BWZGeVXi5Hx6uuUOQEI2Fa43qvmUjTRQNZnGqe9t0Zntexeuw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-freebsd-x64": { + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.13.tgz", + "integrity": "sha512-Wt8KvASHwSXhKE/dJLCCWcTSVmBj3xhVhp/aF3RpAhGeZ3sVo7+NTfgiN8Vey/Fi8prRClDs6/f0KXPDTZE6nQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.13.tgz", + "integrity": "sha512-mbVbcAsW3Gkm2MGwA93eLtWrwajz91aXZCNSkGTx/R5eb6KpKD5q8Ueckkh9YNboU8RH7jiv+ol/I7ZyQ9H7Bw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.13.tgz", + "integrity": "sha512-wdtfkmpXiwej/yoAkrCP2DNzRXCALq9NVLgLELgLim1QpSfhQM5+ZxQQF8fkOiEpuNoKLp4nKZ6RC4kmeFH0HQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-musl": { + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.13.tgz", + "integrity": "sha512-hZQrmtLdhyqzXHB7mkXfq0IYbxegaqTmfa1p9MBj72WPoDD3oNOh1Lnxf6xZLY9C3OV6qiCYkO1i/LrzEdW2mg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-gnu": { + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.13.tgz", + "integrity": "sha512-uaZTYWxSXyMWDJZNY1Ul7XkJTCBRFZ5Fo6wtjrgBKzZLoJNrG+WderJwAjPzuNZOnmdrVg260DKwXCFtJ/hWRQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-musl": { + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.13.tgz", + "integrity": "sha512-oXiPj5mi4Hdn50v5RdnuuIms0PVPI/EG4fxAfFiIKQh5TgQgX7oSuDWntHW7WNIi/yVLAiS+CRGW4RkoGSSgVQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi": { + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.13.tgz", + "integrity": "sha512-+LC2nNtPovtrDwBc/nqnIKYh/W2+R69FA0hgoeOn64BdCX522u19ryLh3Vf3F8W49XBcMIxSe665kwy21FkhvA==", + "bundleDependencies": [ + "@napi-rs/wasm-runtime", + "@emnapi/core", + "@emnapi/runtime", + "@tybys/wasm-util", + "@emnapi/wasi-threads", + "tslib" + ], + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.4.5", + "@emnapi/runtime": "^1.4.5", + "@emnapi/wasi-threads": "^1.0.4", + "@napi-rs/wasm-runtime": "^0.2.12", + "@tybys/wasm-util": "^0.10.0", + "tslib": "^2.8.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.13.tgz", + "integrity": "sha512-dziTNeQXtoQ2KBXmrjCxsuPk3F3CQ/yb7ZNZNA+UkNTeiTGgfeh+gH5Pi7mRncVgcPD2xgHvkFCh/MhZWSgyQg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-win32-x64-msvc": { + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.13.tgz", + "integrity": "sha512-3+LKesjXydTkHk5zXX01b5KMzLV1xl2mcktBJkje7rhFUpUlYJy7IMOLqjIRQncLTa1WZZiFY/foAeB5nmaiTw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/typography": { + "version": "0.5.19", + "resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.19.tgz", + "integrity": "sha512-w31dd8HOx3k9vPtcQh5QHP9GwKcgbMp87j58qi6xgiBnFFtKEAgCWnDw4qUT8aHwkCp8bKvb/KGKWWHedP0AAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "6.0.10" + }, + "peerDependencies": { + "tailwindcss": ">=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1" + } + }, + "node_modules/@tailwindcss/vite": { + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.1.13.tgz", + "integrity": "sha512-0PmqLQ010N58SbMTJ7BVJ4I2xopiQn/5i6nlb4JmxzQf8zcS5+m2Cv6tqh+sfDwtIdjoEnOvwsGQ1hkUi8QEHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tailwindcss/node": "4.1.13", + "@tailwindcss/oxide": "4.1.13", + "tailwindcss": "4.1.13" + }, + "peerDependencies": { + "vite": "^5.2.0 || ^6 || ^7" + } + }, + "node_modules/@tanstack/query-core": { + "version": "5.90.2", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.90.2.tgz", + "integrity": "sha512-k/TcR3YalnzibscALLwxeiLUub6jN5EDLwKDiO7q5f4ICEoptJ+n9+7vcEFy5/x/i6Q+Lb/tXrsKCggf5uQJXQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/svelte-query": { + "version": "5.90.2", + "resolved": "https://registry.npmjs.org/@tanstack/svelte-query/-/svelte-query-5.90.2.tgz", + "integrity": "sha512-owjnp0w8sOXlMhLZhucHrsYvCjgjHrVyII/wlqMGefxKFyroZS3xCwTee+IUx7UHbL+QmKr/HQTeTqhgxmxPQw==", + "license": "MIT", + "dependencies": { + "@tanstack/query-core": "5.90.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "svelte": "^3.54.0 || ^4.0.0 || ^5.0.0" + } + }, + "node_modules/@tanstack/table-core": { + "version": "8.21.3", + "resolved": "https://registry.npmjs.org/@tanstack/table-core/-/table-core-8.21.3.tgz", + "integrity": "sha512-ldZXEhOBb8Is7xLs01fR3YEc3DERiz5silj8tnGkFZytt1abEvl/GhUmCE0PMLaMPTa3Jk4HbKmRlHmu+gCftg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@types/argon2": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/@types/argon2/-/argon2-0.14.1.tgz", + "integrity": "sha512-PH5bYzOBbjluvhsbrIjhst7hkfRH8FUkJWRpRpahRpks6M3RjuMQQrW4n+Qrp616o8nBoM5ooRkDYIiT7Gb+tA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/better-sqlite3": { + "version": "7.6.13", + "resolved": "https://registry.npmjs.org/@types/better-sqlite3/-/better-sqlite3-7.6.13.tgz", + "integrity": "sha512-NMv9ASNARoKksWtsq/SHakpYAYnhBrQgGD8zkLYk/jaK8jUGn08CfEdTRgYhMypUQAfzSP8W6gNLe0q19/t4VA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/cookie": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==", + "license": "MIT" + }, + "node_modules/@types/d3-scale": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz", + "integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/d3-time": "*" + } + }, + "node_modules/@types/d3-time": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz", + "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/@types/mdast": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/node": { + "version": "22.18.6", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.18.6.tgz", + "integrity": "sha512-r8uszLPpeIWbNKtvWRt/DbVi5zbqZyj1PTmhRMqBMvDnaz1QpmSKujUtJLrqGZeoM8v72MfYggDceY4K1itzWQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/resolve": { + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz", + "integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/unist": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/validator": { + "version": "13.15.3", + "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.15.3.tgz", + "integrity": "sha512-7bcUmDyS6PN3EuD9SlGGOxM77F8WLVsrwkxyWxKnxzmXoequ6c7741QBrANq6htVRGOITJ7z72mTP6Z4XyuG+Q==", + "license": "MIT", + "optional": true + }, + "node_modules/@typeschema/class-validator": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@typeschema/class-validator/-/class-validator-0.3.0.tgz", + "integrity": "sha512-OJSFeZDIQ8EK1HTljKLT5CItM2wsbgczLN8tMEfz3I1Lmhc5TBfkZ0eikFzUC16tI3d1Nag7um6TfCgp2I2Bww==", + "license": "MIT", + "optional": true, + "dependencies": { + "@typeschema/core": "0.14.0" + }, + "peerDependencies": { + "class-validator": "^0.14.1" + }, + "peerDependenciesMeta": { + "class-validator": { + "optional": true + } + } + }, + "node_modules/@typeschema/core": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/@typeschema/core/-/core-0.14.0.tgz", + "integrity": "sha512-Ia6PtZHcL3KqsAWXjMi5xIyZ7XMH4aSnOQes8mfMLx+wGFGtGRNlwe6Y7cYvX+WfNK67OL0/HSe9t8QDygV0/w==", + "license": "MIT", + "optional": true, + "peerDependencies": { + "@types/json-schema": "^7.0.15" + }, + "peerDependenciesMeta": { + "@types/json-schema": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.44.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.44.1.tgz", + "integrity": "sha512-molgphGqOBT7t4YKCSkbasmu1tb1MgrZ2szGzHbclF7PNmOkSTQVHy+2jXOSnxvR3+Xe1yySHFZoqMpz3TfQsw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "8.44.1", + "@typescript-eslint/type-utils": "8.44.1", + "@typescript-eslint/utils": "8.44.1", + "@typescript-eslint/visitor-keys": "8.44.1", + "graphemer": "^1.4.0", + "ignore": "^7.0.0", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.44.1", + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.44.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.44.1.tgz", + "integrity": "sha512-EHrrEsyhOhxYt8MTg4zTF+DJMuNBzWwgvvOYNj/zm1vnaD/IC5zCXFehZv94Piqa2cRFfXrTFxIvO95L7Qc/cw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.44.1", + "@typescript-eslint/types": "8.44.1", + "@typescript-eslint/typescript-estree": "8.44.1", + "@typescript-eslint/visitor-keys": "8.44.1", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.44.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.44.1.tgz", + "integrity": "sha512-ycSa60eGg8GWAkVsKV4E6Nz33h+HjTXbsDT4FILyL8Obk5/mx4tbvCNsLf9zret3ipSumAOG89UcCs/KRaKYrA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.44.1", + "@typescript-eslint/types": "^8.44.1", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.44.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.44.1.tgz", + "integrity": "sha512-NdhWHgmynpSvyhchGLXh+w12OMT308Gm25JoRIyTZqEbApiBiQHD/8xgb6LqCWCFcxFtWwaVdFsLPQI3jvhywg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.44.1", + "@typescript-eslint/visitor-keys": "8.44.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.44.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.44.1.tgz", + "integrity": "sha512-B5OyACouEjuIvof3o86lRMvyDsFwZm+4fBOqFHccIctYgBjqR3qT39FBYGN87khcgf0ExpdCBeGKpKRhSFTjKQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.44.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.44.1.tgz", + "integrity": "sha512-KdEerZqHWXsRNKjF9NYswNISnFzXfXNDfPxoTh7tqohU/PRIbwTmsjGK6V9/RTYWau7NZvfo52lgVk+sJh0K3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.44.1", + "@typescript-eslint/typescript-estree": "8.44.1", + "@typescript-eslint/utils": "8.44.1", + "debug": "^4.3.4", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.44.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.44.1.tgz", + "integrity": "sha512-Lk7uj7y9uQUOEguiDIDLYLJOrYHQa7oBiURYVFqIpGxclAFQ78f6VUOM8lI2XEuNOKNB7XuvM2+2cMXAoq4ALQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.44.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.44.1.tgz", + "integrity": "sha512-qnQJ+mVa7szevdEyvfItbO5Vo+GfZ4/GZWWDRRLjrxYPkhM+6zYB2vRYwCsoJLzqFCdZT4mEqyJoyzkunsZ96A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.44.1", + "@typescript-eslint/tsconfig-utils": "8.44.1", + "@typescript-eslint/types": "8.44.1", + "@typescript-eslint/visitor-keys": "8.44.1", + "debug": "^4.3.4", + "fast-glob": "^3.3.2", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.44.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.44.1.tgz", + "integrity": "sha512-DpX5Fp6edTlocMCwA+mHY8Mra+pPjRZ0TfHkXI8QFelIKcbADQz1LUPNtzOFUriBB2UYqw4Pi9+xV4w9ZczHFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.7.0", + "@typescript-eslint/scope-manager": "8.44.1", + "@typescript-eslint/types": "8.44.1", + "@typescript-eslint/typescript-estree": "8.44.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.44.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.44.1.tgz", + "integrity": "sha512-576+u0QD+Jp3tZzvfRfxon0EA2lzcDt3lhUbsC6Lgzy9x2VR4E+JUiNyGHi5T8vk0TV+fpJ5GLG1JsJuWCaKhw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.44.1", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@vinejs/compiler": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@vinejs/compiler/-/compiler-3.0.0.tgz", + "integrity": "sha512-v9Lsv59nR56+bmy2p0+czjZxsLHwaibJ+SV5iK9JJfehlJMa501jUJQqqz4X/OqKXrxtE3uTQmSqjUqzF3B2mw==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@vinejs/vine": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@vinejs/vine/-/vine-3.0.1.tgz", + "integrity": "sha512-ZtvYkYpZOYdvbws3uaOAvTFuvFXoQGAtmzeiXu+XSMGxi5GVsODpoI9Xu9TplEMuD/5fmAtBbKb9cQHkWkLXDQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "@poppinss/macroable": "^1.0.4", + "@types/validator": "^13.12.2", + "@vinejs/compiler": "^3.0.0", + "camelcase": "^8.0.0", + "dayjs": "^1.11.13", + "dlv": "^1.1.3", + "normalize-url": "^8.0.1", + "validator": "^13.12.0" + }, + "engines": { + "node": ">=18.16.0" + } + }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argon2": { + "version": "0.44.0", + "resolved": "https://registry.npmjs.org/argon2/-/argon2-0.44.0.tgz", + "integrity": "sha512-zHPGN3S55sihSQo0dBbK0A5qpi2R31z7HZDZnry3ifOyj8bZZnpZND2gpmhnRGO1V/d555RwBqIK5W4Mrmv3ig==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "@phc/format": "^1.0.0", + "cross-env": "^10.0.0", + "node-addon-api": "^8.5.0", + "node-gyp-build": "^4.8.4" + }, + "engines": { + "node": ">=16.17.0" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/aria-query": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", + "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/arktype": { + "version": "2.1.22", + "resolved": "https://registry.npmjs.org/arktype/-/arktype-2.1.22.tgz", + "integrity": "sha512-xdzl6WcAhrdahvRRnXaNwsipCgHuNoLobRqhiP8RjnfL9Gp947abGlo68GAIyLtxbD+MLzNyH2YR4kEqioMmYQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "@ark/schema": "0.49.0", + "@ark/util": "0.49.0" + } + }, + "node_modules/array-timsort": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array-timsort/-/array-timsort-1.0.3.tgz", + "integrity": "sha512-/+3GRL7dDAGEfM6TseQk/U+mi18TU2Ms9I3UlLdUMhz2hbvGNTKdj9xniwXfUqgYhHxRx0+8UnKkvlNwVU+cWQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/axobject-query": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", + "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/bits-ui": { + "version": "2.11.4", + "resolved": "https://registry.npmjs.org/bits-ui/-/bits-ui-2.11.4.tgz", + "integrity": "sha512-OlVBJhNUMDHbIAf8oDAyPchIrU8b1S5NAMm6enMZSKx5HKcf/QPI485/BL1r4EPlv4O3m45e59hBRCETtYFdxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@floating-ui/core": "^1.7.1", + "@floating-ui/dom": "^1.7.1", + "esm-env": "^1.1.2", + "runed": "^0.31.1", + "svelte-toolbelt": "^0.10.4", + "tabbable": "^6.2.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/huntabyte" + }, + "peerDependencies": { + "@internationalized/date": "^3.8.1", + "svelte": "^5.33.0" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-8.0.0.tgz", + "integrity": "sha512-8WB3Jcas3swSvjIeA2yvCJ+Miyz5l1ZmB6HFb9R1317dt9LCQoswg/BGrmAmkWVEszSrrg4RwmO46qIm2OEnSA==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/case-anything": { + "version": "2.1.13", + "resolved": "https://registry.npmjs.org/case-anything/-/case-anything-2.1.13.tgz", + "integrity": "sha512-zlOQ80VrQ2Ue+ymH5OuM/DlDq64mEm+B9UTdHULv5osUMD6HalNTblf2b1u/m6QecjsnOkBpqVZ+XPwIVsy7Ng==", + "license": "MIT", + "engines": { + "node": ">=12.13" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/chownr": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", + "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/class-validator": { + "version": "0.14.2", + "resolved": "https://registry.npmjs.org/class-validator/-/class-validator-0.14.2.tgz", + "integrity": "sha512-3kMVRF2io8N8pY1IFIXlho9r8IPUUIfHe2hYVtiebvAzU2XeQFXTv+XI4WX+TnXmtwXMDcjngcpkiPM0O9PvLw==", + "license": "MIT", + "optional": true, + "dependencies": { + "@types/validator": "^13.11.8", + "libphonenumber-js": "^1.11.1", + "validator": "^13.9.0" + } + }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/commander": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz", + "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + } + }, + "node_modules/comment-json": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/comment-json/-/comment-json-4.2.5.tgz", + "integrity": "sha512-bKw/r35jR3HGt5PEPm1ljsQQGyCrR8sFGNiN5L+ykDHdpO8Smxkrkla9Yi6NkQyUrb8V54PGhfMs6NrIwtxtdw==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-timsort": "^1.0.3", + "core-util-is": "^1.0.3", + "esprima": "^4.0.1", + "has-own-prop": "^2.0.0", + "repeat-string": "^1.6.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", + "dev": true, + "license": "MIT" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/consola": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/consola/-/consola-3.4.0.tgz", + "integrity": "sha512-EiPU8G6dQG0GFHNR8ljnZFki/8a+cQwEQ+7wpxdChl02Q8HXlwEZWD5lqAF8vC2sEC3Tehr8hy7vErz88LHyUA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.18.0 || >=16.10.0" + } + }, + "node_modules/cookie": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz", + "integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/cross-env": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-10.0.0.tgz", + "integrity": "sha512-aU8qlEK/nHYtVuN4p7UQgAwVljzMg8hB4YK5ThRqD2l/ziSnryncPNn7bMLt5cFYsKVKBh8HqLqyCoTupEUu7Q==", + "license": "MIT", + "dependencies": { + "@epic-web/invariant": "^1.0.0", + "cross-spawn": "^7.0.6" + }, + "bin": { + "cross-env": "dist/bin/cross-env.js", + "cross-env-shell": "dist/bin/cross-env-shell.js" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/d3-array": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "license": "ISC", + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-delaunay": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-6.0.4.tgz", + "integrity": "sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==", + "dev": true, + "license": "ISC", + "dependencies": { + "delaunator": "5" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dispatch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz", + "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dsv": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-3.0.1.tgz", + "integrity": "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==", + "dev": true, + "license": "ISC", + "dependencies": { + "commander": "7", + "iconv-lite": "0.6", + "rw": "1" + }, + "bin": { + "csv2json": "bin/dsv2json.js", + "csv2tsv": "bin/dsv2dsv.js", + "dsv2dsv": "bin/dsv2dsv.js", + "dsv2json": "bin/dsv2json.js", + "json2csv": "bin/json2dsv.js", + "json2dsv": "bin/json2dsv.js", + "json2tsv": "bin/json2dsv.js", + "tsv2csv": "bin/dsv2dsv.js", + "tsv2json": "bin/dsv2json.js" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dsv/node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/d3-force": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-3.0.0.tgz", + "integrity": "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==", + "dev": true, + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-quadtree": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-format": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz", + "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-geo": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-3.1.1.tgz", + "integrity": "sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==", + "dev": true, + "license": "ISC", + "dependencies": { + "d3-array": "2.5.0 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-geo-voronoi": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/d3-geo-voronoi/-/d3-geo-voronoi-2.1.0.tgz", + "integrity": "sha512-kqE4yYuOjPbKdBXG0xztCacPwkVSK2REF1opSNrnqqtXJmNcM++UbwQ8SxvwP6IQTj9RvIjjK4qeiVsEfj0Z2Q==", + "dev": true, + "license": "ISC", + "dependencies": { + "d3-array": "3", + "d3-delaunay": "6", + "d3-geo": "3", + "d3-tricontour": "1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-hierarchy": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz", + "integrity": "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate-path": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/d3-interpolate-path/-/d3-interpolate-path-2.3.0.tgz", + "integrity": "sha512-tZYtGXxBmbgHsIc9Wms6LS5u4w6KbP8C09a4/ZYc4KLMYYqub57rRBUgpUr2CIarIrJEpdAWWxWQvofgaMpbKQ==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/d3-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-quadtree": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-3.0.1.tgz", + "integrity": "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-random": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-random/-/d3-random-3.0.1.tgz", + "integrity": "sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-sankey": { + "version": "0.12.3", + "resolved": "https://registry.npmjs.org/d3-sankey/-/d3-sankey-0.12.3.tgz", + "integrity": "sha512-nQhsBRmM19Ax5xEIPLMY9ZmJ/cDvd1BG3UVvt5h3WRxKg5zGRbvnteTyWAbzeSvlh3tW7ZEmq4VwR5mB3tutmQ==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "d3-array": "1 - 2", + "d3-shape": "^1.2.0" + } + }, + "node_modules/d3-sankey/node_modules/d3-array": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.12.1.tgz", + "integrity": "sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "internmap": "^1.0.0" + } + }, + "node_modules/d3-sankey/node_modules/d3-path": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-1.0.9.tgz", + "integrity": "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/d3-sankey/node_modules/d3-shape": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-1.3.7.tgz", + "integrity": "sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "d3-path": "1" + } + }, + "node_modules/d3-sankey/node_modules/internmap": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-1.0.1.tgz", + "integrity": "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==", + "dev": true, + "license": "ISC" + }, + "node_modules/d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", + "license": "ISC", + "dependencies": { + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale-chromatic": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", + "integrity": "sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3", + "d3-interpolate": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-shape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", + "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", + "dev": true, + "license": "ISC", + "dependencies": { + "d3-path": "^3.1.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-tile": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/d3-tile/-/d3-tile-1.0.0.tgz", + "integrity": "sha512-79fnTKpPMPDS5xQ0xuS9ir0165NEwwkFpe/DSOmc2Gl9ldYzKKRDWogmTTE8wAJ8NA7PMapNfEcyKhI9Lxdu5Q==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/d3-time": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", + "license": "ISC", + "dependencies": { + "d3-array": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time-format": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", + "license": "ISC", + "dependencies": { + "d3-time": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-tricontour": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/d3-tricontour/-/d3-tricontour-1.0.2.tgz", + "integrity": "sha512-HIRxHzHagPtUPNabjOlfcyismJYIsc+Xlq4mlsts4e8eAcwyq9Tgk/sYdyhlBpQ0MHwVquc/8j+e29YjXnmxeA==", + "dev": true, + "license": "ISC", + "dependencies": { + "d3-delaunay": "6", + "d3-scale": "4" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/dayjs": { + "version": "1.11.18", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.18.tgz", + "integrity": "sha512-zFBQ7WFRvVRhKcWoUh+ZA1g2HVgUbsZm9sbddh8EC5iv93sui8DVVz1Npvz+r6meo9VKfa8NyLWBsQK1VvIKPA==", + "license": "MIT", + "optional": true + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/dedent": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.1.tgz", + "integrity": "sha512-+LxW+KLWxu3HW3M2w2ympwtqPrqYRzU8fqi6Fhd18fBALe15blJPI/I4+UHveMVG6lJqB4JNd4UG0S5cnVHwIg==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "babel-plugin-macros": "^3.1.0" + }, + "peerDependenciesMeta": { + "babel-plugin-macros": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/delaunator": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.0.1.tgz", + "integrity": "sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw==", + "dev": true, + "license": "ISC", + "dependencies": { + "robust-predicates": "^3.0.2" + } + }, + "node_modules/detect-libc": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.1.tgz", + "integrity": "sha512-ecqj/sy1jcK1uWrwpR67UhYrIFQ+5WlGxth34WquCbamhFA6hkkwiu37o6J5xCHdo1oixJRfVRw+ywV+Hq/0Aw==", + "devOptional": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/devalue": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.3.2.tgz", + "integrity": "sha512-UDsjUbpQn9kvm68slnrs+mfxwFkIflOhkanmyabZ8zOYk8SMEIbJ3TK+88g70hSIeytu4y18f0z/hYHMTrXIWw==", + "license": "MIT" + }, + "node_modules/dlv": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", + "license": "MIT", + "optional": true + }, + "node_modules/dotenv": { + "version": "17.2.2", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.2.tgz", + "integrity": "sha512-Sf2LSQP+bOlhKWWyhFsn0UsfdK/kCWRv1iuA2gXAwt3dyNabr6QSj00I2V10pidqz69soatm9ZwZvpQMTIOd5Q==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dprint-node": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/dprint-node/-/dprint-node-1.0.8.tgz", + "integrity": "sha512-iVKnUtYfGrYcW1ZAlfR/F59cUVL8QIhWoBJoSjkkdua/dkWIgjZfiLMeTjiB06X0ZLkQ0M2C1VbUj/CxkIf1zg==", + "license": "MIT", + "dependencies": { + "detect-libc": "^1.0.3" + } + }, + "node_modules/dprint-node/node_modules/detect-libc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", + "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==", + "license": "Apache-2.0", + "bin": { + "detect-libc": "bin/detect-libc.js" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/drizzle-kit": { + "version": "0.30.6", + "resolved": "https://registry.npmjs.org/drizzle-kit/-/drizzle-kit-0.30.6.tgz", + "integrity": "sha512-U4wWit0fyZuGuP7iNmRleQyK2V8wCuv57vf5l3MnG4z4fzNTjY/U13M8owyQ5RavqvqxBifWORaR3wIUzlN64g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@drizzle-team/brocli": "^0.10.2", + "@esbuild-kit/esm-loader": "^2.5.5", + "esbuild": "^0.19.7", + "esbuild-register": "^3.5.0", + "gel": "^2.0.0" + }, + "bin": { + "drizzle-kit": "bin.cjs" + } + }, + "node_modules/drizzle-orm": { + "version": "0.40.1", + "resolved": "https://registry.npmjs.org/drizzle-orm/-/drizzle-orm-0.40.1.tgz", + "integrity": "sha512-aPNhtiJiPfm3qxz1czrnIDkfvkSdKGXYeZkpG55NPTVI186LmK2fBLMi4dsHpPHlJrZeQ92D322YFPHADBALew==", + "license": "Apache-2.0", + "peerDependencies": { + "@aws-sdk/client-rds-data": ">=3", + "@cloudflare/workers-types": ">=4", + "@electric-sql/pglite": ">=0.2.0", + "@libsql/client": ">=0.10.0", + "@libsql/client-wasm": ">=0.10.0", + "@neondatabase/serverless": ">=0.10.0", + "@op-engineering/op-sqlite": ">=2", + "@opentelemetry/api": "^1.4.1", + "@planetscale/database": ">=1", + "@prisma/client": "*", + "@tidbcloud/serverless": "*", + "@types/better-sqlite3": "*", + "@types/pg": "*", + "@types/sql.js": "*", + "@vercel/postgres": ">=0.8.0", + "@xata.io/client": "*", + "better-sqlite3": ">=7", + "bun-types": "*", + "expo-sqlite": ">=14.0.0", + "gel": ">=2", + "knex": "*", + "kysely": "*", + "mysql2": ">=2", + "pg": ">=8", + "postgres": ">=3", + "sql.js": ">=1", + "sqlite3": ">=5" + }, + "peerDependenciesMeta": { + "@aws-sdk/client-rds-data": { + "optional": true + }, + "@cloudflare/workers-types": { + "optional": true + }, + "@electric-sql/pglite": { + "optional": true + }, + "@libsql/client": { + "optional": true + }, + "@libsql/client-wasm": { + "optional": true + }, + "@neondatabase/serverless": { + "optional": true + }, + "@op-engineering/op-sqlite": { + "optional": true + }, + "@opentelemetry/api": { + "optional": true + }, + "@planetscale/database": { + "optional": true + }, + "@prisma/client": { + "optional": true + }, + "@tidbcloud/serverless": { + "optional": true + }, + "@types/better-sqlite3": { + "optional": true + }, + "@types/pg": { + "optional": true + }, + "@types/sql.js": { + "optional": true + }, + "@vercel/postgres": { + "optional": true + }, + "@xata.io/client": { + "optional": true + }, + "better-sqlite3": { + "optional": true + }, + "bun-types": { + "optional": true + }, + "expo-sqlite": { + "optional": true + }, + "gel": { + "optional": true + }, + "knex": { + "optional": true + }, + "kysely": { + "optional": true + }, + "mysql2": { + "optional": true + }, + "pg": { + "optional": true + }, + "postgres": { + "optional": true + }, + "prisma": { + "optional": true + }, + "sql.js": { + "optional": true + }, + "sqlite3": { + "optional": true + } + } + }, + "node_modules/drizzle-zod": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/drizzle-zod/-/drizzle-zod-0.8.3.tgz", + "integrity": "sha512-66yVOuvGhKJnTdiqj1/Xaaz9/qzOdRJADpDa68enqS6g3t0kpNkwNYjUuaeXgZfO/UWuIM9HIhSlJ6C5ZraMww==", + "license": "Apache-2.0", + "peerDependencies": { + "drizzle-orm": ">=0.36.0", + "zod": "^3.25.0 || ^4.0.0" + } + }, + "node_modules/effect": { + "version": "3.17.14", + "resolved": "https://registry.npmjs.org/effect/-/effect-3.17.14.tgz", + "integrity": "sha512-VpIZz72+cg3357vLkNHN8CG+Uq2X0QHNv3qkyGInvG3lG5K7Ala1JliGIAdgNvwzaN37J27FuzVD8m0kEiv2Ig==", + "license": "MIT", + "optional": true, + "dependencies": { + "@standard-schema/spec": "^1.0.0", + "fast-check": "^3.23.1" + } + }, + "node_modules/enhanced-resolve": { + "version": "5.18.3", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.3.tgz", + "integrity": "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/env-paths": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-3.0.0.tgz", + "integrity": "sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/esbuild": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.12.tgz", + "integrity": "sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==", + "devOptional": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=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" + } + }, + "node_modules/esbuild-register": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/esbuild-register/-/esbuild-register-3.6.0.tgz", + "integrity": "sha512-H2/S7Pm8a9CL1uhp9OvjwrBh5Pvx0H8qVOxNu8Wed9Y7qv56MPtq+GGM8RJpq6glYJn9Wspr8uw7l55uyinNeg==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.3.4" + }, + "peerDependencies": { + "esbuild": ">=0.12 <1" + } + }, + "node_modules/esbuild-runner": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/esbuild-runner/-/esbuild-runner-2.2.2.tgz", + "integrity": "sha512-fRFVXcmYVmSmtYm2mL8RlUASt2TDkGh3uRcvHFOKNr/T58VrfVeKD9uT9nlgxk96u0LS0ehS/GY7Da/bXWKkhw==", + "license": "Apache License 2.0", + "optional": true, + "dependencies": { + "source-map-support": "0.5.21", + "tslib": "2.4.0" + }, + "bin": { + "esr": "bin/esr.js" + }, + "peerDependencies": { + "esbuild": "*" + } + }, + "node_modules/esbuild-runner/node_modules/tslib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==", + "license": "0BSD", + "optional": true + }, + "node_modules/esbuild-wasm": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/esbuild-wasm/-/esbuild-wasm-0.19.12.tgz", + "integrity": "sha512-Zmc4hk6FibJZBcTx5/8K/4jT3/oG1vkGTEeKJUQFCUQKimD6Q7+adp/bdVQyYJFolMKaXkQnVZdV4O5ZaTYmyQ==", + "dev": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "9.36.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.36.0.tgz", + "integrity": "sha512-hB4FIzXovouYzwzECDcUkJ4OcfOEkXTv2zRY6B9bkwjx/cprAq0uvm1nl7zvQ0/TsUk0zQiN4uPfJpB9m+rPMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.0", + "@eslint/config-helpers": "^0.3.1", + "@eslint/core": "^0.15.2", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.36.0", + "@eslint/plugin-kit": "^0.3.5", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "@types/json-schema": "^7.0.15", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-config-prettier": { + "version": "10.1.8", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.8.tgz", + "integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==", + "dev": true, + "license": "MIT", + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "funding": { + "url": "https://opencollective.com/eslint-config-prettier" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-plugin-svelte": { + "version": "3.12.4", + "resolved": "https://registry.npmjs.org/eslint-plugin-svelte/-/eslint-plugin-svelte-3.12.4.tgz", + "integrity": "sha512-hD7wPe+vrPgx3U2X2b/wyTMtWobm660PygMGKrWWYTc9lvtY8DpNFDaU2CJQn1szLjGbn/aJ3g8WiXuKakrEkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.6.1", + "@jridgewell/sourcemap-codec": "^1.5.0", + "esutils": "^2.0.3", + "globals": "^16.0.0", + "known-css-properties": "^0.37.0", + "postcss": "^8.4.49", + "postcss-load-config": "^3.1.4", + "postcss-safe-parser": "^7.0.0", + "semver": "^7.6.3", + "svelte-eslint-parser": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://github.com/sponsors/ota-meshi" + }, + "peerDependencies": { + "eslint": "^8.57.1 || ^9.0.0", + "svelte": "^3.37.0 || ^4.0.0 || ^5.0.0" + }, + "peerDependenciesMeta": { + "svelte": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-unused-imports": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-unused-imports/-/eslint-plugin-unused-imports-4.2.0.tgz", + "integrity": "sha512-hLbJ2/wnjKq4kGA9AUaExVFIbNzyxYdVo49QZmKCnhk5pc9wcYRbfgLHvWJ8tnsdcseGhoUAddm9gn/lt+d74w==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@typescript-eslint/eslint-plugin": "^8.0.0-0 || ^7.0.0 || ^6.0.0 || ^5.0.0", + "eslint": "^9.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "@typescript-eslint/eslint-plugin": { + "optional": true + } + } + }, + "node_modules/eslint-scope": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/@eslint/core": { + "version": "0.15.2", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.2.tgz", + "integrity": "sha512-78Md3/Rrxh83gCxoUc0EiciuOHsIITzLy53m3d9UyiW8y9Dj2D29FeETqyKA+BRK76tnTp6RXWb3pCay8Oyomg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/eslint/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/esm-env": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.2.2.tgz", + "integrity": "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==", + "license": "MIT" + }, + "node_modules/espree": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/esquery": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrap": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/esrap/-/esrap-2.1.0.tgz", + "integrity": "sha512-yzmPNpl7TBbMRC5Lj2JlJZNPml0tzqoqP5B1JXycNUwtqma9AKCO0M2wHrdgsHcy1WRW7S9rJknAMtByg3usgA==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.4.15" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "dev": true, + "license": "MIT" + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-check": { + "version": "3.23.2", + "resolved": "https://registry.npmjs.org/fast-check/-/fast-check-3.23.2.tgz", + "integrity": "sha512-h5+1OzzfCC3Ef7VbtKdcv7zsstUQwUDlYpUTvjeUsJAssPgLn7QzbboPtL5ro04Mq0rPOsMzl7q5hIbRs2wD1A==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], + "license": "MIT", + "optional": true, + "dependencies": { + "pure-rand": "^6.1.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fastq": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", + "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "dev": true, + "license": "ISC" + }, + "node_modules/formsnap": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/formsnap/-/formsnap-2.0.1.tgz", + "integrity": "sha512-iJSe4YKd/W6WhLwKDVJU9FQeaJRpEFuolhju7ZXlRpUVyDdqFdMP8AUBICgnVvQPyP41IPAlBa/v0Eo35iE6wQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "svelte-toolbelt": "^0.5.0" + }, + "engines": { + "node": ">=18", + "pnpm": ">=8.7.0" + }, + "funding": { + "url": "https://github.com/sponsors/huntabyte" + }, + "peerDependencies": { + "svelte": "^5.0.0", + "sveltekit-superforms": "^2.19.0" + } + }, + "node_modules/formsnap/node_modules/svelte-toolbelt": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/svelte-toolbelt/-/svelte-toolbelt-0.5.0.tgz", + "integrity": "sha512-t3tenZcnfQoIeRuQf/jBU7bvTeT3TGkcEE+1EUr5orp0lR7NEpprflpuie3x9Dn0W9nOKqs3HwKGJeeN5Ok1sQ==", + "dev": true, + "funding": [ + "https://github.com/sponsors/huntabyte" + ], + "dependencies": { + "clsx": "^2.1.1", + "style-to-object": "^1.0.8" + }, + "engines": { + "node": ">=18", + "pnpm": ">=8.7.0" + }, + "peerDependencies": { + "svelte": "^5.0.0-next.126" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gel": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/gel/-/gel-2.1.1.tgz", + "integrity": "sha512-Newg9X7mRYskoBjSw70l1YnJ/ZGbq64VPyR821H5WVkTGpHG2O0mQILxCeUhxdYERLFY9B4tUyKLyf3uMTjtKw==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@petamoriken/float16": "^3.8.7", + "debug": "^4.3.4", + "env-paths": "^3.0.0", + "semver": "^7.6.2", + "shell-quote": "^1.8.1", + "which": "^4.0.0" + }, + "bin": { + "gel": "dist/cli.mjs" + }, + "engines": { + "node": ">= 18.0.0" + } + }, + "node_modules/gel/node_modules/isexe": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", + "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", + "devOptional": true, + "license": "ISC", + "engines": { + "node": ">=16" + } + }, + "node_modules/gel/node_modules/which": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", + "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", + "devOptional": true, + "license": "ISC", + "dependencies": { + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^16.13.0 || >=18.0.0" + } + }, + "node_modules/get-tsconfig": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.10.1.tgz", + "integrity": "sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "16.4.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-16.4.0.tgz", + "integrity": "sha512-ob/2LcVVaVGCYN+r14cnwnoDPUufjiYgSqRhiFD0Q1iI4Odora5RE8Iv1D24hAz5oMophRGkGz+yuvQmmUMnMw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true, + "license": "MIT" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-own-prop": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-own-prop/-/has-own-prop-2.0.0.tgz", + "integrity": "sha512-Pq0h+hvsVm6dDEa8x82GnLSYHOzNDt7f0ddFa3FqcQlgzEiptPqL+XrOJNavjOzSYiYWIrgeVYYgGlLmnxwilQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/human-id": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/human-id/-/human-id-4.1.1.tgz", + "integrity": "sha512-3gKm/gCSUipeLsRYZbbdA1BD83lBoWUkZ7G9VFrhWPAU76KwYo5KR8V28bpoPm/ygy0x5/GCbpRQdY7VLYCoIg==", + "dev": true, + "license": "MIT", + "bin": { + "human-id": "dist/cli.js" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inline-style-parser": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.4.tgz", + "integrity": "sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/internmap": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", + "integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-reference": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz", + "integrity": "sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "*" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "license": "ISC" + }, + "node_modules/jiti": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.0.tgz", + "integrity": "sha512-VXe6RjJkBPj0ohtqaO8vSWP3ZhAKo66fKrFNCll4BTcwljPLz03pCbaNKfzGP5MbrCYcbJ7v0nOYYwUzTEIdXQ==", + "devOptional": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/joi": { + "version": "17.13.3", + "resolved": "https://registry.npmjs.org/joi/-/joi-17.13.3.tgz", + "integrity": "sha512-otDA4ldcIx+ZXsKHWmp0YizCweVRZG96J10b0FevjfuncLO1oX59THoAmHkNubYJ+9gWsYsp5k8v4ib6oDv1fA==", + "license": "BSD-3-Clause", + "optional": true, + "dependencies": { + "@hapi/hoek": "^9.3.0", + "@hapi/topo": "^5.1.0", + "@sideway/address": "^4.1.5", + "@sideway/formula": "^3.0.1", + "@sideway/pinpoint": "^2.0.0" + } + }, + "node_modules/jose": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jose/-/jose-6.1.0.tgz", + "integrity": "sha512-TTQJyoEoKcC1lscpVDCSsVgYzUDg/0Bt3WE//WiTPK6uOCQC2KZS4MpugbMWt/zyjkopgZoXhZuCi00gLudfUA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "node_modules/js-sha256": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/js-sha256/-/js-sha256-0.11.1.tgz", + "integrity": "sha512-o6WSo/LUvY2uC4j7mO50a2ms7E/EAdbP0swigLV+nzHKTTaYnaLIWJ02VdXrsJX0vGedDESQnLsOekr94ryfjg==", + "dev": true, + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-to-ts": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/json-schema-to-ts/-/json-schema-to-ts-3.1.1.tgz", + "integrity": "sha512-+DWg8jCJG2TEnpy7kOm/7/AxaYoaRbjVB4LFZLySZlWn8exGs3A4OLJR966cVvU26N7X9TWxl+Jsw7dzAqKT6g==", + "license": "MIT", + "optional": true, + "dependencies": { + "@babel/runtime": "^7.18.3", + "ts-algebra": "^2.0.0" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/kleur": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", + "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/known-css-properties": { + "version": "0.37.0", + "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.37.0.tgz", + "integrity": "sha512-JCDrsP4Z1Sb9JwG0aJ8Eo2r7k4Ou5MwmThS/6lcIe1ICyb7UBJKGRIUUdqc2ASdE/42lgz6zFUnzAIhtXnBVrQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/kysely": { + "version": "0.27.6", + "resolved": "https://registry.npmjs.org/kysely/-/kysely-0.27.6.tgz", + "integrity": "sha512-FIyV/64EkKhJmjgC0g2hygpBv5RNWVPyNCqSAD7eTCv6eFWNIi4PN1UvdSJGicN/o35bnevgis4Y0UDC0qi8jQ==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/layerchart": { + "version": "2.0.0-next.39", + "resolved": "https://registry.npmjs.org/layerchart/-/layerchart-2.0.0-next.39.tgz", + "integrity": "sha512-IS2OQnNFAhkcKL1snNb3G51U99ZxGDosrIMrhi2Ov8dmAZD5NnOtrx9UR4Vm+dbUuUNLuZ/b2tkaYvvvSsg+DQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@dagrejs/dagre": "^1.1.5", + "@layerstack/svelte-actions": "1.0.1-next.14", + "@layerstack/svelte-state": "0.1.0-next.19", + "@layerstack/tailwind": "2.0.0-next.17", + "@layerstack/utils": "2.0.0-next.14", + "d3-array": "^3.2.4", + "d3-color": "^3.1.0", + "d3-delaunay": "^6.0.4", + "d3-dsv": "^3.0.1", + "d3-force": "^3.0.0", + "d3-geo": "^3.1.1", + "d3-geo-voronoi": "^2.1.0", + "d3-hierarchy": "^3.1.2", + "d3-interpolate": "^3.0.1", + "d3-interpolate-path": "^2.3.0", + "d3-path": "^3.1.0", + "d3-quadtree": "^3.0.1", + "d3-random": "^3.0.1", + "d3-sankey": "^0.12.3", + "d3-scale": "^4.0.2", + "d3-scale-chromatic": "^3.1.0", + "d3-shape": "^3.2.0", + "d3-tile": "^1.0.0", + "d3-time": "^3.1.0", + "lodash-es": "^4.17.21", + "memoize": "^10.1.0", + "runed": "^0.31.1" + }, + "peerDependencies": { + "svelte": "^5.0.0" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/libphonenumber-js": { + "version": "1.12.22", + "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.12.22.tgz", + "integrity": "sha512-nzdkDyqlcLV754o1RrOJxh8kycG+63odJVUqnK4dxhw7buNkdTqJc/a/CE0h599dTJgFbzvr6GEOemFBSBryAA==", + "license": "MIT", + "optional": true + }, + "node_modules/lightningcss": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.1.tgz", + "integrity": "sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg==", + "devOptional": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-darwin-arm64": "1.30.1", + "lightningcss-darwin-x64": "1.30.1", + "lightningcss-freebsd-x64": "1.30.1", + "lightningcss-linux-arm-gnueabihf": "1.30.1", + "lightningcss-linux-arm64-gnu": "1.30.1", + "lightningcss-linux-arm64-musl": "1.30.1", + "lightningcss-linux-x64-gnu": "1.30.1", + "lightningcss-linux-x64-musl": "1.30.1", + "lightningcss-win32-arm64-msvc": "1.30.1", + "lightningcss-win32-x64-msvc": "1.30.1" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.1.tgz", + "integrity": "sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.1.tgz", + "integrity": "sha512-k1EvjakfumAQoTfcXUcHQZhSpLlkAuEkdMBsI/ivWw9hL+7FtilQc0Cy3hrx0AAQrVtQAbMI7YjCgYgvn37PzA==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.1.tgz", + "integrity": "sha512-kmW6UGCGg2PcyUE59K5r0kWfKPAVy4SltVeut+umLCFoJ53RdCUWxcRDzO1eTaxf/7Q2H7LTquFHPL5R+Gjyig==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.1.tgz", + "integrity": "sha512-MjxUShl1v8pit+6D/zSPq9S9dQ2NPFSQwGvxBCYaBYLPlCWuPh9/t1MRS8iUaR8i+a6w7aps+B4N0S1TYP/R+Q==", + "cpu": [ + "arm" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.1.tgz", + "integrity": "sha512-gB72maP8rmrKsnKYy8XUuXi/4OctJiuQjcuqWNlJQ6jZiWqtPvqFziskH3hnajfvKB27ynbVCucKSm2rkQp4Bw==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.1.tgz", + "integrity": "sha512-jmUQVx4331m6LIX+0wUhBbmMX7TCfjF5FoOH6SD1CttzuYlGNVpA7QnrmLxrsub43ClTINfGSYyHe2HWeLl5CQ==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.1.tgz", + "integrity": "sha512-piWx3z4wN8J8z3+O5kO74+yr6ze/dKmPnI7vLqfSqI8bccaTGY5xiSGVIJBDd5K5BHlvVLpUB3S2YCfelyJ1bw==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.1.tgz", + "integrity": "sha512-rRomAK7eIkL+tHY0YPxbc5Dra2gXlI63HL+v1Pdi1a3sC+tJTcFrHX+E86sulgAXeI7rSzDYhPSeHHjqFhqfeQ==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.1.tgz", + "integrity": "sha512-mSL4rqPi4iXq5YVqzSsJgMVFENoa4nGTT/GjO2c0Yl9OuQfPsIfncvLrEW6RbbB24WtZ3xP/2CCmI3tNkNV4oA==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.1.tgz", + "integrity": "sha512-PVqXh48wh4T53F/1CCu8PIPCxLzWyCnn/9T5W1Jpmdy5h9Cwd+0YQS6/LwhHXSafuc61/xg9Lv5OrCby6a++jg==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lilconfig": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", + "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/locate-character": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-character/-/locate-character-3.0.0.tgz", + "integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==", + "license": "MIT" + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash-es": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", + "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/magic-string": { + "version": "0.30.19", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.19.tgz", + "integrity": "sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/mdsvex": { + "version": "0.12.6", + "resolved": "https://registry.npmjs.org/mdsvex/-/mdsvex-0.12.6.tgz", + "integrity": "sha512-pupx2gzWh3hDtm/iDW4WuCpljmyHbHi34r7ktOqpPGvyiM4MyfNgdJ3qMizXdgCErmvYC9Nn/qyjePy+4ss9Wg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.4", + "@types/unist": "^2.0.3", + "prism-svelte": "^0.4.7", + "prismjs": "^1.17.1", + "unist-util-visit": "^2.0.1", + "vfile-message": "^2.0.4" + }, + "peerDependencies": { + "svelte": "^3.56.0 || ^4.0.0 || ^5.0.0-next.120" + } + }, + "node_modules/memoize": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/memoize/-/memoize-10.1.0.tgz", + "integrity": "sha512-MMbFhJzh4Jlg/poq1si90XRlTZRDHVqdlz2mPyGJ6kqMpyHUyVpDd5gpFAvVehW64+RA1eKE9Yt8aSLY7w2Kgg==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-function": "^5.0.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sindresorhus/memoize?sponsor=1" + } + }, + "node_modules/memoize-weak": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/memoize-weak/-/memoize-weak-1.0.2.tgz", + "integrity": "sha512-gj39xkrjEw7nCn4nJ1M5ms6+MyMlyiGmttzsqAUsAKn6bYKwuTHh/AO3cKPF8IBrTIYTxb0wWXFs3E//Y8VoWQ==", + "license": "ISC" + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/micromatch/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/mimic-function": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", + "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mini-svg-data-uri": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/mini-svg-data-uri/-/mini-svg-data-uri-1.4.4.tgz", + "integrity": "sha512-r9deDe9p5FJUPZAk3A59wGH7Ii9YrjjWw0jmw/liSbHl2CHiyXj6FcDXDu2K3TjVAXqiJdaw3xxwlZZr9E6nHg==", + "dev": true, + "license": "MIT", + "bin": { + "mini-svg-data-uri": "cli.js" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/minizlib": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.1.0.tgz", + "integrity": "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "minipass": "^7.1.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/mode-watcher": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/mode-watcher/-/mode-watcher-1.1.0.tgz", + "integrity": "sha512-mUT9RRGPDYenk59qJauN1rhsIMKBmWA3xMF+uRwE8MW/tjhaDSCCARqkSuDTq8vr4/2KcAxIGVjACxTjdk5C3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "runed": "^0.25.0", + "svelte-toolbelt": "^0.7.1" + }, + "peerDependencies": { + "svelte": "^5.27.0" + } + }, + "node_modules/mode-watcher/node_modules/runed": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/runed/-/runed-0.25.0.tgz", + "integrity": "sha512-7+ma4AG9FT2sWQEA0Egf6mb7PBT2vHyuHail1ie8ropfSjvZGtEAx8YTmUjv/APCsdRRxEVvArNjALk9zFSOrg==", + "dev": true, + "funding": [ + "https://github.com/sponsors/huntabyte", + "https://github.com/sponsors/tglide" + ], + "dependencies": { + "esm-env": "^1.0.0" + }, + "peerDependencies": { + "svelte": "^5.7.0" + } + }, + "node_modules/mode-watcher/node_modules/svelte-toolbelt": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/svelte-toolbelt/-/svelte-toolbelt-0.7.1.tgz", + "integrity": "sha512-HcBOcR17Vx9bjaOceUvxkY3nGmbBmCBBbuWLLEWO6jtmWH8f/QoWmbyUfQZrpDINH39en1b8mptfPQT9VKQ1xQ==", + "dev": true, + "funding": [ + "https://github.com/sponsors/huntabyte" + ], + "dependencies": { + "clsx": "^2.1.1", + "runed": "^0.23.2", + "style-to-object": "^1.0.8" + }, + "engines": { + "node": ">=18", + "pnpm": ">=8.7.0" + }, + "peerDependencies": { + "svelte": "^5.0.0" + } + }, + "node_modules/mode-watcher/node_modules/svelte-toolbelt/node_modules/runed": { + "version": "0.23.4", + "resolved": "https://registry.npmjs.org/runed/-/runed-0.23.4.tgz", + "integrity": "sha512-9q8oUiBYeXIDLWNK5DfCWlkL0EW3oGbk845VdKlPeia28l751VpfesaB/+7pI6rnbx1I6rqoZ2fZxptOJLxILA==", + "dev": true, + "funding": [ + "https://github.com/sponsors/huntabyte", + "https://github.com/sponsors/tglide" + ], + "dependencies": { + "esm-env": "^1.0.0" + }, + "peerDependencies": { + "svelte": "^5.7.0" + } + }, + "node_modules/mri": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", + "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/mrmime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", + "integrity": "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-addon-api": { + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.5.0.tgz", + "integrity": "sha512-/bRZty2mXUIFY/xU5HLvveNHlswNJej+RnxBjOMkidWfwZzgTbPG1E3K5TOxRLOR+5hX7bSofy8yf1hZevMS8A==", + "license": "MIT", + "engines": { + "node": "^18 || ^20 || >= 21" + } + }, + "node_modules/node-gyp-build": { + "version": "4.8.4", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz", + "integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==", + "license": "MIT", + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" + } + }, + "node_modules/normalize-url": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-8.1.0.tgz", + "integrity": "sha512-X06Mfd/5aKsRHc0O0J5CUedwnPmnDtLF2+nq+KN9KSDlJHkPuh0JUviWjEWMe0SW/9TDdSLVPuk7L5gGTIA1/w==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/openapi-types": { + "version": "12.1.3", + "resolved": "https://registry.npmjs.org/openapi-types/-/openapi-types-12.1.3.tgz", + "integrity": "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==", + "license": "MIT" + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-load-config": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-3.1.4.tgz", + "integrity": "sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==", + "dev": true, + "license": "MIT", + "dependencies": { + "lilconfig": "^2.0.5", + "yaml": "^1.10.2" + }, + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": ">=8.0.9", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "postcss": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/postcss-load-config/node_modules/yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 6" + } + }, + "node_modules/postcss-safe-parser": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-safe-parser/-/postcss-safe-parser-7.0.1.tgz", + "integrity": "sha512-0AioNCJZ2DPYz5ABT6bddIqlhgwhpHZ/l65YAYo0BCIn0xiDpsnTHz0gnoTGk0OXZW0JRs+cDwL8u/teRdz+8A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss-safe-parser" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "engines": { + "node": ">=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-scss": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/postcss-scss/-/postcss-scss-4.0.9.tgz", + "integrity": "sha512-AjKOeiwAitL/MXxQW2DliT28EKukvvbEWx3LBmJIRN8KfBGZbRTxNYW0kSqi1COiTZ57nZ9NW06S6ux//N1c9A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss-scss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "engines": { + "node": ">=12.0" + }, + "peerDependencies": { + "postcss": "^8.4.29" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.0.10", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz", + "integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postgres": { + "version": "3.4.7", + "resolved": "https://registry.npmjs.org/postgres/-/postgres-3.4.7.tgz", + "integrity": "sha512-Jtc2612XINuBjIl/QTWsV5UvE8UHuNblcO3vVADSrKsrc6RqGX6lOW1cEo3CM2v0XG4Nat8nI+YM7/f26VxXLw==", + "license": "Unlicense", + "engines": { + "node": ">=12" + }, + "funding": { + "type": "individual", + "url": "https://github.com/sponsors/porsager" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz", + "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/prettier-plugin-svelte": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/prettier-plugin-svelte/-/prettier-plugin-svelte-3.4.0.tgz", + "integrity": "sha512-pn1ra/0mPObzqoIQn/vUTR3ZZI6UuZ0sHqMK5x2jMLGrs53h0sXhkVuDcrlssHwIMk7FYrMjHBPoUSyyEEDlBQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "prettier": "^3.0.0", + "svelte": "^3.2.0 || ^4.0.0-next.0 || ^5.0.0-next.0" + } + }, + "node_modules/prettier-plugin-tailwindcss": { + "version": "0.6.14", + "resolved": "https://registry.npmjs.org/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.6.14.tgz", + "integrity": "sha512-pi2e/+ZygeIqntN+vC573BcW5Cve8zUB0SSAGxqpB4f96boZF4M3phPVoOFCeypwkpRYdi7+jQ5YJJUwrkGUAg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.21.3" + }, + "peerDependencies": { + "@ianvs/prettier-plugin-sort-imports": "*", + "@prettier/plugin-hermes": "*", + "@prettier/plugin-oxc": "*", + "@prettier/plugin-pug": "*", + "@shopify/prettier-plugin-liquid": "*", + "@trivago/prettier-plugin-sort-imports": "*", + "@zackad/prettier-plugin-twig": "*", + "prettier": "^3.0", + "prettier-plugin-astro": "*", + "prettier-plugin-css-order": "*", + "prettier-plugin-import-sort": "*", + "prettier-plugin-jsdoc": "*", + "prettier-plugin-marko": "*", + "prettier-plugin-multiline-arrays": "*", + "prettier-plugin-organize-attributes": "*", + "prettier-plugin-organize-imports": "*", + "prettier-plugin-sort-imports": "*", + "prettier-plugin-style-order": "*", + "prettier-plugin-svelte": "*" + }, + "peerDependenciesMeta": { + "@ianvs/prettier-plugin-sort-imports": { + "optional": true + }, + "@prettier/plugin-hermes": { + "optional": true + }, + "@prettier/plugin-oxc": { + "optional": true + }, + "@prettier/plugin-pug": { + "optional": true + }, + "@shopify/prettier-plugin-liquid": { + "optional": true + }, + "@trivago/prettier-plugin-sort-imports": { + "optional": true + }, + "@zackad/prettier-plugin-twig": { + "optional": true + }, + "prettier-plugin-astro": { + "optional": true + }, + "prettier-plugin-css-order": { + "optional": true + }, + "prettier-plugin-import-sort": { + "optional": true + }, + "prettier-plugin-jsdoc": { + "optional": true + }, + "prettier-plugin-marko": { + "optional": true + }, + "prettier-plugin-multiline-arrays": { + "optional": true + }, + "prettier-plugin-organize-attributes": { + "optional": true + }, + "prettier-plugin-organize-imports": { + "optional": true + }, + "prettier-plugin-sort-imports": { + "optional": true + }, + "prettier-plugin-style-order": { + "optional": true + }, + "prettier-plugin-svelte": { + "optional": true + } + } + }, + "node_modules/prism-svelte": { + "version": "0.4.7", + "resolved": "https://registry.npmjs.org/prism-svelte/-/prism-svelte-0.4.7.tgz", + "integrity": "sha512-yABh19CYbM24V7aS7TuPYRNMqthxwbvx6FF/Rw920YbyBWO3tnyPIqRMgHuSVsLmuHkkBS1Akyof463FVdkeDQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/prismjs": { + "version": "1.30.0", + "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.30.0.tgz", + "integrity": "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/property-expr": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/property-expr/-/property-expr-2.0.6.tgz", + "integrity": "sha512-SVtmxhRE/CGkn3eZY1T6pC8Nln6Fr/lu1mKSgRud0eC73whjGfoAogbn78LkD8aFL0zz3bAFerKSnOl7NlErBA==", + "license": "MIT", + "optional": true + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/pure-rand": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", + "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], + "license": "MIT", + "optional": true + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/radash": { + "version": "12.1.1", + "resolved": "https://registry.npmjs.org/radash/-/radash-12.1.1.tgz", + "integrity": "sha512-h36JMxKRqrAxVD8201FrCpyeNuUY9Y5zZwujr20fFO77tpUtGa6EZzfKw/3WaiBX95fq7+MpsuMLNdSnORAwSA==", + "license": "MIT", + "engines": { + "node": ">=14.18.0" + } + }, + "node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/resolve": { + "version": "1.22.10", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", + "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "devOptional": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/robust-predicates": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.2.tgz", + "integrity": "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==", + "dev": true, + "license": "Unlicense" + }, + "node_modules/rollup": { + "version": "4.52.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.52.2.tgz", + "integrity": "sha512-I25/2QgoROE1vYV+NQ1En9T9UFB9Cmfm2CJ83zZOlaDpvz29wGQSZXWKw7MiNXau7wYgB/T9fVIdIuEQ+KbiiA==", + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.52.2", + "@rollup/rollup-android-arm64": "4.52.2", + "@rollup/rollup-darwin-arm64": "4.52.2", + "@rollup/rollup-darwin-x64": "4.52.2", + "@rollup/rollup-freebsd-arm64": "4.52.2", + "@rollup/rollup-freebsd-x64": "4.52.2", + "@rollup/rollup-linux-arm-gnueabihf": "4.52.2", + "@rollup/rollup-linux-arm-musleabihf": "4.52.2", + "@rollup/rollup-linux-arm64-gnu": "4.52.2", + "@rollup/rollup-linux-arm64-musl": "4.52.2", + "@rollup/rollup-linux-loong64-gnu": "4.52.2", + "@rollup/rollup-linux-ppc64-gnu": "4.52.2", + "@rollup/rollup-linux-riscv64-gnu": "4.52.2", + "@rollup/rollup-linux-riscv64-musl": "4.52.2", + "@rollup/rollup-linux-s390x-gnu": "4.52.2", + "@rollup/rollup-linux-x64-gnu": "4.52.2", + "@rollup/rollup-linux-x64-musl": "4.52.2", + "@rollup/rollup-openharmony-arm64": "4.52.2", + "@rollup/rollup-win32-arm64-msvc": "4.52.2", + "@rollup/rollup-win32-ia32-msvc": "4.52.2", + "@rollup/rollup-win32-x64-gnu": "4.52.2", + "@rollup/rollup-win32-x64-msvc": "4.52.2", + "fsevents": "~2.3.2" + } + }, + "node_modules/rou3": { + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/rou3/-/rou3-0.7.5.tgz", + "integrity": "sha512-bwUHDHw1HSARty7TWNV71R0NZs5fOt74OM+hcMdJyPfchfRktEmxLoMSNa7PwEp6WqJ0a3feKztsIfTUEYhskw==", + "license": "MIT" + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/runed": { + "version": "0.31.1", + "resolved": "https://registry.npmjs.org/runed/-/runed-0.31.1.tgz", + "integrity": "sha512-v3czcTnO+EJjiPvD4dwIqfTdHLZ8oH0zJheKqAHh9QMViY7Qb29UlAMRpX7ZtHh7AFqV60KmfxaJ9QMy+L1igQ==", + "dev": true, + "funding": [ + "https://github.com/sponsors/huntabyte", + "https://github.com/sponsors/tglide" + ], + "license": "MIT", + "dependencies": { + "esm-env": "^1.0.0" + }, + "peerDependencies": { + "svelte": "^5.7.0" + } + }, + "node_modules/rw": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", + "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/sade": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz", + "integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==", + "license": "MIT", + "dependencies": { + "mri": "^1.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true, + "license": "MIT" + }, + "node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "devOptional": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/set-cookie-parser": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz", + "integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==", + "license": "MIT" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/shell-quote": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.3.tgz", + "integrity": "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/sirv": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/sirv/-/sirv-3.0.2.tgz", + "integrity": "sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==", + "license": "MIT", + "dependencies": { + "@polka/url": "^1.0.0-next.24", + "mrmime": "^2.0.0", + "totalist": "^3.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "devOptional": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/sqlite-wasm-kysely": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/sqlite-wasm-kysely/-/sqlite-wasm-kysely-0.3.0.tgz", + "integrity": "sha512-TzjBNv7KwRw6E3pdKdlRyZiTmUIE0UttT/Sl56MVwVARl/u5gp978KepazCJZewFUnlWHz9i3NQd4kOtP/Afdg==", + "dev": true, + "dependencies": { + "@sqlite.org/sqlite-wasm": "^3.48.0-build2" + }, + "peerDependencies": { + "kysely": "*" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/style-to-object": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.9.tgz", + "integrity": "sha512-G4qppLgKu/k6FwRpHiGiKPaPTFcG3g4wNVX/Qsfu+RqQM30E7Tyu/TEgxcL9PNLF5pdRLwQdE3YKKf+KF2Dzlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "inline-style-parser": "0.2.4" + } + }, + "node_modules/superstruct": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/superstruct/-/superstruct-2.0.2.tgz", + "integrity": "sha512-uV+TFRZdXsqXTL2pRvujROjdZQ4RAlBUS5BTh9IGm+jTqQntYThciG/qu57Gs69yjnVUSqdxF9YLmSnpupBW9A==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/svelte": { + "version": "5.39.6", + "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.39.6.tgz", + "integrity": "sha512-bOJXmuwLNaoqPCTWO8mPu/fwxI5peGE5Efe7oo6Cakpz/G60vsnVF6mxbGODaxMUFUKEnjm6XOwHEqOht6cbvw==", + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.4", + "@jridgewell/sourcemap-codec": "^1.5.0", + "@sveltejs/acorn-typescript": "^1.0.5", + "@types/estree": "^1.0.5", + "acorn": "^8.12.1", + "aria-query": "^5.3.1", + "axobject-query": "^4.1.0", + "clsx": "^2.1.1", + "esm-env": "^1.2.1", + "esrap": "^2.1.0", + "is-reference": "^3.0.3", + "locate-character": "^3.0.0", + "magic-string": "^0.30.11", + "zimmerframe": "^1.1.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/svelte-check": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/svelte-check/-/svelte-check-4.3.2.tgz", + "integrity": "sha512-71udP5w2kaSTcX8iV0hn3o2FWlabQHhJTJLIQrCqMsrcOeDUO2VhCQKKCA8AMVHSPwdxLEWkUWh9OKxns5PD9w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.25", + "chokidar": "^4.0.1", + "fdir": "^6.2.0", + "picocolors": "^1.0.0", + "sade": "^1.7.4" + }, + "bin": { + "svelte-check": "bin/svelte-check" + }, + "engines": { + "node": ">= 18.0.0" + }, + "peerDependencies": { + "svelte": "^4.0.0 || ^5.0.0-next.0", + "typescript": ">=5.0.0" + } + }, + "node_modules/svelte-eslint-parser": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/svelte-eslint-parser/-/svelte-eslint-parser-1.3.3.tgz", + "integrity": "sha512-oTrDR8Z7Wnguut7QH3YKh7JR19xv1seB/bz4dxU5J/86eJtZOU4eh0/jZq4dy6tAlz/KROxnkRQspv5ZEt7t+Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-scope": "^8.2.0", + "eslint-visitor-keys": "^4.0.0", + "espree": "^10.0.0", + "postcss": "^8.4.49", + "postcss-scss": "^4.0.9", + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://github.com/sponsors/ota-meshi" + }, + "peerDependencies": { + "svelte": "^3.37.0 || ^4.0.0 || ^5.0.0" + }, + "peerDependenciesMeta": { + "svelte": { + "optional": true + } + } + }, + "node_modules/svelte-eslint-parser/node_modules/postcss-selector-parser": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", + "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/svelte-sonner": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/svelte-sonner/-/svelte-sonner-1.0.5.tgz", + "integrity": "sha512-9dpGPFqKb/QWudYqGnEz93vuY+NgCEvyNvxoCLMVGw6sDN/3oVeKV1xiEirW2E1N3vJEyj5imSBNOGltQHA7mg==", + "dev": true, + "license": "MIT", + "dependencies": { + "runed": "^0.28.0" + }, + "peerDependencies": { + "svelte": "^5.0.0" + } + }, + "node_modules/svelte-sonner/node_modules/runed": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/runed/-/runed-0.28.0.tgz", + "integrity": "sha512-k2xx7RuO9hWcdd9f+8JoBeqWtYrm5CALfgpkg2YDB80ds/QE4w0qqu34A7fqiAwiBBSBQOid7TLxwxVC27ymWQ==", + "dev": true, + "funding": [ + "https://github.com/sponsors/huntabyte", + "https://github.com/sponsors/tglide" + ], + "license": "MIT", + "dependencies": { + "esm-env": "^1.0.0" + }, + "peerDependencies": { + "svelte": "^5.7.0" + } + }, + "node_modules/svelte-toolbelt": { + "version": "0.10.5", + "resolved": "https://registry.npmjs.org/svelte-toolbelt/-/svelte-toolbelt-0.10.5.tgz", + "integrity": "sha512-8e+eWTgxw1aiLxhDE8Rb1X6AoLitqpJz+WhAul2W7W58C8KoLoJQf1TgQdFPBiCPJ0Jg5y0Zi1uyua9em4VS0w==", + "dev": true, + "funding": [ + "https://github.com/sponsors/huntabyte" + ], + "dependencies": { + "clsx": "^2.1.1", + "runed": "^0.29.0", + "style-to-object": "^1.0.8" + }, + "engines": { + "node": ">=18", + "pnpm": ">=8.7.0" + }, + "peerDependencies": { + "svelte": "^5.30.2" + } + }, + "node_modules/svelte-toolbelt/node_modules/runed": { + "version": "0.29.2", + "resolved": "https://registry.npmjs.org/runed/-/runed-0.29.2.tgz", + "integrity": "sha512-0cq6cA6sYGZwl/FvVqjx9YN+1xEBu9sDDyuWdDW1yWX7JF2wmvmVKfH+hVCZs+csW+P3ARH92MjI3H9QTagOQA==", + "dev": true, + "funding": [ + "https://github.com/sponsors/huntabyte", + "https://github.com/sponsors/tglide" + ], + "license": "MIT", + "dependencies": { + "esm-env": "^1.0.0" + }, + "peerDependencies": { + "svelte": "^5.7.0" + } + }, + "node_modules/svelte/node_modules/is-reference": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.3.tgz", + "integrity": "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.6" + } + }, + "node_modules/sveltekit-superforms": { + "version": "2.27.1", + "resolved": "https://registry.npmjs.org/sveltekit-superforms/-/sveltekit-superforms-2.27.1.tgz", + "integrity": "sha512-cvq2AevkZ0Zrk0w0gNM3kjcnJMtJ0jzu+2zqDoM9a+lZa+8bGpNl4YqxVkemiJNkGnFgNC8xr5xF5BlMzjookQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ciscoheat" + }, + { + "type": "ko-fi", + "url": "https://ko-fi.com/ciscoheat" + }, + { + "type": "paypal", + "url": "https://www.paypal.com/donate/?hosted_button_id=NY7F5ALHHSVQS" + } + ], + "license": "MIT", + "dependencies": { + "devalue": "^5.1.1", + "memoize-weak": "^1.0.2", + "ts-deepmerge": "^7.0.3" + }, + "optionalDependencies": { + "@exodus/schemasafe": "^1.3.0", + "@gcornut/valibot-json-schema": "^0.42.0", + "@sinclair/typebox": "^0.34.35", + "@typeschema/class-validator": "^0.3.0", + "@vinejs/vine": "^3.0.1", + "arktype": "^2.1.20", + "class-validator": "^0.14.2", + "effect": "^3.16.7", + "joi": "^17.13.3", + "json-schema-to-ts": "^3.1.1", + "superstruct": "^2.0.2", + "valibot": "^1.1.0", + "yup": "^1.6.1", + "zod": "^3.25.64", + "zod-to-json-schema": "^3.24.5" + }, + "peerDependencies": { + "@exodus/schemasafe": "^1.3.0", + "@sinclair/typebox": "^0.34.28", + "@sveltejs/kit": "1.x || 2.x", + "@typeschema/class-validator": "^0.3.0", + "@vinejs/vine": "^1.8.0 || ^2.0.0 || ^3.0.0", + "arktype": ">=2.0.0-rc.23", + "class-validator": "^0.14.1", + "effect": "^3.13.7", + "joi": "^17.13.1", + "superstruct": "^2.0.2", + "svelte": "3.x || 4.x || >=5.0.0-next.51", + "valibot": "^1.0.0", + "yup": "^1.4.0", + "zod": "^3.25.0" + }, + "peerDependenciesMeta": { + "@exodus/schemasafe": { + "optional": true + }, + "@sinclair/typebox": { + "optional": true + }, + "@typeschema/class-validator": { + "optional": true + }, + "@vinejs/vine": { + "optional": true + }, + "arktype": { + "optional": true + }, + "class-validator": { + "optional": true + }, + "effect": { + "optional": true + }, + "joi": { + "optional": true + }, + "superstruct": { + "optional": true + }, + "valibot": { + "optional": true + }, + "yup": { + "optional": true + }, + "zod": { + "optional": true + } + } + }, + "node_modules/sveltekit-superforms/node_modules/@sinclair/typebox": { + "version": "0.34.41", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.41.tgz", + "integrity": "sha512-6gS8pZzSXdyRHTIqoqSVknxolr1kzfy4/CeDnrzsVz8TTIWUbOBr6gnzOmTYJ3eXQNh4IYHIGi5aIL7sOZ2G/g==", + "license": "MIT", + "optional": true + }, + "node_modules/sveltekit-superforms/node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "license": "MIT", + "optional": true, + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/sveltekit-superforms/node_modules/zod-to-json-schema": { + "version": "3.24.6", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.24.6.tgz", + "integrity": "sha512-h/z3PKvcTcTetyjl1fkj79MHNEjm+HpD6NXheWjzOekY7kV+lwDYnHw+ivHkijnCSMz1yJaWBD9vu/Fcmk+vEg==", + "license": "ISC", + "optional": true, + "peerDependencies": { + "zod": "^3.24.1" + } + }, + "node_modules/tabbable": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz", + "integrity": "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==", + "dev": true, + "license": "MIT" + }, + "node_modules/tagged-tag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/tagged-tag/-/tagged-tag-1.0.0.tgz", + "integrity": "sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng==", + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/tailwind-merge": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.3.1.tgz", + "integrity": "sha512-gBXpgUm/3rp1lMZZrM/w7D8GKqshif0zAymAhbCyIt8KMe+0v9DQ7cdYLR4FHH/cKpdTXb+A/tKKU3eolfsI+g==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/dcastil" + } + }, + "node_modules/tailwind-variants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/tailwind-variants/-/tailwind-variants-1.0.0.tgz", + "integrity": "sha512-2WSbv4ulEEyuBKomOunut65D8UZwxrHoRfYnxGcQNnHqlSCp2+B7Yz2W+yrNDrxRodOXtGD/1oCcKGNBnUqMqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "tailwind-merge": "3.0.2" + }, + "engines": { + "node": ">=16.x", + "pnpm": ">=7.x" + }, + "peerDependencies": { + "tailwindcss": "*" + } + }, + "node_modules/tailwind-variants/node_modules/tailwind-merge": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.0.2.tgz", + "integrity": "sha512-l7z+OYZ7mu3DTqrL88RiKrKIqO3NcpEO8V/Od04bNpvk0kiIFndGEoqfuzvj4yuhRkHKjRkII2z+KS2HfPcSxw==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/dcastil" + } + }, + "node_modules/tailwindcss": { + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.13.tgz", + "integrity": "sha512-i+zidfmTqtwquj4hMEwdjshYYgMbOrPzb9a0M3ZgNa0JMoZeFC6bxZvO8yr8ozS6ix2SDz0+mvryPeBs2TFE+w==", + "dev": true, + "license": "MIT" + }, + "node_modules/tapable": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.3.tgz", + "integrity": "sha512-ZL6DDuAlRlLGghwcfmSn9sK3Hr6ArtyudlSAiCqQ6IfE+b+HHbydbYDIG15IfS5do+7XQQBdBiubF/cV2dnDzg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/tar": { + "version": "7.5.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.1.tgz", + "integrity": "sha512-nlGpxf+hv0v7GkWBK2V9spgactGOp0qvfWRxUMjqHyzrt3SgwE48DIv/FhqPHJYLHpgW1opq3nERbz5Anq7n1g==", + "dev": true, + "license": "ISC", + "dependencies": { + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.1.0", + "yallist": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/tiny-case": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-case/-/tiny-case-1.0.3.tgz", + "integrity": "sha512-Eet/eeMhkO6TX8mnUteS9zgPbUMQa4I6Kkp5ORiBD5476/m+PIRiumP5tmh5ioJpH7k51Kehawy2UDfsnxxY8Q==", + "license": "MIT", + "optional": true + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toposort": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/toposort/-/toposort-2.0.2.tgz", + "integrity": "sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg==", + "license": "MIT", + "optional": true + }, + "node_modules/totalist": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", + "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/ts-algebra": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ts-algebra/-/ts-algebra-2.0.0.tgz", + "integrity": "sha512-FPAhNPFMrkwz76P7cdjdmiShwMynZYN6SgOujD1urY4oNm80Ou9oMdmbR45LotcKOXoy7wSmHkRFE6Mxbrhefw==", + "license": "MIT", + "optional": true + }, + "node_modules/ts-api-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", + "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "node_modules/ts-deepmerge": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/ts-deepmerge/-/ts-deepmerge-7.0.3.tgz", + "integrity": "sha512-Du/ZW2RfwV/D4cmA5rXafYjBQVuvu4qGiEEla4EmEHVHgRdx68Gftx7i66jn2bzHPwSVZY36Ae6OuDn9el4ZKA==", + "license": "ISC", + "engines": { + "node": ">=14.13.1" + } + }, + "node_modules/ts-poet": { + "version": "6.12.0", + "resolved": "https://registry.npmjs.org/ts-poet/-/ts-poet-6.12.0.tgz", + "integrity": "sha512-xo+iRNMWqyvXpFTaOAvLPA5QAWO6TZrSUs5s4Odaya3epqofBu/fMLHEWl8jPmjhA0s9sgj9sNvF1BmaQlmQkA==", + "license": "Apache-2.0", + "dependencies": { + "dprint-node": "^1.0.8" + } + }, + "node_modules/ts-proto": { + "version": "2.7.7", + "resolved": "https://registry.npmjs.org/ts-proto/-/ts-proto-2.7.7.tgz", + "integrity": "sha512-/OfN9/Yriji2bbpOysZ/Jzc96isOKz+eBTJEcKaIZ0PR6x1TNgVm4Lz0zfbo+J0jwFO7fJjJyssefBPQ0o1V9A==", + "license": "ISC", + "dependencies": { + "@bufbuild/protobuf": "^2.0.0", + "case-anything": "^2.1.13", + "ts-poet": "^6.12.0", + "ts-proto-descriptors": "2.0.0" + }, + "bin": { + "protoc-gen-ts_proto": "protoc-gen-ts_proto" + } + }, + "node_modules/ts-proto-descriptors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ts-proto-descriptors/-/ts-proto-descriptors-2.0.0.tgz", + "integrity": "sha512-wHcTH3xIv11jxgkX5OyCSFfw27agpInAd6yh89hKG6zqIXnjW9SYqSER2CVQxdPj4czeOhGagNvZBEbJPy7qkw==", + "license": "ISC", + "dependencies": { + "@bufbuild/protobuf": "^2.0.0" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, + "node_modules/tsx": { + "version": "4.20.5", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.20.5.tgz", + "integrity": "sha512-+wKjMNU9w/EaQayHXb7WA7ZaHY6hN8WgfvHNQ3t1PnU91/7O8TcTnIhCDYTZwnt8JsO9IBqZ30Ln1r7pPF52Aw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.25.0", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "node_modules/tsx/node_modules/@esbuild/aix-ppc64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.10.tgz", + "integrity": "sha512-0NFWnA+7l41irNuaSVlLfgNT12caWJVLzp5eAVhZ0z1qpxbockccEt3s+149rE64VUI3Ml2zt8Nv5JVc4QXTsw==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/android-arm": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.10.tgz", + "integrity": "sha512-dQAxF1dW1C3zpeCDc5KqIYuZ1tgAdRXNoZP7vkBIRtKZPYe2xVr/d3SkirklCHudW1B45tGiUlz2pUWDfbDD4w==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/android-arm64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.10.tgz", + "integrity": "sha512-LSQa7eDahypv/VO6WKohZGPSJDq5OVOo3UoFR1E4t4Gj1W7zEQMUhI+lo81H+DtB+kP+tDgBp+M4oNCwp6kffg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/android-x64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.10.tgz", + "integrity": "sha512-MiC9CWdPrfhibcXwr39p9ha1x0lZJ9KaVfvzA0Wxwz9ETX4v5CHfF09bx935nHlhi+MxhA63dKRRQLiVgSUtEg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/darwin-arm64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.10.tgz", + "integrity": "sha512-JC74bdXcQEpW9KkV326WpZZjLguSZ3DfS8wrrvPMHgQOIEIG/sPXEN/V8IssoJhbefLRcRqw6RQH2NnpdprtMA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/darwin-x64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.10.tgz", + "integrity": "sha512-tguWg1olF6DGqzws97pKZ8G2L7Ig1vjDmGTwcTuYHbuU6TTjJe5FXbgs5C1BBzHbJ2bo1m3WkQDbWO2PvamRcg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.10.tgz", + "integrity": "sha512-3ZioSQSg1HT2N05YxeJWYR+Libe3bREVSdWhEEgExWaDtyFbbXWb49QgPvFH8u03vUPX10JhJPcz7s9t9+boWg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/freebsd-x64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.10.tgz", + "integrity": "sha512-LLgJfHJk014Aa4anGDbh8bmI5Lk+QidDmGzuC2D+vP7mv/GeSN+H39zOf7pN5N8p059FcOfs2bVlrRr4SK9WxA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-arm": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.10.tgz", + "integrity": "sha512-oR31GtBTFYCqEBALI9r6WxoU/ZofZl962pouZRTEYECvNF/dtXKku8YXcJkhgK/beU+zedXfIzHijSRapJY3vg==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-arm64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.10.tgz", + "integrity": "sha512-5luJWN6YKBsawd5f9i4+c+geYiVEw20FVW5x0v1kEMWNq8UctFjDiMATBxLvmmHA4bf7F6hTRaJgtghFr9iziQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-ia32": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.10.tgz", + "integrity": "sha512-NrSCx2Kim3EnnWgS4Txn0QGt0Xipoumb6z6sUtl5bOEZIVKhzfyp/Lyw4C1DIYvzeW/5mWYPBFJU3a/8Yr75DQ==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-loong64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.10.tgz", + "integrity": "sha512-xoSphrd4AZda8+rUDDfD9J6FUMjrkTz8itpTITM4/xgerAZZcFW7Dv+sun7333IfKxGG8gAq+3NbfEMJfiY+Eg==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-mips64el": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.10.tgz", + "integrity": "sha512-ab6eiuCwoMmYDyTnyptoKkVS3k8fy/1Uvq7Dj5czXI6DF2GqD2ToInBI0SHOp5/X1BdZ26RKc5+qjQNGRBelRA==", + "cpu": [ + "mips64el" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-ppc64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.10.tgz", + "integrity": "sha512-NLinzzOgZQsGpsTkEbdJTCanwA5/wozN9dSgEl12haXJBzMTpssebuXR42bthOF3z7zXFWH1AmvWunUCkBE4EA==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-riscv64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.10.tgz", + "integrity": "sha512-FE557XdZDrtX8NMIeA8LBJX3dC2M8VGXwfrQWU7LB5SLOajfJIxmSdyL/gU1m64Zs9CBKvm4UAuBp5aJ8OgnrA==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-s390x": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.10.tgz", + "integrity": "sha512-3BBSbgzuB9ajLoVZk0mGu+EHlBwkusRmeNYdqmznmMc9zGASFjSsxgkNsqmXugpPk00gJ0JNKh/97nxmjctdew==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-x64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.10.tgz", + "integrity": "sha512-QSX81KhFoZGwenVyPoberggdW1nrQZSvfVDAIUXr3WqLRZGZqWk/P4T8p2SP+de2Sr5HPcvjhcJzEiulKgnxtA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/netbsd-x64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.10.tgz", + "integrity": "sha512-7RTytDPGU6fek/hWuN9qQpeGPBZFfB4zZgcz2VK2Z5VpdUxEI8JKYsg3JfO0n/Z1E/6l05n0unDCNc4HnhQGig==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/openbsd-x64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.10.tgz", + "integrity": "sha512-XkA4frq1TLj4bEMB+2HnI0+4RnjbuGZfet2gs/LNs5Hc7D89ZQBHQ0gL2ND6Lzu1+QVkjp3x1gIcPKzRNP8bXw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/sunos-x64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.10.tgz", + "integrity": "sha512-fswk3XT0Uf2pGJmOpDB7yknqhVkJQkAQOcW/ccVOtfx05LkbWOaRAtn5SaqXypeKQra1QaEa841PgrSL9ubSPQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/win32-arm64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.10.tgz", + "integrity": "sha512-ah+9b59KDTSfpaCg6VdJoOQvKjI33nTaQr4UluQwW7aEwZQsbMCfTmfEO4VyewOxx4RaDT/xCy9ra2GPWmO7Kw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/win32-ia32": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.10.tgz", + "integrity": "sha512-QHPDbKkrGO8/cz9LKVnJU22HOi4pxZnZhhA2HYHez5Pz4JeffhDjf85E57Oyco163GnzNCVkZK0b/n4Y0UHcSw==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/win32-x64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.10.tgz", + "integrity": "sha512-9KpxSVFCu0iK1owoez6aC/s/EdUQLDN3adTxGCqxMVhrPDj6bt5dbrHDXUuq+Bs2vATFBBrQS5vdQ/Ed2P+nbw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/esbuild": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.10.tgz", + "integrity": "sha512-9RiGKvCwaqxO2owP61uQ4BgNborAQskMR6QusfWzQqv7AZOg5oGehdY2pRJMTKuwxd1IDBP4rSbI5lHzU7SMsQ==", + "devOptional": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.10", + "@esbuild/android-arm": "0.25.10", + "@esbuild/android-arm64": "0.25.10", + "@esbuild/android-x64": "0.25.10", + "@esbuild/darwin-arm64": "0.25.10", + "@esbuild/darwin-x64": "0.25.10", + "@esbuild/freebsd-arm64": "0.25.10", + "@esbuild/freebsd-x64": "0.25.10", + "@esbuild/linux-arm": "0.25.10", + "@esbuild/linux-arm64": "0.25.10", + "@esbuild/linux-ia32": "0.25.10", + "@esbuild/linux-loong64": "0.25.10", + "@esbuild/linux-mips64el": "0.25.10", + "@esbuild/linux-ppc64": "0.25.10", + "@esbuild/linux-riscv64": "0.25.10", + "@esbuild/linux-s390x": "0.25.10", + "@esbuild/linux-x64": "0.25.10", + "@esbuild/netbsd-arm64": "0.25.10", + "@esbuild/netbsd-x64": "0.25.10", + "@esbuild/openbsd-arm64": "0.25.10", + "@esbuild/openbsd-x64": "0.25.10", + "@esbuild/openharmony-arm64": "0.25.10", + "@esbuild/sunos-x64": "0.25.10", + "@esbuild/win32-arm64": "0.25.10", + "@esbuild/win32-ia32": "0.25.10", + "@esbuild/win32-x64": "0.25.10" + } + }, + "node_modules/tw-animate-css": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/tw-animate-css/-/tw-animate-css-1.4.0.tgz", + "integrity": "sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Wombosvideo" + } + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-fest": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-5.0.1.tgz", + "integrity": "sha512-9MpwAI52m8H6ssA542UxSLnSiSD2dsC3/L85g6hVubLSXd82wdI80eZwTWhdOfN67NlA+D+oipAs1MlcTcu3KA==", + "license": "(MIT OR CC0-1.0)", + "dependencies": { + "tagged-tag": "^1.0.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typescript": { + "version": "5.9.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.2.tgz", + "integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==", + "devOptional": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/typescript-eslint": { + "version": "8.44.1", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.44.1.tgz", + "integrity": "sha512-0ws8uWGrUVTjEeN2OM4K1pLKHK/4NiNP/vz6ns+LjT/6sqpaYzIVFajZb1fj/IDwpsrrHb3Jy0Qm5u9CPcKaeg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.44.1", + "@typescript-eslint/parser": "8.44.1", + "@typescript-eslint/typescript-estree": "8.44.1", + "@typescript-eslint/utils": "8.44.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/unist-util-is": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-4.1.0.tgz", + "integrity": "sha512-ZOQSsnce92GrxSqlnEEseX0gi7GH9zTJZ0p9dtu87WRb/37mMPO2Ilx1s/t9vBHrFhbgweUwb+t7cIn5dxPhZg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-stringify-position": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-2.0.3.tgz", + "integrity": "sha512-3faScn5I+hy9VleOq/qNbAd6pAx7iH5jYBMS9I1HgQVijz/4mv5Bvw5iw1sC/90CODiKo81G/ps8AJrISn687g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^2.0.2" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-2.0.3.tgz", + "integrity": "sha512-iJ4/RczbJMkD0712mGktuGpm/U4By4FfDonL7N/9tATGIF4imikjOuagyMY53tnZq3NP6BcmlrHhEKAfGWjh7Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^2.0.0", + "unist-util-is": "^4.0.0", + "unist-util-visit-parents": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit-parents": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-3.1.1.tgz", + "integrity": "sha512-1KROIZWo6bcMrZEwiH2UrXDyalAa0uqzWCxCJj6lPOvTve2WkfgCytoDTPaMnodXh1WrXOq0haVYHj99ynJlsg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^2.0.0", + "unist-util-is": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unplugin": { + "version": "2.3.10", + "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-2.3.10.tgz", + "integrity": "sha512-6NCPkv1ClwH+/BGE9QeoTIl09nuiAt0gS28nn1PvYXsGKRwM2TCbFA2QiilmehPDTXIe684k4rZI1yl3A1PCUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.5", + "acorn": "^8.15.0", + "picomatch": "^4.0.3", + "webpack-virtual-modules": "^0.6.2" + }, + "engines": { + "node": ">=18.12.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/urlpattern-polyfill": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/urlpattern-polyfill/-/urlpattern-polyfill-10.1.0.tgz", + "integrity": "sha512-IGjKp/o0NL3Bso1PymYURCJxMPNAf/ILOpendP9f5B6e1rTJgdgiOvgfoT8VxCAdY+Wisb9uhGaJJf3yZ2V9nw==", + "dev": true, + "license": "MIT" + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/uuid": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-13.0.0.tgz", + "integrity": "sha512-XQegIaBTVUjSHliKqcnFqYypAd4S+WCYt5NIeRs6w/UAry7z8Y9j5ZwRRL4kzq9U3sD6v+85er9FvkEaBpji2w==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist-node/bin/uuid" + } + }, + "node_modules/valibot": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/valibot/-/valibot-1.1.0.tgz", + "integrity": "sha512-Nk8lX30Qhu+9txPYTwM0cFlWLdPFsFr6LblzqIySfbZph9+BFsAHsNvHOymEviUepeIW6KFHzpX8TKhbptBXXw==", + "license": "MIT", + "optional": true, + "peerDependencies": { + "typescript": ">=5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/validator": { + "version": "13.15.15", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.15.15.tgz", + "integrity": "sha512-BgWVbCI72aIQy937xbawcs+hrVaN/CZ2UwutgaJ36hGqRrLNM+f5LUT/YPRbo8IV/ASeFzXszezV+y2+rq3l8A==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/vfile-message": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-2.0.4.tgz", + "integrity": "sha512-DjssxRGkMvifUOJre00juHoP9DPWuzjxKuMDrhNbk2TdaYYBNMStsNhEOt3idrtI12VQYM/1+iM0KOzXi4pxwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^2.0.0", + "unist-util-stringify-position": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vite": { + "version": "7.1.7", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.7.tgz", + "integrity": "sha512-VbA8ScMvAISJNJVbRDTJdCwqQoAareR/wutevKanhR2/1EkoXVZVkkORaYm/tNVCjP/UDTKtcw3bAkwOUdedmA==", + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vite/node_modules/@esbuild/aix-ppc64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.10.tgz", + "integrity": "sha512-0NFWnA+7l41irNuaSVlLfgNT12caWJVLzp5eAVhZ0z1qpxbockccEt3s+149rE64VUI3Ml2zt8Nv5JVc4QXTsw==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/android-arm": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.10.tgz", + "integrity": "sha512-dQAxF1dW1C3zpeCDc5KqIYuZ1tgAdRXNoZP7vkBIRtKZPYe2xVr/d3SkirklCHudW1B45tGiUlz2pUWDfbDD4w==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/android-arm64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.10.tgz", + "integrity": "sha512-LSQa7eDahypv/VO6WKohZGPSJDq5OVOo3UoFR1E4t4Gj1W7zEQMUhI+lo81H+DtB+kP+tDgBp+M4oNCwp6kffg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/android-x64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.10.tgz", + "integrity": "sha512-MiC9CWdPrfhibcXwr39p9ha1x0lZJ9KaVfvzA0Wxwz9ETX4v5CHfF09bx935nHlhi+MxhA63dKRRQLiVgSUtEg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/darwin-arm64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.10.tgz", + "integrity": "sha512-JC74bdXcQEpW9KkV326WpZZjLguSZ3DfS8wrrvPMHgQOIEIG/sPXEN/V8IssoJhbefLRcRqw6RQH2NnpdprtMA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/darwin-x64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.10.tgz", + "integrity": "sha512-tguWg1olF6DGqzws97pKZ8G2L7Ig1vjDmGTwcTuYHbuU6TTjJe5FXbgs5C1BBzHbJ2bo1m3WkQDbWO2PvamRcg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.10.tgz", + "integrity": "sha512-3ZioSQSg1HT2N05YxeJWYR+Libe3bREVSdWhEEgExWaDtyFbbXWb49QgPvFH8u03vUPX10JhJPcz7s9t9+boWg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/freebsd-x64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.10.tgz", + "integrity": "sha512-LLgJfHJk014Aa4anGDbh8bmI5Lk+QidDmGzuC2D+vP7mv/GeSN+H39zOf7pN5N8p059FcOfs2bVlrRr4SK9WxA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-arm": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.10.tgz", + "integrity": "sha512-oR31GtBTFYCqEBALI9r6WxoU/ZofZl962pouZRTEYECvNF/dtXKku8YXcJkhgK/beU+zedXfIzHijSRapJY3vg==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-arm64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.10.tgz", + "integrity": "sha512-5luJWN6YKBsawd5f9i4+c+geYiVEw20FVW5x0v1kEMWNq8UctFjDiMATBxLvmmHA4bf7F6hTRaJgtghFr9iziQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-ia32": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.10.tgz", + "integrity": "sha512-NrSCx2Kim3EnnWgS4Txn0QGt0Xipoumb6z6sUtl5bOEZIVKhzfyp/Lyw4C1DIYvzeW/5mWYPBFJU3a/8Yr75DQ==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-loong64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.10.tgz", + "integrity": "sha512-xoSphrd4AZda8+rUDDfD9J6FUMjrkTz8itpTITM4/xgerAZZcFW7Dv+sun7333IfKxGG8gAq+3NbfEMJfiY+Eg==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-mips64el": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.10.tgz", + "integrity": "sha512-ab6eiuCwoMmYDyTnyptoKkVS3k8fy/1Uvq7Dj5czXI6DF2GqD2ToInBI0SHOp5/X1BdZ26RKc5+qjQNGRBelRA==", + "cpu": [ + "mips64el" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-ppc64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.10.tgz", + "integrity": "sha512-NLinzzOgZQsGpsTkEbdJTCanwA5/wozN9dSgEl12haXJBzMTpssebuXR42bthOF3z7zXFWH1AmvWunUCkBE4EA==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-riscv64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.10.tgz", + "integrity": "sha512-FE557XdZDrtX8NMIeA8LBJX3dC2M8VGXwfrQWU7LB5SLOajfJIxmSdyL/gU1m64Zs9CBKvm4UAuBp5aJ8OgnrA==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-s390x": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.10.tgz", + "integrity": "sha512-3BBSbgzuB9ajLoVZk0mGu+EHlBwkusRmeNYdqmznmMc9zGASFjSsxgkNsqmXugpPk00gJ0JNKh/97nxmjctdew==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-x64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.10.tgz", + "integrity": "sha512-QSX81KhFoZGwenVyPoberggdW1nrQZSvfVDAIUXr3WqLRZGZqWk/P4T8p2SP+de2Sr5HPcvjhcJzEiulKgnxtA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/netbsd-x64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.10.tgz", + "integrity": "sha512-7RTytDPGU6fek/hWuN9qQpeGPBZFfB4zZgcz2VK2Z5VpdUxEI8JKYsg3JfO0n/Z1E/6l05n0unDCNc4HnhQGig==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/openbsd-x64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.10.tgz", + "integrity": "sha512-XkA4frq1TLj4bEMB+2HnI0+4RnjbuGZfet2gs/LNs5Hc7D89ZQBHQ0gL2ND6Lzu1+QVkjp3x1gIcPKzRNP8bXw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/sunos-x64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.10.tgz", + "integrity": "sha512-fswk3XT0Uf2pGJmOpDB7yknqhVkJQkAQOcW/ccVOtfx05LkbWOaRAtn5SaqXypeKQra1QaEa841PgrSL9ubSPQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-arm64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.10.tgz", + "integrity": "sha512-ah+9b59KDTSfpaCg6VdJoOQvKjI33nTaQr4UluQwW7aEwZQsbMCfTmfEO4VyewOxx4RaDT/xCy9ra2GPWmO7Kw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-ia32": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.10.tgz", + "integrity": "sha512-QHPDbKkrGO8/cz9LKVnJU22HOi4pxZnZhhA2HYHez5Pz4JeffhDjf85E57Oyco163GnzNCVkZK0b/n4Y0UHcSw==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-x64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.10.tgz", + "integrity": "sha512-9KpxSVFCu0iK1owoez6aC/s/EdUQLDN3adTxGCqxMVhrPDj6bt5dbrHDXUuq+Bs2vATFBBrQS5vdQ/Ed2P+nbw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/esbuild": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.10.tgz", + "integrity": "sha512-9RiGKvCwaqxO2owP61uQ4BgNborAQskMR6QusfWzQqv7AZOg5oGehdY2pRJMTKuwxd1IDBP4rSbI5lHzU7SMsQ==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.10", + "@esbuild/android-arm": "0.25.10", + "@esbuild/android-arm64": "0.25.10", + "@esbuild/android-x64": "0.25.10", + "@esbuild/darwin-arm64": "0.25.10", + "@esbuild/darwin-x64": "0.25.10", + "@esbuild/freebsd-arm64": "0.25.10", + "@esbuild/freebsd-x64": "0.25.10", + "@esbuild/linux-arm": "0.25.10", + "@esbuild/linux-arm64": "0.25.10", + "@esbuild/linux-ia32": "0.25.10", + "@esbuild/linux-loong64": "0.25.10", + "@esbuild/linux-mips64el": "0.25.10", + "@esbuild/linux-ppc64": "0.25.10", + "@esbuild/linux-riscv64": "0.25.10", + "@esbuild/linux-s390x": "0.25.10", + "@esbuild/linux-x64": "0.25.10", + "@esbuild/netbsd-arm64": "0.25.10", + "@esbuild/netbsd-x64": "0.25.10", + "@esbuild/openbsd-arm64": "0.25.10", + "@esbuild/openbsd-x64": "0.25.10", + "@esbuild/openharmony-arm64": "0.25.10", + "@esbuild/sunos-x64": "0.25.10", + "@esbuild/win32-arm64": "0.25.10", + "@esbuild/win32-ia32": "0.25.10", + "@esbuild/win32-x64": "0.25.10" + } + }, + "node_modules/vitefu": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-1.1.1.tgz", + "integrity": "sha512-B/Fegf3i8zh0yFbpzZ21amWzHmuNlLlmJT6n7bu5e+pCHUKQIfXSYokrqOBGEMMe9UG2sostKQF9mml/vYaWJQ==", + "license": "MIT", + "workspaces": [ + "tests/deps/*", + "tests/projects/*", + "tests/projects/workspace/packages/*" + ], + "peerDependencies": { + "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0-beta.0" + }, + "peerDependenciesMeta": { + "vite": { + "optional": true + } + } + }, + "node_modules/webpack-virtual-modules": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz", + "integrity": "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wildcard-match": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/wildcard-match/-/wildcard-match-5.1.4.tgz", + "integrity": "sha512-wldeCaczs8XXq7hj+5d/F38JE2r7EXgb6WQDM84RVwxy81T/sxB5e9+uZLK9Q9oNz1mlvjut+QtvgaOQFPVq/g==", + "license": "ISC" + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/yallist": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", + "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yup": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/yup/-/yup-1.7.1.tgz", + "integrity": "sha512-GKHFX2nXul2/4Dtfxhozv701jLQHdf6J34YDh2cEkpqoo8le5Mg6/LrdseVLrFarmFygZTlfIhHx/QKfb/QWXw==", + "license": "MIT", + "optional": true, + "dependencies": { + "property-expr": "^2.0.5", + "tiny-case": "^1.0.3", + "toposort": "^2.0.2", + "type-fest": "^2.19.0" + } + }, + "node_modules/yup/node_modules/type-fest": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", + "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", + "license": "(MIT OR CC0-1.0)", + "optional": true, + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zimmerframe": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/zimmerframe/-/zimmerframe-1.1.4.tgz", + "integrity": "sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ==", + "license": "MIT" + }, + "node_modules/zod": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.0.10.tgz", + "integrity": "sha512-3vB+UU3/VmLL2lvwcY/4RV2i9z/YU0DTV/tDuYjrwmx5WeJ7hwy+rGEEx8glHp6Yxw7ibRbKSaIFBgReRPe5KA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..7610287 --- /dev/null +++ b/package.json @@ -0,0 +1,91 @@ +{ + "name": "rfid-cp", + "private": true, + "version": "0.0.1", + "type": "module", + "scripts": { + "dev": "vite dev", + "build": "vite build", + "preview": "vite preview", + "prepare": "svelte-kit sync || echo ''", + "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", + "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", + "format": "prettier --write .", + "lint": "prettier --check . && eslint .", + "db:push": "drizzle-kit push", + "db:generate": "drizzle-kit generate", + "db:migrate": "drizzle-kit migrate", + "db:studio": "drizzle-kit studio", + "db:seed": "tsx scripts/seed.ts", + "db:seed-admin": "tsx scripts/seed-admin.ts", + "docker-compose:up": "docker-compose -f ./docker-compose.yml up -d --build", + "docker-compose:down": "docker-compose -f ./docker-compose.yml down", + "machine-translate": "inlang machine translate --project project.inlang", + "proto:generate": "protoc --plugin=./node_modules/.bin/protoc-gen-ts_proto --ts_proto_out=src/generated/ ./proto/control_communication.proto" + }, + "devDependencies": { + "@eslint/compat": "^1.2.5", + "@eslint/js": "^9.22.0", + "@inlang/cli": "^3.0.0", + "@inlang/paraglide-js": "2.3.2", + "@internationalized/date": "^3.9.0", + "@lucide/svelte": "^0.515.0", + "@sveltejs/adapter-node": "^5.2.12", + "@sveltejs/kit": "^2.22.0", + "@sveltejs/vite-plugin-svelte": "^6.0.0", + "@tailwindcss/forms": "^0.5.9", + "@tailwindcss/typography": "^0.5.15", + "@tailwindcss/vite": "^4.0.0", + "@tanstack/table-core": "^8.21.3", + "@types/argon2": "^0.14.1", + "@types/better-sqlite3": "^7.6.12", + "@types/d3-scale": "^4.0.9", + "@types/node": "^22", + "bits-ui": "^2.11.0", + "clsx": "^2.1.1", + "dotenv": "^17.2.2", + "drizzle-kit": "^0.30.2", + "drizzle-orm": "^0.40.0", + "eslint": "^9.36.0", + "eslint-config-prettier": "^10.0.1", + "eslint-plugin-svelte": "^3.12.4", + "eslint-plugin-unused-imports": "^4.2.0", + "formsnap": "^2.0.1", + "globals": "^16.0.0", + "layerchart": "^2.0.0-next.39", + "mdsvex": "^0.12.3", + "mode-watcher": "^1.1.0", + "prettier": "^3.6.2", + "prettier-plugin-svelte": "^3.4.0", + "prettier-plugin-tailwindcss": "^0.6.11", + "svelte": "^5.0.0", + "svelte-check": "^4.0.0", + "svelte-sonner": "^1.0.5", + "tailwind-merge": "^3.3.1", + "tailwind-variants": "^1.0.0", + "tailwindcss": "^4.0.0", + "tsx": "^4.20.5", + "tw-animate-css": "^1.3.8", + "typescript": "^5.0.0", + "typescript-eslint": "^8.20.0", + "vite": "^7.0.4" + }, + "dependencies": { + "@orpc/client": "^1.8.9", + "@orpc/json-schema": "^1.8.9", + "@orpc/openapi": "^1.8.9", + "@orpc/server": "^1.8.9", + "@orpc/tanstack-query": "^1.8.9", + "@orpc/zod": "^1.8.9", + "@tanstack/svelte-query": "^5.89.0", + "argon2": "^0.44.0", + "d3-scale": "^4.0.2", + "drizzle-zod": "^0.8.3", + "jose": "^6.1.0", + "postgres": "^3.4.7", + "sveltekit-superforms": "^2.27.1", + "ts-proto": "^2.7.7", + "uuid": "^13.0.0", + "zod": "4.0.10" + } +} diff --git a/project.inlang/.gitignore b/project.inlang/.gitignore new file mode 100644 index 0000000..5e46596 --- /dev/null +++ b/project.inlang/.gitignore @@ -0,0 +1 @@ +cache \ No newline at end of file diff --git a/project.inlang/project_id b/project.inlang/project_id new file mode 100644 index 0000000..5243221 --- /dev/null +++ b/project.inlang/project_id @@ -0,0 +1 @@ +8jHSxX0rZat6wCtV9T \ No newline at end of file diff --git a/project.inlang/settings.json b/project.inlang/settings.json new file mode 100644 index 0000000..2cf183f --- /dev/null +++ b/project.inlang/settings.json @@ -0,0 +1,12 @@ +{ + "$schema": "https://inlang.com/schema/project-settings", + "baseLocale": "en", + "locales": ["en", "de"], + "modules": [ + "https://cdn.jsdelivr.net/npm/@inlang/plugin-message-format@4/dist/index.js", + "https://cdn.jsdelivr.net/npm/@inlang/plugin-m-function-matcher@2/dist/index.js" + ], + "plugin.inlang.messageFormat": { + "pathPattern": "./messages/{locale}.json" + } +} diff --git a/proto/control_communication.proto b/proto/control_communication.proto new file mode 100644 index 0000000..7bd4c0c --- /dev/null +++ b/proto/control_communication.proto @@ -0,0 +1,16 @@ +syntax = "proto3"; +package control_communication; + +message RfidId { + uint32 value = 1; +} + +message SyncResponse { + int64 currentTime = 1; + bool pendingChanges = 2; +} + +message SyncRequest { + optional int64 lastSync = 1; + map accessLogs = 2; +} \ No newline at end of file diff --git a/proto/test/blob b/proto/test/blob new file mode 100644 index 0000000..4e8e81e --- /dev/null +++ b/proto/test/blob @@ -0,0 +1 @@ +3 \ No newline at end of file diff --git a/proto/test/sync-binary1 b/proto/test/sync-binary1 new file mode 100644 index 0000000..438ea0e --- /dev/null +++ b/proto/test/sync-binary1 @@ -0,0 +1 @@ +"COXOg+uYMxAB" \ No newline at end of file diff --git a/proto/test/sync-binary2 b/proto/test/sync-binary2 new file mode 100644 index 0000000..e68d123 --- /dev/null +++ b/proto/test/sync-binary2 @@ -0,0 +1 @@ +ߊ3 \ No newline at end of file diff --git a/scripts/seed-admin.ts b/scripts/seed-admin.ts new file mode 100644 index 0000000..6304021 --- /dev/null +++ b/scripts/seed-admin.ts @@ -0,0 +1,59 @@ +#!/usr/bin/env tsx + +import { config } from 'dotenv'; +import { drizzle } from 'drizzle-orm/postgres-js'; +import postgres from 'postgres'; +import * as schema from '../src/schemas/database/schema'; +import { hashPassword } from '../src/lib/utils/passwordHash'; + +// Load environment variables from .env file +config(); + +async function seedAdmin() { + const databaseUrl = process.env.DATABASE_URL; + if (!databaseUrl) { + throw new Error('DATABASE_URL environment variable is not set'); + } + + console.log('👤 Starting admin user seeding...'); + + const client = postgres(databaseUrl); + const db = drizzle(client, { schema }); + + try { + // Create admin user + console.log('👤 Creating admin user...'); + const adminPasswordHash = await hashPassword('admin123'); + + const adminUser = await db + .insert(schema.managementUsers) + .values({ + firstName: 'Admin', + lastName: 'User', + username: 'admin', + passwordHash: adminPasswordHash, + role: 'ADMIN', + enabled: true + }) + .returning() + .onConflictDoNothing(); + + if (adminUser.length > 0) { + console.log('✅ Admin user created with username: admin, password: admin123'); + } else { + console.log('ℹ️ Admin user already exists'); + } + + console.log('🎉 Admin user seeding completed successfully!'); + } catch (error) { + console.error('❌ Error during admin seeding:', error); + process.exit(1); + } finally { + await client.end(); + } +} + +seedAdmin().catch((error) => { + console.error('❌ Admin seeding failed:', error); + process.exit(1); +}); diff --git a/scripts/seed.ts b/scripts/seed.ts new file mode 100644 index 0000000..cebb0ce --- /dev/null +++ b/scripts/seed.ts @@ -0,0 +1,243 @@ +#!/usr/bin/env tsx + +import { config } from 'dotenv'; +import { drizzle } from 'drizzle-orm/postgres-js'; +import postgres from 'postgres'; +import * as schema from '../src/schemas/database/schema'; +import { hashPassword } from '../src/lib/utils/passwordHash'; +import { v4 as uuidv4 } from 'uuid'; + +// Load environment variables from .env file +config(); + +async function seed() { + const databaseUrl = process.env.DATABASE_URL; + if (!databaseUrl) { + throw new Error('DATABASE_URL environment variable is not set'); + } + + console.log('🌱 Starting database seeding...'); + + const client = postgres(databaseUrl); + const db = drizzle(client, { schema }); + + try { + // Create admin user + console.log('👤 Creating admin user...'); + const adminPasswordHash = await hashPassword('admin123'); + + const adminUser = await db + .insert(schema.managementUsers) + .values({ + firstName: 'Admin', + lastName: 'User', + username: 'admin', + passwordHash: adminPasswordHash, + role: 'ADMIN', + enabled: true + }) + .returning() + .onConflictDoNothing(); + + if (adminUser.length > 0) { + console.log('✅ Admin user created with username: admin, password: admin123'); + } else { + console.log('ℹ️ Admin user already exists'); + } + + // Create sample members + console.log('👥 Creating sample members...'); + const members = [ + { + firstName: 'Max', + lastName: 'Mustermann', + email: 'max.mustermann@example.com', + membershipNumber: 'MEM001', + phoneMobile: '+49123456789', + joinedAt: new Date('2024-01-15') + }, + { + firstName: 'Anna', + lastName: 'Schmidt', + email: 'anna.schmidt@example.com', + membershipNumber: 'MEM002', + phoneMobile: '+49987654321', + joinedAt: new Date('2024-02-20') + }, + { + firstName: 'Peter', + lastName: 'Müller', + email: 'peter.mueller@example.com', + membershipNumber: 'MEM003', + phoneMobile: '+49555666777', + joinedAt: new Date('2024-03-10') + } + ]; + + const createdMembers = []; + for (const member of members) { + const result = await db + .insert(schema.members) + .values(member) + .returning() + .onConflictDoNothing(); + + if (result.length > 0) { + createdMembers.push(result[0]); + console.log(`✅ Created member: ${member.firstName} ${member.lastName}`); + } + } + + // Create sample RFID cards + console.log('🆔 Creating sample RFID cards...'); + const rfidCards = [ + { rfidId: 'ABC123456789', status: 'NEW' as const }, + { rfidId: 'DEF987654321', status: 'NEW' as const }, + { rfidId: 'GHI456789123', status: 'ENGRAVED' as const }, + { rfidId: 'JKL789123456', status: 'NEW' as const }, + { rfidId: 'MNO321654987', status: 'NEW' as const } + ]; + + const createdCards = []; + for (const card of rfidCards) { + const result = await db + .insert(schema.rfidCards) + .values(card) + .returning() + .onConflictDoNothing(); + + if (result.length > 0) { + createdCards.push(result[0]); + console.log(`✅ Created RFID card: ${card.rfidId} (${card.status})`); + } + } + + // Create sample assignments + console.log('🔗 Creating sample card assignments...'); + const assignments = [ + { + memberId: createdMembers[0]?.id, + cardId: createdCards[0]?.id, + status: 'ASSIGNED' as const, + issuedAt: new Date('2024-01-20') + }, + { + memberId: createdMembers[1]?.id, + cardId: createdCards[1]?.id, + status: 'ASSIGNED' as const, + issuedAt: new Date('2024-02-25') + }, + { + memberId: createdMembers[2]?.id, + cardId: createdCards[2]?.id, + status: 'ASSIGNED' as const, + issuedAt: new Date('2024-03-15') + } + ]; + + for (const assignment of assignments) { + if (assignment.memberId && assignment.cardId) { + const result = await db + .insert(schema.memberRfidCards) + .values(assignment) + .returning() + .onConflictDoNothing(); + + if (result.length > 0) { + console.log( + `✅ Assigned card ${createdCards.find((c) => c.id === assignment.cardId)?.rfidId} to ${createdMembers.find((m) => m.id === assignment.memberId)?.firstName} ${createdMembers.find((m) => m.id === assignment.memberId)?.lastName}` + ); + } + } + } + + // Create sample devices + console.log('📱 Creating sample devices...'); + const devices = [ + { + name: 'Main Entrance Scanner', + apiKey: 'dev_' + uuidv4().replace(/-/g, ''), + type: 'RFID_SCANNER' as const + }, + { + name: 'Office Door Lock', + apiKey: 'dev_' + uuidv4().replace(/-/g, ''), + type: 'LOCK_SYSTEM' as const + } + ]; + + const createdDevices = []; + for (const device of devices) { + const result = await db + .insert(schema.devices) + .values(device) + .returning() + .onConflictDoNothing(); + + if (result.length > 0) { + createdDevices.push(result[0]); + console.log(`✅ Created device: ${device.name} (${device.type})`); + } + } + + // Get all members and devices for access logs + const allMembers = await db.select().from(schema.members); + const allDevices = await db.select().from(schema.devices); + + if (allMembers.length === 0 || allDevices.length === 0) { + console.log('⚠️ No members or devices found, skipping access log creation'); + } else { + // Create sample access logs for the last 30 days + console.log('📊 Creating sample access logs...'); + const accessLogs = []; + const now = new Date(); + const thirtyDaysAgo = new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000); + + for (let i = 0; i < 30; i++) { + const date = new Date(thirtyDaysAgo.getTime() + i * 24 * 60 * 60 * 1000); + // Random number of accesses per day (5-50) + const accessesPerDay = Math.floor(Math.random() * 46) + 5; + + for (let j = 0; j < accessesPerDay; j++) { + const randomMember = allMembers[Math.floor(Math.random() * allMembers.length)]; + const randomDevice = allDevices[Math.floor(Math.random() * allDevices.length)]; + const randomHour = Math.floor(Math.random() * 24); + const randomMinute = Math.floor(Math.random() * 60); + const accessedAt = new Date(date); + accessedAt.setHours(randomHour, randomMinute, 0, 0); + + accessLogs.push({ + memberId: randomMember.id, + deviceId: randomDevice.id, + accessedAt, + accessGranted: Math.random() > 0.05 // 95% success rate + }); + } + } + + for (const log of accessLogs) { + await db.insert(schema.accessLogs).values(log).onConflictDoNothing(); + } + console.log(`✅ Created ${accessLogs.length} sample access logs over the last 30 days`); + } + + console.log('🎉 Database seeding completed successfully!'); + console.log('\n📋 Summary:'); + console.log('- Admin user: admin / admin123'); + console.log('- Sample members, RFID cards, and assignments created'); + console.log('- Sample devices created'); + if (allMembers.length > 0 && allDevices.length > 0) { + console.log('- Sample access logs created for chart testing'); + } + } catch (error) { + console.error('❌ Error during seeding:', error); + process.exit(1); + } finally { + await client.end(); + } +} + +seed().catch((error) => { + console.error('❌ Seeding failed:', error); + process.exit(1); +}); diff --git a/src/app.css b/src/app.css new file mode 100644 index 0000000..524d16d --- /dev/null +++ b/src/app.css @@ -0,0 +1,157 @@ +@import 'tailwindcss'; + +@import 'tw-animate-css'; + +@custom-variant dark (&:is(.dark *)); + +:root { + --radius: 0.5rem; + + /* Moderne Basis mit den Original-Markenfarben */ + --background: oklch(0.99 0.002 240); + --foreground: oklch(0.25 0.015 255); + + /* Karten mit subtiler Erhebung */ + --card: oklch(1 0 0); + --card-foreground: oklch(0.25 0.015 255); + --popover: oklch(1 0 0); + --popover-foreground: oklch(0.25 0.015 255); + + /* Primary = Modernisiertes #004A9B Blau */ + --primary: oklch(0.45 0.14 255); + --primary-foreground: oklch(0.99 0.002 240); + + /* Secondary = Aufgefrischtes Hellblau */ + --secondary: oklch(0.97 0.015 240); + --secondary-foreground: oklch(0.25 0.015 255); + + /* Muted = Subtiles Grau mit Blaustich */ + --muted: oklch(0.96 0.008 240); + --muted-foreground: oklch(0.55 0.025 240); + + /* Accent = Modernisiertes Türkis #006464 */ + --accent: oklch(0.48 0.08 200); + --accent-foreground: oklch(0.99 0.002 240); + + /* Destructive mit besserer Sichtbarkeit */ + --destructive: oklch(0.58 0.22 25); + --destructive-foreground: oklch(0.99 0.002 240); + + /* Borders & Inputs - dezenter */ + --border: oklch(0.90 0.01 240); + --input: oklch(0.90 0.01 240); + --ring: oklch(0.45 0.14 255); + + /* Charts mit harmonischer Palette */ + --chart-1: oklch(0.45 0.14 255); /* Primary Blau */ + --chart-2: oklch(0.48 0.08 200); /* Accent Türkis */ + --chart-3: oklch(0.60 0.12 280); /* Violett */ + --chart-4: oklch(0.65 0.15 150); /* Grün */ + --chart-5: oklch(0.70 0.18 50); /* Orange */ + + /* Sidebar - aufgehelltes Design */ + --sidebar: oklch(0.98 0.008 240); + --sidebar-foreground: oklch(0.25 0.015 255); + --sidebar-primary: oklch(0.45 0.14 255); + --sidebar-primary-foreground: oklch(0.99 0.002 240); + --sidebar-accent: oklch(0.95 0.012 240); + --sidebar-accent-foreground: oklch(0.25 0.015 255); + --sidebar-border: oklch(0.92 0.01 240); + --sidebar-ring: oklch(0.45 0.14 255); +} + +.dark { + --background: oklch(0.12 0.012 255); + --foreground: oklch(0.92 0.01 240); + + --card: oklch(0.16 0.012 255); + --card-foreground: oklch(0.92 0.01 240); + --popover: oklch(0.16 0.012 255); + --popover-foreground: oklch(0.92 0.01 240); + + /* Primary - leuchtender im Dark Mode */ + --primary: oklch(0.62 0.18 255); + --primary-foreground: oklch(0.12 0.012 255); + + /* Secondary */ + --secondary: oklch(0.20 0.015 240); + --secondary-foreground: oklch(0.92 0.01 240); + + --muted: oklch(0.20 0.015 240); + --muted-foreground: oklch(0.68 0.03 240); + + /* Accent - helleres Türkis */ + --accent: oklch(0.58 0.10 190); + --accent-foreground: oklch(0.12 0.012 255); + + --destructive: oklch(0.65 0.24 25); + --destructive-foreground: oklch(0.92 0.01 240); + + --border: oklch(0.26 0.015 240); + --input: oklch(0.26 0.015 240); + --ring: oklch(0.62 0.18 255); + + --chart-1: oklch(0.62 0.18 255); + --chart-2: oklch(0.58 0.10 190); + --chart-3: oklch(0.65 0.14 280); + --chart-4: oklch(0.70 0.16 150); + --chart-5: oklch(0.75 0.20 50); + + --sidebar: oklch(0.14 0.012 255); + --sidebar-foreground: oklch(0.92 0.01 240); + --sidebar-primary: oklch(0.62 0.18 255); + --sidebar-primary-foreground: oklch(0.12 0.012 255); + --sidebar-accent: oklch(0.24 0.015 240); + --sidebar-accent-foreground: oklch(0.92 0.01 240); + --sidebar-border: oklch(0.26 0.015 240); + --sidebar-ring: oklch(0.62 0.18 255); +} + + +@theme inline { + --radius-sm: calc(var(--radius) - 4px); + --radius-md: calc(var(--radius) - 2px); + --radius-lg: var(--radius); + --radius-xl: calc(var(--radius) + 4px); + --color-background: var(--background); + --color-foreground: var(--foreground); + --color-card: var(--card); + --color-card-foreground: var(--card-foreground); + --color-popover: var(--popover); + --color-popover-foreground: var(--popover-foreground); + --color-primary: var(--primary); + --color-primary-foreground: var(--primary-foreground); + --color-secondary: var(--secondary); + --color-secondary-foreground: var(--secondary-foreground); + --color-muted: var(--muted); + --color-muted-foreground: var(--muted-foreground); + --color-accent: var(--accent); + --color-accent-foreground: var(--accent-foreground); + --color-destructive: var(--destructive); + --color-border: var(--border); + --color-input: var(--input); + --color-ring: var(--ring); + --color-chart-1: var(--chart-1); + --color-chart-2: var(--chart-2); + --color-chart-3: var(--chart-3); + --color-chart-4: var(--chart-4); + --color-chart-5: var(--chart-5); + --color-sidebar: var(--sidebar); + --color-sidebar-foreground: var(--sidebar-foreground); + --color-sidebar-primary: var(--sidebar-primary); + --color-sidebar-primary-foreground: var(--sidebar-primary-foreground); + --color-sidebar-accent: var(--sidebar-accent); + --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); + --color-sidebar-border: var(--sidebar-border); + --color-sidebar-ring: var(--sidebar-ring); +} + +@layer base { + * { + @apply border-border outline-ring/50; + } + + body { + @apply bg-background text-foreground; + } +} diff --git a/src/app.d.ts b/src/app.d.ts new file mode 100644 index 0000000..91493a5 --- /dev/null +++ b/src/app.d.ts @@ -0,0 +1,18 @@ +// See https://svelte.dev/docs/kit/types#app.d.ts +// for information about these interfaces +declare global { + namespace App { + // interface Error {} + interface Locals { + auth: { + admin?: import('$lib/server/auth/jwt').AdminTokenPayload; + member?: import('$lib/server/auth/jwt').MemberTokenPayload; + }; + } + // interface PageData {} + // interface PageState {} + // interface Platform {} + } +} + +export {}; diff --git a/src/app.html b/src/app.html new file mode 100644 index 0000000..ef0f393 --- /dev/null +++ b/src/app.html @@ -0,0 +1,11 @@ + + + + + + %sveltekit.head% + + +
%sveltekit.body%
+ + diff --git a/src/generated/proto/control_communication.ts b/src/generated/proto/control_communication.ts new file mode 100644 index 0000000..2fb5491 --- /dev/null +++ b/src/generated/proto/control_communication.ts @@ -0,0 +1,377 @@ +// Code generated by protoc-gen-ts_proto. DO NOT EDIT. +// versions: +// protoc-gen-ts_proto v2.7.7 +// protoc v3.21.12 +// source: proto/control_communication.proto + +/* eslint-disable */ +import { BinaryReader, BinaryWriter } from "@bufbuild/protobuf/wire"; + +export const protobufPackage = "control_communication"; + +export interface RfidId { + value: number; +} + +export interface SyncResponse { + currentTime: number; + pendingChanges: boolean; +} + +export interface SyncRequest { + lastSync?: number | undefined; + accessLogs: { [key: number]: number }; +} + +export interface SyncRequest_AccessLogsEntry { + key: number; + value: number; +} + +function createBaseRfidId(): RfidId { + return { value: 0 }; +} + +export const RfidId: MessageFns = { + encode(message: RfidId, writer: BinaryWriter = new BinaryWriter()): BinaryWriter { + if (message.value !== 0) { + writer.uint32(8).uint32(message.value); + } + return writer; + }, + + decode(input: BinaryReader | Uint8Array, length?: number): RfidId { + const reader = input instanceof BinaryReader ? input : new BinaryReader(input); + const end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseRfidId(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: { + if (tag !== 8) { + break; + } + + message.value = reader.uint32(); + continue; + } + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skip(tag & 7); + } + return message; + }, + + fromJSON(object: any): RfidId { + return { value: isSet(object.value) ? globalThis.Number(object.value) : 0 }; + }, + + toJSON(message: RfidId): unknown { + const obj: any = {}; + if (message.value !== 0) { + obj.value = Math.round(message.value); + } + return obj; + }, + + create, I>>(base?: I): RfidId { + return RfidId.fromPartial(base ?? ({} as any)); + }, + fromPartial, I>>(object: I): RfidId { + const message = createBaseRfidId(); + message.value = object.value ?? 0; + return message; + }, +}; + +function createBaseSyncResponse(): SyncResponse { + return { currentTime: 0, pendingChanges: false }; +} + +export const SyncResponse: MessageFns = { + encode(message: SyncResponse, writer: BinaryWriter = new BinaryWriter()): BinaryWriter { + if (message.currentTime !== 0) { + writer.uint32(8).int64(message.currentTime); + } + if (message.pendingChanges !== false) { + writer.uint32(16).bool(message.pendingChanges); + } + return writer; + }, + + decode(input: BinaryReader | Uint8Array, length?: number): SyncResponse { + const reader = input instanceof BinaryReader ? input : new BinaryReader(input); + const end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseSyncResponse(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: { + if (tag !== 8) { + break; + } + + message.currentTime = longToNumber(reader.int64()); + continue; + } + case 2: { + if (tag !== 16) { + break; + } + + message.pendingChanges = reader.bool(); + continue; + } + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skip(tag & 7); + } + return message; + }, + + fromJSON(object: any): SyncResponse { + return { + currentTime: isSet(object.currentTime) ? globalThis.Number(object.currentTime) : 0, + pendingChanges: isSet(object.pendingChanges) ? globalThis.Boolean(object.pendingChanges) : false, + }; + }, + + toJSON(message: SyncResponse): unknown { + const obj: any = {}; + if (message.currentTime !== 0) { + obj.currentTime = Math.round(message.currentTime); + } + if (message.pendingChanges !== false) { + obj.pendingChanges = message.pendingChanges; + } + return obj; + }, + + create, I>>(base?: I): SyncResponse { + return SyncResponse.fromPartial(base ?? ({} as any)); + }, + fromPartial, I>>(object: I): SyncResponse { + const message = createBaseSyncResponse(); + message.currentTime = object.currentTime ?? 0; + message.pendingChanges = object.pendingChanges ?? false; + return message; + }, +}; + +function createBaseSyncRequest(): SyncRequest { + return { lastSync: undefined, accessLogs: {} }; +} + +export const SyncRequest: MessageFns = { + encode(message: SyncRequest, writer: BinaryWriter = new BinaryWriter()): BinaryWriter { + if (message.lastSync !== undefined) { + writer.uint32(8).int64(message.lastSync); + } + Object.entries(message.accessLogs).forEach(([key, value]) => { + SyncRequest_AccessLogsEntry.encode({ key: key as any, value }, writer.uint32(18).fork()).join(); + }); + return writer; + }, + + decode(input: BinaryReader | Uint8Array, length?: number): SyncRequest { + const reader = input instanceof BinaryReader ? input : new BinaryReader(input); + const end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseSyncRequest(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: { + if (tag !== 8) { + break; + } + + message.lastSync = longToNumber(reader.int64()); + continue; + } + case 2: { + if (tag !== 18) { + break; + } + + const entry2 = SyncRequest_AccessLogsEntry.decode(reader, reader.uint32()); + if (entry2.value !== undefined) { + message.accessLogs[entry2.key] = entry2.value; + } + continue; + } + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skip(tag & 7); + } + return message; + }, + + fromJSON(object: any): SyncRequest { + return { + lastSync: isSet(object.lastSync) ? globalThis.Number(object.lastSync) : undefined, + accessLogs: isObject(object.accessLogs) + ? Object.entries(object.accessLogs).reduce<{ [key: number]: number }>((acc, [key, value]) => { + acc[globalThis.Number(key)] = Number(value); + return acc; + }, {}) + : {}, + }; + }, + + toJSON(message: SyncRequest): unknown { + const obj: any = {}; + if (message.lastSync !== undefined) { + obj.lastSync = Math.round(message.lastSync); + } + if (message.accessLogs) { + const entries = Object.entries(message.accessLogs); + if (entries.length > 0) { + obj.accessLogs = {}; + entries.forEach(([k, v]) => { + obj.accessLogs[k] = Math.round(v); + }); + } + } + return obj; + }, + + create, I>>(base?: I): SyncRequest { + return SyncRequest.fromPartial(base ?? ({} as any)); + }, + fromPartial, I>>(object: I): SyncRequest { + const message = createBaseSyncRequest(); + message.lastSync = object.lastSync ?? undefined; + message.accessLogs = Object.entries(object.accessLogs ?? {}).reduce<{ [key: number]: number }>( + (acc, [key, value]) => { + if (value !== undefined) { + acc[globalThis.Number(key)] = globalThis.Number(value); + } + return acc; + }, + {}, + ); + return message; + }, +}; + +function createBaseSyncRequest_AccessLogsEntry(): SyncRequest_AccessLogsEntry { + return { key: 0, value: 0 }; +} + +export const SyncRequest_AccessLogsEntry: MessageFns = { + encode(message: SyncRequest_AccessLogsEntry, writer: BinaryWriter = new BinaryWriter()): BinaryWriter { + if (message.key !== 0) { + writer.uint32(8).uint64(message.key); + } + if (message.value !== 0) { + writer.uint32(16).uint32(message.value); + } + return writer; + }, + + decode(input: BinaryReader | Uint8Array, length?: number): SyncRequest_AccessLogsEntry { + const reader = input instanceof BinaryReader ? input : new BinaryReader(input); + const end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseSyncRequest_AccessLogsEntry(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: { + if (tag !== 8) { + break; + } + + message.key = longToNumber(reader.uint64()); + continue; + } + case 2: { + if (tag !== 16) { + break; + } + + message.value = reader.uint32(); + continue; + } + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skip(tag & 7); + } + return message; + }, + + fromJSON(object: any): SyncRequest_AccessLogsEntry { + return { + key: isSet(object.key) ? globalThis.Number(object.key) : 0, + value: isSet(object.value) ? globalThis.Number(object.value) : 0, + }; + }, + + toJSON(message: SyncRequest_AccessLogsEntry): unknown { + const obj: any = {}; + if (message.key !== 0) { + obj.key = Math.round(message.key); + } + if (message.value !== 0) { + obj.value = Math.round(message.value); + } + return obj; + }, + + create, I>>(base?: I): SyncRequest_AccessLogsEntry { + return SyncRequest_AccessLogsEntry.fromPartial(base ?? ({} as any)); + }, + fromPartial, I>>(object: I): SyncRequest_AccessLogsEntry { + const message = createBaseSyncRequest_AccessLogsEntry(); + message.key = object.key ?? 0; + message.value = object.value ?? 0; + return message; + }, +}; + +type Builtin = Date | Function | Uint8Array | string | number | boolean | undefined; + +export type DeepPartial = T extends Builtin ? T + : T extends globalThis.Array ? globalThis.Array> + : T extends ReadonlyArray ? ReadonlyArray> + : T extends {} ? { [K in keyof T]?: DeepPartial } + : Partial; + +type KeysOfUnion = T extends T ? keyof T : never; +export type Exact = P extends Builtin ? P + : P & { [K in keyof P]: Exact } & { [K in Exclude>]: never }; + +function longToNumber(int64: { toString(): string }): number { + const num = globalThis.Number(int64.toString()); + if (num > globalThis.Number.MAX_SAFE_INTEGER) { + throw new globalThis.Error("Value is larger than Number.MAX_SAFE_INTEGER"); + } + if (num < globalThis.Number.MIN_SAFE_INTEGER) { + throw new globalThis.Error("Value is smaller than Number.MIN_SAFE_INTEGER"); + } + return num; +} + +function isObject(value: any): boolean { + return typeof value === "object" && value !== null; +} + +function isSet(value: any): boolean { + return value !== null && value !== undefined; +} + +export interface MessageFns { + encode(message: T, writer?: BinaryWriter): BinaryWriter; + decode(input: BinaryReader | Uint8Array, length?: number): T; + fromJSON(object: any): T; + toJSON(message: T): unknown; + create, I>>(base?: I): T; + fromPartial, I>>(object: I): T; +} diff --git a/src/hooks.server.ts b/src/hooks.server.ts new file mode 100644 index 0000000..752687d --- /dev/null +++ b/src/hooks.server.ts @@ -0,0 +1,52 @@ +import { configManager } from '$lib/config'; +import jwtHelper from '$lib/server/auth/jwt'; +import { handleAuth } from '$lib/server/auth/jwtSessionManager'; +import type { Handle, ServerInit } from '@sveltejs/kit'; +import { sequence } from '@sveltejs/kit/hooks'; +import { paraglideMiddleware } from '$paraglide/server'; +import { setupDatabase } from '$lib/server/db/migrate'; + +let initPromise: Promise | null = null; + +export const init: ServerInit = async () => { + await ensureInit(); + configManager.loadConfig(); + if (import.meta.env.DEV) { + configManager.watchConfig(); + } + await setupDatabase(); +}; + +async function ensureInit(): Promise { + if (!initPromise) { + initPromise = jwtHelper + .init() + .then(() => { + /* initialized */ + }) + .catch((err) => { + // keep logging but rethrow so startup fails loudly if keys cannot be created + // caller can catch this if they want a degraded mode + console.error('jwtHelper.init() failed', err); + throw err; + }); + } + return initPromise; +} + +const paraglideHandle: Handle = ({ event, resolve }) => + paraglideMiddleware(event.request, ({ request: localizedRequest, locale }) => { + event.request = localizedRequest; + return resolve(event, { + transformPageChunk: ({ html }) => { + return html.replace('%lang%', locale); + } + }); + }); + +export const customLogHandle: Handle = async ({ resolve, event }) => { + console.log('Handling request for', event.url.pathname); + // delegate to the JWT session manager SvelteKit handle which sets event.locals.auth + return resolve(event); +}; +export const handle: Handle = sequence(handleAuth, customLogHandle, paraglideHandle); diff --git a/src/hooks.ts b/src/hooks.ts new file mode 100644 index 0000000..5cba559 --- /dev/null +++ b/src/hooks.ts @@ -0,0 +1,8 @@ +import { deLocalizeUrl } from '$paraglide/runtime'; +import type { Reroute } from '@sveltejs/kit'; +import { initZodParaglide } from './schemas/zod_errors'; +initZodParaglide(); + +export const reroute: Reroute = (request) => { + return deLocalizeUrl(request.url).pathname; +}; diff --git a/src/lib/assets/favicon.svg b/src/lib/assets/favicon.svg new file mode 100644 index 0000000..cc5dc66 --- /dev/null +++ b/src/lib/assets/favicon.svg @@ -0,0 +1 @@ +svelte-logo \ No newline at end of file diff --git a/src/lib/components/LanguageSwitcher.svelte b/src/lib/components/LanguageSwitcher.svelte new file mode 100644 index 0000000..ecb0894 --- /dev/null +++ b/src/lib/components/LanguageSwitcher.svelte @@ -0,0 +1,33 @@ + + + + + + {lables[currentLocale]} + + + {#each items as item} + {item.label} + {/each} + + diff --git a/src/lib/components/ThemeToggle.svelte b/src/lib/components/ThemeToggle.svelte new file mode 100644 index 0000000..d9eb294 --- /dev/null +++ b/src/lib/components/ThemeToggle.svelte @@ -0,0 +1,19 @@ + + + diff --git a/src/lib/components/admin/app-sidebar/app-sidebar.svelte b/src/lib/components/admin/app-sidebar/app-sidebar.svelte new file mode 100644 index 0000000..f08ce9e --- /dev/null +++ b/src/lib/components/admin/app-sidebar/app-sidebar.svelte @@ -0,0 +1,248 @@ + + + + +
+
+ +
+
+ {m.sidebar_admin_panel()} + {m.sidebar_management()} +
+
+
+ + + + {m.sidebar_navigation()} + + + {#each navigationItems as item} + + {#if item.items} + toggleExpanded(item.title)}> + + {item.title} + + + {#if expandedItems.has(item.title)} + + {#each item.items as subItem} + + + {subItem.title} + + + {/each} + + {/if} + {:else} + + + {item.title} + {#if item.badge} + + {item.badge} + + {/if} + + {/if} + + {/each} + + + + + + {m.sidebar_system()} + + + {#each settingsItems as item} + + + + {item.title} + + + {/each} + + + + + + +
+
+ +
+
{user.firstName} {user.lastName}
+
{user.username}
+
+ +
+
+
+
diff --git a/src/lib/components/admin/app-sidebar/index.ts b/src/lib/components/admin/app-sidebar/index.ts new file mode 100644 index 0000000..31afc07 --- /dev/null +++ b/src/lib/components/admin/app-sidebar/index.ts @@ -0,0 +1 @@ +export { default as AppSidebar } from './app-sidebar.svelte'; diff --git a/src/lib/components/index.ts b/src/lib/components/index.ts new file mode 100644 index 0000000..ee37037 --- /dev/null +++ b/src/lib/components/index.ts @@ -0,0 +1,2 @@ +export { default as ThemeToggle } from './ThemeToggle.svelte'; +export { default as LanguageSwitcher } from './LanguageSwitcher.svelte'; diff --git a/src/lib/components/ui/alert-dialog/alert-dialog-action.svelte b/src/lib/components/ui/alert-dialog/alert-dialog-action.svelte new file mode 100644 index 0000000..241a48f --- /dev/null +++ b/src/lib/components/ui/alert-dialog/alert-dialog-action.svelte @@ -0,0 +1,16 @@ + + + + {@render children?.()} + diff --git a/src/lib/components/ui/alert-dialog/alert-dialog-cancel.svelte b/src/lib/components/ui/alert-dialog/alert-dialog-cancel.svelte new file mode 100644 index 0000000..16818e2 --- /dev/null +++ b/src/lib/components/ui/alert-dialog/alert-dialog-cancel.svelte @@ -0,0 +1,20 @@ + + + + {@render children?.()} + diff --git a/src/lib/components/ui/alert-dialog/alert-dialog-content.svelte b/src/lib/components/ui/alert-dialog/alert-dialog-content.svelte new file mode 100644 index 0000000..35e4446 --- /dev/null +++ b/src/lib/components/ui/alert-dialog/alert-dialog-content.svelte @@ -0,0 +1,27 @@ + + + + + + diff --git a/src/lib/components/ui/alert-dialog/alert-dialog-description.svelte b/src/lib/components/ui/alert-dialog/alert-dialog-description.svelte new file mode 100644 index 0000000..82e3079 --- /dev/null +++ b/src/lib/components/ui/alert-dialog/alert-dialog-description.svelte @@ -0,0 +1,17 @@ + + + diff --git a/src/lib/components/ui/alert-dialog/alert-dialog-footer.svelte b/src/lib/components/ui/alert-dialog/alert-dialog-footer.svelte new file mode 100644 index 0000000..94bbfaa --- /dev/null +++ b/src/lib/components/ui/alert-dialog/alert-dialog-footer.svelte @@ -0,0 +1,20 @@ + + +
+ {@render children?.()} +
diff --git a/src/lib/components/ui/alert-dialog/alert-dialog-header.svelte b/src/lib/components/ui/alert-dialog/alert-dialog-header.svelte new file mode 100644 index 0000000..1f9604a --- /dev/null +++ b/src/lib/components/ui/alert-dialog/alert-dialog-header.svelte @@ -0,0 +1,20 @@ + + +
+ {@render children?.()} +
diff --git a/src/lib/components/ui/alert-dialog/alert-dialog-overlay.svelte b/src/lib/components/ui/alert-dialog/alert-dialog-overlay.svelte new file mode 100644 index 0000000..766b86b --- /dev/null +++ b/src/lib/components/ui/alert-dialog/alert-dialog-overlay.svelte @@ -0,0 +1,20 @@ + + + diff --git a/src/lib/components/ui/alert-dialog/alert-dialog-title.svelte b/src/lib/components/ui/alert-dialog/alert-dialog-title.svelte new file mode 100644 index 0000000..c292699 --- /dev/null +++ b/src/lib/components/ui/alert-dialog/alert-dialog-title.svelte @@ -0,0 +1,17 @@ + + + diff --git a/src/lib/components/ui/alert-dialog/alert-dialog-trigger.svelte b/src/lib/components/ui/alert-dialog/alert-dialog-trigger.svelte new file mode 100644 index 0000000..51a3da1 --- /dev/null +++ b/src/lib/components/ui/alert-dialog/alert-dialog-trigger.svelte @@ -0,0 +1,7 @@ + + + diff --git a/src/lib/components/ui/alert-dialog/index.ts b/src/lib/components/ui/alert-dialog/index.ts new file mode 100644 index 0000000..a4439bc --- /dev/null +++ b/src/lib/components/ui/alert-dialog/index.ts @@ -0,0 +1,39 @@ +import { AlertDialog as AlertDialogPrimitive } from 'bits-ui'; +import Trigger from './alert-dialog-trigger.svelte'; +import Title from './alert-dialog-title.svelte'; +import Action from './alert-dialog-action.svelte'; +import Cancel from './alert-dialog-cancel.svelte'; +import Footer from './alert-dialog-footer.svelte'; +import Header from './alert-dialog-header.svelte'; +import Overlay from './alert-dialog-overlay.svelte'; +import Content from './alert-dialog-content.svelte'; +import Description from './alert-dialog-description.svelte'; + +const Root = AlertDialogPrimitive.Root; +const Portal = AlertDialogPrimitive.Portal; + +export { + Root, + Title, + Action, + Cancel, + Portal, + Footer, + Header, + Trigger, + Overlay, + Content, + Description, + // + Root as AlertDialog, + Title as AlertDialogTitle, + Action as AlertDialogAction, + Cancel as AlertDialogCancel, + Portal as AlertDialogPortal, + Footer as AlertDialogFooter, + Header as AlertDialogHeader, + Trigger as AlertDialogTrigger, + Overlay as AlertDialogOverlay, + Content as AlertDialogContent, + Description as AlertDialogDescription +}; diff --git a/src/lib/components/ui/alert/alert-description.svelte b/src/lib/components/ui/alert/alert-description.svelte new file mode 100644 index 0000000..ceccee5 --- /dev/null +++ b/src/lib/components/ui/alert/alert-description.svelte @@ -0,0 +1,23 @@ + + +
+ {@render children?.()} +
diff --git a/src/lib/components/ui/alert/alert-title.svelte b/src/lib/components/ui/alert/alert-title.svelte new file mode 100644 index 0000000..8a35d09 --- /dev/null +++ b/src/lib/components/ui/alert/alert-title.svelte @@ -0,0 +1,20 @@ + + +
+ {@render children?.()} +
diff --git a/src/lib/components/ui/alert/alert.svelte b/src/lib/components/ui/alert/alert.svelte new file mode 100644 index 0000000..a7e6d06 --- /dev/null +++ b/src/lib/components/ui/alert/alert.svelte @@ -0,0 +1,44 @@ + + + + + diff --git a/src/lib/components/ui/alert/index.ts b/src/lib/components/ui/alert/index.ts new file mode 100644 index 0000000..5e0f854 --- /dev/null +++ b/src/lib/components/ui/alert/index.ts @@ -0,0 +1,14 @@ +import Root from './alert.svelte'; +import Description from './alert-description.svelte'; +import Title from './alert-title.svelte'; +export { alertVariants, type AlertVariant } from './alert.svelte'; + +export { + Root, + Description, + Title, + // + Root as Alert, + Description as AlertDescription, + Title as AlertTitle +}; diff --git a/src/lib/components/ui/avatar/avatar-fallback.svelte b/src/lib/components/ui/avatar/avatar-fallback.svelte new file mode 100644 index 0000000..4a452e9 --- /dev/null +++ b/src/lib/components/ui/avatar/avatar-fallback.svelte @@ -0,0 +1,17 @@ + + + diff --git a/src/lib/components/ui/avatar/avatar-image.svelte b/src/lib/components/ui/avatar/avatar-image.svelte new file mode 100644 index 0000000..7ccc3ce --- /dev/null +++ b/src/lib/components/ui/avatar/avatar-image.svelte @@ -0,0 +1,17 @@ + + + diff --git a/src/lib/components/ui/avatar/avatar.svelte b/src/lib/components/ui/avatar/avatar.svelte new file mode 100644 index 0000000..3fd4dc2 --- /dev/null +++ b/src/lib/components/ui/avatar/avatar.svelte @@ -0,0 +1,19 @@ + + + diff --git a/src/lib/components/ui/avatar/index.ts b/src/lib/components/ui/avatar/index.ts new file mode 100644 index 0000000..9585f8a --- /dev/null +++ b/src/lib/components/ui/avatar/index.ts @@ -0,0 +1,13 @@ +import Root from './avatar.svelte'; +import Image from './avatar-image.svelte'; +import Fallback from './avatar-fallback.svelte'; + +export { + Root, + Image, + Fallback, + // + Root as Avatar, + Image as AvatarImage, + Fallback as AvatarFallback +}; diff --git a/src/lib/components/ui/badge/badge.svelte b/src/lib/components/ui/badge/badge.svelte new file mode 100644 index 0000000..7c242f7 --- /dev/null +++ b/src/lib/components/ui/badge/badge.svelte @@ -0,0 +1,49 @@ + + + + + + {@render children?.()} + diff --git a/src/lib/components/ui/badge/index.ts b/src/lib/components/ui/badge/index.ts new file mode 100644 index 0000000..f05fb87 --- /dev/null +++ b/src/lib/components/ui/badge/index.ts @@ -0,0 +1,2 @@ +export { default as Badge } from './badge.svelte'; +export { badgeVariants, type BadgeVariant } from './badge.svelte'; diff --git a/src/lib/components/ui/breadcrumb/breadcrumb-ellipsis.svelte b/src/lib/components/ui/breadcrumb/breadcrumb-ellipsis.svelte new file mode 100644 index 0000000..030197f --- /dev/null +++ b/src/lib/components/ui/breadcrumb/breadcrumb-ellipsis.svelte @@ -0,0 +1,23 @@ + + + diff --git a/src/lib/components/ui/breadcrumb/breadcrumb-item.svelte b/src/lib/components/ui/breadcrumb/breadcrumb-item.svelte new file mode 100644 index 0000000..081b636 --- /dev/null +++ b/src/lib/components/ui/breadcrumb/breadcrumb-item.svelte @@ -0,0 +1,20 @@ + + +
  • + {@render children?.()} +
  • diff --git a/src/lib/components/ui/breadcrumb/breadcrumb-link.svelte b/src/lib/components/ui/breadcrumb/breadcrumb-link.svelte new file mode 100644 index 0000000..c56ab6d --- /dev/null +++ b/src/lib/components/ui/breadcrumb/breadcrumb-link.svelte @@ -0,0 +1,31 @@ + + +{#if child} + {@render child({ props: attrs })} +{:else} + + {@render children?.()} + +{/if} diff --git a/src/lib/components/ui/breadcrumb/breadcrumb-list.svelte b/src/lib/components/ui/breadcrumb/breadcrumb-list.svelte new file mode 100644 index 0000000..7bd6d30 --- /dev/null +++ b/src/lib/components/ui/breadcrumb/breadcrumb-list.svelte @@ -0,0 +1,23 @@ + + +
      + {@render children?.()} +
    diff --git a/src/lib/components/ui/breadcrumb/breadcrumb-page.svelte b/src/lib/components/ui/breadcrumb/breadcrumb-page.svelte new file mode 100644 index 0000000..3c6be01 --- /dev/null +++ b/src/lib/components/ui/breadcrumb/breadcrumb-page.svelte @@ -0,0 +1,23 @@ + + + + {@render children?.()} + diff --git a/src/lib/components/ui/breadcrumb/breadcrumb-separator.svelte b/src/lib/components/ui/breadcrumb/breadcrumb-separator.svelte new file mode 100644 index 0000000..c3b70c6 --- /dev/null +++ b/src/lib/components/ui/breadcrumb/breadcrumb-separator.svelte @@ -0,0 +1,27 @@ + + + diff --git a/src/lib/components/ui/breadcrumb/breadcrumb.svelte b/src/lib/components/ui/breadcrumb/breadcrumb.svelte new file mode 100644 index 0000000..c797f82 --- /dev/null +++ b/src/lib/components/ui/breadcrumb/breadcrumb.svelte @@ -0,0 +1,21 @@ + + + diff --git a/src/lib/components/ui/breadcrumb/index.ts b/src/lib/components/ui/breadcrumb/index.ts new file mode 100644 index 0000000..773fd60 --- /dev/null +++ b/src/lib/components/ui/breadcrumb/index.ts @@ -0,0 +1,25 @@ +import Root from './breadcrumb.svelte'; +import Ellipsis from './breadcrumb-ellipsis.svelte'; +import Item from './breadcrumb-item.svelte'; +import Separator from './breadcrumb-separator.svelte'; +import Link from './breadcrumb-link.svelte'; +import List from './breadcrumb-list.svelte'; +import Page from './breadcrumb-page.svelte'; + +export { + Root, + Ellipsis, + Item, + Separator, + Link, + List, + Page, + // + Root as Breadcrumb, + Ellipsis as BreadcrumbEllipsis, + Item as BreadcrumbItem, + Separator as BreadcrumbSeparator, + Link as BreadcrumbLink, + List as BreadcrumbList, + Page as BreadcrumbPage +}; diff --git a/src/lib/components/ui/button/button.svelte b/src/lib/components/ui/button/button.svelte new file mode 100644 index 0000000..16f1a3c --- /dev/null +++ b/src/lib/components/ui/button/button.svelte @@ -0,0 +1,80 @@ + + + + +{#if href} + + {@render children?.()} + +{:else} + +{/if} diff --git a/src/lib/components/ui/button/index.ts b/src/lib/components/ui/button/index.ts new file mode 100644 index 0000000..5414d9d --- /dev/null +++ b/src/lib/components/ui/button/index.ts @@ -0,0 +1,17 @@ +import Root, { + type ButtonProps, + type ButtonSize, + type ButtonVariant, + buttonVariants +} from './button.svelte'; + +export { + Root, + type ButtonProps as Props, + // + Root as Button, + buttonVariants, + type ButtonProps, + type ButtonSize, + type ButtonVariant +}; diff --git a/src/lib/components/ui/calendar/calendar-caption.svelte b/src/lib/components/ui/calendar/calendar-caption.svelte new file mode 100644 index 0000000..670ee83 --- /dev/null +++ b/src/lib/components/ui/calendar/calendar-caption.svelte @@ -0,0 +1,76 @@ + + +{#snippet MonthSelect()} + { + if (!placeholder) return; + const v = Number.parseInt(e.currentTarget.value); + const newPlaceholder = placeholder.set({ month: v }); + placeholder = newPlaceholder.subtract({ months: monthIndex }); + }} + /> +{/snippet} + +{#snippet YearSelect()} + +{/snippet} + +{#if captionLayout === 'dropdown'} + {@render MonthSelect()} + {@render YearSelect()} +{:else if captionLayout === 'dropdown-months'} + {@render MonthSelect()} + {#if placeholder} + {formatYear(placeholder)} + {/if} +{:else if captionLayout === 'dropdown-years'} + {#if placeholder} + {formatMonth(placeholder)} + {/if} + {@render YearSelect()} +{:else} + {formatMonth(month)} {formatYear(month)} +{/if} diff --git a/src/lib/components/ui/calendar/calendar-cell.svelte b/src/lib/components/ui/calendar/calendar-cell.svelte new file mode 100644 index 0000000..6eb8149 --- /dev/null +++ b/src/lib/components/ui/calendar/calendar-cell.svelte @@ -0,0 +1,19 @@ + + + diff --git a/src/lib/components/ui/calendar/calendar-day.svelte b/src/lib/components/ui/calendar/calendar-day.svelte new file mode 100644 index 0000000..c8c692c --- /dev/null +++ b/src/lib/components/ui/calendar/calendar-day.svelte @@ -0,0 +1,35 @@ + + +span]:text-xs [&>span]:opacity-70', + className + )} + {...restProps} +/> diff --git a/src/lib/components/ui/calendar/calendar-grid-body.svelte b/src/lib/components/ui/calendar/calendar-grid-body.svelte new file mode 100644 index 0000000..fb1d89b --- /dev/null +++ b/src/lib/components/ui/calendar/calendar-grid-body.svelte @@ -0,0 +1,12 @@ + + + diff --git a/src/lib/components/ui/calendar/calendar-grid-head.svelte b/src/lib/components/ui/calendar/calendar-grid-head.svelte new file mode 100644 index 0000000..b5b6845 --- /dev/null +++ b/src/lib/components/ui/calendar/calendar-grid-head.svelte @@ -0,0 +1,12 @@ + + + diff --git a/src/lib/components/ui/calendar/calendar-grid-row.svelte b/src/lib/components/ui/calendar/calendar-grid-row.svelte new file mode 100644 index 0000000..2e7d9d8 --- /dev/null +++ b/src/lib/components/ui/calendar/calendar-grid-row.svelte @@ -0,0 +1,12 @@ + + + diff --git a/src/lib/components/ui/calendar/calendar-grid.svelte b/src/lib/components/ui/calendar/calendar-grid.svelte new file mode 100644 index 0000000..bfa49dc --- /dev/null +++ b/src/lib/components/ui/calendar/calendar-grid.svelte @@ -0,0 +1,16 @@ + + + diff --git a/src/lib/components/ui/calendar/calendar-head-cell.svelte b/src/lib/components/ui/calendar/calendar-head-cell.svelte new file mode 100644 index 0000000..7a1dea0 --- /dev/null +++ b/src/lib/components/ui/calendar/calendar-head-cell.svelte @@ -0,0 +1,19 @@ + + + diff --git a/src/lib/components/ui/calendar/calendar-header.svelte b/src/lib/components/ui/calendar/calendar-header.svelte new file mode 100644 index 0000000..8ca9618 --- /dev/null +++ b/src/lib/components/ui/calendar/calendar-header.svelte @@ -0,0 +1,19 @@ + + + diff --git a/src/lib/components/ui/calendar/calendar-heading.svelte b/src/lib/components/ui/calendar/calendar-heading.svelte new file mode 100644 index 0000000..7a85b55 --- /dev/null +++ b/src/lib/components/ui/calendar/calendar-heading.svelte @@ -0,0 +1,16 @@ + + + diff --git a/src/lib/components/ui/calendar/calendar-month-select.svelte b/src/lib/components/ui/calendar/calendar-month-select.svelte new file mode 100644 index 0000000..3202334 --- /dev/null +++ b/src/lib/components/ui/calendar/calendar-month-select.svelte @@ -0,0 +1,44 @@ + + + + + {#snippet child({ props, monthItems, selectedMonthItem })} + + + {/snippet} + + diff --git a/src/lib/components/ui/calendar/calendar-month.svelte b/src/lib/components/ui/calendar/calendar-month.svelte new file mode 100644 index 0000000..799d2c5 --- /dev/null +++ b/src/lib/components/ui/calendar/calendar-month.svelte @@ -0,0 +1,15 @@ + + +
    + {@render children?.()} +
    diff --git a/src/lib/components/ui/calendar/calendar-months.svelte b/src/lib/components/ui/calendar/calendar-months.svelte new file mode 100644 index 0000000..023cad3 --- /dev/null +++ b/src/lib/components/ui/calendar/calendar-months.svelte @@ -0,0 +1,19 @@ + + +
    + {@render children?.()} +
    diff --git a/src/lib/components/ui/calendar/calendar-nav.svelte b/src/lib/components/ui/calendar/calendar-nav.svelte new file mode 100644 index 0000000..2fea3e9 --- /dev/null +++ b/src/lib/components/ui/calendar/calendar-nav.svelte @@ -0,0 +1,19 @@ + + + diff --git a/src/lib/components/ui/calendar/calendar-next-button.svelte b/src/lib/components/ui/calendar/calendar-next-button.svelte new file mode 100644 index 0000000..ab5ca59 --- /dev/null +++ b/src/lib/components/ui/calendar/calendar-next-button.svelte @@ -0,0 +1,31 @@ + + +{#snippet Fallback()} + +{/snippet} + + diff --git a/src/lib/components/ui/calendar/calendar-prev-button.svelte b/src/lib/components/ui/calendar/calendar-prev-button.svelte new file mode 100644 index 0000000..c39b48a --- /dev/null +++ b/src/lib/components/ui/calendar/calendar-prev-button.svelte @@ -0,0 +1,31 @@ + + +{#snippet Fallback()} + +{/snippet} + + diff --git a/src/lib/components/ui/calendar/calendar-year-select.svelte b/src/lib/components/ui/calendar/calendar-year-select.svelte new file mode 100644 index 0000000..eb08611 --- /dev/null +++ b/src/lib/components/ui/calendar/calendar-year-select.svelte @@ -0,0 +1,43 @@ + + + + + {#snippet child({ props, yearItems, selectedYearItem })} + + + {/snippet} + + diff --git a/src/lib/components/ui/calendar/calendar.svelte b/src/lib/components/ui/calendar/calendar.svelte new file mode 100644 index 0000000..40ea145 --- /dev/null +++ b/src/lib/components/ui/calendar/calendar.svelte @@ -0,0 +1,115 @@ + + + + + {#snippet children({ months, weekdays })} + + + + + + {#each months as month, monthIndex (month)} + + + + + + + + {#each weekdays as weekday (weekday)} + + {weekday.slice(0, 2)} + + {/each} + + + + {#each month.weeks as weekDates (weekDates)} + + {#each weekDates as date (date)} + + {#if day} + {@render day({ + day: date, + outsideMonth: !isEqualMonth(date, month.value) + })} + {:else} + + {/if} + + {/each} + + {/each} + + + + {/each} + + {/snippet} + diff --git a/src/lib/components/ui/calendar/index.ts b/src/lib/components/ui/calendar/index.ts new file mode 100644 index 0000000..180d173 --- /dev/null +++ b/src/lib/components/ui/calendar/index.ts @@ -0,0 +1,40 @@ +import Root from './calendar.svelte'; +import Cell from './calendar-cell.svelte'; +import Day from './calendar-day.svelte'; +import Grid from './calendar-grid.svelte'; +import Header from './calendar-header.svelte'; +import Months from './calendar-months.svelte'; +import GridRow from './calendar-grid-row.svelte'; +import Heading from './calendar-heading.svelte'; +import GridBody from './calendar-grid-body.svelte'; +import GridHead from './calendar-grid-head.svelte'; +import HeadCell from './calendar-head-cell.svelte'; +import NextButton from './calendar-next-button.svelte'; +import PrevButton from './calendar-prev-button.svelte'; +import MonthSelect from './calendar-month-select.svelte'; +import YearSelect from './calendar-year-select.svelte'; +import Month from './calendar-month.svelte'; +import Nav from './calendar-nav.svelte'; +import Caption from './calendar-caption.svelte'; + +export { + Day, + Cell, + Grid, + Header, + Months, + GridRow, + Heading, + GridBody, + GridHead, + HeadCell, + NextButton, + PrevButton, + Nav, + Month, + YearSelect, + MonthSelect, + Caption, + // + Root as Calendar +}; diff --git a/src/lib/components/ui/card/card-action.svelte b/src/lib/components/ui/card/card-action.svelte new file mode 100644 index 0000000..bbaafd2 --- /dev/null +++ b/src/lib/components/ui/card/card-action.svelte @@ -0,0 +1,20 @@ + + +
    + {@render children?.()} +
    diff --git a/src/lib/components/ui/card/card-content.svelte b/src/lib/components/ui/card/card-content.svelte new file mode 100644 index 0000000..1d60124 --- /dev/null +++ b/src/lib/components/ui/card/card-content.svelte @@ -0,0 +1,15 @@ + + +
    + {@render children?.()} +
    diff --git a/src/lib/components/ui/card/card-description.svelte b/src/lib/components/ui/card/card-description.svelte new file mode 100644 index 0000000..b46a1ad --- /dev/null +++ b/src/lib/components/ui/card/card-description.svelte @@ -0,0 +1,20 @@ + + +

    + {@render children?.()} +

    diff --git a/src/lib/components/ui/card/card-footer.svelte b/src/lib/components/ui/card/card-footer.svelte new file mode 100644 index 0000000..4e390bf --- /dev/null +++ b/src/lib/components/ui/card/card-footer.svelte @@ -0,0 +1,20 @@ + + +
    + {@render children?.()} +
    diff --git a/src/lib/components/ui/card/card-header.svelte b/src/lib/components/ui/card/card-header.svelte new file mode 100644 index 0000000..9cdc602 --- /dev/null +++ b/src/lib/components/ui/card/card-header.svelte @@ -0,0 +1,23 @@ + + +
    + {@render children?.()} +
    diff --git a/src/lib/components/ui/card/card-title.svelte b/src/lib/components/ui/card/card-title.svelte new file mode 100644 index 0000000..0eae8ec --- /dev/null +++ b/src/lib/components/ui/card/card-title.svelte @@ -0,0 +1,20 @@ + + +
    + {@render children?.()} +
    diff --git a/src/lib/components/ui/card/card.svelte b/src/lib/components/ui/card/card.svelte new file mode 100644 index 0000000..1030435 --- /dev/null +++ b/src/lib/components/ui/card/card.svelte @@ -0,0 +1,23 @@ + + +
    + {@render children?.()} +
    diff --git a/src/lib/components/ui/card/index.ts b/src/lib/components/ui/card/index.ts new file mode 100644 index 0000000..77d3674 --- /dev/null +++ b/src/lib/components/ui/card/index.ts @@ -0,0 +1,25 @@ +import Root from './card.svelte'; +import Content from './card-content.svelte'; +import Description from './card-description.svelte'; +import Footer from './card-footer.svelte'; +import Header from './card-header.svelte'; +import Title from './card-title.svelte'; +import Action from './card-action.svelte'; + +export { + Root, + Content, + Description, + Footer, + Header, + Title, + Action, + // + Root as Card, + Content as CardContent, + Description as CardDescription, + Footer as CardFooter, + Header as CardHeader, + Title as CardTitle, + Action as CardAction +}; diff --git a/src/lib/components/ui/chart/chart-container.svelte b/src/lib/components/ui/chart/chart-container.svelte new file mode 100644 index 0000000..2b7b697 --- /dev/null +++ b/src/lib/components/ui/chart/chart-container.svelte @@ -0,0 +1,80 @@ + + +
    + + {@render children?.()} +
    diff --git a/src/lib/components/ui/chart/chart-style.svelte b/src/lib/components/ui/chart/chart-style.svelte new file mode 100644 index 0000000..9775663 --- /dev/null +++ b/src/lib/components/ui/chart/chart-style.svelte @@ -0,0 +1,37 @@ + + +{#if themeContents} + {#key id} + + {themeContents} + + {/key} +{/if} diff --git a/src/lib/components/ui/chart/chart-tooltip.svelte b/src/lib/components/ui/chart/chart-tooltip.svelte new file mode 100644 index 0000000..53ea0b0 --- /dev/null +++ b/src/lib/components/ui/chart/chart-tooltip.svelte @@ -0,0 +1,155 @@ + + +{#snippet TooltipLabel()} + {#if formattedLabel} +
    + {#if typeof formattedLabel === 'function'} + {@render formattedLabel()} + {:else} + {formattedLabel} + {/if} +
    + {/if} +{/snippet} + + +
    + {#if !nestLabel} + {@render TooltipLabel()} + {/if} +
    + {#each tooltipCtx.payload as item, i (item.key + i)} + {@const key = `${nameKey || item.key || item.name || 'value'}`} + {@const itemConfig = getPayloadConfigFromPayload(chart.config, item, key)} + {@const indicatorColor = color || item.payload?.color || item.color} +
    svg]:size-2.5 [&>svg]:text-muted-foreground', + indicator === 'dot' && 'items-center' + )} + > + {#if formatter && item.value !== undefined && item.name} + {@render formatter({ + value: item.value, + name: item.name, + item, + index: i, + payload: tooltipCtx.payload + })} + {:else} + {#if itemConfig?.icon} + + {:else if !hideIndicator} +
    + {/if} +
    +
    + {#if nestLabel} + {@render TooltipLabel()} + {/if} + + {itemConfig?.label || item.name} + +
    + {#if item.value !== undefined} + + {item.value.toLocaleString()} + + {/if} +
    + {/if} +
    + {/each} +
    +
    +
    diff --git a/src/lib/components/ui/chart/chart-utils.ts b/src/lib/components/ui/chart/chart-utils.ts new file mode 100644 index 0000000..f2e682d --- /dev/null +++ b/src/lib/components/ui/chart/chart-utils.ts @@ -0,0 +1,66 @@ +import type { Tooltip } from 'layerchart'; +import { getContext, setContext, type Component, type ComponentProps, type Snippet } from 'svelte'; + +export const THEMES = { light: '', dark: '.dark' } as const; + +export type ChartConfig = { + [k in string]: { + label?: string; + icon?: Component; + } & ( + | { color?: string; theme?: never } + | { color?: never; theme: Record } + ); +}; + +export type ExtractSnippetParams = T extends Snippet<[infer P]> ? P : never; + +export type TooltipPayload = ExtractSnippetParams< + ComponentProps['children'] +>['payload'][number]; + +// Helper to extract item config from a payload. +export function getPayloadConfigFromPayload( + config: ChartConfig, + payload: TooltipPayload, + key: string +) { + if (typeof payload !== 'object' || payload === null) return undefined; + + const payloadPayload = + 'payload' in payload && typeof payload.payload === 'object' && payload.payload !== null + ? payload.payload + : undefined; + + let configLabelKey: string = key; + + if (payload.key === key) { + configLabelKey = payload.key; + } else if (payload.name === key) { + configLabelKey = payload.name; + } else if (key in payload && typeof payload[key as keyof typeof payload] === 'string') { + configLabelKey = payload[key as keyof typeof payload] as string; + } else if ( + payloadPayload !== undefined && + key in payloadPayload && + typeof payloadPayload[key as keyof typeof payloadPayload] === 'string' + ) { + configLabelKey = payloadPayload[key as keyof typeof payloadPayload] as string; + } + + return configLabelKey in config ? config[configLabelKey] : config[key as keyof typeof config]; +} + +type ChartContextValue = { + config: ChartConfig; +}; + +const chartContextKey = Symbol('chart-context'); + +export function setChartContext(value: ChartContextValue) { + return setContext(chartContextKey, value); +} + +export function useChart() { + return getContext(chartContextKey); +} diff --git a/src/lib/components/ui/chart/index.ts b/src/lib/components/ui/chart/index.ts new file mode 100644 index 0000000..25cfcd4 --- /dev/null +++ b/src/lib/components/ui/chart/index.ts @@ -0,0 +1,6 @@ +import ChartContainer from './chart-container.svelte'; +import ChartTooltip from './chart-tooltip.svelte'; + +export { getPayloadConfigFromPayload, type ChartConfig } from './chart-utils.js'; + +export { ChartContainer, ChartTooltip, ChartContainer as Container, ChartTooltip as Tooltip }; diff --git a/src/lib/components/ui/checkbox/checkbox.svelte b/src/lib/components/ui/checkbox/checkbox.svelte new file mode 100644 index 0000000..3a7817d --- /dev/null +++ b/src/lib/components/ui/checkbox/checkbox.svelte @@ -0,0 +1,36 @@ + + + + {#snippet children({ checked, indeterminate })} +
    + {#if checked} + + {:else if indeterminate} + + {/if} +
    + {/snippet} +
    diff --git a/src/lib/components/ui/checkbox/index.ts b/src/lib/components/ui/checkbox/index.ts new file mode 100644 index 0000000..5c27671 --- /dev/null +++ b/src/lib/components/ui/checkbox/index.ts @@ -0,0 +1,6 @@ +import Root from './checkbox.svelte'; +export { + Root, + // + Root as Checkbox +}; diff --git a/src/lib/components/ui/data-table/data-table.svelte.ts b/src/lib/components/ui/data-table/data-table.svelte.ts new file mode 100644 index 0000000..5dab15a --- /dev/null +++ b/src/lib/components/ui/data-table/data-table.svelte.ts @@ -0,0 +1,141 @@ +import { + type RowData, + type TableOptions, + type TableOptionsResolved, + type TableState, + createTable +} from '@tanstack/table-core'; + +/** + * Creates a reactive TanStack table object for Svelte. + * @param options Table options to create the table with. + * @returns A reactive table object. + * @example + * ```svelte + * + * + * + * + * {#each table.getHeaderGroups() as headerGroup} + * + * {#each headerGroup.headers as header} + * + * {/each} + * + * {/each} + * + * + *
    + * + *
    + * ``` + */ +export function createSvelteTable(options: TableOptions) { + const resolvedOptions: TableOptionsResolved = mergeObjects( + { + state: {}, + onStateChange() {}, + renderFallbackValue: null, + mergeOptions: ( + defaultOptions: TableOptions, + options: Partial> + ) => { + return mergeObjects(defaultOptions, options); + } + }, + options + ); + + const table = createTable(resolvedOptions); + let state = $state>(table.initialState); + + function updateOptions() { + table.setOptions((prev) => { + return mergeObjects(prev, options, { + state: mergeObjects(state, options.state || {}), + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + onStateChange: (updater: any) => { + if (updater instanceof Function) state = updater(state); + else state = mergeObjects(state, updater); + + options.onStateChange?.(updater); + } + }); + }); + } + + updateOptions(); + + $effect.pre(() => { + updateOptions(); + }); + + return table; +} + +type MaybeThunk = T | (() => T | null | undefined); +type Intersection = (T extends [infer H, ...infer R] + ? H & Intersection + : unknown) & {}; + +/** + * Lazily merges several objects (or thunks) while preserving + * getter semantics from every source. + * + * Proxy-based to avoid known WebKit recursion issue. + */ +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export function mergeObjects[]>( + ...sources: Sources +): Intersection<{ [K in keyof Sources]: Sources[K] }> { + const resolve = (src: MaybeThunk): T | undefined => + typeof src === 'function' ? (src() ?? undefined) : src; + + const findSourceWithKey = (key: PropertyKey) => { + for (let i = sources.length - 1; i >= 0; i--) { + const obj = resolve(sources[i]); + if (obj && key in obj) return obj; + } + return undefined; + }; + + return new Proxy(Object.create(null), { + get(_, key) { + const src = findSourceWithKey(key); + + return src?.[key as never]; + }, + + has(_, key) { + return !!findSourceWithKey(key); + }, + + ownKeys(): (string | symbol)[] { + const all = new Set(); + for (const s of sources) { + const obj = resolve(s); + if (obj) { + for (const k of Reflect.ownKeys(obj) as (string | symbol)[]) { + all.add(k); + } + } + } + return [...all]; + }, + + getOwnPropertyDescriptor(_, key) { + const src = findSourceWithKey(key); + if (!src) return undefined; + return { + configurable: true, + enumerable: true, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + value: (src as any)[key], + writable: true + }; + } + }) as Intersection<{ [K in keyof Sources]: Sources[K] }>; +} diff --git a/src/lib/components/ui/data-table/flex-render.svelte b/src/lib/components/ui/data-table/flex-render.svelte new file mode 100644 index 0000000..2cdff3b --- /dev/null +++ b/src/lib/components/ui/data-table/flex-render.svelte @@ -0,0 +1,36 @@ + + +{#if typeof content === 'string'} + {content} +{:else if content instanceof Function} + + + {@const result = content(context as any)} + {#if result instanceof RenderComponentConfig} + {@const { component: Component, props } = result} + + {:else if result instanceof RenderSnippetConfig} + {@const { snippet, params } = result} + {@render snippet(params)} + {:else} + {result} + {/if} +{/if} diff --git a/src/lib/components/ui/data-table/index.ts b/src/lib/components/ui/data-table/index.ts new file mode 100644 index 0000000..05fb821 --- /dev/null +++ b/src/lib/components/ui/data-table/index.ts @@ -0,0 +1,3 @@ +export { default as FlexRender } from './flex-render.svelte'; +export { renderComponent, renderSnippet } from './render-helpers.js'; +export { createSvelteTable } from './data-table.svelte.js'; diff --git a/src/lib/components/ui/data-table/render-helpers.ts b/src/lib/components/ui/data-table/render-helpers.ts new file mode 100644 index 0000000..aea5dc2 --- /dev/null +++ b/src/lib/components/ui/data-table/render-helpers.ts @@ -0,0 +1,111 @@ +import type { Component, ComponentProps, Snippet } from 'svelte'; + +/** + * A helper class to make it easy to identify Svelte components in + * `columnDef.cell` and `columnDef.header` properties. + * + * > NOTE: This class should only be used internally by the adapter. If you're + * reading this and you don't know what this is for, you probably don't need it. + * + * @example + * ```svelte + * {@const result = content(context as any)} + * {#if result instanceof RenderComponentConfig} + * {@const { component: Component, props } = result} + * + * {/if} + * ``` + */ +export class RenderComponentConfig { + component: TComponent; + props: ComponentProps | Record; + constructor( + component: TComponent, + props: ComponentProps | Record = {} + ) { + this.component = component; + this.props = props; + } +} + +/** + * A helper class to make it easy to identify Svelte Snippets in `columnDef.cell` and `columnDef.header` properties. + * + * > NOTE: This class should only be used internally by the adapter. If you're + * reading this and you don't know what this is for, you probably don't need it. + * + * @example + * ```svelte + * {@const result = content(context as any)} + * {#if result instanceof RenderSnippetConfig} + * {@const { snippet, params } = result} + * {@render snippet(params)} + * {/if} + * ``` + */ +export class RenderSnippetConfig { + snippet: Snippet<[TProps]>; + params: TProps; + constructor(snippet: Snippet<[TProps]>, params: TProps) { + this.snippet = snippet; + this.params = params; + } +} + +/** + * A helper function to help create cells from Svelte components through ColumnDef's `cell` and `header` properties. + * + * This is only to be used with Svelte Components - use `renderSnippet` for Svelte Snippets. + * + * @param component A Svelte component + * @param props The props to pass to `component` + * @returns A `RenderComponentConfig` object that helps svelte-table know how to render the header/cell component. + * @example + * ```ts + * // +page.svelte + * const defaultColumns = [ + * columnHelper.accessor('name', { + * header: header => renderComponent(SortHeader, { label: 'Name', header }), + * }), + * columnHelper.accessor('state', { + * header: header => renderComponent(SortHeader, { label: 'State', header }), + * }), + * ] + * ``` + * @see {@link https://tanstack.com/table/latest/docs/guide/column-defs} + */ +export function renderComponent< + // eslint-disable-next-line @typescript-eslint/no-explicit-any + T extends Component, + Props extends ComponentProps +>(component: T, props: Props = {} as Props) { + return new RenderComponentConfig(component, props); +} + +/** + * A helper function to help create cells from Svelte Snippets through ColumnDef's `cell` and `header` properties. + * + * The snippet must only take one parameter. + * + * This is only to be used with Snippets - use `renderComponent` for Svelte Components. + * + * @param snippet + * @param params + * @returns - A `RenderSnippetConfig` object that helps svelte-table know how to render the header/cell snippet. + * @example + * ```ts + * // +page.svelte + * const defaultColumns = [ + * columnHelper.accessor('name', { + * cell: cell => renderSnippet(nameSnippet, { name: cell.row.name }), + * }), + * columnHelper.accessor('state', { + * cell: cell => renderSnippet(stateSnippet, { state: cell.row.state }), + * }), + * ] + * ``` + * @see {@link https://tanstack.com/table/latest/docs/guide/column-defs} + */ +export function renderSnippet(snippet: Snippet<[TProps]>, params: TProps = {} as TProps) { + return new RenderSnippetConfig(snippet, params); +} diff --git a/src/lib/components/ui/date-picker/date-picker.svelte b/src/lib/components/ui/date-picker/date-picker.svelte new file mode 100644 index 0000000..bd01f1e --- /dev/null +++ b/src/lib/components/ui/date-picker/date-picker.svelte @@ -0,0 +1,92 @@ + + + + + {#snippet child({ props })} + + {/snippet} + + + + + + + + diff --git a/src/lib/components/ui/date-picker/index.ts b/src/lib/components/ui/date-picker/index.ts new file mode 100644 index 0000000..ec868e1 --- /dev/null +++ b/src/lib/components/ui/date-picker/index.ts @@ -0,0 +1,7 @@ +import DatePicker from './date-picker.svelte'; + +export { + DatePicker, + // + DatePicker as Root +}; diff --git a/src/lib/components/ui/dialog/dialog-close.svelte b/src/lib/components/ui/dialog/dialog-close.svelte new file mode 100644 index 0000000..e8a96a7 --- /dev/null +++ b/src/lib/components/ui/dialog/dialog-close.svelte @@ -0,0 +1,7 @@ + + + diff --git a/src/lib/components/ui/dialog/dialog-content.svelte b/src/lib/components/ui/dialog/dialog-content.svelte new file mode 100644 index 0000000..9e43951 --- /dev/null +++ b/src/lib/components/ui/dialog/dialog-content.svelte @@ -0,0 +1,43 @@ + + + + + + {@render children?.()} + {#if showCloseButton} + + + Close + + {/if} + + diff --git a/src/lib/components/ui/dialog/dialog-description.svelte b/src/lib/components/ui/dialog/dialog-description.svelte new file mode 100644 index 0000000..7539190 --- /dev/null +++ b/src/lib/components/ui/dialog/dialog-description.svelte @@ -0,0 +1,17 @@ + + + diff --git a/src/lib/components/ui/dialog/dialog-footer.svelte b/src/lib/components/ui/dialog/dialog-footer.svelte new file mode 100644 index 0000000..c457d56 --- /dev/null +++ b/src/lib/components/ui/dialog/dialog-footer.svelte @@ -0,0 +1,20 @@ + + +
    + {@render children?.()} +
    diff --git a/src/lib/components/ui/dialog/dialog-header.svelte b/src/lib/components/ui/dialog/dialog-header.svelte new file mode 100644 index 0000000..5fe4145 --- /dev/null +++ b/src/lib/components/ui/dialog/dialog-header.svelte @@ -0,0 +1,20 @@ + + +
    + {@render children?.()} +
    diff --git a/src/lib/components/ui/dialog/dialog-overlay.svelte b/src/lib/components/ui/dialog/dialog-overlay.svelte new file mode 100644 index 0000000..34166e1 --- /dev/null +++ b/src/lib/components/ui/dialog/dialog-overlay.svelte @@ -0,0 +1,20 @@ + + + diff --git a/src/lib/components/ui/dialog/dialog-title.svelte b/src/lib/components/ui/dialog/dialog-title.svelte new file mode 100644 index 0000000..7073699 --- /dev/null +++ b/src/lib/components/ui/dialog/dialog-title.svelte @@ -0,0 +1,17 @@ + + + diff --git a/src/lib/components/ui/dialog/dialog-trigger.svelte b/src/lib/components/ui/dialog/dialog-trigger.svelte new file mode 100644 index 0000000..ac04d9f --- /dev/null +++ b/src/lib/components/ui/dialog/dialog-trigger.svelte @@ -0,0 +1,7 @@ + + + diff --git a/src/lib/components/ui/dialog/index.ts b/src/lib/components/ui/dialog/index.ts new file mode 100644 index 0000000..d9e5fb8 --- /dev/null +++ b/src/lib/components/ui/dialog/index.ts @@ -0,0 +1,37 @@ +import { Dialog as DialogPrimitive } from 'bits-ui'; + +import Title from './dialog-title.svelte'; +import Footer from './dialog-footer.svelte'; +import Header from './dialog-header.svelte'; +import Overlay from './dialog-overlay.svelte'; +import Content from './dialog-content.svelte'; +import Description from './dialog-description.svelte'; +import Trigger from './dialog-trigger.svelte'; +import Close from './dialog-close.svelte'; + +const Root = DialogPrimitive.Root; +const Portal = DialogPrimitive.Portal; + +export { + Root, + Title, + Portal, + Footer, + Header, + Trigger, + Overlay, + Content, + Description, + Close, + // + Root as Dialog, + Title as DialogTitle, + Portal as DialogPortal, + Footer as DialogFooter, + Header as DialogHeader, + Trigger as DialogTrigger, + Overlay as DialogOverlay, + Content as DialogContent, + Description as DialogDescription, + Close as DialogClose +}; diff --git a/src/lib/components/ui/dropdown-menu/dropdown-menu-checkbox-item.svelte b/src/lib/components/ui/dropdown-menu/dropdown-menu-checkbox-item.svelte new file mode 100644 index 0000000..ed52f67 --- /dev/null +++ b/src/lib/components/ui/dropdown-menu/dropdown-menu-checkbox-item.svelte @@ -0,0 +1,41 @@ + + + + {#snippet children({ checked, indeterminate })} + + {#if indeterminate} + + {:else} + + {/if} + + {@render childrenProp?.()} + {/snippet} + diff --git a/src/lib/components/ui/dropdown-menu/dropdown-menu-content.svelte b/src/lib/components/ui/dropdown-menu/dropdown-menu-content.svelte new file mode 100644 index 0000000..305557b --- /dev/null +++ b/src/lib/components/ui/dropdown-menu/dropdown-menu-content.svelte @@ -0,0 +1,27 @@ + + + + + diff --git a/src/lib/components/ui/dropdown-menu/dropdown-menu-group-heading.svelte b/src/lib/components/ui/dropdown-menu/dropdown-menu-group-heading.svelte new file mode 100644 index 0000000..920848e --- /dev/null +++ b/src/lib/components/ui/dropdown-menu/dropdown-menu-group-heading.svelte @@ -0,0 +1,22 @@ + + + diff --git a/src/lib/components/ui/dropdown-menu/dropdown-menu-group.svelte b/src/lib/components/ui/dropdown-menu/dropdown-menu-group.svelte new file mode 100644 index 0000000..261ab7e --- /dev/null +++ b/src/lib/components/ui/dropdown-menu/dropdown-menu-group.svelte @@ -0,0 +1,7 @@ + + + diff --git a/src/lib/components/ui/dropdown-menu/dropdown-menu-item.svelte b/src/lib/components/ui/dropdown-menu/dropdown-menu-item.svelte new file mode 100644 index 0000000..b0d4ed3 --- /dev/null +++ b/src/lib/components/ui/dropdown-menu/dropdown-menu-item.svelte @@ -0,0 +1,27 @@ + + + diff --git a/src/lib/components/ui/dropdown-menu/dropdown-menu-label.svelte b/src/lib/components/ui/dropdown-menu/dropdown-menu-label.svelte new file mode 100644 index 0000000..14e40f7 --- /dev/null +++ b/src/lib/components/ui/dropdown-menu/dropdown-menu-label.svelte @@ -0,0 +1,24 @@ + + +
    + {@render children?.()} +
    diff --git a/src/lib/components/ui/dropdown-menu/dropdown-menu-radio-group.svelte b/src/lib/components/ui/dropdown-menu/dropdown-menu-radio-group.svelte new file mode 100644 index 0000000..3e98749 --- /dev/null +++ b/src/lib/components/ui/dropdown-menu/dropdown-menu-radio-group.svelte @@ -0,0 +1,16 @@ + + + diff --git a/src/lib/components/ui/dropdown-menu/dropdown-menu-radio-item.svelte b/src/lib/components/ui/dropdown-menu/dropdown-menu-radio-item.svelte new file mode 100644 index 0000000..ca8ccaf --- /dev/null +++ b/src/lib/components/ui/dropdown-menu/dropdown-menu-radio-item.svelte @@ -0,0 +1,31 @@ + + + + {#snippet children({ checked })} + + {#if checked} + + {/if} + + {@render childrenProp?.({ checked })} + {/snippet} + diff --git a/src/lib/components/ui/dropdown-menu/dropdown-menu-separator.svelte b/src/lib/components/ui/dropdown-menu/dropdown-menu-separator.svelte new file mode 100644 index 0000000..a076e43 --- /dev/null +++ b/src/lib/components/ui/dropdown-menu/dropdown-menu-separator.svelte @@ -0,0 +1,17 @@ + + + diff --git a/src/lib/components/ui/dropdown-menu/dropdown-menu-shortcut.svelte b/src/lib/components/ui/dropdown-menu/dropdown-menu-shortcut.svelte new file mode 100644 index 0000000..eb86e67 --- /dev/null +++ b/src/lib/components/ui/dropdown-menu/dropdown-menu-shortcut.svelte @@ -0,0 +1,20 @@ + + + + {@render children?.()} + diff --git a/src/lib/components/ui/dropdown-menu/dropdown-menu-sub-content.svelte b/src/lib/components/ui/dropdown-menu/dropdown-menu-sub-content.svelte new file mode 100644 index 0000000..8528d25 --- /dev/null +++ b/src/lib/components/ui/dropdown-menu/dropdown-menu-sub-content.svelte @@ -0,0 +1,20 @@ + + + diff --git a/src/lib/components/ui/dropdown-menu/dropdown-menu-sub-trigger.svelte b/src/lib/components/ui/dropdown-menu/dropdown-menu-sub-trigger.svelte new file mode 100644 index 0000000..96bb810 --- /dev/null +++ b/src/lib/components/ui/dropdown-menu/dropdown-menu-sub-trigger.svelte @@ -0,0 +1,29 @@ + + + + {@render children?.()} + + diff --git a/src/lib/components/ui/dropdown-menu/dropdown-menu-trigger.svelte b/src/lib/components/ui/dropdown-menu/dropdown-menu-trigger.svelte new file mode 100644 index 0000000..032b645 --- /dev/null +++ b/src/lib/components/ui/dropdown-menu/dropdown-menu-trigger.svelte @@ -0,0 +1,7 @@ + + + diff --git a/src/lib/components/ui/dropdown-menu/index.ts b/src/lib/components/ui/dropdown-menu/index.ts new file mode 100644 index 0000000..aeb398e --- /dev/null +++ b/src/lib/components/ui/dropdown-menu/index.ts @@ -0,0 +1,49 @@ +import { DropdownMenu as DropdownMenuPrimitive } from 'bits-ui'; +import CheckboxItem from './dropdown-menu-checkbox-item.svelte'; +import Content from './dropdown-menu-content.svelte'; +import Group from './dropdown-menu-group.svelte'; +import Item from './dropdown-menu-item.svelte'; +import Label from './dropdown-menu-label.svelte'; +import RadioGroup from './dropdown-menu-radio-group.svelte'; +import RadioItem from './dropdown-menu-radio-item.svelte'; +import Separator from './dropdown-menu-separator.svelte'; +import Shortcut from './dropdown-menu-shortcut.svelte'; +import Trigger from './dropdown-menu-trigger.svelte'; +import SubContent from './dropdown-menu-sub-content.svelte'; +import SubTrigger from './dropdown-menu-sub-trigger.svelte'; +import GroupHeading from './dropdown-menu-group-heading.svelte'; +const Sub = DropdownMenuPrimitive.Sub; +const Root = DropdownMenuPrimitive.Root; + +export { + CheckboxItem, + Content, + Root as DropdownMenu, + CheckboxItem as DropdownMenuCheckboxItem, + Content as DropdownMenuContent, + Group as DropdownMenuGroup, + Item as DropdownMenuItem, + Label as DropdownMenuLabel, + RadioGroup as DropdownMenuRadioGroup, + RadioItem as DropdownMenuRadioItem, + Separator as DropdownMenuSeparator, + Shortcut as DropdownMenuShortcut, + Sub as DropdownMenuSub, + SubContent as DropdownMenuSubContent, + SubTrigger as DropdownMenuSubTrigger, + Trigger as DropdownMenuTrigger, + GroupHeading as DropdownMenuGroupHeading, + Group, + GroupHeading, + Item, + Label, + RadioGroup, + RadioItem, + Root, + Separator, + Shortcut, + Sub, + SubContent, + SubTrigger, + Trigger +}; diff --git a/src/lib/components/ui/form/form-button.svelte b/src/lib/components/ui/form/form-button.svelte new file mode 100644 index 0000000..550f6ac --- /dev/null +++ b/src/lib/components/ui/form/form-button.svelte @@ -0,0 +1,7 @@ + + + diff --git a/src/lib/components/ui/form/form-description.svelte b/src/lib/components/ui/form/form-description.svelte new file mode 100644 index 0000000..034270a --- /dev/null +++ b/src/lib/components/ui/form/form-description.svelte @@ -0,0 +1,17 @@ + + + diff --git a/src/lib/components/ui/form/form-element-field.svelte b/src/lib/components/ui/form/form-element-field.svelte new file mode 100644 index 0000000..0300c8f --- /dev/null +++ b/src/lib/components/ui/form/form-element-field.svelte @@ -0,0 +1,24 @@ + + + + {#snippet children({ constraints, errors, tainted, value })} +
    + {@render childrenProp?.({ constraints, errors, tainted, value: value as T[U] })} +
    + {/snippet} +
    diff --git a/src/lib/components/ui/form/form-field-errors.svelte b/src/lib/components/ui/form/form-field-errors.svelte new file mode 100644 index 0000000..5ba048b --- /dev/null +++ b/src/lib/components/ui/form/form-field-errors.svelte @@ -0,0 +1,45 @@ + + + + {#snippet children({ errors, errorProps })} + {#if childrenProp} + {@render childrenProp({ errors, errorProps })} + {:else} + {#each errors as error (error)} +
    {translateError(error)}
    + {/each} + {/if} + {/snippet} +
    diff --git a/src/lib/components/ui/form/form-field.svelte b/src/lib/components/ui/form/form-field.svelte new file mode 100644 index 0000000..6f061ca --- /dev/null +++ b/src/lib/components/ui/form/form-field.svelte @@ -0,0 +1,24 @@ + + + + {#snippet children({ constraints, errors, tainted, value })} +
    + {@render childrenProp?.({ constraints, errors, tainted, value: value as T[U] })} +
    + {/snippet} +
    diff --git a/src/lib/components/ui/form/form-fieldset.svelte b/src/lib/components/ui/form/form-fieldset.svelte new file mode 100644 index 0000000..1ee7463 --- /dev/null +++ b/src/lib/components/ui/form/form-fieldset.svelte @@ -0,0 +1,15 @@ + + + diff --git a/src/lib/components/ui/form/form-label.svelte b/src/lib/components/ui/form/form-label.svelte new file mode 100644 index 0000000..6d92ce7 --- /dev/null +++ b/src/lib/components/ui/form/form-label.svelte @@ -0,0 +1,24 @@ + + + + {#snippet child({ props })} + + {/snippet} + diff --git a/src/lib/components/ui/form/form-legend.svelte b/src/lib/components/ui/form/form-legend.svelte new file mode 100644 index 0000000..2778f9c --- /dev/null +++ b/src/lib/components/ui/form/form-legend.svelte @@ -0,0 +1,16 @@ + + + diff --git a/src/lib/components/ui/form/index.ts b/src/lib/components/ui/form/index.ts new file mode 100644 index 0000000..2d129aa --- /dev/null +++ b/src/lib/components/ui/form/index.ts @@ -0,0 +1,33 @@ +import * as FormPrimitive from 'formsnap'; +import Description from './form-description.svelte'; +import Label from './form-label.svelte'; +import FieldErrors from './form-field-errors.svelte'; +import Field from './form-field.svelte'; +import Fieldset from './form-fieldset.svelte'; +import Legend from './form-legend.svelte'; +import ElementField from './form-element-field.svelte'; +import Button from './form-button.svelte'; + +const Control = FormPrimitive.Control; + +export { + Field, + Control, + Label, + Button, + FieldErrors, + Description, + Fieldset, + Legend, + ElementField, + // + Field as FormField, + Control as FormControl, + Description as FormDescription, + Label as FormLabel, + FieldErrors as FormFieldErrors, + Fieldset as FormFieldset, + Legend as FormLegend, + ElementField as FormElementField, + Button as FormButton +}; diff --git a/src/lib/components/ui/input/index.ts b/src/lib/components/ui/input/index.ts new file mode 100644 index 0000000..15c0933 --- /dev/null +++ b/src/lib/components/ui/input/index.ts @@ -0,0 +1,7 @@ +import Root from './input.svelte'; + +export { + Root, + // + Root as Input +}; diff --git a/src/lib/components/ui/input/input.svelte b/src/lib/components/ui/input/input.svelte new file mode 100644 index 0000000..0fc980b --- /dev/null +++ b/src/lib/components/ui/input/input.svelte @@ -0,0 +1,51 @@ + + +{#if type === 'file'} + +{:else} + +{/if} diff --git a/src/lib/components/ui/label/index.ts b/src/lib/components/ui/label/index.ts new file mode 100644 index 0000000..808d141 --- /dev/null +++ b/src/lib/components/ui/label/index.ts @@ -0,0 +1,7 @@ +import Root from './label.svelte'; + +export { + Root, + // + Root as Label +}; diff --git a/src/lib/components/ui/label/label.svelte b/src/lib/components/ui/label/label.svelte new file mode 100644 index 0000000..7fef72e --- /dev/null +++ b/src/lib/components/ui/label/label.svelte @@ -0,0 +1,20 @@ + + + diff --git a/src/lib/components/ui/pagination/index.ts b/src/lib/components/ui/pagination/index.ts new file mode 100644 index 0000000..2f5532a --- /dev/null +++ b/src/lib/components/ui/pagination/index.ts @@ -0,0 +1,25 @@ +import Root from './pagination.svelte'; +import Content from './pagination-content.svelte'; +import Item from './pagination-item.svelte'; +import Link from './pagination-link.svelte'; +import PrevButton from './pagination-prev-button.svelte'; +import NextButton from './pagination-next-button.svelte'; +import Ellipsis from './pagination-ellipsis.svelte'; + +export { + Root, + Content, + Item, + Link, + PrevButton, + NextButton, + Ellipsis, + // + Root as Pagination, + Content as PaginationContent, + Item as PaginationItem, + Link as PaginationLink, + PrevButton as PaginationPrevButton, + NextButton as PaginationNextButton, + Ellipsis as PaginationEllipsis +}; diff --git a/src/lib/components/ui/pagination/pagination-content.svelte b/src/lib/components/ui/pagination/pagination-content.svelte new file mode 100644 index 0000000..ce650ce --- /dev/null +++ b/src/lib/components/ui/pagination/pagination-content.svelte @@ -0,0 +1,20 @@ + + +
      + {@render children?.()} +
    diff --git a/src/lib/components/ui/pagination/pagination-ellipsis.svelte b/src/lib/components/ui/pagination/pagination-ellipsis.svelte new file mode 100644 index 0000000..a5c800e --- /dev/null +++ b/src/lib/components/ui/pagination/pagination-ellipsis.svelte @@ -0,0 +1,22 @@ + + + diff --git a/src/lib/components/ui/pagination/pagination-item.svelte b/src/lib/components/ui/pagination/pagination-item.svelte new file mode 100644 index 0000000..f185bf5 --- /dev/null +++ b/src/lib/components/ui/pagination/pagination-item.svelte @@ -0,0 +1,14 @@ + + +
  • + {@render children?.()} +
  • diff --git a/src/lib/components/ui/pagination/pagination-link.svelte b/src/lib/components/ui/pagination/pagination-link.svelte new file mode 100644 index 0000000..61cdae9 --- /dev/null +++ b/src/lib/components/ui/pagination/pagination-link.svelte @@ -0,0 +1,39 @@ + + +{#snippet Fallback()} + {page.value} +{/snippet} + + diff --git a/src/lib/components/ui/pagination/pagination-next-button.svelte b/src/lib/components/ui/pagination/pagination-next-button.svelte new file mode 100644 index 0000000..8fed8d3 --- /dev/null +++ b/src/lib/components/ui/pagination/pagination-next-button.svelte @@ -0,0 +1,33 @@ + + +{#snippet Fallback()} + Next + +{/snippet} + + diff --git a/src/lib/components/ui/pagination/pagination-prev-button.svelte b/src/lib/components/ui/pagination/pagination-prev-button.svelte new file mode 100644 index 0000000..016e7bf --- /dev/null +++ b/src/lib/components/ui/pagination/pagination-prev-button.svelte @@ -0,0 +1,33 @@ + + +{#snippet Fallback()} + + Previous +{/snippet} + + diff --git a/src/lib/components/ui/pagination/pagination.svelte b/src/lib/components/ui/pagination/pagination.svelte new file mode 100644 index 0000000..dac89a0 --- /dev/null +++ b/src/lib/components/ui/pagination/pagination.svelte @@ -0,0 +1,28 @@ + + + diff --git a/src/lib/components/ui/popover/index.ts b/src/lib/components/ui/popover/index.ts new file mode 100644 index 0000000..0dcc3cc --- /dev/null +++ b/src/lib/components/ui/popover/index.ts @@ -0,0 +1,17 @@ +import { Popover as PopoverPrimitive } from 'bits-ui'; +import Content from './popover-content.svelte'; +import Trigger from './popover-trigger.svelte'; +const Root = PopoverPrimitive.Root; +const Close = PopoverPrimitive.Close; + +export { + Root, + Content, + Trigger, + Close, + // + Root as Popover, + Content as PopoverContent, + Trigger as PopoverTrigger, + Close as PopoverClose +}; diff --git a/src/lib/components/ui/popover/popover-content.svelte b/src/lib/components/ui/popover/popover-content.svelte new file mode 100644 index 0000000..8ddbcac --- /dev/null +++ b/src/lib/components/ui/popover/popover-content.svelte @@ -0,0 +1,29 @@ + + + + + diff --git a/src/lib/components/ui/popover/popover-trigger.svelte b/src/lib/components/ui/popover/popover-trigger.svelte new file mode 100644 index 0000000..f21b1b3 --- /dev/null +++ b/src/lib/components/ui/popover/popover-trigger.svelte @@ -0,0 +1,17 @@ + + + diff --git a/src/lib/components/ui/range-calendar/index.ts b/src/lib/components/ui/range-calendar/index.ts new file mode 100644 index 0000000..e9d63e3 --- /dev/null +++ b/src/lib/components/ui/range-calendar/index.ts @@ -0,0 +1,42 @@ +import { RangeCalendar as RangeCalendarPrimitive } from 'bits-ui'; +import Root from './range-calendar.svelte'; +import Cell from './range-calendar-cell.svelte'; +import Day from './range-calendar-day.svelte'; +import Grid from './range-calendar-grid.svelte'; +import Header from './range-calendar-header.svelte'; +import Months from './range-calendar-months.svelte'; +import GridRow from './range-calendar-grid-row.svelte'; +import Heading from './range-calendar-heading.svelte'; +import HeadCell from './range-calendar-head-cell.svelte'; +import NextButton from './range-calendar-next-button.svelte'; +import PrevButton from './range-calendar-prev-button.svelte'; +import MonthSelect from './range-calendar-month-select.svelte'; +import YearSelect from './range-calendar-year-select.svelte'; +import Caption from './range-calendar-caption.svelte'; +import Nav from './range-calendar-nav.svelte'; +import Month from './range-calendar-month.svelte'; + +const GridHead = RangeCalendarPrimitive.GridHead; +const GridBody = RangeCalendarPrimitive.GridBody; + +export { + Day, + Cell, + Grid, + Header, + Months, + GridRow, + Heading, + GridBody, + GridHead, + HeadCell, + NextButton, + PrevButton, + MonthSelect, + YearSelect, + Caption, + Nav, + Month, + // + Root as RangeCalendar +}; diff --git a/src/lib/components/ui/range-calendar/range-calendar-caption.svelte b/src/lib/components/ui/range-calendar/range-calendar-caption.svelte new file mode 100644 index 0000000..406bc49 --- /dev/null +++ b/src/lib/components/ui/range-calendar/range-calendar-caption.svelte @@ -0,0 +1,76 @@ + + +{#snippet MonthSelect()} + { + if (!placeholder) return; + const v = Number.parseInt(e.currentTarget.value); + const newPlaceholder = placeholder.set({ month: v }); + placeholder = newPlaceholder.subtract({ months: monthIndex }); + }} + /> +{/snippet} + +{#snippet YearSelect()} + +{/snippet} + +{#if captionLayout === 'dropdown'} + {@render MonthSelect()} + {@render YearSelect()} +{:else if captionLayout === 'dropdown-months'} + {@render MonthSelect()} + {#if placeholder} + {formatYear(placeholder)} + {/if} +{:else if captionLayout === 'dropdown-years'} + {#if placeholder} + {formatMonth(placeholder)} + {/if} + {@render YearSelect()} +{:else} + {formatMonth(month)} {formatYear(month)} +{/if} diff --git a/src/lib/components/ui/range-calendar/range-calendar-cell.svelte b/src/lib/components/ui/range-calendar/range-calendar-cell.svelte new file mode 100644 index 0000000..2b8e6ff --- /dev/null +++ b/src/lib/components/ui/range-calendar/range-calendar-cell.svelte @@ -0,0 +1,19 @@ + + + diff --git a/src/lib/components/ui/range-calendar/range-calendar-day.svelte b/src/lib/components/ui/range-calendar/range-calendar-day.svelte new file mode 100644 index 0000000..025c4f5 --- /dev/null +++ b/src/lib/components/ui/range-calendar/range-calendar-day.svelte @@ -0,0 +1,39 @@ + + +span]:text-xs [&>span]:opacity-70', + className + )} + {...restProps} +/> diff --git a/src/lib/components/ui/range-calendar/range-calendar-grid-row.svelte b/src/lib/components/ui/range-calendar/range-calendar-grid-row.svelte new file mode 100644 index 0000000..af534f1 --- /dev/null +++ b/src/lib/components/ui/range-calendar/range-calendar-grid-row.svelte @@ -0,0 +1,12 @@ + + + diff --git a/src/lib/components/ui/range-calendar/range-calendar-grid.svelte b/src/lib/components/ui/range-calendar/range-calendar-grid.svelte new file mode 100644 index 0000000..3065e1a --- /dev/null +++ b/src/lib/components/ui/range-calendar/range-calendar-grid.svelte @@ -0,0 +1,16 @@ + + + diff --git a/src/lib/components/ui/range-calendar/range-calendar-head-cell.svelte b/src/lib/components/ui/range-calendar/range-calendar-head-cell.svelte new file mode 100644 index 0000000..75048a2 --- /dev/null +++ b/src/lib/components/ui/range-calendar/range-calendar-head-cell.svelte @@ -0,0 +1,19 @@ + + + diff --git a/src/lib/components/ui/range-calendar/range-calendar-header.svelte b/src/lib/components/ui/range-calendar/range-calendar-header.svelte new file mode 100644 index 0000000..1e50164 --- /dev/null +++ b/src/lib/components/ui/range-calendar/range-calendar-header.svelte @@ -0,0 +1,19 @@ + + + diff --git a/src/lib/components/ui/range-calendar/range-calendar-heading.svelte b/src/lib/components/ui/range-calendar/range-calendar-heading.svelte new file mode 100644 index 0000000..11773e2 --- /dev/null +++ b/src/lib/components/ui/range-calendar/range-calendar-heading.svelte @@ -0,0 +1,16 @@ + + + diff --git a/src/lib/components/ui/range-calendar/range-calendar-month-select.svelte b/src/lib/components/ui/range-calendar/range-calendar-month-select.svelte new file mode 100644 index 0000000..b4ffb59 --- /dev/null +++ b/src/lib/components/ui/range-calendar/range-calendar-month-select.svelte @@ -0,0 +1,44 @@ + + + + + {#snippet child({ props, monthItems, selectedMonthItem })} + + + {/snippet} + + diff --git a/src/lib/components/ui/range-calendar/range-calendar-month.svelte b/src/lib/components/ui/range-calendar/range-calendar-month.svelte new file mode 100644 index 0000000..799d2c5 --- /dev/null +++ b/src/lib/components/ui/range-calendar/range-calendar-month.svelte @@ -0,0 +1,15 @@ + + +
    + {@render children?.()} +
    diff --git a/src/lib/components/ui/range-calendar/range-calendar-months.svelte b/src/lib/components/ui/range-calendar/range-calendar-months.svelte new file mode 100644 index 0000000..023cad3 --- /dev/null +++ b/src/lib/components/ui/range-calendar/range-calendar-months.svelte @@ -0,0 +1,19 @@ + + +
    + {@render children?.()} +
    diff --git a/src/lib/components/ui/range-calendar/range-calendar-nav.svelte b/src/lib/components/ui/range-calendar/range-calendar-nav.svelte new file mode 100644 index 0000000..2fea3e9 --- /dev/null +++ b/src/lib/components/ui/range-calendar/range-calendar-nav.svelte @@ -0,0 +1,19 @@ + + + diff --git a/src/lib/components/ui/range-calendar/range-calendar-next-button.svelte b/src/lib/components/ui/range-calendar/range-calendar-next-button.svelte new file mode 100644 index 0000000..d06055d --- /dev/null +++ b/src/lib/components/ui/range-calendar/range-calendar-next-button.svelte @@ -0,0 +1,31 @@ + + +{#snippet Fallback()} + +{/snippet} + + diff --git a/src/lib/components/ui/range-calendar/range-calendar-prev-button.svelte b/src/lib/components/ui/range-calendar/range-calendar-prev-button.svelte new file mode 100644 index 0000000..621f6fb --- /dev/null +++ b/src/lib/components/ui/range-calendar/range-calendar-prev-button.svelte @@ -0,0 +1,31 @@ + + +{#snippet Fallback()} + +{/snippet} + + diff --git a/src/lib/components/ui/range-calendar/range-calendar-year-select.svelte b/src/lib/components/ui/range-calendar/range-calendar-year-select.svelte new file mode 100644 index 0000000..e490960 --- /dev/null +++ b/src/lib/components/ui/range-calendar/range-calendar-year-select.svelte @@ -0,0 +1,43 @@ + + + + + {#snippet child({ props, yearItems, selectedYearItem })} + + + {/snippet} + + diff --git a/src/lib/components/ui/range-calendar/range-calendar.svelte b/src/lib/components/ui/range-calendar/range-calendar.svelte new file mode 100644 index 0000000..371b98e --- /dev/null +++ b/src/lib/components/ui/range-calendar/range-calendar.svelte @@ -0,0 +1,112 @@ + + + + {#snippet children({ months, weekdays })} + + + + + + {#each months as month, monthIndex (month)} + + + + + + + + + {#each weekdays as weekday (weekday)} + + {weekday.slice(0, 2)} + + {/each} + + + + {#each month.weeks as weekDates (weekDates)} + + {#each weekDates as date (date)} + + {#if day} + {@render day({ + day: date, + outsideMonth: !isEqualMonth(date, month.value) + })} + {:else} + + {/if} + + {/each} + + {/each} + + + + {/each} + + {/snippet} + diff --git a/src/lib/components/ui/select/index.ts b/src/lib/components/ui/select/index.ts new file mode 100644 index 0000000..bfa73d9 --- /dev/null +++ b/src/lib/components/ui/select/index.ts @@ -0,0 +1,37 @@ +import { Select as SelectPrimitive } from 'bits-ui'; + +import Group from './select-group.svelte'; +import Label from './select-label.svelte'; +import Item from './select-item.svelte'; +import Content from './select-content.svelte'; +import Trigger from './select-trigger.svelte'; +import Separator from './select-separator.svelte'; +import ScrollDownButton from './select-scroll-down-button.svelte'; +import ScrollUpButton from './select-scroll-up-button.svelte'; +import GroupHeading from './select-group-heading.svelte'; + +const Root = SelectPrimitive.Root; + +export { + Root, + Group, + Label, + Item, + Content, + Trigger, + Separator, + ScrollDownButton, + ScrollUpButton, + GroupHeading, + // + Root as Select, + Group as SelectGroup, + Label as SelectLabel, + Item as SelectItem, + Content as SelectContent, + Trigger as SelectTrigger, + Separator as SelectSeparator, + ScrollDownButton as SelectScrollDownButton, + ScrollUpButton as SelectScrollUpButton, + GroupHeading as SelectGroupHeading +}; diff --git a/src/lib/components/ui/select/select-content.svelte b/src/lib/components/ui/select/select-content.svelte new file mode 100644 index 0000000..f00fc75 --- /dev/null +++ b/src/lib/components/ui/select/select-content.svelte @@ -0,0 +1,40 @@ + + + + + + + {@render children?.()} + + + + diff --git a/src/lib/components/ui/select/select-group-heading.svelte b/src/lib/components/ui/select/select-group-heading.svelte new file mode 100644 index 0000000..3dc0282 --- /dev/null +++ b/src/lib/components/ui/select/select-group-heading.svelte @@ -0,0 +1,21 @@ + + + + {@render children?.()} + diff --git a/src/lib/components/ui/select/select-group.svelte b/src/lib/components/ui/select/select-group.svelte new file mode 100644 index 0000000..2520795 --- /dev/null +++ b/src/lib/components/ui/select/select-group.svelte @@ -0,0 +1,7 @@ + + + diff --git a/src/lib/components/ui/select/select-item.svelte b/src/lib/components/ui/select/select-item.svelte new file mode 100644 index 0000000..d527d4c --- /dev/null +++ b/src/lib/components/ui/select/select-item.svelte @@ -0,0 +1,38 @@ + + + + {#snippet children({ selected, highlighted })} + + {#if selected} + + {/if} + + {#if childrenProp} + {@render childrenProp({ selected, highlighted })} + {:else} + {label || value} + {/if} + {/snippet} + diff --git a/src/lib/components/ui/select/select-label.svelte b/src/lib/components/ui/select/select-label.svelte new file mode 100644 index 0000000..8849c2a --- /dev/null +++ b/src/lib/components/ui/select/select-label.svelte @@ -0,0 +1,20 @@ + + +
    + {@render children?.()} +
    diff --git a/src/lib/components/ui/select/select-scroll-down-button.svelte b/src/lib/components/ui/select/select-scroll-down-button.svelte new file mode 100644 index 0000000..3502148 --- /dev/null +++ b/src/lib/components/ui/select/select-scroll-down-button.svelte @@ -0,0 +1,20 @@ + + + + + diff --git a/src/lib/components/ui/select/select-scroll-up-button.svelte b/src/lib/components/ui/select/select-scroll-up-button.svelte new file mode 100644 index 0000000..3d35e04 --- /dev/null +++ b/src/lib/components/ui/select/select-scroll-up-button.svelte @@ -0,0 +1,20 @@ + + + + + diff --git a/src/lib/components/ui/select/select-separator.svelte b/src/lib/components/ui/select/select-separator.svelte new file mode 100644 index 0000000..bfd1eb7 --- /dev/null +++ b/src/lib/components/ui/select/select-separator.svelte @@ -0,0 +1,18 @@ + + + diff --git a/src/lib/components/ui/select/select-trigger.svelte b/src/lib/components/ui/select/select-trigger.svelte new file mode 100644 index 0000000..f7ac421 --- /dev/null +++ b/src/lib/components/ui/select/select-trigger.svelte @@ -0,0 +1,29 @@ + + + + {@render children?.()} + + diff --git a/src/lib/components/ui/separator/index.ts b/src/lib/components/ui/separator/index.ts new file mode 100644 index 0000000..768efac --- /dev/null +++ b/src/lib/components/ui/separator/index.ts @@ -0,0 +1,7 @@ +import Root from './separator.svelte'; + +export { + Root, + // + Root as Separator +}; diff --git a/src/lib/components/ui/separator/separator.svelte b/src/lib/components/ui/separator/separator.svelte new file mode 100644 index 0000000..d3338da --- /dev/null +++ b/src/lib/components/ui/separator/separator.svelte @@ -0,0 +1,20 @@ + + + diff --git a/src/lib/components/ui/sheet/index.ts b/src/lib/components/ui/sheet/index.ts new file mode 100644 index 0000000..139e2d2 --- /dev/null +++ b/src/lib/components/ui/sheet/index.ts @@ -0,0 +1,36 @@ +import { Dialog as SheetPrimitive } from 'bits-ui'; +import Trigger from './sheet-trigger.svelte'; +import Close from './sheet-close.svelte'; +import Overlay from './sheet-overlay.svelte'; +import Content from './sheet-content.svelte'; +import Header from './sheet-header.svelte'; +import Footer from './sheet-footer.svelte'; +import Title from './sheet-title.svelte'; +import Description from './sheet-description.svelte'; + +const Root = SheetPrimitive.Root; +const Portal = SheetPrimitive.Portal; + +export { + Root, + Close, + Trigger, + Portal, + Overlay, + Content, + Header, + Footer, + Title, + Description, + // + Root as Sheet, + Close as SheetClose, + Trigger as SheetTrigger, + Portal as SheetPortal, + Overlay as SheetOverlay, + Content as SheetContent, + Header as SheetHeader, + Footer as SheetFooter, + Title as SheetTitle, + Description as SheetDescription +}; diff --git a/src/lib/components/ui/sheet/sheet-close.svelte b/src/lib/components/ui/sheet/sheet-close.svelte new file mode 100644 index 0000000..b0180c0 --- /dev/null +++ b/src/lib/components/ui/sheet/sheet-close.svelte @@ -0,0 +1,7 @@ + + + diff --git a/src/lib/components/ui/sheet/sheet-content.svelte b/src/lib/components/ui/sheet/sheet-content.svelte new file mode 100644 index 0000000..2ff0fea --- /dev/null +++ b/src/lib/components/ui/sheet/sheet-content.svelte @@ -0,0 +1,60 @@ + + + + + + + + {@render children?.()} + + + Close + + + diff --git a/src/lib/components/ui/sheet/sheet-description.svelte b/src/lib/components/ui/sheet/sheet-description.svelte new file mode 100644 index 0000000..b6aae8e --- /dev/null +++ b/src/lib/components/ui/sheet/sheet-description.svelte @@ -0,0 +1,17 @@ + + + diff --git a/src/lib/components/ui/sheet/sheet-footer.svelte b/src/lib/components/ui/sheet/sheet-footer.svelte new file mode 100644 index 0000000..aea83f7 --- /dev/null +++ b/src/lib/components/ui/sheet/sheet-footer.svelte @@ -0,0 +1,20 @@ + + +
    + {@render children?.()} +
    diff --git a/src/lib/components/ui/sheet/sheet-header.svelte b/src/lib/components/ui/sheet/sheet-header.svelte new file mode 100644 index 0000000..00cd281 --- /dev/null +++ b/src/lib/components/ui/sheet/sheet-header.svelte @@ -0,0 +1,20 @@ + + +
    + {@render children?.()} +
    diff --git a/src/lib/components/ui/sheet/sheet-overlay.svelte b/src/lib/components/ui/sheet/sheet-overlay.svelte new file mode 100644 index 0000000..cd637be --- /dev/null +++ b/src/lib/components/ui/sheet/sheet-overlay.svelte @@ -0,0 +1,20 @@ + + + diff --git a/src/lib/components/ui/sheet/sheet-title.svelte b/src/lib/components/ui/sheet/sheet-title.svelte new file mode 100644 index 0000000..7539897 --- /dev/null +++ b/src/lib/components/ui/sheet/sheet-title.svelte @@ -0,0 +1,17 @@ + + + diff --git a/src/lib/components/ui/sheet/sheet-trigger.svelte b/src/lib/components/ui/sheet/sheet-trigger.svelte new file mode 100644 index 0000000..d95719a --- /dev/null +++ b/src/lib/components/ui/sheet/sheet-trigger.svelte @@ -0,0 +1,7 @@ + + + diff --git a/src/lib/components/ui/sidebar/constants.ts b/src/lib/components/ui/sidebar/constants.ts new file mode 100644 index 0000000..2d3bbfb --- /dev/null +++ b/src/lib/components/ui/sidebar/constants.ts @@ -0,0 +1,6 @@ +export const SIDEBAR_COOKIE_NAME = 'sidebar:state'; +export const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7; +export const SIDEBAR_WIDTH = '16rem'; +export const SIDEBAR_WIDTH_MOBILE = '18rem'; +export const SIDEBAR_WIDTH_ICON = '3rem'; +export const SIDEBAR_KEYBOARD_SHORTCUT = 'b'; diff --git a/src/lib/components/ui/sidebar/context.svelte.ts b/src/lib/components/ui/sidebar/context.svelte.ts new file mode 100644 index 0000000..6fa2aa3 --- /dev/null +++ b/src/lib/components/ui/sidebar/context.svelte.ts @@ -0,0 +1,79 @@ +import { IsMobile } from '$lib/hooks/is-mobile.svelte.js'; +import { getContext, setContext } from 'svelte'; +import { SIDEBAR_KEYBOARD_SHORTCUT } from './constants.js'; + +type Getter = () => T; + +export type SidebarStateProps = { + /** + * A getter function that returns the current open state of the sidebar. + * We use a getter function here to support `bind:open` on the `Sidebar.Provider` + * component. + */ + open: Getter; + + /** + * A function that sets the open state of the sidebar. To support `bind:open`, we need + * a source of truth for changing the open state to ensure it will be synced throughout + * the sub-components and any `bind:` references. + */ + setOpen: (open: boolean) => void; +}; + +class SidebarState { + readonly props: SidebarStateProps; + open = $derived.by(() => this.props.open()); + openMobile = $state(false); + setOpen: SidebarStateProps['setOpen']; + #isMobile: IsMobile; + state = $derived.by(() => (this.open ? 'expanded' : 'collapsed')); + + constructor(props: SidebarStateProps) { + this.setOpen = props.setOpen; + this.#isMobile = new IsMobile(); + this.props = props; + } + + // Convenience getter for checking if the sidebar is mobile + // without this, we would need to use `sidebar.isMobile.current` everywhere + get isMobile() { + return this.#isMobile.current; + } + + // Event handler to apply to the `` + handleShortcutKeydown = (e: KeyboardEvent) => { + if (e.key === SIDEBAR_KEYBOARD_SHORTCUT && (e.metaKey || e.ctrlKey)) { + e.preventDefault(); + this.toggle(); + } + }; + + setOpenMobile = (value: boolean) => { + this.openMobile = value; + }; + + toggle = () => { + return this.#isMobile.current ? (this.openMobile = !this.openMobile) : this.setOpen(!this.open); + }; +} + +const SYMBOL_KEY = 'scn-sidebar'; + +/** + * Instantiates a new `SidebarState` instance and sets it in the context. + * + * @param props The constructor props for the `SidebarState` class. + * @returns The `SidebarState` instance. + */ +export function setSidebar(props: SidebarStateProps): SidebarState { + return setContext(Symbol.for(SYMBOL_KEY), new SidebarState(props)); +} + +/** + * Retrieves the `SidebarState` instance from the context. This is a class instance, + * so you cannot destructure it. + * @returns The `SidebarState` instance. + */ +export function useSidebar(): SidebarState { + return getContext(Symbol.for(SYMBOL_KEY)); +} diff --git a/src/lib/components/ui/sidebar/index.ts b/src/lib/components/ui/sidebar/index.ts new file mode 100644 index 0000000..280e640 --- /dev/null +++ b/src/lib/components/ui/sidebar/index.ts @@ -0,0 +1,75 @@ +import { useSidebar } from './context.svelte.js'; +import Content from './sidebar-content.svelte'; +import Footer from './sidebar-footer.svelte'; +import GroupAction from './sidebar-group-action.svelte'; +import GroupContent from './sidebar-group-content.svelte'; +import GroupLabel from './sidebar-group-label.svelte'; +import Group from './sidebar-group.svelte'; +import Header from './sidebar-header.svelte'; +import Input from './sidebar-input.svelte'; +import Inset from './sidebar-inset.svelte'; +import MenuAction from './sidebar-menu-action.svelte'; +import MenuBadge from './sidebar-menu-badge.svelte'; +import MenuButton from './sidebar-menu-button.svelte'; +import MenuItem from './sidebar-menu-item.svelte'; +import MenuSkeleton from './sidebar-menu-skeleton.svelte'; +import MenuSubButton from './sidebar-menu-sub-button.svelte'; +import MenuSubItem from './sidebar-menu-sub-item.svelte'; +import MenuSub from './sidebar-menu-sub.svelte'; +import Menu from './sidebar-menu.svelte'; +import Provider from './sidebar-provider.svelte'; +import Rail from './sidebar-rail.svelte'; +import Separator from './sidebar-separator.svelte'; +import Trigger from './sidebar-trigger.svelte'; +import Root from './sidebar.svelte'; + +export { + Content, + Footer, + Group, + GroupAction, + GroupContent, + GroupLabel, + Header, + Input, + Inset, + Menu, + MenuAction, + MenuBadge, + MenuButton, + MenuItem, + MenuSkeleton, + MenuSub, + MenuSubButton, + MenuSubItem, + Provider, + Rail, + Root, + Separator, + // + Root as Sidebar, + Content as SidebarContent, + Footer as SidebarFooter, + Group as SidebarGroup, + GroupAction as SidebarGroupAction, + GroupContent as SidebarGroupContent, + GroupLabel as SidebarGroupLabel, + Header as SidebarHeader, + Input as SidebarInput, + Inset as SidebarInset, + Menu as SidebarMenu, + MenuAction as SidebarMenuAction, + MenuBadge as SidebarMenuBadge, + MenuButton as SidebarMenuButton, + MenuItem as SidebarMenuItem, + MenuSkeleton as SidebarMenuSkeleton, + MenuSub as SidebarMenuSub, + MenuSubButton as SidebarMenuSubButton, + MenuSubItem as SidebarMenuSubItem, + Provider as SidebarProvider, + Rail as SidebarRail, + Separator as SidebarSeparator, + Trigger as SidebarTrigger, + Trigger, + useSidebar +}; diff --git a/src/lib/components/ui/sidebar/sidebar-content.svelte b/src/lib/components/ui/sidebar/sidebar-content.svelte new file mode 100644 index 0000000..f1d195e --- /dev/null +++ b/src/lib/components/ui/sidebar/sidebar-content.svelte @@ -0,0 +1,24 @@ + + +
    + {@render children?.()} +
    diff --git a/src/lib/components/ui/sidebar/sidebar-footer.svelte b/src/lib/components/ui/sidebar/sidebar-footer.svelte new file mode 100644 index 0000000..24d67f6 --- /dev/null +++ b/src/lib/components/ui/sidebar/sidebar-footer.svelte @@ -0,0 +1,21 @@ + + +
    + {@render children?.()} +
    diff --git a/src/lib/components/ui/sidebar/sidebar-group-action.svelte b/src/lib/components/ui/sidebar/sidebar-group-action.svelte new file mode 100644 index 0000000..929993e --- /dev/null +++ b/src/lib/components/ui/sidebar/sidebar-group-action.svelte @@ -0,0 +1,36 @@ + + +{#if child} + {@render child({ props: mergedProps })} +{:else} + +{/if} diff --git a/src/lib/components/ui/sidebar/sidebar-group-content.svelte b/src/lib/components/ui/sidebar/sidebar-group-content.svelte new file mode 100644 index 0000000..fafacee --- /dev/null +++ b/src/lib/components/ui/sidebar/sidebar-group-content.svelte @@ -0,0 +1,21 @@ + + +
    + {@render children?.()} +
    diff --git a/src/lib/components/ui/sidebar/sidebar-group-label.svelte b/src/lib/components/ui/sidebar/sidebar-group-label.svelte new file mode 100644 index 0000000..1de5e84 --- /dev/null +++ b/src/lib/components/ui/sidebar/sidebar-group-label.svelte @@ -0,0 +1,34 @@ + + +{#if child} + {@render child({ props: mergedProps })} +{:else} +
    + {@render children?.()} +
    +{/if} diff --git a/src/lib/components/ui/sidebar/sidebar-group.svelte b/src/lib/components/ui/sidebar/sidebar-group.svelte new file mode 100644 index 0000000..897445d --- /dev/null +++ b/src/lib/components/ui/sidebar/sidebar-group.svelte @@ -0,0 +1,21 @@ + + +
    + {@render children?.()} +
    diff --git a/src/lib/components/ui/sidebar/sidebar-header.svelte b/src/lib/components/ui/sidebar/sidebar-header.svelte new file mode 100644 index 0000000..a546474 --- /dev/null +++ b/src/lib/components/ui/sidebar/sidebar-header.svelte @@ -0,0 +1,21 @@ + + +
    + {@render children?.()} +
    diff --git a/src/lib/components/ui/sidebar/sidebar-input.svelte b/src/lib/components/ui/sidebar/sidebar-input.svelte new file mode 100644 index 0000000..1a3610b --- /dev/null +++ b/src/lib/components/ui/sidebar/sidebar-input.svelte @@ -0,0 +1,21 @@ + + + diff --git a/src/lib/components/ui/sidebar/sidebar-inset.svelte b/src/lib/components/ui/sidebar/sidebar-inset.svelte new file mode 100644 index 0000000..e2a30a2 --- /dev/null +++ b/src/lib/components/ui/sidebar/sidebar-inset.svelte @@ -0,0 +1,24 @@ + + +
    + {@render children?.()} +
    diff --git a/src/lib/components/ui/sidebar/sidebar-menu-action.svelte b/src/lib/components/ui/sidebar/sidebar-menu-action.svelte new file mode 100644 index 0000000..3f178c8 --- /dev/null +++ b/src/lib/components/ui/sidebar/sidebar-menu-action.svelte @@ -0,0 +1,43 @@ + + +{#if child} + {@render child({ props: mergedProps })} +{:else} + +{/if} diff --git a/src/lib/components/ui/sidebar/sidebar-menu-badge.svelte b/src/lib/components/ui/sidebar/sidebar-menu-badge.svelte new file mode 100644 index 0000000..aef8db8 --- /dev/null +++ b/src/lib/components/ui/sidebar/sidebar-menu-badge.svelte @@ -0,0 +1,29 @@ + + +
    + {@render children?.()} +
    diff --git a/src/lib/components/ui/sidebar/sidebar-menu-button.svelte b/src/lib/components/ui/sidebar/sidebar-menu-button.svelte new file mode 100644 index 0000000..c358b70 --- /dev/null +++ b/src/lib/components/ui/sidebar/sidebar-menu-button.svelte @@ -0,0 +1,101 @@ + + + + +{#snippet Button({ props }: { props?: Record })} + {@const mergedProps = mergeProps(buttonProps, props)} + {#if child} + {@render child({ props: mergedProps })} + {:else} + + {/if} +{/snippet} + +{#if !tooltipContent} + {@render Button({})} +{:else} + + + {#snippet child({ props })} + {@render Button({ props })} + {/snippet} + + + +{/if} diff --git a/src/lib/components/ui/sidebar/sidebar-menu-item.svelte b/src/lib/components/ui/sidebar/sidebar-menu-item.svelte new file mode 100644 index 0000000..acd2860 --- /dev/null +++ b/src/lib/components/ui/sidebar/sidebar-menu-item.svelte @@ -0,0 +1,21 @@ + + +
  • + {@render children?.()} +
  • diff --git a/src/lib/components/ui/sidebar/sidebar-menu-skeleton.svelte b/src/lib/components/ui/sidebar/sidebar-menu-skeleton.svelte new file mode 100644 index 0000000..49a18f1 --- /dev/null +++ b/src/lib/components/ui/sidebar/sidebar-menu-skeleton.svelte @@ -0,0 +1,36 @@ + + +
    + {#if showIcon} + + {/if} + + {@render children?.()} +
    diff --git a/src/lib/components/ui/sidebar/sidebar-menu-sub-button.svelte b/src/lib/components/ui/sidebar/sidebar-menu-sub-button.svelte new file mode 100644 index 0000000..95191c2 --- /dev/null +++ b/src/lib/components/ui/sidebar/sidebar-menu-sub-button.svelte @@ -0,0 +1,43 @@ + + +{#if child} + {@render child({ props: mergedProps })} +{:else} + + {@render children?.()} + +{/if} diff --git a/src/lib/components/ui/sidebar/sidebar-menu-sub-item.svelte b/src/lib/components/ui/sidebar/sidebar-menu-sub-item.svelte new file mode 100644 index 0000000..4274f0e --- /dev/null +++ b/src/lib/components/ui/sidebar/sidebar-menu-sub-item.svelte @@ -0,0 +1,21 @@ + + +
  • + {@render children?.()} +
  • diff --git a/src/lib/components/ui/sidebar/sidebar-menu-sub.svelte b/src/lib/components/ui/sidebar/sidebar-menu-sub.svelte new file mode 100644 index 0000000..609ca64 --- /dev/null +++ b/src/lib/components/ui/sidebar/sidebar-menu-sub.svelte @@ -0,0 +1,25 @@ + + +
      + {@render children?.()} +
    diff --git a/src/lib/components/ui/sidebar/sidebar-menu.svelte b/src/lib/components/ui/sidebar/sidebar-menu.svelte new file mode 100644 index 0000000..094f0f0 --- /dev/null +++ b/src/lib/components/ui/sidebar/sidebar-menu.svelte @@ -0,0 +1,21 @@ + + +
      + {@render children?.()} +
    diff --git a/src/lib/components/ui/sidebar/sidebar-provider.svelte b/src/lib/components/ui/sidebar/sidebar-provider.svelte new file mode 100644 index 0000000..2e615f4 --- /dev/null +++ b/src/lib/components/ui/sidebar/sidebar-provider.svelte @@ -0,0 +1,53 @@ + + + + + +
    + {@render children?.()} +
    +
    diff --git a/src/lib/components/ui/sidebar/sidebar-rail.svelte b/src/lib/components/ui/sidebar/sidebar-rail.svelte new file mode 100644 index 0000000..62ebb55 --- /dev/null +++ b/src/lib/components/ui/sidebar/sidebar-rail.svelte @@ -0,0 +1,36 @@ + + + diff --git a/src/lib/components/ui/sidebar/sidebar-separator.svelte b/src/lib/components/ui/sidebar/sidebar-separator.svelte new file mode 100644 index 0000000..45c1ebc --- /dev/null +++ b/src/lib/components/ui/sidebar/sidebar-separator.svelte @@ -0,0 +1,19 @@ + + + diff --git a/src/lib/components/ui/sidebar/sidebar-trigger.svelte b/src/lib/components/ui/sidebar/sidebar-trigger.svelte new file mode 100644 index 0000000..285e6c4 --- /dev/null +++ b/src/lib/components/ui/sidebar/sidebar-trigger.svelte @@ -0,0 +1,35 @@ + + + diff --git a/src/lib/components/ui/sidebar/sidebar.svelte b/src/lib/components/ui/sidebar/sidebar.svelte new file mode 100644 index 0000000..0eee3bb --- /dev/null +++ b/src/lib/components/ui/sidebar/sidebar.svelte @@ -0,0 +1,101 @@ + + +{#if collapsible === 'none'} +
    + {@render children?.()} +
    +{:else if sidebar.isMobile} + sidebar.openMobile, (v) => sidebar.setOpenMobile(v)} {...restProps}> + + + Sidebar + Displays the mobile sidebar. + +
    + {@render children?.()} +
    +
    +
    +{:else} + +{/if} diff --git a/src/lib/components/ui/skeleton/index.ts b/src/lib/components/ui/skeleton/index.ts new file mode 100644 index 0000000..3120ce1 --- /dev/null +++ b/src/lib/components/ui/skeleton/index.ts @@ -0,0 +1,7 @@ +import Root from './skeleton.svelte'; + +export { + Root, + // + Root as Skeleton +}; diff --git a/src/lib/components/ui/skeleton/skeleton.svelte b/src/lib/components/ui/skeleton/skeleton.svelte new file mode 100644 index 0000000..cdd10e0 --- /dev/null +++ b/src/lib/components/ui/skeleton/skeleton.svelte @@ -0,0 +1,17 @@ + + +
    diff --git a/src/lib/components/ui/sonner/index.ts b/src/lib/components/ui/sonner/index.ts new file mode 100644 index 0000000..fcaf06b --- /dev/null +++ b/src/lib/components/ui/sonner/index.ts @@ -0,0 +1 @@ +export { default as Toaster } from './sonner.svelte'; diff --git a/src/lib/components/ui/sonner/sonner.svelte b/src/lib/components/ui/sonner/sonner.svelte new file mode 100644 index 0000000..cb1f7c1 --- /dev/null +++ b/src/lib/components/ui/sonner/sonner.svelte @@ -0,0 +1,13 @@ + + + diff --git a/src/lib/components/ui/switch/index.ts b/src/lib/components/ui/switch/index.ts new file mode 100644 index 0000000..129f8f5 --- /dev/null +++ b/src/lib/components/ui/switch/index.ts @@ -0,0 +1,7 @@ +import Root from './switch.svelte'; + +export { + Root, + // + Root as Switch +}; diff --git a/src/lib/components/ui/switch/switch.svelte b/src/lib/components/ui/switch/switch.svelte new file mode 100644 index 0000000..9762142 --- /dev/null +++ b/src/lib/components/ui/switch/switch.svelte @@ -0,0 +1,29 @@ + + + + + diff --git a/src/lib/components/ui/table/index.ts b/src/lib/components/ui/table/index.ts new file mode 100644 index 0000000..99239ae --- /dev/null +++ b/src/lib/components/ui/table/index.ts @@ -0,0 +1,28 @@ +import Root from './table.svelte'; +import Body from './table-body.svelte'; +import Caption from './table-caption.svelte'; +import Cell from './table-cell.svelte'; +import Footer from './table-footer.svelte'; +import Head from './table-head.svelte'; +import Header from './table-header.svelte'; +import Row from './table-row.svelte'; + +export { + Root, + Body, + Caption, + Cell, + Footer, + Head, + Header, + Row, + // + Root as Table, + Body as TableBody, + Caption as TableCaption, + Cell as TableCell, + Footer as TableFooter, + Head as TableHead, + Header as TableHeader, + Row as TableRow +}; diff --git a/src/lib/components/ui/table/table-body.svelte b/src/lib/components/ui/table/table-body.svelte new file mode 100644 index 0000000..cf720f4 --- /dev/null +++ b/src/lib/components/ui/table/table-body.svelte @@ -0,0 +1,20 @@ + + + + {@render children?.()} + diff --git a/src/lib/components/ui/table/table-caption.svelte b/src/lib/components/ui/table/table-caption.svelte new file mode 100644 index 0000000..7ad3aee --- /dev/null +++ b/src/lib/components/ui/table/table-caption.svelte @@ -0,0 +1,20 @@ + + + + {@render children?.()} + diff --git a/src/lib/components/ui/table/table-cell.svelte b/src/lib/components/ui/table/table-cell.svelte new file mode 100644 index 0000000..d769915 --- /dev/null +++ b/src/lib/components/ui/table/table-cell.svelte @@ -0,0 +1,23 @@ + + + + {@render children?.()} + diff --git a/src/lib/components/ui/table/table-footer.svelte b/src/lib/components/ui/table/table-footer.svelte new file mode 100644 index 0000000..565b244 --- /dev/null +++ b/src/lib/components/ui/table/table-footer.svelte @@ -0,0 +1,20 @@ + + +tr]:last:border-b-0', className)} + {...restProps} +> + {@render children?.()} + diff --git a/src/lib/components/ui/table/table-head.svelte b/src/lib/components/ui/table/table-head.svelte new file mode 100644 index 0000000..e68b74f --- /dev/null +++ b/src/lib/components/ui/table/table-head.svelte @@ -0,0 +1,23 @@ + + + + {@render children?.()} + diff --git a/src/lib/components/ui/table/table-header.svelte b/src/lib/components/ui/table/table-header.svelte new file mode 100644 index 0000000..615b5d8 --- /dev/null +++ b/src/lib/components/ui/table/table-header.svelte @@ -0,0 +1,20 @@ + + + + {@render children?.()} + diff --git a/src/lib/components/ui/table/table-row.svelte b/src/lib/components/ui/table/table-row.svelte new file mode 100644 index 0000000..251bbd9 --- /dev/null +++ b/src/lib/components/ui/table/table-row.svelte @@ -0,0 +1,23 @@ + + +svelte-css-wrapper]:[&>th,td]:bg-muted/50', + className + )} + {...restProps} +> + {@render children?.()} + diff --git a/src/lib/components/ui/table/table.svelte b/src/lib/components/ui/table/table.svelte new file mode 100644 index 0000000..38b0176 --- /dev/null +++ b/src/lib/components/ui/table/table.svelte @@ -0,0 +1,22 @@ + + +
    + + {@render children?.()} +
    +
    diff --git a/src/lib/components/ui/tabs/index.ts b/src/lib/components/ui/tabs/index.ts new file mode 100644 index 0000000..d2a7939 --- /dev/null +++ b/src/lib/components/ui/tabs/index.ts @@ -0,0 +1,16 @@ +import Root from './tabs.svelte'; +import Content from './tabs-content.svelte'; +import List from './tabs-list.svelte'; +import Trigger from './tabs-trigger.svelte'; + +export { + Root, + Content, + List, + Trigger, + // + Root as Tabs, + Content as TabsContent, + List as TabsList, + Trigger as TabsTrigger +}; diff --git a/src/lib/components/ui/tabs/tabs-content.svelte b/src/lib/components/ui/tabs/tabs-content.svelte new file mode 100644 index 0000000..92044c8 --- /dev/null +++ b/src/lib/components/ui/tabs/tabs-content.svelte @@ -0,0 +1,17 @@ + + + diff --git a/src/lib/components/ui/tabs/tabs-list.svelte b/src/lib/components/ui/tabs/tabs-list.svelte new file mode 100644 index 0000000..4929200 --- /dev/null +++ b/src/lib/components/ui/tabs/tabs-list.svelte @@ -0,0 +1,16 @@ + + + diff --git a/src/lib/components/ui/tabs/tabs-trigger.svelte b/src/lib/components/ui/tabs/tabs-trigger.svelte new file mode 100644 index 0000000..9c5b49b --- /dev/null +++ b/src/lib/components/ui/tabs/tabs-trigger.svelte @@ -0,0 +1,20 @@ + + + diff --git a/src/lib/components/ui/tabs/tabs.svelte b/src/lib/components/ui/tabs/tabs.svelte new file mode 100644 index 0000000..b275bda --- /dev/null +++ b/src/lib/components/ui/tabs/tabs.svelte @@ -0,0 +1,19 @@ + + + diff --git a/src/lib/components/ui/textarea/index.ts b/src/lib/components/ui/textarea/index.ts new file mode 100644 index 0000000..9ccb3bf --- /dev/null +++ b/src/lib/components/ui/textarea/index.ts @@ -0,0 +1,7 @@ +import Root from './textarea.svelte'; + +export { + Root, + // + Root as Textarea +}; diff --git a/src/lib/components/ui/textarea/textarea.svelte b/src/lib/components/ui/textarea/textarea.svelte new file mode 100644 index 0000000..e759ddb --- /dev/null +++ b/src/lib/components/ui/textarea/textarea.svelte @@ -0,0 +1,22 @@ + + + diff --git a/src/lib/components/ui/tooltip/index.ts b/src/lib/components/ui/tooltip/index.ts new file mode 100644 index 0000000..273d831 --- /dev/null +++ b/src/lib/components/ui/tooltip/index.ts @@ -0,0 +1,21 @@ +import { Tooltip as TooltipPrimitive } from 'bits-ui'; +import Trigger from './tooltip-trigger.svelte'; +import Content from './tooltip-content.svelte'; + +const Root = TooltipPrimitive.Root; +const Provider = TooltipPrimitive.Provider; +const Portal = TooltipPrimitive.Portal; + +export { + Root, + Trigger, + Content, + Provider, + Portal, + // + Root as Tooltip, + Content as TooltipContent, + Trigger as TooltipTrigger, + Provider as TooltipProvider, + Portal as TooltipPortal +}; diff --git a/src/lib/components/ui/tooltip/tooltip-content.svelte b/src/lib/components/ui/tooltip/tooltip-content.svelte new file mode 100644 index 0000000..bc33b18 --- /dev/null +++ b/src/lib/components/ui/tooltip/tooltip-content.svelte @@ -0,0 +1,47 @@ + + + + + {@render children?.()} + + {#snippet child({ props })} +
    + {/snippet} +
    +
    +
    diff --git a/src/lib/components/ui/tooltip/tooltip-trigger.svelte b/src/lib/components/ui/tooltip/tooltip-trigger.svelte new file mode 100644 index 0000000..5631d1b --- /dev/null +++ b/src/lib/components/ui/tooltip/tooltip-trigger.svelte @@ -0,0 +1,7 @@ + + + diff --git a/src/lib/config/index.ts b/src/lib/config/index.ts new file mode 100644 index 0000000..b0f7225 --- /dev/null +++ b/src/lib/config/index.ts @@ -0,0 +1,14 @@ +import { ConfigManager } from './manager.js'; +import type { AppConfig } from '../../schemas/configSchema.js'; + +export const configManager = new ConfigManager(process.env.APP_CONFIG_PATH ?? './data/app.json'); +export const config = configManager.store; + +// Export types and utilities +export type { AppConfig }; +export { ConfigSchema } from '../../schemas/configSchema.js'; + +// Convenience functions +export const getConfig = () => configManager.getConfig(); +export const updateConfig = (updates: Partial) => configManager.updateConfig(updates); +export const reloadConfig = () => configManager.reloadConfig(); diff --git a/src/lib/config/manager.ts b/src/lib/config/manager.ts new file mode 100644 index 0000000..561fcbe --- /dev/null +++ b/src/lib/config/manager.ts @@ -0,0 +1,152 @@ +import { ConfigSchema, type AppConfig } from '../../schemas/configSchema.js'; +import { + readFileSync, + writeFileSync, + existsSync, + mkdirSync, + statSync, + watchFile, + unwatchFile +} from 'fs'; +import type { Stats } from 'fs'; +import { dirname } from 'path'; +import { writable, type Writable } from 'svelte/store'; + +export class ConfigManager { + private configPath: string; + private cache: AppConfig | null = null; + private lastModified: number = 0; + public store: Writable; + + constructor(configPath: string) { + this.configPath = configPath; + this.store = writable(this.getDefaultConfig()); + this.ensureConfigExists(); + this.loadConfig(); + } + + private getDefaultConfig(): AppConfig { + return ConfigSchema.parse({}); + } + + private ensureConfigExists(): void { + const dir = dirname(this.configPath); + if (!existsSync(dir)) { + mkdirSync(dir, { recursive: true }); + } + + if (!existsSync(this.configPath)) { + this.writeConfigFile(this.getDefaultConfig()); + } + } + + private writeConfigFile(config: AppConfig): void { + writeFileSync(this.configPath, JSON.stringify(config, null, 2), 'utf-8'); + } + + public loadConfig(): AppConfig { + try { + if (!existsSync(this.configPath)) { + console.warn(`Config file not found: ${this.configPath}, using defaults`); + this.cache = this.getDefaultConfig(); + this.store.set(this.cache); + return this.cache; + } + + const stats = statSync(this.configPath); + const currentModified = stats.mtime.getTime(); + + // Return cache if file hasn't changed + if (this.cache && this.lastModified === currentModified) { + return this.cache; + } + + const configData = JSON.parse(readFileSync(this.configPath, 'utf-8')); + this.cache = ConfigSchema.parse(configData); + this.lastModified = currentModified; + + // Update store + this.store.set(this.cache); + + console.log('Config loaded successfully'); + return this.cache; + } catch (error) { + console.error('Failed to load config, using defaults:', error); + this.cache = this.getDefaultConfig(); + this.store.set(this.cache); + return this.cache; + } + } + + public getConfig(): AppConfig { + return this.cache || this.loadConfig(); + } + + public updateConfig(updates: Partial): void { + try { + const currentConfig = this.getConfig(); + + // Deep merge updates + const newConfig = this.deepMerge(currentConfig, updates); + + // Validate with Zod + const validatedConfig = ConfigSchema.parse(newConfig); + + // Update cache and store + this.cache = validatedConfig; + this.lastModified = Date.now(); + this.store.set(validatedConfig); + // Write to file + this.writeConfigFile(validatedConfig); + } catch (error) { + console.error('Failed to update config:', error); + throw error; + } + } + + public reloadConfig(): AppConfig { + this.cache = null; + this.lastModified = 0; + return this.loadConfig(); + } + + // Watch for file changes (optional - for development) + public watchConfig(): () => void { + if (typeof window !== 'undefined') { + console.warn('Config watching is not available in browser'); + // return a no-op cleanup function for consistency + return () => {}; + } + + watchFile(this.configPath, (curr: Stats, prev: Stats) => { + if (curr.mtime.getTime() !== prev.mtime.getTime()) { + console.log('Config file changed, reloading...'); + this.reloadConfig(); + } + }); + + // Cleanup function + return () => unwatchFile(this.configPath); + } + + private deepMerge( + target: Record, + source: Record + ): Record { + const result: Record = { ...(target as Record) }; + + for (const key in source) { + const sourceVal = source[key]; + if (sourceVal && typeof sourceVal === 'object' && !Array.isArray(sourceVal)) { + const targetVal = (target as Record)[key] as + | Record + | undefined; + result[key] = this.deepMerge(targetVal || {}, sourceVal as Record); + } else { + result[key] = sourceVal; + } + } + + return result; + } +} diff --git a/src/lib/hooks/is-mobile.svelte.ts b/src/lib/hooks/is-mobile.svelte.ts new file mode 100644 index 0000000..acbe8ef --- /dev/null +++ b/src/lib/hooks/is-mobile.svelte.ts @@ -0,0 +1,9 @@ +import { MediaQuery } from 'svelte/reactivity'; + +const DEFAULT_MOBILE_BREAKPOINT = 768; + +export class IsMobile extends MediaQuery { + constructor(breakpoint: number = DEFAULT_MOBILE_BREAKPOINT) { + super(`max-width: ${breakpoint - 1}px`); + } +} diff --git a/src/lib/index.ts b/src/lib/index.ts new file mode 100644 index 0000000..856f2b6 --- /dev/null +++ b/src/lib/index.ts @@ -0,0 +1 @@ +// place files you want to import through the `$lib` alias in this folder. diff --git a/src/lib/orpc.ts b/src/lib/orpc.ts new file mode 100644 index 0000000..f5c680a --- /dev/null +++ b/src/lib/orpc.ts @@ -0,0 +1,16 @@ +import type { RouterClient } from '@orpc/server'; +import type { router } from '../rpc-routes/index.js'; +import { createORPCClient } from '@orpc/client'; +import { RPCLink } from '@orpc/client/fetch'; +import { createTanstackQueryUtils } from '@orpc/tanstack-query'; + +const rpcLink = new RPCLink({ + url: `${typeof window !== 'undefined' ? window.location.origin : 'http://localhost:3000'}/rpc`, + headers: () => ({ + Authorization: 'Bearer default-token' + }) +}); + +export const client: RouterClient = createORPCClient(rpcLink); + +export const orpc = createTanstackQueryUtils(client); diff --git a/src/lib/server/auth/jwt.ts b/src/lib/server/auth/jwt.ts new file mode 100644 index 0000000..b81c531 --- /dev/null +++ b/src/lib/server/auth/jwt.ts @@ -0,0 +1,242 @@ +import { promises as fs } from 'fs'; +import { + decodeJwt, + exportPKCS8, + exportSPKI, + generateKeyPair, + importPKCS8, + importSPKI, + jwtVerify, + SignJWT +} from 'jose'; +import path from 'path'; +import { z } from 'zod'; + +const DEFAULT_PRIVATE_PATH = process.env.JWT_PRIVATE_KEY_PATH ?? './data/jwt_private.pem'; +const DEFAULT_PUBLIC_PATH = process.env.JWT_PUBLIC_KEY_PATH ?? './data/jwt_public.pem'; + +export const AdminTokenPayloadSchema = z.object({ + type: z.literal('admin'), + sessionId: z.uuid(), + userId: z.uuid(), + roleId: z.number().int() +}); + +export const MemberTokenPayloadSchema = z.object({ + type: z.literal('member'), + sessionId: z.string().uuid(), + memberId: z.string().uuid() +}); + +export const TokenPayloadSchema = z.discriminatedUnion('type', [ + AdminTokenPayloadSchema, + MemberTokenPayloadSchema +]); + +export type AdminTokenPayload = z.infer; +export type MemberTokenPayload = z.infer; +export type TokenPayload = z.infer; + +export type JwtHelperOptions = { + privateKeyPath?: string; + publicKeyPath?: string; + alg?: 'RS256' | 'RS384' | 'RS512'; +}; + +class JwtHelper { + private privateKeyPem?: string; + private publicKeyPem?: string; + private privateKey?: CryptoKey; + private publicKey?: CryptoKey; + private alg: 'RS256' | 'RS384' | 'RS512'; + private privateKeyPath: string; + private publicKeyPath: string; + + // static cache shared across instances keyed by config (alg + paths) + private static keyCache = new Map< + string, + { + privateKey: CryptoKey; + publicKey: CryptoKey; + privateKeyPem: string; + publicKeyPem: string; + } + >(); + + // simple verification cache to avoid repeated expensive jwtVerify on same token + private verificationCache = new Map(); + private verificationCacheMax = 1000; + private verificationCacheDefaultTtlMs = 5 * 60 * 1000; // 5 minutes + + constructor(options?: JwtHelperOptions) { + this.alg = options?.alg ?? 'RS256'; + this.privateKeyPath = + options?.privateKeyPath ?? process.env.JWT_PRIVATE_KEY_PATH ?? DEFAULT_PRIVATE_PATH; + this.publicKeyPath = + options?.publicKeyPath ?? process.env.JWT_PUBLIC_KEY_PATH ?? DEFAULT_PUBLIC_PATH; + } + + private getCacheKey() { + return `${this.alg}::${this.privateKeyPath}::${this.publicKeyPath}`; + } + + async init(options?: JwtHelperOptions) { + // allow overriding per-call + if (options?.privateKeyPath) this.privateKeyPath = options.privateKeyPath; + if (options?.publicKeyPath) this.publicKeyPath = options.publicKeyPath; + if (options?.alg) this.alg = options.alg; + + const cacheKey = this.getCacheKey(); + const cached = JwtHelper.keyCache.get(cacheKey); + if (cached) { + this.privateKey = cached.privateKey; + this.publicKey = cached.publicKey; + this.privateKeyPem = cached.privateKeyPem; + this.publicKeyPem = cached.publicKeyPem; + return; + } + + const privResolved = path.resolve(this.privateKeyPath); + const pubResolved = path.resolve(this.publicKeyPath); + + await fs.mkdir(path.dirname(privResolved), { recursive: true }); + + let privExists = false; + let pubExists = false; + try { + await fs.access(privResolved); + privExists = true; + } catch { + privExists = false; + } + try { + await fs.access(pubResolved); + pubExists = true; + } catch { + pubExists = false; + } + + if (!privExists || !pubExists) { + // generate keys (make private key extractable so we can export PKCS#8) + const { publicKey, privateKey } = await generateKeyPair(this.alg, { extractable: true }); + const pkcs8 = await exportPKCS8(privateKey); + const spki = await exportSPKI(publicKey); + await fs.writeFile(privResolved, pkcs8, { encoding: 'utf8', mode: 0o600 }); + await fs.writeFile(pubResolved, spki, { encoding: 'utf8', mode: 0o644 }); + this.privateKeyPem = pkcs8; + this.publicKeyPem = spki; + } else { + this.privateKeyPem = await fs.readFile(privResolved, 'utf8'); + this.publicKeyPem = await fs.readFile(pubResolved, 'utf8'); + } + + if (!this.privateKeyPem || !this.publicKeyPem) { + throw new Error('Failed to load or generate JWT keys'); + } + + // import keys for jose usage + this.privateKey = await importPKCS8(this.privateKeyPem, this.alg); + this.publicKey = await importSPKI(this.publicKeyPem, this.alg); + + // store in cache + JwtHelper.keyCache.set(cacheKey, { + privateKey: this.privateKey, + publicKey: this.publicKey, + privateKeyPem: this.privateKeyPem, + publicKeyPem: this.publicKeyPem + }); + } + + private cacheVerification(token: string, payload: TokenPayload, expMs?: number) { + // eviction if necessary + if (this.verificationCache.size >= this.verificationCacheMax) { + // remove oldest entry + const firstKey = this.verificationCache.keys().next().value; + if (firstKey) this.verificationCache.delete(firstKey); + } + const expiresAt = expMs ?? Date.now() + this.verificationCacheDefaultTtlMs; + this.verificationCache.set(token, { payload, expiresAt }); + } + + private getCachedVerification(token: string) { + const e = this.verificationCache.get(token); + if (!e) return null; + if (e.expiresAt < Date.now()) { + this.verificationCache.delete(token); + return null; + } + // refresh LRU by reinserting + this.verificationCache.delete(token); + this.verificationCache.set(token, e); + return e.payload; + } + + async signAdmin(payload: AdminTokenPayload, ttlSeconds = 3600): Promise { + const parsed = AdminTokenPayloadSchema.parse(payload); + if (!this.privateKey) throw new Error('JwtHelper not initialized'); + const issuedAtSec = Math.floor(Date.now() / 1000); + const jwt = await new SignJWT(parsed) + .setProtectedHeader({ alg: this.alg }) + .setIssuedAt(issuedAtSec) + .setExpirationTime(issuedAtSec + ttlSeconds) + .sign(this.privateKey); + return jwt; + } + + async signMember(payload: MemberTokenPayload, ttlSeconds = 3600): Promise { + const parsed = MemberTokenPayloadSchema.parse(payload); + if (!this.privateKey) throw new Error('JwtHelper not initialized'); + const issuedAtSec = Math.floor(Date.now() / 1000); + const jwt = await new SignJWT(parsed) + .setProtectedHeader({ alg: this.alg }) + .setIssuedAt(issuedAtSec) + .setExpirationTime(issuedAtSec + ttlSeconds) + .sign(this.privateKey); + return jwt; + } + + async verify(token: string): Promise { + // fast-path cache + const cached = this.getCachedVerification(token); + if (cached) return cached; + + if (!this.publicKey) throw new Error('JwtHelper not initialized'); + const { payload } = await jwtVerify(token, this.publicKey, { algorithms: [this.alg] }); + const validated = TokenPayloadSchema.parse(payload as unknown); + + // try to cache until token expiry if present, else use default ttl + const payloadRecord = payload as Record; + const rawExp = + typeof payloadRecord.exp === 'number' ? (payloadRecord.exp as number) : undefined; + const expMs = rawExp ? rawExp * 1000 : undefined; + this.cacheVerification(token, validated, expMs); + + return validated; + } + + async verifyAdmin(token: string): Promise { + const payload = await this.verify(token); + if (payload.type !== 'admin') throw new Error('Token is not an admin token'); + return payload as AdminTokenPayload; + } + + async verifyMember(token: string): Promise { + const payload = await this.verify(token); + if (payload.type !== 'member') throw new Error('Token is not a member token'); + return payload as MemberTokenPayload; + } + + decode(token: string): TokenPayload | null { + const decoded = decodeJwt(token) as Record | undefined; + if (!decoded) return null; + try { + const validated = TokenPayloadSchema.parse(decoded as unknown); + return validated; + } catch { + return null; + } + } +} + +export const jwtHelper = new JwtHelper(); +export default jwtHelper; diff --git a/src/lib/server/auth/jwtSessionManager.ts b/src/lib/server/auth/jwtSessionManager.ts new file mode 100644 index 0000000..4a917df --- /dev/null +++ b/src/lib/server/auth/jwtSessionManager.ts @@ -0,0 +1,88 @@ +import type { Handle, RequestEvent } from '@sveltejs/kit'; +import { decodeJwt } from 'jose'; +import type { AdminTokenPayload, MemberTokenPayload, TokenPayload } from './jwt'; +import jwtHelper from './jwt'; + +const COOKIE_NAME = process.env.JWT_COOKIE_NAME ?? 'app_jwt'; +const COOKIE_ADMIN_NAME = process.env.JWT_ADMIN_COOKIE_NAME ?? 'admin_jwt'; +const DEFAULT_TTL = Number(process.env.JWT_TTL_SECONDS ?? '3600'); +const IS_PROD = process.env.NODE_ENV === 'production'; + +type AuthLocals = globalThis.App.Locals['auth']; + +async function refreshTokenIfNeeded( + token: string, + payload: TokenPayload, + event: RequestEvent, + cookieName: string +) { + const decoded = decodeJwt(token) as { exp?: number; iat?: number } | undefined; + if (!decoded?.exp || !decoded.iat) return; + const nowSec = Math.floor(Date.now() / 1000); + const ttl = decoded.exp - decoded.iat; + const remaining = decoded.exp - nowSec; + if (remaining > Math.floor(ttl / 2)) return; + let newToken: string; + if (payload.type === 'admin') { + newToken = await jwtHelper.signAdmin(payload as AdminTokenPayload, ttl); + } else if (payload.type === 'member') { + newToken = await jwtHelper.signMember(payload as MemberTokenPayload, ttl); + } else { + throw new Error('Invalid token payload type'); + } + event.cookies.set(cookieName, newToken, { + httpOnly: true, + secure: IS_PROD, + sameSite: 'lax', + path: '/', + maxAge: ttl + }); +} + +async function processCookie( + cookie: string | undefined, + auth: AuthLocals, + event: RequestEvent, + isAdmin: boolean +) { + if (!cookie) return; + const cookieName = isAdmin ? COOKIE_ADMIN_NAME : COOKIE_NAME; + try { + const payload = await jwtHelper.verify(cookie); + if (isAdmin && payload.type === 'admin') { + auth.admin = payload as AdminTokenPayload; + } else if (!isAdmin && payload.type === 'member') { + auth.member = payload as MemberTokenPayload; + } + await refreshTokenIfNeeded(cookie, payload, event, cookieName); + } catch (err) { + void err; + event.cookies.set(cookieName, '', { maxAge: 0, path: '/', httpOnly: true, secure: IS_PROD }); + } +} + +// Create tokens and return cookie header (admin) +export async function loginAdminReturnCookie(payload: AdminTokenPayload, ttlSeconds = DEFAULT_TTL) { + const token = await jwtHelper.signAdmin(payload, ttlSeconds); + return { token }; +} + +// Create tokens and return cookie header (member) +export async function loginMemberReturnCookie( + payload: MemberTokenPayload, + ttlSeconds = DEFAULT_TTL +) { + const token = await jwtHelper.signMember(payload, ttlSeconds); + return { token }; +} + +export const handleAuth: Handle = async ({ event, resolve }) => { + const { locals } = event; + const appCookie = event.cookies.get(COOKIE_NAME); + const adminCookie = event.cookies.get(COOKIE_ADMIN_NAME); + const auth: AuthLocals = {}; + await processCookie(appCookie, auth, event, false); + await processCookie(adminCookie, auth, event, true); + locals.auth = auth; + return resolve(event); +}; diff --git a/src/lib/server/auth/permissions.ts b/src/lib/server/auth/permissions.ts new file mode 100644 index 0000000..622f1b2 --- /dev/null +++ b/src/lib/server/auth/permissions.ts @@ -0,0 +1,54 @@ +import { ManagementUserRole, PermissionActions } from '../../../schemas'; + +export const Scopes = [ + 'MANAGE_USERS', + 'MANAGE_MEMBERS', + 'MANAGE_CARDS', + 'VIEW_REPORTS', + 'MANAGE_DEVICES', + 'CONFIGURE_SYSTEM' +] as const; +export type Scope = (typeof Scopes)[number]; + +export type RoleMap = { + [key in keyof typeof ManagementUserRole]: Partial>; +}; + +export const Roles: RoleMap = { + ADMIN: { + MANAGE_USERS: PermissionActions.ADMIN, + MANAGE_MEMBERS: PermissionActions.ADMIN, + MANAGE_CARDS: PermissionActions.ADMIN, + VIEW_REPORTS: PermissionActions.ADMIN, + MANAGE_DEVICES: PermissionActions.ADMIN, + CONFIGURE_SYSTEM: PermissionActions.ADMIN + }, + MANAGER: { + // Admin can fully manage users, members and cards, and view reports + MANAGE_USERS: PermissionActions.READ | PermissionActions.WRITE | PermissionActions.DELETE, + MANAGE_MEMBERS: PermissionActions.READ | PermissionActions.WRITE | PermissionActions.DELETE, + MANAGE_CARDS: PermissionActions.READ | PermissionActions.WRITE | PermissionActions.DELETE, + VIEW_REPORTS: PermissionActions.READ + }, + VIEWER: { + // Manager has limited management rights and can view reports + MANAGE_MEMBERS: PermissionActions.READ, + MANAGE_CARDS: PermissionActions.READ, + VIEW_REPORTS: PermissionActions.READ + } +} as const; + +/** + * Helper to check if a role has specific action(s) for a scope. + * Example: hasPermission('ADMIN', 'MANAGE_USERS', PermissionActions.DELETE) + */ +export function hasPermission( + roleKey: keyof typeof ManagementUserRole, + scope: Scope, + actionMask: number +): boolean { + const role = Roles[roleKey]; + if (!role) return false; + const allowed = role[scope] ?? 0; + return (allowed & actionMask) === actionMask; +} diff --git a/src/lib/server/db/index.ts b/src/lib/server/db/index.ts new file mode 100644 index 0000000..79f3555 --- /dev/null +++ b/src/lib/server/db/index.ts @@ -0,0 +1,13 @@ +import dotenv from 'dotenv'; +import { drizzle } from 'drizzle-orm/postgres-js'; +import postgres from 'postgres'; +import * as schema from '../../../schemas/database/schema'; + +// Load environment variables from .env file +dotenv.config(); +const dbUrl = process.env.DATABASE_URL; +if (!dbUrl) throw new Error('DATABASE_URL is not set'); + +const client = postgres(dbUrl); + +export const db = drizzle(client, { schema }); diff --git a/src/lib/server/db/mapper/accessLogMapper.ts b/src/lib/server/db/mapper/accessLogMapper.ts new file mode 100644 index 0000000..11db8ab --- /dev/null +++ b/src/lib/server/db/mapper/accessLogMapper.ts @@ -0,0 +1,15 @@ +import { + type AccessLog, + AccessLogSelectSchema, + type AccessLogInsert, + AccessLogInsertSchema, + type AccessLogUpdate, + AccessLogUpdateSchema +} from '../../../../schemas/accessLogSchema'; +import type { AccessLogDbo, AccessLogDbi } from '../../../../schemas/database/schema'; + +export const accessLogMapper = { + toDomain: (dbo: AccessLogDbo): AccessLog => AccessLogSelectSchema.parse(dbo), + fromInsert: (log: AccessLogInsert): AccessLogDbi => AccessLogInsertSchema.parse(log), + fromUpdate: (log: AccessLogUpdate): Partial => AccessLogUpdateSchema.parse(log) +}; diff --git a/src/lib/server/db/mapper/deviceMapper.ts b/src/lib/server/db/mapper/deviceMapper.ts new file mode 100644 index 0000000..b440ebc --- /dev/null +++ b/src/lib/server/db/mapper/deviceMapper.ts @@ -0,0 +1,19 @@ +import { + DeviceInsertSchema, + DeviceSelectSchema, + DeviceUpdateSchema, + type Device, + type DeviceInsert, + type DeviceUpdate +} from '../../../../schemas'; +import type { DeviceDbi, DeviceDbo } from '../../../../schemas/database/schema'; + +export const deviceMapper = { + toDomain: (dbo: DeviceDbo): Device => DeviceSelectSchema.parse(dbo), + fromInsert: (device: DeviceInsert): DeviceDbi => { + const parsed = DeviceInsertSchema.parse(device); + const apiKey = parsed.apiKey || crypto.randomUUID(); + return { ...parsed, apiKey }; + }, + fromUpdate: (device: DeviceUpdate): Partial => DeviceUpdateSchema.parse(device) +}; diff --git a/src/lib/server/db/mapper/managementUserMapper.ts b/src/lib/server/db/mapper/managementUserMapper.ts new file mode 100644 index 0000000..fbcbd63 --- /dev/null +++ b/src/lib/server/db/mapper/managementUserMapper.ts @@ -0,0 +1,28 @@ +import { + ManagementUserInsertSchema, + ManagementUserPublicSchema, + ManagementUserSelectSchema, + ManagementUserUpdateSchema, + type ManagementUser, + type ManagementUserInsert, + type ManagementUserPublic, + type ManagementUserUpdate +} from '../../../../schemas'; +import type { ManagementUserDbi, ManagementUserDbo } from '../../../../schemas/database/schema'; + +export const managementUserDboMapper = { + toDomain: (user: ManagementUserDbo): ManagementUser => ManagementUserSelectSchema.parse(user), + toPublic: (user: ManagementUserDbo): ManagementUserPublic => + ManagementUserPublicSchema.parse(user), + fromInsert: (user: ManagementUserInsert, passwordHash: string): ManagementUserDbi => ({ + ...ManagementUserInsertSchema.parse(user), + passwordHash + }), + fromUpdate: ( + user: ManagementUserUpdate, + passwordHash: string | undefined + ): Partial => ({ + ...ManagementUserUpdateSchema.parse(user), + ...(passwordHash ? { passwordHash } : {}) + }) +}; diff --git a/src/lib/server/db/mapper/memberCardMapper.ts b/src/lib/server/db/mapper/memberCardMapper.ts new file mode 100644 index 0000000..7352e46 --- /dev/null +++ b/src/lib/server/db/mapper/memberCardMapper.ts @@ -0,0 +1,31 @@ +import { + type RfidCard, + RfidCardSelectSchema, + type RfidCardInsert, + RfidCardInsertSchema, + type RfidCardUpdate, + RfidCardUpdateSchema, + type MemberRfidCardInsert, + type MemberRfidCardUpdate, + type MemberRfidCard, + MemberRfidCardSelectSchema, + MemberRfidCardUpdateSchema +} from '../../../../schemas'; +import type { + RfidCardDbo, + RfidCardDbi, + MemberRfidCardDbi +} from '../../../../schemas/database/schema'; + +export const rfidCardMapper = { + toDomain: (dbo: RfidCardDbo): RfidCard => RfidCardSelectSchema.parse(dbo), + fromInsert: (card: RfidCardInsert): RfidCardDbi => RfidCardInsertSchema.parse(card), + fromUpdate: (card: RfidCardUpdate): Partial => RfidCardUpdateSchema.parse(card) +}; + +export const memberRfidCardMapper = { + toDomain: (dbo: MemberRfidCard): MemberRfidCard => MemberRfidCardSelectSchema.parse(dbo), + fromInsert: (card: MemberRfidCardInsert): MemberRfidCardDbi => card as MemberRfidCardDbi, + fromUpdate: (card: MemberRfidCardUpdate): Partial => + MemberRfidCardUpdateSchema.parse(card) +}; diff --git a/src/lib/server/db/mapper/memberMapper.ts b/src/lib/server/db/mapper/memberMapper.ts new file mode 100644 index 0000000..3ae04bf --- /dev/null +++ b/src/lib/server/db/mapper/memberMapper.ts @@ -0,0 +1,15 @@ +import { + MemberInsertSchema, + MemberSelectSchema, + MemberUpdateSchema, + type Member, + type MemberInsert, + type MemberUpdate +} from '../../../../schemas'; +import type { MemberDbi, MemberDbo } from '../../../../schemas/database/schema'; + +export const memberDboMapper = { + toDomain: (member: MemberDbo): Member => MemberSelectSchema.parse(member), + fromInsert: (member: MemberInsert): MemberDbi => MemberInsertSchema.parse(member), + fromUpdate: (member: MemberUpdate): Partial => MemberUpdateSchema.parse(member) +}; diff --git a/src/lib/server/db/migrate.ts b/src/lib/server/db/migrate.ts new file mode 100644 index 0000000..f40d094 --- /dev/null +++ b/src/lib/server/db/migrate.ts @@ -0,0 +1,57 @@ +import { drizzle } from 'drizzle-orm/postgres-js'; +import { migrate } from 'drizzle-orm/postgres-js/migrator'; +import postgres from 'postgres'; +import 'dotenv/config'; +import * as schema from '$schemas/database/schema'; // Dein Schema importieren +import { hashPassword } from '$lib/utils/passwordHash'; // Dein Hash-Helfer +import { eq, count } from 'drizzle-orm'; + +const databaseUrl = process.env.DATABASE_URL; +if (!databaseUrl) { + throw new Error('DATABASE_URL is not set.'); +} + +const migrationClient = postgres(databaseUrl, { max: 1 }); +const db = drizzle(migrationClient, { schema }); + +export async function setupDatabase() { + console.log('⏳ Starting database setup...'); + const start = Date.now(); + + try { + // 1. Migrationen ausführen + console.log('Running migrations...'); + await migrate(db, { migrationsFolder: 'drizzle' }); + console.log('Migrations are up to date.'); + + // 2. Admin-User Seeding (die Logik aus deinem Hook) + const adminCountResult = await db + .select({ value: count() }) + .from(schema.managementUsers) + .where(eq(schema.managementUsers.role, 'ADMIN')); + + if (adminCountResult[0].value === 0) { + console.log('👤 Admin user not found, seeding admin...'); + const adminPasswordHash = await hashPassword('admin123'); // Passwort aus .env laden! + await db.insert(schema.managementUsers).values({ + firstName: 'Admin', + lastName: 'User', + username: 'admin', + passwordHash: adminPasswordHash, + role: 'ADMIN', + enabled: true + }); + console.log('✅ Admin user created.'); + } else { + console.log('ℹ️ Admin user already exists.'); + } + } catch (error) { + console.error('❌ Database setup failed:', error); + process.exit(1); + } finally { + await migrationClient.end(); + } + + const end = Date.now(); + console.log(`✅ Database setup finished in ${end - start}ms.`); +} diff --git a/src/lib/server/importSessionStore.ts b/src/lib/server/importSessionStore.ts new file mode 100644 index 0000000..1863d07 --- /dev/null +++ b/src/lib/server/importSessionStore.ts @@ -0,0 +1,129 @@ +import { MemberInsertSchema, RfidCardInsertSchema } from '$schemas'; +import * as z from 'zod'; +export const importSessionSchema = z.object({ + raw: z.array(z.array(z.string())), + rawColCount: z.number(), + parsed: z.array( + z.tuple([MemberInsertSchema.partial(), RfidCardInsertSchema.partial().optional()]) + ), + final: z.array(z.tuple([MemberInsertSchema, RfidCardInsertSchema.optional()])), + createdMemberIds: z.set(z.string()), + errors: z.array( + z.object({ + row: z.number(), + error: z.string() + }) + ), + duplicates: z.array( + z.object({ + type: z.string(), + newRecord: MemberInsertSchema.partial(), + existingRecord: MemberInsertSchema.or(RfidCardInsertSchema).optional(), + index: z.number() + }) + ), + step: z.number().min(1).max(5), + delimiter: z.string(), + hasHeader: z.boolean(), + fieldMapping: z.record(z.string(), z.number().min(0)), + createdAt: z.date(), + updatedAt: z.date(), + completed: z.boolean().optional().default(false) +}); + +export const importSessionSchemaPublic = importSessionSchema.transform((session) => ({ + rawColCount: session.rawColCount, + rawPreview: session.raw.slice(0, 3), + parsedCount: session.parsed.length, + parsedPreview: session.parsed.slice(0, 10), + finalCount: session.final.length, + errorCount: session.errors.length, + errors: session.errors.slice(0, 15), + duplicateCount: session.duplicates.length, + duplicates: session.duplicates, + step: session.step, + delimiter: session.delimiter, + hasHeader: session.hasHeader, + fieldMapping: session.fieldMapping, + createdAt: session.createdAt, + updatedAt: session.updatedAt, + completed: session.completed +})); + +export type ImportSession = z.infer; +export type ImportSessionPublic = z.infer; + +class ImportSessionStore { + private sessions = new Map(); + + generateKey(): string { + return ( + Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15) + ); + } + + create(raw: string[][], delimiter: string, rawColCount: number): string { + const key = this.generateKey(); + const session: ImportSession = { + raw, + rawColCount, + parsed: [], + final: [], + createdMemberIds: new Set(), + errors: [], + duplicates: [], + step: 2, + delimiter, + hasHeader: true, + fieldMapping: {}, + createdAt: new Date(), + updatedAt: new Date(), + completed: false + }; + this.sessions.set(key, session); + return key; + } + + get(key: string): ImportSession | undefined { + return this.sessions.get(key); + } + + update(key: string, updates: Partial): boolean { + const session = this.sessions.get(key); + if (!session) return false; + const step = updates.step && updates.step > session.step ? updates.step : session.step; + Object.assign(session, updates, { updatedAt: new Date(), step }); + return true; + } + + delete(key: string): boolean { + return this.sessions.delete(key); + } + + list(): { key: string; session: ImportSession }[] { + return Array.from(this.sessions.entries()).map(([key, session]) => ({ key, session })); + } + + // Cleanup expired sessions (older than 1 hour) + cleanup(): void { + const now = new Date(); + const oneHourAgo = new Date(now.getTime() - 60 * 60 * 1000); + for (const [key, session] of this.sessions.entries()) { + if (session.updatedAt < oneHourAgo) { + this.sessions.delete(key); + } + } + } +} + +export const importSessionStore = new ImportSessionStore(); + +// Periodic cleanup every 10 minutes +if (typeof globalThis !== 'undefined') { + setInterval( + () => { + importSessionStore.cleanup(); + }, + 10 * 60 * 1000 + ); +} diff --git a/src/lib/server/logger.ts b/src/lib/server/logger.ts new file mode 100644 index 0000000..aa779c1 --- /dev/null +++ b/src/lib/server/logger.ts @@ -0,0 +1,164 @@ +import { systemLogRepository } from './repositories'; +import { getConfig } from '../config'; +import type { SystemLogType } from '../../schemas/systemLogSchema'; + +export interface LogContext { + userId?: string; + name?: string; + payload?: Record; +} + +/** + * Zentrale Logging-Bibliothek für System-Events + */ +export class Logger { + /** + * Prüft, ob ein Log-Typ gespeichert werden soll + */ + private static shouldSave(type: SystemLogType): boolean { + const config = getConfig(); + const saveTypes = config.features.logging.save; + return saveTypes.includes(type); + } + + /** + * Prüft, ob ein Log-Typ auf der Console ausgegeben werden soll + */ + private static shouldLogToConsole(type: SystemLogType): boolean { + const config = getConfig(); + const consoleTypes = config.features.logging.console; + return consoleTypes.includes(type); + } + + /** + * Loggt eine Aktion + */ + static async action(name: string, context?: LogContext): Promise { + const type: SystemLogType = 'ACTION'; + + // Console-Ausgabe falls konfiguriert + if (this.shouldLogToConsole(type)) { + console.log(`[ACTION] ${name}`, context); + } + + // Speicherung falls konfiguriert + if (this.shouldSave(type)) { + try { + await systemLogRepository.create({ + type, + name, + userId: context?.userId || undefined, + payload: context?.payload ? JSON.stringify(context.payload) : undefined + }); + } catch (error) { + // Silent fail - Logging-Fehler sollten nicht die Anwendung stoppen + console.error('Failed to log action:', error); + } + } + } + + /** + * Loggt eine Info-Nachricht + */ + static async info(name: string, context?: LogContext): Promise { + const type: SystemLogType = 'INFO'; + + // Console-Ausgabe falls konfiguriert + if (this.shouldLogToConsole(type)) { + console.info(`[INFO] ${name}`, context); + } + + // Speicherung falls konfiguriert + if (this.shouldSave(type)) { + try { + await systemLogRepository.create({ + type, + name, + userId: context?.userId || undefined, + payload: context?.payload ? JSON.stringify(context.payload) : undefined + }); + } catch (error) { + console.error('Failed to log info:', error); + } + } + } + + /** + * Loggt eine Warnung + */ + static async warning(name: string, context?: LogContext): Promise { + const type: SystemLogType = 'WARNING'; + + // Console-Ausgabe falls konfiguriert + if (this.shouldLogToConsole(type)) { + console.warn(`[WARNING] ${name}`, context); + } + + // Speicherung falls konfiguriert + if (this.shouldSave(type)) { + try { + await systemLogRepository.create({ + type, + name, + userId: context?.userId || undefined, + payload: context?.payload ? JSON.stringify(context.payload) : undefined + }); + } catch (error) { + console.error('Failed to log warning:', error); + } + } + } + + /** + * Loggt einen Fehler + */ + static async error(name: string, context?: LogContext): Promise { + const type: SystemLogType = 'ERROR'; + + // Console-Ausgabe falls konfiguriert + if (this.shouldLogToConsole(type)) { + console.error(`[ERROR] ${name}`, context); + } + + // Speicherung falls konfiguriert + if (this.shouldSave(type)) { + try { + await systemLogRepository.create({ + type, + name, + userId: context?.userId || undefined, + payload: context?.payload ? JSON.stringify(context.payload) : undefined + }); + } catch (error) { + console.error('Failed to log error:', error); + } + } + } + + /** + * Loggt ein Event mit einem bestimmten Typ + */ + static async log(type: SystemLogType, name: string, context?: LogContext): Promise { + // Console-Ausgabe falls konfiguriert + if (this.shouldLogToConsole(type)) { + console.log(`[${type}] ${name}`, context); + } + + // Speicherung falls konfiguriert + if (this.shouldSave(type)) { + try { + await systemLogRepository.create({ + type, + name, + userId: context?.userId || undefined, + payload: context?.payload ? JSON.stringify(context.payload) : undefined + }); + } catch (error) { + console.error('Failed to log event:', error); + } + } + } +} + +// Exportiere eine Instanz für einfacheren Import +export const logger = Logger; diff --git a/src/lib/server/repositories/accessLogRepository.ts b/src/lib/server/repositories/accessLogRepository.ts new file mode 100644 index 0000000..6a54770 --- /dev/null +++ b/src/lib/server/repositories/accessLogRepository.ts @@ -0,0 +1,253 @@ +import type { Pagination } from '$schemas'; +import { and, asc, count, desc, eq, gte, ilike, lte, sql } from 'drizzle-orm'; +import type { + AccessLog, + AccessLogInsert, + AccessLogsListQuery, + AccessLogUpdate, + AccessLogWithMemberAndDevice +} from '../../../schemas/accessLogSchema'; +import { accessLogs, devices, members } from '../../../schemas/database/schema'; +import { db } from '../db'; +import { accessLogMapper } from '../db/mapper/accessLogMapper.js'; + +/** + * Repository für Access Logs (Zutritte). + * Stil und Typisierung analog zu vorhandenen Repositories. + */ +export class AccessLogRepository { + /** + * Erstellt einen neuen Access Log + */ + async create(logData: AccessLogInsert): Promise { + const dbData = accessLogMapper.fromInsert(logData); + const [log] = await db.insert(accessLogs).values(dbData).returning(); + return accessLogMapper.toDomain(log); + } + + /** + * Findet einen Log anhand der internen ID + */ + async findById(id: string): Promise { + const rows = await db.select().from(accessLogs).where(eq(accessLogs.id, id)).limit(1); + return rows.length > 0 ? accessLogMapper.toDomain(rows[0]) : null; + } + + /** + * Gibt alle Logs zurück mit optionaler Sortierung / Pagination + */ + async findAll(options?: { + orderBy?: 'accessedAt' | 'createdAt'; + orderDirection?: 'asc' | 'desc'; + limit?: number; + offset?: number; + }): Promise { + let query = db.select().from(accessLogs); + + const orderFn = options?.orderDirection === 'asc' ? asc : desc; + if (options?.orderBy) { + switch (options.orderBy) { + case 'accessedAt': + query = query.orderBy(orderFn(accessLogs.accessedAt)) as typeof query; + break; + case 'createdAt': + query = query.orderBy(orderFn(accessLogs.createdAt)) as typeof query; + break; + } + }else{ + query = query.orderBy(orderFn(accessLogs.accessedAt)) as typeof query; + console.log(options?.orderDirection) + } + + if (options?.limit) query = query.limit(options.limit) as typeof query; + if (options?.offset) query = query.offset(options.offset) as typeof query; + + const rows = await query; + return rows.map(accessLogMapper.toDomain); + } + + /** + * List Access logs with pagination support + */ + async list(options: AccessLogsListQuery): Promise<{ + accessLogs: AccessLogWithMemberAndDevice[]; + pagination: Pagination; + }> { + const orderFn = options.sortOrder === 'asc' ? asc : desc; + let orderBy; + switch (options.orderBy) { + case 'accessedAt': + orderBy = orderFn(accessLogs.accessedAt); + break; + case 'createdAt': + orderBy = orderFn(accessLogs.createdAt); + break; + default: + orderBy = orderFn(accessLogs.accessedAt); + } + + // Build where conditions + const conditions = []; + if (options.search) { + conditions.push(ilike(members.firstName, `%${options.search}%`)); + } + if (options.memberId) { + conditions.push(eq(accessLogs.memberId, options.memberId)); + } + if (options.deviceId) { + conditions.push(eq(accessLogs.deviceId, options.deviceId)); + } + if (options.fromDate) { + conditions.push(gte(accessLogs.accessedAt, options.fromDate)); + } + if (options.toDate) { + conditions.push(lte(accessLogs.accessedAt, options.toDate)); + } + + const whereCondition = conditions.length > 0 ? and(...conditions) : undefined; + + const totalRows = await db + .select({ count: count() }) + .from(accessLogs) + .leftJoin(members, eq(accessLogs.memberId, members.id)) + .where(whereCondition); + const total = totalRows[0].count; + + let rows; + if (total > 0) { + rows = await db + .select({ + accessLog: accessLogs, + member: members, + device: devices + }) + .from(accessLogs) + .leftJoin(members, eq(accessLogs.memberId, members.id)) + .leftJoin(devices, eq(accessLogs.deviceId, devices.id)) + .where(whereCondition) + .limit(options.limit) + .offset(options.cursor) + .orderBy(orderBy); + } + + return { + accessLogs: + rows?.map((row) => ({ + ...accessLogMapper.toDomain(row.accessLog), + member: row.member || undefined, + device: row.device || undefined + })) || [], + pagination: { + cursor: options.cursor, + limit: options.limit, + total + } + }; + } + + /** + * Aktualisiert einen Log + */ + async update(id: string, logData: AccessLogUpdate): Promise { + const dbData = accessLogMapper.fromUpdate(logData); + const [updated] = await db + .update(accessLogs) + .set(dbData) + .where(eq(accessLogs.id, id)) + .returning(); + return accessLogMapper.toDomain(updated); + } + + /** + * Löscht einen Log + */ + async delete(id: string): Promise { + const result = await db.delete(accessLogs).where(eq(accessLogs.id, id)); + return result.count > 0; + } + + /** + * Findet Logs für ein bestimmtes Mitglied + */ + async findByMemberId( + memberId: string, + options?: { + orderBy?: 'accessedAt' | 'createdAt'; + orderDirection?: 'asc' | 'desc'; + limit?: number; + offset?: number; + } + ): Promise { + let query = db.select().from(accessLogs).where(eq(accessLogs.memberId, memberId)); + + if (options?.orderBy) { + const orderFn = options.orderDirection === 'desc' ? desc : asc; + switch (options.orderBy) { + case 'accessedAt': + query = query.orderBy(orderFn(accessLogs.accessedAt)) as typeof query; + break; + case 'createdAt': + query = query.orderBy(orderFn(accessLogs.createdAt)) as typeof query; + break; + } + } + + if (options?.limit) query = query.limit(options.limit) as typeof query; + if (options?.offset) query = query.offset(options.offset) as typeof query; + + const rows = await query; + return rows.map(accessLogMapper.toDomain); + } + + /** + * Findet Logs für ein bestimmtes Gerät + */ + async findByDeviceId( + deviceId: string, + options?: { + orderBy?: 'accessedAt' | 'createdAt'; + orderDirection?: 'asc' | 'desc'; + limit?: number; + offset?: number; + } + ): Promise { + let query = db.select().from(accessLogs).where(eq(accessLogs.deviceId, deviceId)); + + if (options?.orderBy) { + const orderFn = options.orderDirection === 'desc' ? desc : asc; + switch (options.orderBy) { + case 'accessedAt': + query = query.orderBy(orderFn(accessLogs.accessedAt)) as typeof query; + break; + case 'createdAt': + query = query.orderBy(orderFn(accessLogs.createdAt)) as typeof query; + break; + } + } + + if (options?.limit) query = query.limit(options.limit) as typeof query; + if (options?.offset) query = query.offset(options.offset) as typeof query; + + const rows = await query; + return rows.map(accessLogMapper.toDomain); + } + + /** + * Gibt die Zugriffe pro Tag für die letzten 30 Tage zurück + */ + async getAccessCountsByDayLast30Days(): Promise<{ date: string; count: number }[]> { + const thirtyDaysAgo = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000); + const rows = await db + .select({ + date: sql`to_char(${accessLogs.accessedAt}, 'YYYY-MM-DD')`, + count: count() + }) + .from(accessLogs) + .where(gte(accessLogs.accessedAt, thirtyDaysAgo)) + .groupBy(sql`to_char(${accessLogs.accessedAt}, 'YYYY-MM-DD')`) + .orderBy(sql`to_char(${accessLogs.accessedAt}, 'YYYY-MM-DD')`); + return rows; + } +} + +export const accessLogRepository = new AccessLogRepository(); diff --git a/src/lib/server/repositories/deviceRepository.ts b/src/lib/server/repositories/deviceRepository.ts new file mode 100644 index 0000000..8a1d300 --- /dev/null +++ b/src/lib/server/repositories/deviceRepository.ts @@ -0,0 +1,121 @@ +import { eq, desc, asc, ilike, count, and } from 'drizzle-orm'; +import { db } from '../db'; +import { devices, type DeviceDbo } from '../../../schemas/database/schema'; +import type { Device, DeviceInsert, DeviceUpdate } from '../../../schemas/devicesSchema'; +import { deviceMapper } from '../db/mapper/deviceMapper'; +import type { Pagination } from '$schemas'; +import type { DevicesListQuery } from '../../../schemas/devicesSchema'; +// import v4 from uuid for UUID generation +import { v4 as uuidv4 } from 'uuid'; + +export class DeviceRepository { + async create(deviceData: DeviceInsert): Promise { + if (!deviceData.apiKey) { + // generate random apiKey if not provided + deviceData.apiKey = uuidv4(); + } + const [device] = await db + .insert(devices) + .values(deviceMapper.fromInsert(deviceData)) + .returning(); + return deviceMapper.toDomain(device); + } + + async getById(id: string): Promise { + const row = await db.select().from(devices).where(eq(devices.id, id)).limit(1); + return row.length > 0 ? deviceMapper.toDomain(row[0]) : null; + } + + async findByApiKey(apiKey: string): Promise { + const row = await db.select().from(devices).where(eq(devices.apiKey, apiKey)).limit(1); + return row.length > 0 ? deviceMapper.toDomain(row[0]) : null; + } + + async setLastSeen(id: string): Promise { + await db.update(devices).set({ lastSeenAt: new Date() }).where(eq(devices.id, id)); + } + + async list(options: DevicesListQuery): Promise<{ + devices: Device[]; + pagination: Pagination; + }> { + const orderFn = options.sortOrder === 'desc' ? desc : asc; + let orderBy; + switch (options.orderBy) { + case 'name': + orderBy = orderFn(devices.name); + break; + case 'type': + orderBy = orderFn(devices.type); + break; + case 'createdAt': + orderBy = orderFn(devices.createdAt); + break; + default: + orderBy = orderFn(devices.id); + } + const search = options.search ? ilike(devices.name, `%${options.search}%`) : undefined; + const typeFilter = options.type + ? eq(devices.type, options.type as 'RFID_SCANNER' | 'LOCK_SYSTEM') + : undefined; + + // Combine filters with and() + let whereClause; + if (search && typeFilter) { + whereClause = and(search, typeFilter); + } else if (search) { + whereClause = search; + } else if (typeFilter) { + whereClause = typeFilter; + } + + const totalRows = await db.select({ count: count() }).from(devices).where(whereClause); + const total = totalRows[0].count; + + let rows; + if (total > 0) { + rows = await db + .select() + .from(devices) + .where(whereClause) + .limit(options.limit) + .offset(options.cursor) + .orderBy(orderBy); + } + + return { + devices: rows?.map(deviceMapper.toDomain) || [], + pagination: { + cursor: options.cursor, + limit: options.limit, + total + } + }; + } + + async update(id: string, deviceData: DeviceUpdate): Promise { + const [updated] = await db + .update(devices) + .set(deviceMapper.fromUpdate(deviceData) as Partial) + .where(eq(devices.id, id)) + .returning(); + return deviceMapper.toDomain(updated); + } + + async delete(id: string): Promise { + const result = await db.delete(devices).where(eq(devices.id, id)); + return result.count > 0; + } + + async count(): Promise { + const result = await db.select({ count: count() }).from(devices); + return result[0].count; + } + + async countByType(type: 'RFID_SCANNER' | 'LOCK_SYSTEM'): Promise { + const result = await db.select({ count: count() }).from(devices).where(eq(devices.type, type)); + return result[0].count; + } +} + +export const deviceRepository = new DeviceRepository(); diff --git a/src/lib/server/repositories/index.ts b/src/lib/server/repositories/index.ts new file mode 100644 index 0000000..ab31659 --- /dev/null +++ b/src/lib/server/repositories/index.ts @@ -0,0 +1,7 @@ +export { managementUserRepository } from './managementUserRepository'; +export { memberRepository } from './memberRepository'; +export { deviceRepository } from './deviceRepository'; +export { rfidCardRepository } from './rfidCardRepository'; +export { memberRfidCardRepository } from './memberRfidCardRepository'; +export { accessLogRepository } from './accessLogRepository'; +export { systemLogRepository } from './systemLogRepository'; diff --git a/src/lib/server/repositories/managementUserRepository.ts b/src/lib/server/repositories/managementUserRepository.ts new file mode 100644 index 0000000..50eca94 --- /dev/null +++ b/src/lib/server/repositories/managementUserRepository.ts @@ -0,0 +1,202 @@ +import { and, asc, count, desc, eq, gte, ilike, or } from 'drizzle-orm'; +// Kein zusätzlicher Import für den Typ mehr nötig +import { maybeMapUndefined } from '$lib/utils/functional'; +import { hashPassword, verifyPassword } from '$lib/utils/passwordHash'; +import type { Pagination } from '$schemas'; +import { managementUsers } from '../../../schemas/database/schema'; +import { + ManagementUserSelectSchema, + type ManagementUser, + type ManagementUserInsert, + type ManagementUserPublic, + type ManagementUsersListQuery, + type ManagementUserUpdate +} from '../../../schemas/managementUserSchema'; +import { db } from '../db'; +import { managementUserDboMapper } from '../db/mapper/managementUserMapper'; + +export class ManagementUserRepository { + async create(userData: ManagementUserInsert): Promise { + const passwordHash = await hashPassword(userData.password); + + const [user] = await db + .insert(managementUsers) + .values(managementUserDboMapper.fromInsert(userData, passwordHash)) + .returning(); + + return managementUserDboMapper.toDomain(user); + } + + async getById(id: string): Promise { + const user = await db.select().from(managementUsers).where(eq(managementUsers.id, id)).limit(1); + + return user.length > 0 ? managementUserDboMapper.toPublic(user[0]) : null; + } + + async checkAuth(username: string, password: string): Promise { + const users = await db + .select() + .from(managementUsers) + .where(and(eq(managementUsers.username, username))) + .limit(1); + console.log(users); + + if (users.length === 0) return null; + const user = users[0]; + const ok = verifyPassword(user.passwordHash, password); + if (!ok) return null; + return managementUserDboMapper.toPublic(user); + } + + async list( + options: ManagementUsersListQuery + ): Promise<{ users: ManagementUserPublic[]; pagination: Pagination }> { + const orderFn = options.sortOrder === 'desc' ? desc : asc; + let orderBy; + switch (options.orderBy) { + case 'username': + orderBy = orderFn(managementUsers.username); + break; + case 'firstName': + orderBy = orderFn(managementUsers.firstName); + break; + case 'lastName': + orderBy = orderFn(managementUsers.lastName); + break; + case 'createdAt': + orderBy = orderFn(managementUsers.createdAt); + break; + default: + orderBy = orderFn(managementUsers.id); + } + + const search = options.search + ? or( + ilike(managementUsers.username, `%${options.search}%`), + ilike(managementUsers.firstName, `%${options.search}%`), + ilike(managementUsers.lastName, `%${options.search}%`) + ) + : undefined; + + const filters = []; + if (options.enabled !== undefined) { + filters.push(eq(managementUsers.enabled, options.enabled)); + } + if (options.dateFrom) { + filters.push(gte(managementUsers.createdAt, options.dateFrom)); + } + if (options.dateTo) { + filters.push(gte(managementUsers.createdAt, options.dateTo)); + } + if ( + options.role && + ManagementUserSelectSchema.shape.role.options.includes(options.role as ManagementUser['role']) + ) { + // Cast options.role to the correct enum type if necessary + filters.push(eq(managementUsers.role, options.role as ManagementUser['role'])); + } + + const totalRows = await db + .select({ count: count() }) + .from(managementUsers) + .where(and(search, or(...filters))); + const total = totalRows[0].count; + + let rows; + if (total > 0) { + rows = await db + .select() + .from(managementUsers) + .where(and(search, or(...filters))) + .limit(options.limit) + .offset(options.cursor) + .orderBy(orderBy); + } + + return { + users: rows?.map(managementUserDboMapper.toPublic) || [], + pagination: { + cursor: options.cursor, + limit: options.limit, + total + } + }; + } + + /** + * Aktualisiert einen Management-User + */ + async update(id: string, userData: ManagementUserUpdate): Promise { + const passwordHash = await maybeMapUndefined(hashPassword)(userData.password); + const [updatedUser] = await db + .update(managementUsers) + .set(managementUserDboMapper.fromUpdate(userData, passwordHash)) + .where(eq(managementUsers.id, id)) + .returning(); + + return managementUserDboMapper.toPublic(updatedUser); + } + + async delete(id: string): Promise { + const result = await db.delete(managementUsers).where(eq(managementUsers.id, id)); + + return result.count > 0; + } + + async search(searchTerm: string): Promise { + const users = await db + .select() + .from(managementUsers) + .where( + or( + eq(managementUsers.username, searchTerm), + eq(managementUsers.firstName, searchTerm), + eq(managementUsers.lastName, searchTerm) + ) + ); + + return users.map(managementUserDboMapper.toPublic); + } + + async usernameExists(username: string, excludeId?: string): Promise { + if (excludeId) { + const result = await db + .select({ id: managementUsers.id }) + .from(managementUsers) + .where(and(eq(managementUsers.username, username), eq(managementUsers.id, excludeId))) + .limit(1); + return result.length > 0; + } + + const result = await db + .select({ id: managementUsers.id }) + .from(managementUsers) + .where(eq(managementUsers.username, username)) + .limit(1); + + return result.length > 0; + } + + async count(): Promise { + const result = await db.select({ count: count() }).from(managementUsers); + return result[0].count; + } + + async countByEnabled(enabled: boolean): Promise { + const result = await db + .select({ count: count() }) + .from(managementUsers) + .where(eq(managementUsers.enabled, enabled)); + return result[0].count; + } + + async countByRole(role: 'ADMIN'): Promise { + const result = await db + .select({ count: count() }) + .from(managementUsers) + .where(eq(managementUsers.role, role)); + return result[0].count; + } +} + +export const managementUserRepository = new ManagementUserRepository(); diff --git a/src/lib/server/repositories/memberRepository.ts b/src/lib/server/repositories/memberRepository.ts new file mode 100644 index 0000000..669ce7f --- /dev/null +++ b/src/lib/server/repositories/memberRepository.ts @@ -0,0 +1,477 @@ +import type { + Member, + MemberInsert, + MemberUpdate, + MembersListQuery +} from '../../../schemas/memberSchema'; +import { verifyPassword } from '../../utils/passwordHash'; +import { + eq, + desc, + asc, + ilike, + count, + and, + or, + like, + isNotNull, + isNull, + gte, + lte +} from 'drizzle-orm'; +import { db } from '../db'; +import { memberDboMapper } from '../db/mapper/memberMapper'; +import { members } from '../../../schemas/database/schema'; +import type { Pagination } from '$schemas'; + +export class MemberRepository { + /** + * Erstellt ein neues Mitglied + */ + async create(memberData: MemberInsert): Promise { + const dbData = memberDboMapper.fromInsert(memberData); + const [member] = await db.insert(members).values(dbData).returning(); + + return memberDboMapper.toDomain(member); + } + + /** + * Findet ein Mitglied anhand der ID + */ + async getById(id: string): Promise { + const member = await db.select().from(members).where(eq(members.id, id)).limit(1); + + return member.length > 0 ? memberDboMapper.toDomain(member[0]) : null; + } + + /** + * Findet ein Mitglied anhand der Mitgliedsnummer + */ + async findByMembershipNumber(membershipNumber: string): Promise { + const member = await db + .select() + .from(members) + .where(eq(members.membershipNumber, membershipNumber)) + .limit(1); + + return member.length > 0 ? memberDboMapper.toDomain(member[0]) : null; + } + + /** + * Findet ein Mitglied anhand der E-Mail-Adresse + */ + async findByEmail(email: string): Promise { + const member = await db.select().from(members).where(eq(members.email, email)).limit(1); + + return member.length > 0 ? memberDboMapper.toDomain(member[0]) : null; + } + + async list(options: MembersListQuery): Promise<{ + members: Member[]; + pagination: Pagination; + }> { + const orderFn = options.sortOrder === 'desc' ? desc : asc; + let orderBy; + switch (options.orderBy) { + case 'firstName': + orderBy = orderFn(members.firstName); + break; + case 'lastName': + orderBy = orderFn(members.lastName); + break; + case 'email': + orderBy = orderFn(members.email); + break; + case 'membershipNumber': + orderBy = orderFn(members.membershipNumber); + break; + case 'createdAt': + orderBy = orderFn(members.createdAt); + break; + default: + orderBy = orderFn(members.id); + } + + // Build filter conditions + const conditions = []; + + // Search filter + if (options.search) { + conditions.push( + or( + ilike(members.firstName, `%${options.search}%`), + ilike(members.lastName, `%${options.search}%`), + ilike(members.email, `%${options.search}%`), + ilike(members.membershipNumber, `%${options.search}%`) + ) + ); + } + + // Guest account filter + if (options.guestAccount !== undefined) { + conditions.push(eq(members.guestAccount, options.guestAccount)); + } + + // Joined date range filter + if (options.joinedAtFrom) { + conditions.push(gte(members.joinedAt, new Date(options.joinedAtFrom))); + } + if (options.joinedAtTo) { + conditions.push(lte(members.joinedAt, new Date(options.joinedAtTo))); + } + + // Birth date range filter + if (options.birthDateFrom) { + conditions.push(gte(members.birthDate, new Date(options.birthDateFrom))); + } + if (options.birthDateTo) { + conditions.push(lte(members.birthDate, new Date(options.birthDateTo))); + } + + const whereCondition = conditions.length > 0 ? and(...conditions) : undefined; + + const totalRows = await db.select({ count: count() }).from(members).where(whereCondition); + const total = totalRows[0].count; + + let rows; + if (total > 0) { + rows = await db + .select() + .from(members) + .where(whereCondition) + .limit(options.limit) + .offset(options.cursor) + .orderBy(orderBy); + } + + return { + members: rows?.map(memberDboMapper.toDomain) || [], + pagination: { + cursor: options.cursor, + limit: options.limit, + total + } + }; + } + + /** + * Gibt alle Mitglieder zurück mit optionalem Filter + */ + async findAll(options?: { + guestAccount?: boolean; + hasEmail?: boolean; + hasMembershipNumber?: boolean; + orderBy?: 'firstName' | 'lastName' | 'email' | 'membershipNumber' | 'createdAt' | 'joinedAt'; + orderDirection?: 'asc' | 'desc'; + limit?: number; + offset?: number; + }): Promise { + let baseQuery = db.select().from(members); + + if (!options) { + const memberList = await baseQuery; + return memberList.map(memberDboMapper.toDomain); + } + + const conditions = []; + if (options.guestAccount !== undefined) { + conditions.push(eq(members.guestAccount, options.guestAccount)); + } + if (options.hasEmail !== undefined) { + conditions.push(options.hasEmail ? isNotNull(members.email) : isNull(members.email)); + } + if (options.hasMembershipNumber !== undefined) { + conditions.push( + options.hasMembershipNumber + ? isNotNull(members.membershipNumber) + : isNull(members.membershipNumber) + ); + } + + if (conditions.length === 1) { + baseQuery = baseQuery.where(conditions[0]) as typeof baseQuery; + } else if (conditions.length > 1) { + baseQuery = baseQuery.where(and(...conditions)) as typeof baseQuery; + } + + if (options.orderBy) { + const orderFn = options.orderDirection === 'desc' ? desc : asc; + switch (options.orderBy) { + case 'firstName': + baseQuery = baseQuery.orderBy(orderFn(members.firstName)) as typeof baseQuery; + break; + case 'lastName': + baseQuery = baseQuery.orderBy(orderFn(members.lastName)) as typeof baseQuery; + break; + case 'email': + baseQuery = baseQuery.orderBy(orderFn(members.email)) as typeof baseQuery; + break; + case 'membershipNumber': + baseQuery = baseQuery.orderBy(orderFn(members.membershipNumber)) as typeof baseQuery; + break; + case 'createdAt': + baseQuery = baseQuery.orderBy(orderFn(members.createdAt)) as typeof baseQuery; + break; + case 'joinedAt': + baseQuery = baseQuery.orderBy(orderFn(members.joinedAt)) as typeof baseQuery; + break; + } + } + + if (options.limit) { + baseQuery = baseQuery.limit(options.limit) as typeof baseQuery; + } + + if (options.offset) { + baseQuery = baseQuery.offset(options.offset) as typeof baseQuery; + } + + const memberList = await baseQuery; + return memberList.map(memberDboMapper.toDomain); + } + + async update(id: string, memberData: MemberUpdate): Promise { + const dbData = memberDboMapper.fromUpdate(memberData); + const [updatedMember] = await db + .update(members) + .set(dbData) + .where(eq(members.id, id)) + .returning(); + + return memberDboMapper.toDomain(updatedMember); + } + + async delete(id: string): Promise { + const result = await db.delete(members).where(eq(members.id, id)); + + return result.count > 0; + } + + async search(searchTerm: string): Promise { + const memberList = await db + .select() + .from(members) + .where( + or( + like(members.firstName, `%${searchTerm}%`), + like(members.lastName, `%${searchTerm}%`), + like(members.email, `%${searchTerm}%`), + like(members.membershipNumber, `%${searchTerm}%`), + like(members.city, `%${searchTerm}%`), + like(members.occupation, `%${searchTerm}%`) + ) + ); + + return memberList.map(memberDboMapper.toDomain); + } + + async exists(values: { email?: string; membershipNumber?: string }): Promise { + const conditions = []; + if (values.email !== undefined) { + conditions.push(eq(members.email, values.email)); + } + if (values.membershipNumber !== undefined) { + conditions.push(eq(members.membershipNumber, values.membershipNumber)); + } + + let whereCondition; + if (conditions.length === 1) { + whereCondition = conditions[0]; + } else if (conditions.length > 1) { + whereCondition = or(...conditions); + } else { + return false; + } + const result = await db.select({ id: members.id }).from(members).where(whereCondition).limit(1); + + return result.length > 0; + } + + /** + * Authenticates a member using email or membership number and password + */ + async checkAuth(identifier: string, password: string): Promise { + // First, try to find by email + let member = await this.findByEmail(identifier); + + // If not found by email, try membership number + if (!member) { + member = await this.findByMembershipNumber(identifier); + } + console.log('Member found for authentication:', member); + // If no member found + if (!member) { + return null; + } + + // Check if member has a password hash + if (!member.passwordHash) { + return null; + } + + // Verify password + const isValidPassword = await verifyPassword(member.passwordHash, password); + if (!isValidPassword) { + console.log('Invalid password for member:', member.id); + return null; + } + + return member; + } + + /** + * Updates member password hash + */ + async updatePassword(id: string, passwordHash: string): Promise { + const result = await db + .update(members) + .set({ passwordHash, updatedAt: new Date() }) + .where(eq(members.id, id)); + + return result.count > 0; + } + + /** + * Generates and sets a new password for a member + */ + async generateAndSetPassword( + id: string, + options: { + passwordLength?: number; + includeUppercase?: boolean; + includeLowercase?: boolean; + includeNumbers?: boolean; + includeSpecialChars?: boolean; + forcePasswordReset?: boolean; + passwordExpiresAt?: Date; + } = {} + ): Promise<{ password: string; expiresAt?: Date } | null> { + const member = await this.getById(id); + if (!member) { + return null; + } + + const { generateSecurePassword } = await import('$lib/utils/passwordGenerator'); + const password = generateSecurePassword({ + length: options.passwordLength || 12, + includeUppercase: options.includeUppercase ?? true, + includeLowercase: options.includeLowercase ?? true, + includeNumbers: options.includeNumbers ?? true, + includeSpecialChars: options.includeSpecialChars ?? true + }); + + const { hashPassword } = await import('$lib/utils/passwordHash'); + const passwordHash = await hashPassword(password); + + const updateData: { + passwordHash: string; + updatedAt: Date; + forcePasswordReset?: boolean; + passwordExpiresAt?: Date; + } = { + passwordHash, + updatedAt: new Date() + }; + + // Add force password reset flag if specified + if (options.forcePasswordReset) { + updateData.forcePasswordReset = true; + } + + // Add password expiration if specified + if (options.passwordExpiresAt) { + updateData.passwordExpiresAt = options.passwordExpiresAt; + } + + const result = await db.update(members).set(updateData).where(eq(members.id, id)); + + if (result.count === 0) { + return null; + } + + return { + password, + expiresAt: options.passwordExpiresAt + }; + } + + /** + * Bulk generates passwords for multiple members + */ + async bulkGeneratePasswords( + memberIds: string[], + options: { + passwordLength?: number; + includeUppercase?: boolean; + includeLowercase?: boolean; + includeNumbers?: boolean; + includeSpecialChars?: boolean; + } = {} + ): Promise> { + const { generateBulkPasswords } = await import('$lib/utils/passwordGenerator'); + const passwords = generateBulkPasswords(memberIds.length, { + length: options.passwordLength || 12, + includeUppercase: options.includeUppercase ?? true, + includeLowercase: options.includeLowercase ?? true, + includeNumbers: options.includeNumbers ?? true, + includeSpecialChars: options.includeSpecialChars ?? true + }); + + const { hashPassword } = await import('$lib/utils/passwordHash'); + const results: Array<{ memberId: string; password: string }> = []; + + for (let i = 0; i < memberIds.length; i++) { + const memberId = memberIds[i]; + const password = passwords[i]; + + try { + const passwordHash = await hashPassword(password); + + const result = await db + .update(members) + .set({ + passwordHash, + updatedAt: new Date() + }) + .where(eq(members.id, memberId)); + + if (result.count > 0) { + results.push({ memberId, password }); + } + } catch (error) { + // Skip failed updates but continue with others + console.error(`Failed to update password for member ${memberId}:`, error); + } + } + + return results; + } + + /** + * Enables or disables self-service access for a member + */ + async updateSelfServiceAccess(id: string, allowSelfService: boolean): Promise { + const result = await db + .update(members) + .set({ allowSelfService, updatedAt: new Date() }) + .where(eq(members.id, id)); + + return result.count > 0; + } + + async count(): Promise { + const result = await db.select({ count: count() }).from(members); + return result[0].count; + } + + async countByGuestAccount(guestAccount: boolean): Promise { + const result = await db + .select({ count: count() }) + .from(members) + .where(eq(members.guestAccount, guestAccount)); + return result[0].count; + } +} + +// Singleton-Instanz exportieren +export const memberRepository = new MemberRepository(); diff --git a/src/lib/server/repositories/memberRfidCardRepository.ts b/src/lib/server/repositories/memberRfidCardRepository.ts new file mode 100644 index 0000000..11924b2 --- /dev/null +++ b/src/lib/server/repositories/memberRfidCardRepository.ts @@ -0,0 +1,146 @@ +import type { + MemberRfidCard, + MemberRfidCardInsert, + MemberRfidCardUpdate, + MemberRfidCardWithMember +} from '$schemas'; +import { asc, desc, eq, and } from 'drizzle-orm'; +import { db } from '../db'; +import { memberRfidCardMapper } from '../db/mapper/memberCardMapper'; +import { memberRfidCards, members } from '../../../schemas/database/schema'; + +/** + * Repository für Member <-> RFID Card Zuordnungen + * Stil konsistent mit bestehenden Repositories. + */ +export class MemberRfidCardRepository { + /** + * Erstellt eine neue Zuordnung + */ + async create(data: MemberRfidCardInsert): Promise { + const dbData = memberRfidCardMapper.fromInsert(data); + const [row] = await db.insert(memberRfidCards).values(dbData).returning(); + return memberRfidCardMapper.toDomain(row); + } + + /** + * Findet Zuordnung anhand der internen ID + */ + async findById(id: string): Promise { + const rows = await db.select().from(memberRfidCards).where(eq(memberRfidCards.id, id)).limit(1); + return rows.length > 0 ? memberRfidCardMapper.toDomain(rows[0]) : null; + } + + /** + * Findet alle Zuordnungen für ein Mitglied + */ + async findByMemberId(memberId: string): Promise { + const rows = await db + .select() + .from(memberRfidCards) + .where(eq(memberRfidCards.memberId, memberId)); + return rows.map(memberRfidCardMapper.toDomain); + } + + /** + * Findet alle Zuordnungen für eine Karte + */ + async findByCardId(cardId: string): Promise { + const rows = await db + .select({ + memberRfidCard: memberRfidCards, + member: members + }) + .from(memberRfidCards) + .leftJoin(members, eq(memberRfidCards.memberId, members.id)) + .where(eq(memberRfidCards.cardId, cardId)); + + return rows.map((row) => ({ + ...memberRfidCardMapper.toDomain(row.memberRfidCard), + member: row.member || undefined + })); + } + + /** + * Findet Zuordnung für eine spezifische Member-Card Kombination + */ + async findByMemberAndCard( + memberId: string, + cardId: string + ): Promise { + const rows = await db + .select({ + memberRfidCard: memberRfidCards, + member: members + }) + .from(memberRfidCards) + .leftJoin(members, eq(memberRfidCards.memberId, members.id)) + .where(and(eq(memberRfidCards.memberId, memberId), eq(memberRfidCards.cardId, cardId))) + + .limit(1); + + if (rows.length === 0) return null; + + const row = rows[0]; + return { + ...memberRfidCardMapper.toDomain(row.memberRfidCard), + member: row.member || undefined + }; + } + + /** + * Gibt alle Zuordnungen zurück mit optionaler Sortierung / Pagination + */ + async findAll(options?: { + orderBy?: 'issuedAt' | 'returnedAt' | 'createdAt'; + orderDirection?: 'asc' | 'desc'; + limit?: number; + offset?: number; + }): Promise { + let query = db.select().from(memberRfidCards); + + if (options?.orderBy) { + const orderFn = options.orderDirection === 'desc' ? desc : asc; + switch (options.orderBy) { + case 'issuedAt': + query = query.orderBy(orderFn(memberRfidCards.issuedAt)) as typeof query; + break; + case 'returnedAt': + query = query.orderBy(orderFn(memberRfidCards.returnedAt)) as typeof query; + break; + case 'createdAt': + query = query.orderBy(orderFn(memberRfidCards.createdAt)) as typeof query; + break; + } + } + + if (options?.limit) query = query.limit(options.limit) as typeof query; + if (options?.offset) query = query.offset(options.offset) as typeof query; + + const rows = await query; + return rows.map(memberRfidCardMapper.toDomain); + } + + /** + * Aktualisiert eine Zuordnung + */ + async update(id: string, data: MemberRfidCardUpdate): Promise { + const dbData = memberRfidCardMapper.fromUpdate(data); + const [updated] = await db + .update(memberRfidCards) + .set(dbData) + .where(eq(memberRfidCards.id, id)) + .returning(); + return memberRfidCardMapper.toDomain(updated); + } + + /** + * Löscht eine Zuordnung + */ + async delete(id: string): Promise { + const result = await db.delete(memberRfidCards).where(eq(memberRfidCards.id, id)); + return result.count > 0; + } +} + +export const memberRfidCardRepository = new MemberRfidCardRepository(); diff --git a/src/lib/server/repositories/rfidCardRepository.ts b/src/lib/server/repositories/rfidCardRepository.ts new file mode 100644 index 0000000..a30aaef --- /dev/null +++ b/src/lib/server/repositories/rfidCardRepository.ts @@ -0,0 +1,242 @@ +import type { Pagination, PaginationQuery } from '$schemas'; +import { and, asc, count, desc, eq, gte, ilike, isNull, lte, max, or, sql } from 'drizzle-orm'; +import { memberRfidCards, rfidCards } from '../../../schemas/database/schema'; +import type { + MemberRfidCard, + RfidCard, + RfidCardInsert, + RfidCardUpdate +} from '../../../schemas/memberCardSchema'; +import { db } from '../db'; +import { memberRfidCardMapper, rfidCardMapper } from '../db/mapper/memberCardMapper'; + +/** + * Repository für RFID-Karten (physische Karten). + * Stil und Typisierung analog zu vorhandenen Repositories. + */ +export class RfidCardRepository { + /** + * Erstellt eine neue RFID-Karte + */ + async create(cardData: RfidCardInsert): Promise { + const dbData = rfidCardMapper.fromInsert(cardData); + const [card] = await db.insert(rfidCards).values(dbData).returning(); + return rfidCardMapper.toDomain(card); + } + + /** + * Findet eine Karte anhand der internen ID + */ + async findById(id: string): Promise { + const rows = await db.select().from(rfidCards).where(eq(rfidCards.id, id)).limit(1); + return rows.length > 0 ? rfidCardMapper.toDomain(rows[0]) : null; + } + + /** + * Findet eine Karte anhand der RFID Kennung (rfidId) + */ + async findByRfidId(rfidId: string): Promise { + const rows = await db.select().from(rfidCards).where(eq(rfidCards.rfidId, rfidId)).limit(1); + return rows.length > 0 ? rfidCardMapper.toDomain(rows[0]) : null; + } + + /** + * Gibt alle Karten zurück mit optionaler Sortierung / Pagination + */ + async findAll(options?: { + orderBy?: 'rfidId' | 'status' | 'createdAt'; + orderDirection?: 'asc' | 'desc'; + limit?: number; + offset?: number; + }): Promise { + let query = db.select().from(rfidCards); + + if (options?.orderBy) { + const orderFn = options.orderDirection === 'desc' ? desc : asc; + switch (options.orderBy) { + case 'rfidId': + query = query.orderBy(orderFn(rfidCards.rfidId)) as typeof query; + break; + case 'status': + query = query.orderBy(orderFn(rfidCards.status)) as typeof query; + break; + case 'createdAt': + query = query.orderBy(orderFn(rfidCards.createdAt)) as typeof query; + break; + } + } + + if (options?.limit) query = query.limit(options.limit) as typeof query; + if (options?.offset) query = query.offset(options.offset) as typeof query; + + const rows = await query; + return rows.map(rfidCardMapper.toDomain); + } + + /** + * List RFID cards with pagination support + */ + async list(options: PaginationQuery): Promise<{ + rfidCards: RfidCard[]; + pagination: Pagination; + }> { + const orderFn = options.sortOrder === 'desc' ? desc : asc; + let orderBy; + switch (options.orderBy) { + case 'rfidId': + orderBy = orderFn(rfidCards.rfidId); + break; + case 'status': + orderBy = orderFn(rfidCards.status); + break; + case 'createdAt': + orderBy = orderFn(rfidCards.createdAt); + break; + default: + orderBy = orderFn(rfidCards.id); + } + const search = options.search ? ilike(rfidCards.rfidId, `%${options.search}%`) : undefined; + const totalRows = await db.select({ count: count() }).from(rfidCards).where(search); + const total = totalRows[0].count; + + let rows; + if (total > 0) { + rows = await db + .select() + .from(rfidCards) + .where(search) + .limit(options.limit) + .offset(options.cursor) + .orderBy(orderBy); + } + + return { + rfidCards: rows?.map(rfidCardMapper.toDomain) || [], + pagination: { + cursor: options.cursor, + limit: options.limit, + total + } + }; + } + + /** + * Aktualisiert eine Karte + */ + async update(id: string, cardData: RfidCardUpdate): Promise { + const dbData = rfidCardMapper.fromUpdate(cardData); + const [updated] = await db + .update(rfidCards) + .set(dbData) + .where(eq(rfidCards.id, id)) + .returning(); + return rfidCardMapper.toDomain(updated); + } + + /** + * Löscht eine Karte + */ + async delete(id: string): Promise { + const result = await db.delete(rfidCards).where(eq(rfidCards.id, id)); + return result.count > 0; + } + + /** + * Liefert alle Zuordnungen (member_rfid_cards) zu einer Karte + */ + async findAssignmentsForCard(cardId: string): Promise { + const rows = await db.select().from(memberRfidCards).where(eq(memberRfidCards.cardId, cardId)); + return rows.map(memberRfidCardMapper.toDomain); + } + + /** + * Retrieves all assigned and enabled RFID card IDs and the date of the latest change + */ + async getAssignedEnabledRfidIds(): Promise<{ + rfidIds: string[]; + }> { + // Get RFID IDs for assigned and enabled cards + const rfidRows = await db + .select({ rfidId: rfidCards.rfidId }) + .from(rfidCards) + .innerJoin(memberRfidCards, eq(rfidCards.id, memberRfidCards.cardId)) + .where( + sql`${memberRfidCards.status} = 'ASSIGNED' AND ${rfidCards.status} NOT IN ('LOST', 'DISPOSED')` + ); + + const rfidIds = rfidRows.map((row) => row.rfidId); + + return { rfidIds }; + } + + /** + * Retrieves the date of the latest change + */ + async getLatestChange(): Promise<{ + latestChange: Date | null; + }> { + // Get latest change date from assignments + const latestChangeRows = await db + .select({ latestChange: max(memberRfidCards.updatedAt) }) + .from(memberRfidCards); + + const latestChangeRows2 = await db + .select({ latestChange: max(rfidCards.updatedAt) }) + .from(rfidCards); + + const latestChange1 = latestChangeRows[0].latestChange; + const latestChange2 = latestChangeRows2[0].latestChange; + + let latestChange: Date | null = null; + if (latestChange1 && latestChange2) { + latestChange = latestChange1 > latestChange2 ? latestChange1 : latestChange2; + } else if (latestChange1) { + latestChange = latestChange1; + } else if (latestChange2) { + latestChange = latestChange2; + } + + return { latestChange }; + } + + /** + * Resolves RFID ID to Member ID for assignments active at the given timestamp + */ + async resolveRfidIdToMemberId(rfidId: string[], accessTime: Date): Promise { + const idOr = + rfidId.length > 1 + ? or(...rfidId.map((id) => eq(rfidCards.rfidId, id))) + : eq(rfidCards.rfidId, rfidId[0]); + + const rows = await db + .select({ memberId: memberRfidCards.memberId }) + .from(rfidCards) + .innerJoin(memberRfidCards, eq(rfidCards.id, memberRfidCards.cardId)) + .where( + and( + idOr, + lte(memberRfidCards.issuedAt, accessTime), + or(isNull(memberRfidCards.returnedAt), gte(memberRfidCards.returnedAt, accessTime)) + ) + ) + .limit(1) + .orderBy(desc(memberRfidCards.issuedAt)); + + return rows.length > 0 ? rows[0].memberId : null; + } + + async count(): Promise { + const result = await db.select({ count: count() }).from(rfidCards); + return result[0].count; + } + + async countByStatus(status: 'NEW' | 'ENGRAVED' | 'LOST' | 'DISPOSED'): Promise { + const result = await db + .select({ count: count() }) + .from(rfidCards) + .where(eq(rfidCards.status, status)); + return result[0].count; + } +} + +export const rfidCardRepository = new RfidCardRepository(); diff --git a/src/lib/server/repositories/systemLogRepository.ts b/src/lib/server/repositories/systemLogRepository.ts new file mode 100644 index 0000000..6e4bc2d --- /dev/null +++ b/src/lib/server/repositories/systemLogRepository.ts @@ -0,0 +1,81 @@ +import type { + SystemLog, + SystemLogInsert, + SystemLogsListQuery +} from '../../../schemas/systemLogSchema'; +import { eq, desc, ilike, count, and, gte, lte, or } from 'drizzle-orm'; +import { db } from '../db'; +import { systemLogs } from '../../../schemas/database/schema'; +import type { Pagination } from '$schemas'; + +export class SystemLogRepository { + /** + * Erstellt einen neuen System-Log-Eintrag + */ + async create(logData: SystemLogInsert): Promise { + const [log] = await db.insert(systemLogs).values(logData).returning(); + return log; + } + + /** + * Gibt System-Logs mit Filterung und Paginierung zurück + */ + async list(options: SystemLogsListQuery): Promise<{ + logs: SystemLog[]; + pagination: Pagination; + }> { + // Build filter conditions + const conditions = []; + + // Search filter (name and payload) + if (options.search) { + conditions.push( + or( + ilike(systemLogs.name, `%${options.search}%`), + ilike(systemLogs.payload, `%${options.search}%`) + ) + ); + } + + // Type filter + if (options.type) { + conditions.push(eq(systemLogs.type, options.type)); + } + + // Date range filter + if (options.dateFrom) { + conditions.push(gte(systemLogs.createdAt, new Date(options.dateFrom))); + } + if (options.dateTo) { + conditions.push(lte(systemLogs.createdAt, new Date(options.dateTo))); + } + + const whereCondition = conditions.length > 0 ? and(...conditions) : undefined; + + const totalRows = await db.select({ count: count() }).from(systemLogs).where(whereCondition); + const total = totalRows[0].count; + + let rows; + if (total > 0) { + rows = await db + .select() + .from(systemLogs) + .where(whereCondition) + .limit(options.limit) + .offset(options.cursor) + .orderBy(desc(systemLogs.createdAt)); + } + + return { + logs: rows || [], + pagination: { + cursor: options.cursor, + limit: options.limit, + total + } + }; + } +} + +// Singleton-Instanz exportieren +export const systemLogRepository = new SystemLogRepository(); diff --git a/src/lib/stores/theme.ts b/src/lib/stores/theme.ts new file mode 100644 index 0000000..5e6d151 --- /dev/null +++ b/src/lib/stores/theme.ts @@ -0,0 +1,39 @@ +import { browser } from '$app/environment'; +import { writable } from 'svelte/store'; + +type Theme = 'light' | 'dark'; + +function createThemeStore() { + // Get initial theme from localStorage or default to dark + const initialTheme: Theme = browser ? (localStorage.getItem('theme') as Theme) || 'dark' : 'dark'; + + const { subscribe, set, update } = writable(initialTheme); + + return { + subscribe, + set: (theme: Theme) => { + if (browser) { + localStorage.setItem('theme', theme); + document.documentElement.classList.toggle('dark', theme === 'dark'); + } + set(theme); + }, + toggle: () => { + update((current) => { + const newTheme = current === 'light' ? 'dark' : 'light'; + if (browser) { + localStorage.setItem('theme', newTheme); + document.documentElement.classList.toggle('dark', newTheme === 'dark'); + } + return newTheme; + }); + }, + init: () => { + if (browser) { + document.documentElement.classList.toggle('dark', initialTheme === 'dark'); + } + } + }; +} + +export const theme = createThemeStore(); diff --git a/src/lib/utils.ts b/src/lib/utils.ts new file mode 100644 index 0000000..f92bfcb --- /dev/null +++ b/src/lib/utils.ts @@ -0,0 +1,13 @@ +import { clsx, type ClassValue } from 'clsx'; +import { twMerge } from 'tailwind-merge'; + +export function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)); +} + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export type WithoutChild = T extends { child?: any } ? Omit : T; +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export type WithoutChildren = T extends { children?: any } ? Omit : T; +export type WithoutChildrenOrChild = WithoutChildren>; +export type WithElementRef = T & { ref?: U | null }; diff --git a/src/lib/utils/clean.ts b/src/lib/utils/clean.ts new file mode 100644 index 0000000..9564739 --- /dev/null +++ b/src/lib/utils/clean.ts @@ -0,0 +1,10 @@ +export const cleanQueryObject = >(obj: T): Partial => { + const cleanedObj: Partial = {}; + for (const key in obj) { + const value = obj[key]; + if (value !== null && value !== undefined && value !== '') { + cleanedObj[key] = value; + } + } + return cleanedObj; +}; diff --git a/src/lib/utils/functional.ts b/src/lib/utils/functional.ts new file mode 100644 index 0000000..72618a3 --- /dev/null +++ b/src/lib/utils/functional.ts @@ -0,0 +1,19 @@ +export const maybeMapNull = + (mapper: (obj: T) => U) => + (obj: T | null): U | null => { + return obj ? mapper(obj) : null; + }; + +export const maybeMapUndefined = + (mapper: (obj: T) => U) => + (obj: T | undefined): U | undefined => { + return obj ? mapper(obj) : undefined; + }; + +export const maybeMapNullish = + (mapper: (obj: T) => U) => + (obj: T | undefined | null): U | undefined | null => { + if (obj === undefined) return undefined; + if (obj === null) return null; + return mapper(obj); + }; diff --git a/src/lib/utils/handlePostgresError.ts b/src/lib/utils/handlePostgresError.ts new file mode 100644 index 0000000..d1ff3ea --- /dev/null +++ b/src/lib/utils/handlePostgresError.ts @@ -0,0 +1,29 @@ +import Postgres from 'postgres'; +import { setError, type FormPathLeavesWithErrors, type SuperValidated } from 'sveltekit-superforms'; + +export type PostgresConstraintMap< + T extends Record, + Path extends FormPathLeavesWithErrors +> = Record; + +export const postgresErrorToFieldErrorCode = < + T extends Record, + Path extends FormPathLeavesWithErrors, + M, + In extends Record +>( + err: unknown, + form: SuperValidated, + fildMapping: PostgresConstraintMap +) => { + if (err instanceof Postgres.PostgresError) { + if (err.constraint_name) { + const mapping = fildMapping[err.constraint_name]; + if (mapping) { + return setError(form, mapping.field, mapping.i18nKey); + } else { + console.warn(`No mapping found for constraint: ${err.constraint_name}`); + } + } + } +}; diff --git a/src/lib/utils/passwordGenerator.ts b/src/lib/utils/passwordGenerator.ts new file mode 100644 index 0000000..a0ff90b --- /dev/null +++ b/src/lib/utils/passwordGenerator.ts @@ -0,0 +1,146 @@ +import { randomBytes } from 'crypto'; + +export interface PasswordGenerationOptions { + length?: number; + includeUppercase?: boolean; + includeLowercase?: boolean; + includeNumbers?: boolean; + includeSpecialChars?: boolean; +} + +/** + * Generates a secure random password based on specified criteria + */ +export function generateSecurePassword(options: PasswordGenerationOptions = {}): string { + const { + length = 12, + includeUppercase = true, + includeLowercase = true, + includeNumbers = true, + includeSpecialChars = true + } = options; + + const uppercase = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; + const lowercase = 'abcdefghijklmnopqrstuvwxyz'; + const numbers = '0123456789'; + const specialChars = '!@#$%^&*()_+-=[]{}|;:,.<>?'; + + let charset = ''; + let password = ''; + + // Build charset based on options + if (includeLowercase) charset += lowercase; + if (includeUppercase) charset += uppercase; + if (includeNumbers) charset += numbers; + if (includeSpecialChars) charset += specialChars; + + if (charset.length === 0) { + throw new Error('At least one character type must be enabled'); + } + + // Generate password + const bytes = randomBytes(length); + for (let i = 0; i < length; i++) { + password += charset[bytes[i] % charset.length]; + } + + // Ensure password meets minimum requirements + const hasLowercase = includeLowercase && /[a-z]/.test(password); + const hasUppercase = includeUppercase && /[A-Z]/.test(password); + const hasNumbers = includeNumbers && /\d/.test(password); + const hasSpecialChars = includeSpecialChars && /[!@#$%^&*()_+\-=[\]{}|;:,.<>?]/.test(password); + + const meetsRequirements = + (!includeLowercase || hasLowercase) && + (!includeUppercase || hasUppercase) && + (!includeNumbers || hasNumbers) && + (!includeSpecialChars || hasSpecialChars); + + if (!meetsRequirements) { + // If requirements not met, recursively generate until they are + return generateSecurePassword(options); + } + + return password; +} + +/** + * Generates multiple passwords for bulk operations + */ +export function generateBulkPasswords( + count: number, + options: PasswordGenerationOptions = {} +): string[] { + const passwords: string[] = []; + for (let i = 0; i < count; i++) { + passwords.push(generateSecurePassword(options)); + } + return passwords; +} + +/** + * Validates password strength based on common security requirements + */ +export function validatePasswordStrength(password: string): { + isValid: boolean; + score: number; + feedback: string[]; +} { + const feedback: string[] = []; + let score = 0; + + // Length check + if (password.length >= 8) { + score += 1; + } else { + feedback.push('Password should be at least 8 characters long'); + } + + if (password.length >= 12) { + score += 1; + } + + // Character variety checks + if (/[a-z]/.test(password)) { + score += 1; + } else { + feedback.push('Include at least one lowercase letter'); + } + + if (/[A-Z]/.test(password)) { + score += 1; + } else { + feedback.push('Include at least one uppercase letter'); + } + + if (/\d/.test(password)) { + score += 1; + } else { + feedback.push('Include at least one number'); + } + + if (/[!@#$%^&*()_+\-=[\]{}|;:,.<>?]/.test(password)) { + score += 1; + } else { + feedback.push('Include at least one special character'); + } + + // Common patterns to avoid + if (/(.)\1{2,}/.test(password)) { + score -= 1; + feedback.push('Avoid repeated characters'); + } + + if (/123|abc|qwe|password/i.test(password)) { + score -= 1; + feedback.push('Avoid common sequences'); + } + + const isValid = score >= 4 && password.length >= 8; + + return { + isValid, + score: Math.max(0, score), + feedback + }; +} diff --git a/src/lib/utils/passwordHash.ts b/src/lib/utils/passwordHash.ts new file mode 100644 index 0000000..dd0647c --- /dev/null +++ b/src/lib/utils/passwordHash.ts @@ -0,0 +1,9 @@ +import argon2 from 'argon2'; + +export async function hashPassword(password: string): Promise { + return argon2.hash(password, { type: argon2.argon2id }); +} + +export async function verifyPassword(hash: string, password: string): Promise { + return argon2.verify(hash, password); +} diff --git a/src/orpc-base.ts b/src/orpc-base.ts new file mode 100644 index 0000000..b8f33bf --- /dev/null +++ b/src/orpc-base.ts @@ -0,0 +1,45 @@ +import { ErrorItemSchema, UnauthorizedApiError, type Device } from '$schemas'; +import { os } from '@orpc/server'; +import type { Cookies } from '@sveltejs/kit'; +import type { ResponseHeadersPluginContext } from '@orpc/server/plugins'; + +export interface ORPCContext extends ResponseHeadersPluginContext { + auth?: globalThis.App.Locals['auth']; + cookies: Cookies; + device?: Device; +} + +export const pub = os.$context().errors({ + UNAUTHORIZED: { + message: 'Authentication required', + data: ErrorItemSchema + }, + INTERNAL_SERVER_ERROR: { + message: 'Internal server error', + data: ErrorItemSchema + } +}); + +export const authed = pub.use(({ context, next, errors }) => { + if (!context.auth) { + const error = UnauthorizedApiError('Authentication required'); + throw errors.UNAUTHORIZED({ data: error }); + } + + return next(); +}); + +export const authedDevice = pub + .errors({ + INVALID_DEVICE_KEY: { + message: 'Invalid device key', + status: 401 + } + }) + .use(({ context, next, errors }) => { + if (!context.device) { + const error = UnauthorizedApiError('Device authentication required'); + throw errors.UNAUTHORIZED({ data: error }); + } + return next(); + }); diff --git a/src/routes/(auth)/login/+page.server.ts b/src/routes/(auth)/login/+page.server.ts new file mode 100644 index 0000000..7389385 --- /dev/null +++ b/src/routes/(auth)/login/+page.server.ts @@ -0,0 +1,94 @@ +import { loginMemberReturnCookie } from '$lib/server/auth/jwtSessionManager'; +import { logger } from '$lib/server/logger'; +import { memberRepository } from '$lib/server/repositories'; +import { getConfig } from '$lib/config'; +import { m } from '$paraglide/messages'; +import { MemberLoginSchema } from '$schemas/loginSchema'; +import { fail, redirect, type Actions, type ServerLoad } from '@sveltejs/kit'; +import { randomUUID } from 'crypto'; +import { superValidate } from 'sveltekit-superforms'; +import { zod4 } from 'sveltekit-superforms/adapters'; + +export const load: ServerLoad = async ({ locals }) => { + console.log(JSON.stringify(locals.auth)); + // If user is already authenticated as member, redirect to self-service portal + if (locals.auth?.member) { + throw redirect(302, '/'); + } + + const form = await superValidate(zod4(MemberLoginSchema)); + const config = getConfig(); + return { form, footerText: config.app.footerText }; +}; + +export const actions: Actions = { + login: async ({ request, cookies }) => { + const form = await superValidate(request, zod4(MemberLoginSchema)); + + if (!form.valid) { + return fail(400, { form }); + } + + try { + // Authenticate member + const member = await memberRepository.checkAuth(form.data.identifier, form.data.password); + if (!member) { + return fail(400, { + form, + message: m.invalid_credentials() + }); + } + + // Check if self-service is enabled + // if (!member.allowSelfService) { + // return fail(403, { + // form, + // message: 'Self-service access is not enabled for this account' + // }); + // } + + // Create JWT token and set cookie + const { token } = await loginMemberReturnCookie({ + sessionId: randomUUID(), + memberId: member.id, + type: 'member' + }); + + // Set the cookie in the response + cookies.set('app_jwt', token, { + path: '/', + httpOnly: false, // More secure for member sessions + secure: false, // Set to true in production with HTTPS + sameSite: 'lax', + maxAge: 3600 // 1 hour + }); + + await logger.action('MEMBER_LOGIN', { + payload: { + memberId: member.id, + memberName: `${member.firstName} ${member.lastName}`, + loginTime: new Date() + } + }); + + throw redirect(302, '/'); + } catch (error) { + console.error('Member login error:', error); + return fail(500, { + form, + message: m.login_failed() + }); + } + }, + + logout: async ({ cookies }) => { + cookies.set('app_jwt', '', { + path: '/', + httpOnly: true, + secure: process.env.NODE_ENV === 'production', + sameSite: 'lax', + maxAge: 0 + }); + throw redirect(302, '/login'); + } +}; diff --git a/src/routes/(auth)/login/+page.svelte b/src/routes/(auth)/login/+page.svelte new file mode 100644 index 0000000..0d4e1f4 --- /dev/null +++ b/src/routes/(auth)/login/+page.svelte @@ -0,0 +1,209 @@ + + +
    + +
    + +
    +
    + +
    +
    + +
    +

    + {m['selfservice.member_portal']()} +

    +

    + {m['selfservice.access_selfservice_portal']()} +

    +
    + + + + + {m['selfservice.welcome_back']()} + + {m['selfservice.sign_in_description']()} + + + +
    + + {#if $message} + + {$message} + + {/if} + + +
    + + + {#if $errors.identifier} +

    {$errors.identifier}

    + {/if} +
    + + +
    + +
    + + +
    + {#if $errors.password} +

    {$errors.password}

    + {/if} +
    + + + + + + +
    + + +
    +

    {m['selfservice.no_selfservice_access']()}

    +

    {m['selfservice.contact_admin']()}

    + + + +
    +
    +
    + + +
    +

    {@html data.footerText}

    +
    +
    +
    diff --git a/src/routes/(auth)/logout/+page.server.ts b/src/routes/(auth)/logout/+page.server.ts new file mode 100644 index 0000000..bff15f7 --- /dev/null +++ b/src/routes/(auth)/logout/+page.server.ts @@ -0,0 +1,13 @@ +import { redirect } from '@sveltejs/kit'; +import type { PageServerLoad } from './$types'; + +export const load: PageServerLoad = async ({ cookies }) => { + cookies.set('app_jwt', '', { + path: '/', + httpOnly: true, + secure: true, + sameSite: 'lax', + maxAge: 0 + }); + throw redirect(302, '/login'); +}; diff --git a/src/routes/(selfservice)/+layout.server.ts b/src/routes/(selfservice)/+layout.server.ts new file mode 100644 index 0000000..c8d926c --- /dev/null +++ b/src/routes/(selfservice)/+layout.server.ts @@ -0,0 +1,45 @@ +import { redirect, type ServerLoad } from '@sveltejs/kit'; +import { memberRepository } from '$lib/server/repositories'; + +export const load: ServerLoad = async ({ locals }) => { + // Check if user is authenticated as member + if (!locals.auth?.member) { + throw redirect(302, '/login'); + } + + // Get member details for the layout + const member = await memberRepository.getById(locals.auth.member.memberId); + + if (!member) { + // Member not found, clear auth and redirect + throw redirect(302, '/login'); + } + + // Check if self-service is still enabled + if (!member.allowSelfService) { + throw redirect(302, '/login?error=self_service_disabled'); + } + + // Return member data for the layout (excluding sensitive fields) + return { + member: { + id: member.id, + firstName: member.firstName, + lastName: member.lastName, + email: member.email, + membershipNumber: member.membershipNumber, + title: member.title, + birthDate: member.birthDate, + occupation: member.occupation, + street: member.street, + houseNumber: member.houseNumber, + postalCode: member.postalCode, + city: member.city, + phoneHome: member.phoneHome, + phoneWork: member.phoneWork, + phoneMobile: member.phoneMobile, + guestAccount: member.guestAccount, + joinedAt: member.joinedAt + } + }; +}; diff --git a/src/routes/(selfservice)/+layout.svelte b/src/routes/(selfservice)/+layout.svelte new file mode 100644 index 0000000..6e568e6 --- /dev/null +++ b/src/routes/(selfservice)/+layout.svelte @@ -0,0 +1,185 @@ + + +
    + +
    +
    + +
    +
    +
    + +
    + + {m['selfservice.member_portal']()} + +
    + +
    + + + + + +
    +
    +
    + {getUserInitials()} +
    +
    +

    + {data.member.firstName} + {data.member.lastName} +

    +

    + {data.member.email || `Member #${data.member.membershipNumber}`} +

    +
    +
    +
    +
    +
    + + +
    + +
    + + + + + +
    +
    + +
    +
    + + + + + +
    + +
    +
    +
    +
    + + +
    +
    + {@render children?.()} +
    +
    +
    +
    + + +{#if sidebarOpen} +
    (sidebarOpen = false)} + aria-hidden="true" + >
    +{/if} + + diff --git a/src/routes/(selfservice)/+page.server.ts b/src/routes/(selfservice)/+page.server.ts new file mode 100644 index 0000000..825ecae --- /dev/null +++ b/src/routes/(selfservice)/+page.server.ts @@ -0,0 +1,12 @@ +import { getConfig } from '$lib/config'; +import type { ServerLoad } from '@sveltejs/kit'; + +export const load: ServerLoad = async ({ parent }) => { + const parentData = await parent(); + const config = getConfig(); + + return { + ...parentData, + supportEmail: config.app.supportEmail + }; +}; diff --git a/src/routes/(selfservice)/+page.svelte b/src/routes/(selfservice)/+page.svelte new file mode 100644 index 0000000..33d2901 --- /dev/null +++ b/src/routes/(selfservice)/+page.svelte @@ -0,0 +1,301 @@ + + + + {m['selfservice.dashboard_title']()} - {m['selfservice.member_portal']()} + + +
    + +
    +

    + {m['selfservice.welcome_back']()}, {member.firstName}! +

    +

    + {m['selfservice.manage_profile_description']()} +

    +
    + + +
    + +
    + + +
    +
    + + + {m['selfservice.member_profile']()} + + {m['selfservice.current_member_info']()} +
    + + {getStatusText(member.guestAccount)} + +
    +
    + + +
    +
    +
    + +
    +

    + {member.firstName} + {member.lastName} +

    +

    + {member.title || m['selfservice.no_title_set']()} +

    +
    +
    + + {#if member.email} +
    + +
    +

    {member.email}

    +

    {m['selfservice.email_address']()}

    +
    +
    + {/if} + + {#if member.membershipNumber} +
    + +
    +

    #{member.membershipNumber}

    +

    {m['selfservice.membership_number']()}

    +
    +
    + {/if} +
    + +
    + {#if member.birthDate} +
    + +
    +

    + {formatDate(member.birthDate)} +

    +

    {m['selfservice.date_of_birth']()}

    +
    +
    + {/if} + + {#if member.joinedAt} +
    + +
    +

    + {formatDate(member.joinedAt)} +

    +

    {m['selfservice.member_since']()}

    +
    +
    + {/if} + + {#if member.occupation} +
    + +
    +

    {member.occupation}

    +

    {m['selfservice.occupation']()}

    +
    +
    + {/if} +
    +
    + + + {#if member.street || member.city || member.phoneHome || member.phoneMobile} +
    +

    + {m['selfservice.contact_information']()} +

    +
    + {#if member.street || member.city} +
    + +
    +

    + {#if member.street} + {member.street} + {#if member.houseNumber}{member.houseNumber}{/if} +
    + {/if} + {#if member.postalCode || member.city} + {member.postalCode || ''} {member.city || ''} + {/if} +

    +

    {m['selfservice.address']()}

    +
    +
    + {/if} + +
    + {#if member.phoneHome} +
    + +
    +

    {member.phoneHome}

    +

    {m['selfservice.home_phone']()}

    +
    +
    + {/if} + {#if member.phoneMobile} +
    + +
    +

    {member.phoneMobile}

    +

    {m['selfservice.mobile_phone']()}

    +
    +
    + {/if} +
    +
    +
    + {/if} + + +
    + + +
    +
    +
    +
    + + +
    + + + + + + {m['selfservice.account_status']()} + {m['selfservice.your_account_info']()} + + +
    + {m['selfservice.account_type']()} + + {getStatusText(member.guestAccount)} + +
    + +
    + {m['selfservice.self_service_access']()} +
    + + {m['selfservice.enabled']()} +
    +
    + + {#if member.joinedAt} +
    + {m['selfservice.member_since']()} + + {formatDate(member.joinedAt)} + +
    + {/if} +
    +
    + + + + + {m['selfservice.need_help']()} + {m['selfservice.contact_support_assistance']()} + + +

    + {m['selfservice.need_assistance_text']()} +

    + +
    +
    +
    +
    +
    diff --git a/src/routes/(selfservice)/profile/+page.server.ts b/src/routes/(selfservice)/profile/+page.server.ts new file mode 100644 index 0000000..1d6a0ec --- /dev/null +++ b/src/routes/(selfservice)/profile/+page.server.ts @@ -0,0 +1,108 @@ +import { memberRepository } from '$lib/server/repositories'; +import { MemberSelfServiceUpdateSchema } from '$schemas/memberSelfServiceSchema'; +import { fail, type Actions, type ServerLoad } from '@sveltejs/kit'; +import { message, superValidate } from 'sveltekit-superforms'; +import { zod4 } from 'sveltekit-superforms/adapters'; +import { logger } from '$lib/server/logger'; + +export const load: ServerLoad = async ({ locals }) => { + // Get current member data (already verified in layout) + const member = await memberRepository.getById(locals.auth!.member!.memberId); + + if (!member) { + throw new Error('Member not found'); + } + + // Create form with current member data (only editable fields) + const form = await superValidate( + { + firstName: member.firstName, + lastName: member.lastName, + email: member.email, + phoneHome: member.phoneHome, + phoneWork: member.phoneWork, + phoneMobile: member.phoneMobile, + street: member.street, + houseNumber: member.houseNumber, + postalCode: member.postalCode, + city: member.city + }, + zod4(MemberSelfServiceUpdateSchema) + ); + + return { + form, + member: { + id: member.id, + firstName: member.firstName, + lastName: member.lastName, + email: member.email, + membershipNumber: member.membershipNumber, + title: member.title, + birthDate: member.birthDate, + occupation: member.occupation, + street: member.street, + houseNumber: member.houseNumber, + postalCode: member.postalCode, + city: member.city, + phoneHome: member.phoneHome, + phoneWork: member.phoneWork, + phoneMobile: member.phoneMobile, + guestAccount: member.guestAccount, + joinedAt: member.joinedAt + } + }; +}; + +export const actions: Actions = { + updateProfile: async ({ request, locals }) => { + const memberId = locals.auth!.member!.memberId; + const form = await superValidate(request, zod4(MemberSelfServiceUpdateSchema)); + + if (!form.valid) { + return fail(400, { form }); + } + + try { + // Get current member for comparison + const currentMember = await memberRepository.getById(memberId); + if (!currentMember) { + return fail(404, { + form, + message: 'Member not found' + }); + } + + // Update member with only the changed fields + const updatedMember = await memberRepository.update(memberId, form.data); + + if (!updatedMember) { + return fail(500, { + form, + message: 'Failed to update profile' + }); + } + + // Log the profile update for audit purposes + await logger.action('MEMBER_PROFILE_UPDATE', { + userId: memberId, + payload: { + memberId: memberId, + memberName: `${updatedMember.firstName} ${updatedMember.lastName}`, + changes: form.data, + updatedFields: Object.keys(form.data).filter( + (key) => form.data[key as keyof typeof form.data] !== undefined + ) + } + }); + + return message(form, 'Profile updated successfully'); + } catch (error) { + console.error('Profile update error:', error); + return fail(500, { + form, + message: 'An error occurred while updating your profile' + }); + } + } +}; diff --git a/src/routes/(selfservice)/profile/+page.svelte b/src/routes/(selfservice)/profile/+page.svelte new file mode 100644 index 0000000..2930e0f --- /dev/null +++ b/src/routes/(selfservice)/profile/+page.svelte @@ -0,0 +1,452 @@ + + + + {m['selfservice.profile_title']()} - {m['selfservice.member_portal']()} + + +
    + +
    +

    + {m['selfservice.profile_title']()} +

    +

    + {m['selfservice.profile_description']()} +

    +
    + + + {#if $message} +
    + + + Profile Update + {$message} + +
    + {/if} + +
    + +
    + + + + + {m['selfservice.account_information']()} + + {m['selfservice.account_info_description']()} + + + +
    + {m['selfservice.status']()} + + {getStatusText(data.member.guestAccount)} + +
    + + + {#if data.member.membershipNumber} +
    + +
    +

    + #{data.member.membershipNumber} +

    +

    {m['selfservice.membership_number']()}

    +
    +
    + {/if} + + + {#if data.member.birthDate} +
    + +
    +

    + {formatDate(data.member.birthDate)} +

    +

    {m['selfservice.date_of_birth']()}

    +
    +
    + {/if} + + + {#if data.member.title} +
    + +
    +

    + {data.member.title} +

    +

    {m['selfservice.title']()}

    +
    +
    + {/if} + + + {#if data.member.occupation} +
    + +
    +

    + {data.member.occupation} +

    +

    {m['selfservice.occupation']()}

    +
    +
    + {/if} + + + {#if data.member.joinedAt} +
    + +
    +

    + {formatDate(data.member.joinedAt)} +

    +

    {m['selfservice.member_since']()}

    +
    +
    + {/if} + +
    +

    + + {m['selfservice.admin_only_fields']()} +

    +
    +
    +
    +
    + + +
    + + + + + {m['selfservice.personal_information']()} + + {m['selfservice.personal_info_description']()} + + +
    + +
    +

    + {m['selfservice.personal_details']()} +

    + +
    + +
    + + + {#if $errors.firstName} +

    {$errors.firstName}

    + {/if} +
    + + +
    + + + {#if $errors.lastName} +

    {$errors.lastName}

    + {/if} +
    +
    +
    + + + + +
    +

    + {m['selfservice.contact_information']()} +

    + + +
    + + + {#if $errors.email} +

    {$errors.email}

    + {/if} +
    + + +
    +
    + + + {#if $errors.phoneHome} +

    {$errors.phoneHome}

    + {/if} +
    + +
    + + + {#if $errors.phoneWork} +

    {$errors.phoneWork}

    + {/if} +
    + +
    + + + {#if $errors.phoneMobile} +

    {$errors.phoneMobile}

    + {/if} +
    +
    +
    + + + + +
    +

    + + {m['selfservice.address']()} +

    + +
    + +
    + + + {#if $errors.street} +

    {$errors.street}

    + {/if} +
    + + +
    + + + {#if $errors.houseNumber} +

    {$errors.houseNumber}

    + {/if} +
    +
    + +
    + +
    + + + {#if $errors.postalCode} +

    {$errors.postalCode}

    + {/if} +
    + + +
    + + + {#if $errors.city} +

    {$errors.city}

    + {/if} +
    +
    +
    + + +
    + + +
    + +
    +
    +
    +
    +
    diff --git a/src/routes/(selfservice)/security/+page.server.ts b/src/routes/(selfservice)/security/+page.server.ts new file mode 100644 index 0000000..c2adcba --- /dev/null +++ b/src/routes/(selfservice)/security/+page.server.ts @@ -0,0 +1,58 @@ +import { memberRepository } from '$lib/server/repositories'; +import { MemberChangePasswordSchema } from '$schemas'; +import { fail } from '@sveltejs/kit'; +import { message, superValidate } from 'sveltekit-superforms'; +import { zod4 } from 'sveltekit-superforms/adapters'; +import type { Actions, PageServerLoad } from './$types'; + +export const load: PageServerLoad = async () => { + const form = await superValidate(zod4(MemberChangePasswordSchema)); + + return { + form + }; +}; + +export const actions: Actions = { + changePassword: async ({ request, locals }) => { + const form = await superValidate(request, zod4(MemberChangePasswordSchema)); + + if (!form.valid) { + return fail(400, { + form + }); + } + + const member = locals.auth?.member; + if (!member) { + return fail(401, { + form, + message: 'Unauthorized' + }); + } + + const { currentPassword, newPassword } = form.data; + + // Verify current password + const authenticatedMember = await memberRepository.checkAuth(member.memberId, currentPassword); + + if (!authenticatedMember || authenticatedMember.id !== member.memberId) { + return message(form, 'Current password is incorrect', { status: 400 }); + } + + // Update password + const { hashPassword } = await import('$lib/utils/passwordHash'); + const passwordHash = await hashPassword(newPassword); + + const success = await memberRepository.updatePassword(member.memberId, passwordHash); + + if (!success) { + return fail(500, { + form, + message: 'Failed to update password' + }); + } + + return message(form, 'Password updated successfully'); + } +}; diff --git a/src/routes/(selfservice)/security/+page.svelte b/src/routes/(selfservice)/security/+page.svelte new file mode 100644 index 0000000..feff42d --- /dev/null +++ b/src/routes/(selfservice)/security/+page.svelte @@ -0,0 +1,142 @@ + + +
    + +
    +

    + {m['selfservice.security_settings']()} +

    +

    + {m['selfservice.manage_security_password']()} +

    +
    + + + + {m['selfservice.change_password']()} + {m['selfservice.update_password_secure']()} + + +
    +
    + +
    + + +
    +
    + +
    + +
    + + +
    +
    + +
    + +
    + + +
    +
    + + +
    +
    +
    +
    diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte new file mode 100644 index 0000000..ab6eec3 --- /dev/null +++ b/src/routes/+layout.svelte @@ -0,0 +1,21 @@ + + + + + + + {@render children?.()} + diff --git a/src/routes/admin/(auth)/login/+layout.server.ts b/src/routes/admin/(auth)/login/+layout.server.ts new file mode 100644 index 0000000..34b68a5 --- /dev/null +++ b/src/routes/admin/(auth)/login/+layout.server.ts @@ -0,0 +1,11 @@ +import { managementUserRepository } from '$lib/server/repositories'; +import { redirect, type ServerLoad } from '@sveltejs/kit'; + +export const load: ServerLoad = async ({ locals }) => { + // Check if user is authenticated + if (locals.auth?.admin) { + // check if user id exists + const user = await managementUserRepository.getById(locals.auth.admin.userId); + if (user) throw redirect(302, '/admin'); + } +}; diff --git a/src/routes/admin/(auth)/login/+page.server.ts b/src/routes/admin/(auth)/login/+page.server.ts new file mode 100644 index 0000000..a9b5df6 --- /dev/null +++ b/src/routes/admin/(auth)/login/+page.server.ts @@ -0,0 +1,84 @@ +import { loginAdminReturnCookie } from '$lib/server/auth/jwtSessionManager'; +import { managementUserRepository } from '$lib/server/repositories'; +import { getConfig } from '$lib/config'; +import { m } from '$paraglide/messages'; +import { ManagementUserLoginSchema } from '$schemas/loginSchema'; +import { fail, redirect, type Actions, type ServerLoad } from '@sveltejs/kit'; +import { randomUUID } from 'crypto'; +import { superValidate } from 'sveltekit-superforms'; +import { zod4 } from 'sveltekit-superforms/adapters'; + +export const load: ServerLoad = async ({ locals }) => { + // If user is already authenticated, redirect to dashboard + if (locals.auth?.admin) { + throw redirect(302, '/admin'); + } + + const form = await superValidate(zod4(ManagementUserLoginSchema)); + const config = getConfig(); + return { form, footerText: config.app.footerText }; +}; + +export const actions: Actions = { + login: async ({ request, cookies }) => { + const form = await superValidate(request, zod4(ManagementUserLoginSchema)); + + if (!form.valid) { + return fail(400, { form }); + } + + try { + // Authenticate user + const user = await managementUserRepository.checkAuth(form.data.username, form.data.password); + if (!user) { + return fail(400, { + form, + message: m.invalid_credentials() + }); + } + + // Check if user is enabled + if (!user.enabled) { + return fail(400, { + form, + message: m.account_disabled() + }); + } + + // Create JWT token and set cookie + const { token } = await loginAdminReturnCookie({ + sessionId: randomUUID(), + userId: user.id, + roleId: 1, // TODO: Get actual role from user + type: 'admin' + }); + + // Set the cookie in the response + cookies.set('admin_jwt', token, { + path: '/', + httpOnly: false, // Allow client-side access for admin interface + secure: false, // Set to true in production with HTTPS + sameSite: 'lax', + maxAge: 3600 // 1 hour + }); + + throw redirect(302, '/admin'); + } catch (error) { + console.error('Login error:', error); + return fail(500, { + form, + message: m.login_failed() + }); + } + }, + logout: async ({ cookies }) => { + cookies.set('admin_jwt', '', { + path: '/', + httpOnly: true, + secure: true, + sameSite: 'lax', + maxAge: 0 + }); + throw redirect(302, '/admin/login'); + } +}; diff --git a/src/routes/admin/(auth)/login/+page.svelte b/src/routes/admin/(auth)/login/+page.svelte new file mode 100644 index 0000000..c7a5d3c --- /dev/null +++ b/src/routes/admin/(auth)/login/+page.svelte @@ -0,0 +1,163 @@ + + +
    +
    + +
    +
    + +
    +

    + {m.admin_login_title()} +

    +

    + {m.admin_login_subtitle()} +

    +
    + + + + + {m.sign_in()} + + {m.enter_credentials()} + + + +
    + + {#if $message} + + {$message} + + {/if} + + +
    + + + {#if $errors.username} +

    {$errors.username}

    + {/if} +
    + + +
    + +
    + + +
    + {#if $errors.password} +

    {$errors.password}

    + {/if} +
    + + + +
    + + +
    +

    {m.secure_admin_access()}

    +
    +
    +
    + + +
    +

    {@html data.footerText}

    +
    +
    +
    + + diff --git a/src/routes/admin/(auth)/logout/+page.server.ts b/src/routes/admin/(auth)/logout/+page.server.ts new file mode 100644 index 0000000..a60f230 --- /dev/null +++ b/src/routes/admin/(auth)/logout/+page.server.ts @@ -0,0 +1,13 @@ +import { redirect } from '@sveltejs/kit'; +import type { PageServerLoad } from './$types'; + +export const load: PageServerLoad = async ({ cookies }) => { + cookies.set('admin_jwt', '', { + path: '/', + httpOnly: true, + secure: true, + sameSite: 'lax', + maxAge: 0 + }); + throw redirect(302, '/admin/login'); +}; diff --git a/src/routes/admin/(authorized)/+layout.server.ts b/src/routes/admin/(authorized)/+layout.server.ts new file mode 100644 index 0000000..ffa94ed --- /dev/null +++ b/src/routes/admin/(authorized)/+layout.server.ts @@ -0,0 +1,20 @@ +import { redirect, type ServerLoad } from '@sveltejs/kit'; +import { managementUserRepository } from '$lib/server/repositories'; + +export const load: ServerLoad = async ({ locals }) => { + // Check if user is authenticated + + if (!locals.auth?.admin) { + throw redirect(302, '/admin/login'); + } + + // Fetch full user details + const user = await managementUserRepository.getById(locals.auth.admin.userId); + if (!user) { + throw redirect(302, '/admin/login'); + } + + return { + user + }; +}; diff --git a/src/routes/admin/(authorized)/+layout.svelte b/src/routes/admin/(authorized)/+layout.svelte new file mode 100644 index 0000000..f9f7a4e --- /dev/null +++ b/src/routes/admin/(authorized)/+layout.svelte @@ -0,0 +1,69 @@ + + + + + +
    +
    + + + + + {#each breadcrumbs as breadcrumb, index} + + {#if breadcrumb.isLast} + {breadcrumb.name} + {:else} + {breadcrumb.name} + {/if} + + {#if !breadcrumb.isLast} + + {/if} + {/each} + + +
    +
    + + +
    +
    +
    + {@render children?.()} +
    +
    +
    + + diff --git a/src/routes/admin/(authorized)/+page.server.ts b/src/routes/admin/(authorized)/+page.server.ts new file mode 100644 index 0000000..606b550 --- /dev/null +++ b/src/routes/admin/(authorized)/+page.server.ts @@ -0,0 +1,76 @@ +import { + deviceRepository, + memberRepository, + rfidCardRepository, + managementUserRepository, + accessLogRepository, + systemLogRepository +} from '$lib/server/repositories'; +import type { PageServerLoad } from './$types'; + +export const load: PageServerLoad = async () => { + // Load counts for stats + const [ + totalDevices, + rfidScanners, + lockSystems, + totalMembers, + guestAccounts, + regularMembers, + totalRfidCards, + activeRfidCards, + inactiveRfidCards, + totalUsers, + enabledUsers + ] = await Promise.all([ + deviceRepository.count(), + deviceRepository.countByType('RFID_SCANNER'), + deviceRepository.countByType('LOCK_SYSTEM'), + memberRepository.count(), + memberRepository.countByGuestAccount(true), + memberRepository.countByGuestAccount(false), + rfidCardRepository.count(), + rfidCardRepository.countByStatus('ENGRAVED'), + rfidCardRepository.countByStatus('NEW'), + managementUserRepository.count(), + managementUserRepository.countByEnabled(true) + ]); + + // Load recent access logs (last 10) + const { accessLogs } = await accessLogRepository.list({ + limit: 10, + cursor: 0, + orderBy: 'createdAt', + sortOrder: 'desc' + }); + + // Load recent system logs (last 10) + const { logs: systemLogs } = await systemLogRepository.list({ + limit: 10, + cursor: 0, + orderBy: 'createdAt', + sortOrder: 'desc' + }); + + // Load access counts by day for the last 30 days + const accessCounts = await accessLogRepository.getAccessCountsByDayLast30Days(); + + return { + stats: { + totalDevices, + rfidScanners, + lockSystems, + totalMembers, + guestAccounts, + regularMembers, + totalRfidCards, + activeRfidCards, + inactiveRfidCards, + totalUsers, + enabledUsers + }, + accessLogs, + systemLogs, + accessCounts + }; +}; diff --git a/src/routes/admin/(authorized)/+page.svelte b/src/routes/admin/(authorized)/+page.svelte new file mode 100644 index 0000000..8249761 --- /dev/null +++ b/src/routes/admin/(authorized)/+page.svelte @@ -0,0 +1,327 @@ + + +
    + +
    +
    +

    {m.dashboard_title()}

    +

    + {m.dashboard_welcome()} +

    +
    +
    + + +
    + {#each stats as stat} + + + {stat.title} + + + +
    {stat.value}
    +
    + {#if stat.trend === 'up'} + + {:else} + + {/if} + + {stat.change} + + {stat.description} +
    +
    +
    + {/each} +
    + + +
    + + + + {m.analytics_overview()} + {m.monthly_performance_metrics()} + + + + { + return d.toLocaleDateString('de-DE', { + month: 'short', + day: '2-digit' + }); + }, + ticks: (scale) => scaleUtc(scale.domain(), scale.range()).ticks() + } + }} + > + {#snippet belowMarks()} + + {/snippet} + {#snippet tooltip()} + { + return v.toLocaleDateString('de-DE', { + month: 'short', + day: 'numeric', + year: 'numeric' + }); + }} + /> + {/snippet} + + + +
    +
    +
    + Zugriffe in den letzten 30 Tagen +
    +
    + Tägliche Zugriffsstatistiken +
    +
    +
    +
    + + + + + {m.recent_activity()} + {m.latest_user_actions()} + + +
    + {#each recentActivity as activity} +
    +
    +
    +

    + {activity.user} +

    +

    + {activity.action} +

    +
    +
    + {activity.time} +
    +
    + {/each} +
    +
    + +
    +
    +
    +
    + + + + + {m.quick_actions()} + {m.frequently_used_tasks()} + + +
    + + + + + + +
    +
    +
    +
    diff --git a/src/routes/admin/(authorized)/access-logs/+page.server.ts b/src/routes/admin/(authorized)/access-logs/+page.server.ts new file mode 100644 index 0000000..6754fd0 --- /dev/null +++ b/src/routes/admin/(authorized)/access-logs/+page.server.ts @@ -0,0 +1,19 @@ +import { accessLogRepository } from '$lib/server/repositories/accessLogRepository'; +import { cleanQueryObject } from '$lib/utils/clean'; +import { AccessLogsListQuerySchema } from '$schemas/accessLogSchema'; +import type { PageServerLoad } from './$types'; + +export const load: PageServerLoad = async ({ url }) => { + // Parse URL search params for filters + const searchParams = cleanQueryObject(Object.fromEntries(url.searchParams)); + + const filters = AccessLogsListQuerySchema.parse(searchParams); + + const { accessLogs, pagination } = await accessLogRepository.list(filters); + + return { + accessLogs, + filters, + pagination + }; +}; diff --git a/src/routes/admin/(authorized)/access-logs/+page.svelte b/src/routes/admin/(authorized)/access-logs/+page.svelte new file mode 100644 index 0000000..266fe70 --- /dev/null +++ b/src/routes/admin/(authorized)/access-logs/+page.svelte @@ -0,0 +1,173 @@ + + +
    + +
    +
    +

    {m.access_logs_title()}

    +

    {m.access_logs_description()}

    +
    +
    + +
    +
    + + + + + + {#if data.pagination} + + {#snippet children({ pages, currentPage })} + + + + + {#each pages as page (page.key)} + {#if page.type === 'ellipsis'} + + + + {:else} + + + {page.value} + + + {/if} + {/each} + + + + + {/snippet} + + {/if} + + + (showFilters = false)} + /> +
    diff --git a/src/routes/admin/(authorized)/access-logs/components/AccessLogFilters.svelte b/src/routes/admin/(authorized)/access-logs/components/AccessLogFilters.svelte new file mode 100644 index 0000000..8639dac --- /dev/null +++ b/src/routes/admin/(authorized)/access-logs/components/AccessLogFilters.svelte @@ -0,0 +1,156 @@ + + + + + + {m.advanced_filters_title()} + {m.advanced_filters_access_logs_description()} + + +
    + +
    + +
    +
    + + +
    +
    + + +
    +
    +
    + + +
    + +
    + + + {localFilters.orderBy === 'accessedAt' ? m.accessed_at_column() : m.created_column()} + + + {m.accessed_at_column()} + {m.created_column()} + + + + + + {localFilters.sortOrder === 'asc' ? m.ascending() : m.descending()} + + + {m.ascending()} + {m.descending()} + + +
    +
    +
    + + +
    + + +
    +
    +
    +
    diff --git a/src/routes/admin/(authorized)/access-logs/components/AccessLogTable.svelte b/src/routes/admin/(authorized)/access-logs/components/AccessLogTable.svelte new file mode 100644 index 0000000..7b848d0 --- /dev/null +++ b/src/routes/admin/(authorized)/access-logs/components/AccessLogTable.svelte @@ -0,0 +1,129 @@ + + + + +
    +
    + {m.access_logs_title()} + {m.access_logs_description()} +
    +
    +
    + + +
    +
    +
    +
    + + + + + {m.member_column()} + {m.device_column()} + {m.accessed_at_column()} + + + + {#each accessLogs as accessLog (accessLog.id)} + + + {#if accessLog.member} + + {:else} + Unbekannt + {/if} + + + {#if accessLog.device} + + {:else} + - + {/if} + + + + + + {:else} + + + {m.no_access_logs_found()} + + + {/each} + +
    +
    +
    diff --git a/src/routes/admin/(authorized)/access-logs/components/index.ts b/src/routes/admin/(authorized)/access-logs/components/index.ts new file mode 100644 index 0000000..d3c8d38 --- /dev/null +++ b/src/routes/admin/(authorized)/access-logs/components/index.ts @@ -0,0 +1,2 @@ +export { default as AccessLogTable } from './AccessLogTable.svelte'; +export { default as AccessLogFilters } from './AccessLogFilters.svelte'; diff --git a/src/routes/admin/(authorized)/devices/+page.server.ts b/src/routes/admin/(authorized)/devices/+page.server.ts new file mode 100644 index 0000000..3b3449d --- /dev/null +++ b/src/routes/admin/(authorized)/devices/+page.server.ts @@ -0,0 +1,173 @@ +import { deviceRepository } from '$lib/server/repositories'; +import { logger } from '$lib/server/logger'; +import { DeviceInsertSchema, DevicesListQuerySchema, DeviceUpdateSchema } from '$schemas'; +import { fail } from '@sveltejs/kit'; +import { message, superValidate } from 'sveltekit-superforms'; +import { zod4 } from 'sveltekit-superforms/adapters'; +import { m } from '$paraglide/messages'; +import type { Actions, PageServerLoad } from './$types'; +import { cleanQueryObject } from '$lib/utils/clean'; + +export const load: PageServerLoad = async ({ url }) => { + // Parse URL search params for filters + const searchParams = cleanQueryObject(Object.fromEntries(url.searchParams)); + + const filters = DevicesListQuerySchema.parse(searchParams); + + const { devices, pagination } = await deviceRepository.list(filters); + + const formInsert = await superValidate(zod4(DeviceInsertSchema)); + const formUpdate = await superValidate(zod4(DeviceUpdateSchema)); + + return { + devices, + formInsert, + formUpdate, + filters, + pagination + }; +}; + +export const actions: Actions = { + create: async ({ request, locals }) => { + const form = await superValidate(request, zod4(DeviceInsertSchema)); + + if (!form.valid) { + return fail(400, { + form, + message: m.form_check_errors() + }); + } + + try { + const device = await deviceRepository.create(form.data); + + // Log device creation + await logger.action('CREATE_DEVICE_PAGE', { + userId: locals.auth?.admin?.userId, + payload: { + deviceId: device.id, + deviceName: device.name, + deviceType: device.type + } + }); + + return message(form, m.device_created_success()); + } catch (err) { + await logger.error('DEVICE_CREATE_FAILED', { + userId: locals.auth?.admin?.userId, + payload: { + error: err instanceof Error ? err.message : 'Unknown error', + deviceData: form.data + } + }); + return message(form, m.device_create_failed(), { status: 500 }); + } + }, + + update: async ({ request, locals }) => { + const form = await superValidate(request, zod4(DeviceUpdateSchema)); + + if (!form.valid) { + return fail(400, { + form, + message: m.form_check_errors() + }); + } + + const id = form.data.id; + if (!id) { + return fail(400, { + form, + message: m.device_id_required() + }); + } + + try { + const existingDevice = await deviceRepository.getById(id); + if (!existingDevice) { + return message(form, m.device_not_found(), { status: 404 }); + } + + const updatedDevice = await deviceRepository.update(id, form.data); + + // Log device update + await logger.action('UPDATE_DEVICE_PAGE', { + userId: locals.auth?.admin?.userId, + payload: { + deviceId: id, + deviceName: updatedDevice?.name || existingDevice.name, + changes: form.data + } + }); + + return message(form, m.device_updated_success()); + } catch (err) { + await logger.error('DEVICE_UPDATE_FAILED', { + userId: locals.auth?.admin?.userId, + payload: { + error: err instanceof Error ? err.message : 'Unknown error', + deviceId: id, + updateData: form.data + } + }); + return message(form, m.device_update_failed(), { status: 500 }); + } + }, + + delete: async ({ request, locals }) => { + const formData = await request.formData(); + const idRaw = formData.get('id'); + + if (!idRaw) { + return fail(400, { + error: m.device_id_required() + }); + } + + const id = idRaw.toString(); + + try { + // Check if device exists + const existingDevice = await deviceRepository.getById(id); + if (!existingDevice) { + return fail(404, { + error: m.device_not_found() + }); + } + + const success = await deviceRepository.delete(id); + if (!success) { + return fail(500, { + error: m.delete_device_failed() + }); + } + + // Log device deletion + await logger.action('DELETE_DEVICE_PAGE', { + userId: locals.auth?.admin?.userId, + payload: { + deviceId: id, + deviceName: existingDevice.name, + deviceType: existingDevice.type + } + }); + + return { + success: true, + message: m.device_deleted_success() + }; + } catch (err) { + await logger.error('DEVICE_DELETE_FAILED', { + userId: locals.auth?.admin?.userId, + payload: { + error: err instanceof Error ? err.message : 'Unknown error', + deviceId: id + } + }); + return fail(500, { + error: m.device_delete_failed_retry() + }); + } + } +}; diff --git a/src/routes/admin/(authorized)/devices/+page.svelte b/src/routes/admin/(authorized)/devices/+page.svelte new file mode 100644 index 0000000..77bb35b --- /dev/null +++ b/src/routes/admin/(authorized)/devices/+page.svelte @@ -0,0 +1,313 @@ + + +
    + +
    +
    +

    {m.devices_title()}

    +

    {m.devices_description()}

    +
    +
    + + + +
    +
    + + + + + + + + + {#if data.pagination} + + {#snippet children({ pages, currentPage })} + + + + + {#each pages as page (page.key)} + {#if page.type === 'ellipsis'} + + + + {:else} + + + {page.value} + + + {/if} + {/each} + + + + + {/snippet} + + {/if} + + + (showFilters = false)} + /> + + + + + + +
    diff --git a/src/routes/admin/(authorized)/devices/components/DeviceDeleteDialog.svelte b/src/routes/admin/(authorized)/devices/components/DeviceDeleteDialog.svelte new file mode 100644 index 0000000..8110215 --- /dev/null +++ b/src/routes/admin/(authorized)/devices/components/DeviceDeleteDialog.svelte @@ -0,0 +1,46 @@ + + + + + + {m.confirm_delete()} + + {device + ? m.confirm_delete_device_description({ name: device.name }) + : m.confirm_delete_generic()} + + + + +
    + + +
    +
    +
    +
    diff --git a/src/routes/admin/(authorized)/devices/components/DeviceDetails.svelte b/src/routes/admin/(authorized)/devices/components/DeviceDetails.svelte new file mode 100644 index 0000000..e5ce2a7 --- /dev/null +++ b/src/routes/admin/(authorized)/devices/components/DeviceDetails.svelte @@ -0,0 +1,129 @@ + + + + + + {m.device_details_title()} + {m.device_information()} + + +
    + {#if device} +
    +
    + {m.name_label()} +

    {device.name}

    +
    +
    + {m.type_label()} +

    + {device.type === 'RFID_SCANNER' ? m.rfid_scanner() : m.lock_system()} +

    +
    +
    + {m.last_seen_label()} +

    + {#if device.lastSeenAt} + {device.lastSeenAt.toLocaleString()} + {:else} + Never + {/if} +

    +
    +
    + {m.api_key_column()} +
    +

    {device.apiKey}

    + +
    +
    +
    + {m.created_label()} +

    + {device.createdAt.toLocaleDateString()} +

    +
    +
    + {m.updated_label()} +

    + {device.updatedAt.toLocaleDateString()} +

    +
    +
    + {m.user_id_label()} +

    {device.id}

    +
    +
    + {:else} +

    {m.no_device_selected()}

    + {/if} +
    + + +
    + +
    + + +
    +
    +
    +
    +
    diff --git a/src/routes/admin/(authorized)/devices/components/DeviceFilters.svelte b/src/routes/admin/(authorized)/devices/components/DeviceFilters.svelte new file mode 100644 index 0000000..6f8bde2 --- /dev/null +++ b/src/routes/admin/(authorized)/devices/components/DeviceFilters.svelte @@ -0,0 +1,80 @@ + + + + + + {m.advanced_filters_title()} + {m.advanced_filters_device_description()} + + +
    +
    + + + + {typeOptions.find((t) => t.value === localType)?.label ?? m.all_types()} + + + {#each typeOptions as option} + {option.label} + {/each} + + +
    +
    + + +
    + + +
    +
    +
    +
    diff --git a/src/routes/admin/(authorized)/devices/components/DeviceForm.svelte b/src/routes/admin/(authorized)/devices/components/DeviceForm.svelte new file mode 100644 index 0000000..3af938a --- /dev/null +++ b/src/routes/admin/(authorized)/devices/components/DeviceForm.svelte @@ -0,0 +1,117 @@ + + + + +
    + + {mode === 'create' ? m.add_device_title() : m.edit_device_title()} + + {mode === 'create' ? m.create_device_description() : m.modify_device_description()} + + + +
    + {#if mode === 'edit'} + + {/if} + + + + {#snippet children({ props })} + {m.device_name_label()} + + {/snippet} + + + + + + + {#snippet children({ props })} + {m.device_type_label()} + + + {typeOptions.find((t) => t.value === $formData.type)?.label ?? m.select_label()} + + + {#each typeOptions as option} + {option.label} + {/each} + + + {/snippet} + + + + + {#if mode === 'create'} + + + {#snippet children({ props })} + {m.api_key_label()} + + {/snippet} + + {m.api_key_auto_generation_description()} + + + {/if} +
    + + +
    + + + {mode === 'create' ? m.create_button() : m.save_button()} + +
    +
    +
    +
    +
    diff --git a/src/routes/admin/(authorized)/devices/components/DeviceStats.svelte b/src/routes/admin/(authorized)/devices/components/DeviceStats.svelte new file mode 100644 index 0000000..bf74c1a --- /dev/null +++ b/src/routes/admin/(authorized)/devices/components/DeviceStats.svelte @@ -0,0 +1,43 @@ + + +
    + + + {m.total_devices_stat()} + + +
    {totalDevices}
    +

    {m.all_registered_devices()}

    +
    +
    + + + + {m.rfid_scanners_stat()} + + +
    {rfidScanners}
    +

    {m.rfid_scanner_devices()}

    +
    +
    + + + + {m.lock_systems_stat()} + + +
    {lockSystems}
    +

    {m.lock_system_devices()}

    +
    +
    +
    diff --git a/src/routes/admin/(authorized)/devices/components/DeviceTable.svelte b/src/routes/admin/(authorized)/devices/components/DeviceTable.svelte new file mode 100644 index 0000000..446db34 --- /dev/null +++ b/src/routes/admin/(authorized)/devices/components/DeviceTable.svelte @@ -0,0 +1,196 @@ + + + + +
    +
    + {m.device_management_title()} + {m.device_management_description()} +
    +
    +
    + + +
    +
    +
    +
    + + + + + {m.device_column()} + {m.type_column()} + {m.api_key_column()} + {m.last_seen_column()} + {m.created_column()} + {m.actions_column()} + + + + {#each devices as device (device.id)} + + + + + + + {device.type === 'RFID_SCANNER' ? m.rfid_scanner() : m.lock_system()} + + + + + + + {#if device.lastSeenAt} + + {:else} + Never + {/if} + + + + + + + + + + + {m.actions_column()} + + + + + + + + + + + + + + + {:else} + + + {m.no_devices_found()} + + + {/each} + +
    +
    +
    diff --git a/src/routes/admin/(authorized)/devices/components/index.ts b/src/routes/admin/(authorized)/devices/components/index.ts new file mode 100644 index 0000000..0bdcecd --- /dev/null +++ b/src/routes/admin/(authorized)/devices/components/index.ts @@ -0,0 +1,6 @@ +export { default as DeviceStats } from './DeviceStats.svelte'; +export { default as DeviceForm } from './DeviceForm.svelte'; +export { default as DeviceDetails } from './DeviceDetails.svelte'; +export { default as DeviceTable } from './DeviceTable.svelte'; +export { default as DeviceFilters } from './DeviceFilters.svelte'; +export { default as DeviceDeleteDialog } from './DeviceDeleteDialog.svelte'; diff --git a/src/routes/admin/(authorized)/import/+page.server.ts b/src/routes/admin/(authorized)/import/+page.server.ts new file mode 100644 index 0000000..6289834 --- /dev/null +++ b/src/routes/admin/(authorized)/import/+page.server.ts @@ -0,0 +1,73 @@ +import { importSessionStore } from '$lib/server/importSessionStore'; +import { fail } from '@sveltejs/kit'; +import type { Actions, PageServerLoad } from './$types'; +import type { ImportUploadResponse } from './+page.svelte'; +import { logger } from '$lib/server/logger'; + +export const load: PageServerLoad = async ({ url }) => { + const sessions = importSessionStore.list().map(({ key, session }) => ({ + key, + step: session.step, + createdAt: session.createdAt, + updatedAt: session.updatedAt, + rowCount: session.raw.length, + errorCount: session.errors.length + })); + + const sessionKey = url.searchParams.get('session'); + let session = null; + if (sessionKey) { + session = importSessionStore.get(sessionKey); + } + + return { + step: session?.step, + sessionKey, + sessions + }; +}; + +export const actions: Actions = { + upload: async ({ request }) => { + const formData = await request.formData(); + const file = formData.get('file') as File; + const delimiter = (formData.get('delimiter') as string) || ','; + + if (!file) { + return fail(400, { error: 'No file uploaded' }); + } + + try { + const text = await file.text(); + const lines = text.split('\n').filter((line) => line.trim()); + const raw = lines.map((line) => line.split(delimiter)); + const colCounts = new Set(raw.map((row) => row.length)); + if (colCounts.size > 1) { + // find sample entries with different column counts + for (const count of colCounts) { + const sample = raw.find((row) => row.length === count); + console.warn(`Sample row with ${count} columns:`, sample); + } + logger.error('Inconsistent number of columns in uploaded file', { + payload: { filename: file.name, colCounts: Array.from(colCounts) } + }); + + return fail(400, { error: 'Inconsistent number of columns in rows' }); + } + const rawColCount = raw[0]?.length || 0; + + const key = importSessionStore.create(raw, delimiter, rawColCount); + const rowCount = raw.length; + logger.info('File uploaded and processed', { + payload: { filename: file.name, rowCount, key } + }); + + return { key, rowCount } satisfies ImportUploadResponse; + } catch (error) { + logger.error('Error processing uploaded file', { + payload: { error } + }); + return fail(500, { error: 'Failed to process file' }); + } + } +}; diff --git a/src/routes/admin/(authorized)/import/+page.svelte b/src/routes/admin/(authorized)/import/+page.svelte new file mode 100644 index 0000000..f951e7c --- /dev/null +++ b/src/routes/admin/(authorized)/import/+page.svelte @@ -0,0 +1,325 @@ + + + + +
    + +
    +
    +

    Import Manager

    +

    Import member data from CSV files

    +
    +
    + + +
    + {#each [1, 2, 3, 4, 5] as step (step)} +
    +
    = step + ? 'bg-primary text-primary-foreground' + : 'bg-muted text-muted-foreground' + }`} + > + {step} +
    + {#if step < 5} +
    step ? 'bg-primary' : 'bg-muted'}`}>
    + {/if} +
    + {/each} +
    + + + + + + {#if currentStep === 1} + Step 1: Upload CSV File + {:else if currentStep === 2} + Step 2: Map Fields + {:else if currentStep === 3} + Step 3: Review Data + {:else if currentStep === 4} + Step 4: Check Duplicates + {:else} + Step 5: Import Summary + {/if} + + + + {#if currentStep === 1 || !session || !selectedSession} + + {#if data.sessions.length > 0} +
    +

    Continue Existing Import

    +
    + {#each data.sessions as session (session.key)} +
    +
    +

    Session {session.key.slice(0, 8)}...

    +

    + Step {session.step} • {session.rowCount} rows • {session.errorCount} errors +

    +
    + +
    + {/each} +
    +
    + {/if} + + +
    +

    Upload New File

    +
    +
    + + +
    +
    + + (delimiter = value)} + > + + Delimiter: {delimiter === '\t' + ? 'Tab' + : delimiter === ';' + ? 'Semicolon (;)' + : 'Comma (,)'} + + + Comma (,) + Semicolon (;) + Tab + + +
    + + +
    +
    + + +
    +

    CSV Format Preview

    +

    + Use this example as a template for your CSV file. Copy the table below and paste it into + a spreadsheet or text editor. +

    +
    + + + + {#each exampleHeaders as header} + + {/each} + + + + {#each exampleData as row} + + {#each row as cell} + + {/each} + + {/each} + +
    + {header} +
    + {cell} +
    +
    +
    + +
    +
    + {:else if session.loading} + + + + + Loading... + + + +

    Please wait while we load your import session.

    +
    +
    + {:else if session.error} + + + + + Error + + + +

    {session.error.message}

    +
    +
    + {:else if !session.current} + + + + + Error + + + +

    Import session data is missing or corrupted.

    +
    +
    + {:else if currentStep === 2} + + {:else if currentStep === 3} + + {:else if currentStep === 4} + + {:else} + + {/if} + + +
    diff --git a/src/routes/admin/(authorized)/import/components/Duplicates.svelte b/src/routes/admin/(authorized)/import/components/Duplicates.svelte new file mode 100644 index 0000000..9445c91 --- /dev/null +++ b/src/routes/admin/(authorized)/import/components/Duplicates.svelte @@ -0,0 +1,140 @@ + + +
    + {#if session.duplicates} +
    +

    Duplicate Detection Results

    + + {session.duplicateCount} duplicates found + +
    + + {#if session.duplicateCount > 0} +
    + {#each session.duplicates as duplicate (duplicate.index)} + + + + {duplicate.type === 'new_membership' + ? 'Duplicate Membership Number' + : duplicate.type === 'new_email' + ? 'Duplicate Email' + : duplicate.type === 'new_rfid' + ? 'Duplicate RFID' + : duplicate.type === 'existing_membership' + ? 'Existing Membership Number' + : duplicate.type === 'existing_email' + ? 'Existing Email' + : 'Existing RFID'} + + + +
    +
    +

    New Record

    +

    + Name: {duplicate.newRecord.firstName} + {duplicate.newRecord.lastName} +

    +

    Email: {duplicate.newRecord.email || 'N/A'}

    +

    Membership: {duplicate.newRecord.membershipNumber || 'N/A'}

    +
    + {#if duplicate.existingRecord} +
    +

    Existing Record

    + {#if 'firstName' in duplicate.existingRecord} + +

    + Name: {duplicate.existingRecord.firstName} + {duplicate.existingRecord.lastName} +

    +

    Email: {duplicate.existingRecord.email || 'N/A'}

    +

    + Membership: {duplicate.existingRecord.membershipNumber || 'N/A'} +

    + {:else} + + {#if duplicate.existingRecord.rfidId} +

    RFID ID: {duplicate.existingRecord.rfidId}

    + {:else} +

    Status: {duplicate.existingRecord.status}

    + {/if} + {/if} +
    + {/if} +
    +
    + +
    +
    +
    + {/each} +
    + + {#if session.duplicateCount > 10} +

    + Showing first 10 duplicates. Full list available after import. +

    + {/if} + {:else} +
    + +

    No Duplicates Found

    +

    All records are unique and can be imported safely.

    +
    + {/if} + {:else} +

    Loading duplicate check...

    + {/if} + +
    + + {#if duplicatesResult || session.completed} + + {:else} + + {/if} +
    +
    diff --git a/src/routes/admin/(authorized)/import/components/Mapper.svelte b/src/routes/admin/(authorized)/import/components/Mapper.svelte new file mode 100644 index 0000000..0ccbf89 --- /dev/null +++ b/src/routes/admin/(authorized)/import/components/Mapper.svelte @@ -0,0 +1,130 @@ + + + + + +
    +
    + +
    + + + + + Field + Column + Preview + + + + {#each availableFields as field (field)} + + {field} + + + + + {#if session.rawPreview && session.rawPreview.length > 0} + {getSampleData(fieldMapping[field])} + {:else} + Sample data + {/if} + + + {/each} + + +
    + + +
    +
    diff --git a/src/routes/admin/(authorized)/import/components/Review.svelte b/src/routes/admin/(authorized)/import/components/Review.svelte new file mode 100644 index 0000000..908cb09 --- /dev/null +++ b/src/routes/admin/(authorized)/import/components/Review.svelte @@ -0,0 +1,80 @@ + + + +
    + {#if session.parsedPreview} +
    +

    Review Parsed Data

    + + {session.parsedCount} records parsed + +
    + + {#if session.errors.length > 0} +
    +

    Errors Found

    +
      + {#each session.errors.slice(0, 5) as error (error.row)} +
    • Row {error.row}: {error.error}
    • + {/each} + {#if session.errors.length > 5} +
    • ... and {session.errors.length - 5} more
    • + {/if} +
    +
    + {/if} + + + + + First Name + Last Name + Email + Membership # + RFID + + + + {#each session.parsedPreview as [record, rfid], i (i)} + + {record.firstName || ''} + {record.lastName || ''} + {record.email || ''} + {record.membershipNumber || ''} + {rfid?.rfidId || ''} + + {/each} + + + + {#if session.parsedCount > 10} +

    + Showing first 10 records. Full review available after import. +

    + {/if} + {:else} +

    No data to review

    + {/if} + +
    + + +
    +
    diff --git a/src/routes/admin/(authorized)/import/components/Summary.svelte b/src/routes/admin/(authorized)/import/components/Summary.svelte new file mode 100644 index 0000000..573073e --- /dev/null +++ b/src/routes/admin/(authorized)/import/components/Summary.svelte @@ -0,0 +1,109 @@ + + + +
    +
    +

    Import Summary

    +
    + + +
    + {session.finalCount} +
    +

    Records to Import

    +
    +
    + + +
    + {session.duplicateCount} +
    +

    Duplicates Found

    +
    +
    + + +
    + {session.errorCount} +
    +

    Parse Errors

    +
    +
    +
    +
    + + {#if session.duplicates.length > 0} +
    +

    Duplicates Detected

    +

    + {session.duplicates.length} duplicate records were found. Please review and resolve them before + importing. +

    +
    + {/if} + + {#if session.errors.length > 0} +
    +

    Parse Errors

    +

    + {session.errors.length} records had parsing errors and will be skipped. +

    +
    + {/if} + +
    + {#if session.completed} + Import has already been completed. + Imported {session.finalCount} records. + {:else if session.finalCount === 0} + No records to import. Please resolve issues before proceeding. + {:else} + + {/if} +
    + +
    + +
    +
    diff --git a/src/routes/admin/(authorized)/import/data.remote.ts b/src/routes/admin/(authorized)/import/data.remote.ts new file mode 100644 index 0000000..e9619e6 --- /dev/null +++ b/src/routes/admin/(authorized)/import/data.remote.ts @@ -0,0 +1,302 @@ +import { command, getRequestEvent, query } from '$app/server'; +import { + importSessionSchemaPublic, + importSessionStore, + type ImportSession, + type ImportSessionPublic +} from '$lib/server/importSessionStore'; +import { logger } from '$lib/server/logger'; +import { + memberRepository, + memberRfidCardRepository, + rfidCardRepository +} from '$lib/server/repositories'; +import { MemberInsertSchema, RfidCardInsertSchema } from '$schemas'; +import { redirect } from '@sveltejs/kit'; +import { error } from 'console'; +import * as z from 'zod'; +const memberSchema = MemberInsertSchema; +const rfidCardSchema = RfidCardInsertSchema; + +const checkAdminAuth = query(() => { + const { locals } = getRequestEvent(); + + if (!locals.auth.admin) { + redirect(302, '/admin/login'); + } +}); + +export const getImportSession = query(z.string('Key is required'), async (key) => { + await checkAdminAuth(); + const session = importSessionStore.get(key); + if (!session) { + return error(404, { message: 'Session not found' }); + } + try { + const result: ImportSessionPublic = importSessionSchemaPublic.parse(session); + return result; + } catch (err) { + logger.error('Error parsing import session for public view', { payload: { key, err } }); + return error(500, { message: 'Failed to parse import session' }); + } +}); + +// Step 2: Map fields +export const mapFields = command( + z.object({ + key: z.string(), + fieldMapping: z.record(z.string(), z.number().min(0)), + hasHeader: z.boolean().optional() + }), + async ({ key, fieldMapping, hasHeader }) => { + await checkAdminAuth(); + const session = importSessionStore.get(key); + if (!session) { + return error(404, { message: 'Session not found' }); + } + const parsed: ImportSession['parsed'] = []; + const errors: { row: number; error: string }[] = []; + const startRow = hasHeader ? 1 : 0; + + for (let i = startRow; i < session.raw.length; i++) { + const record: Record = {}; + const row = session.raw[i]; + try { + for (const [field, colIndex] of Object.entries(fieldMapping)) { + const value = row[colIndex]; + if (value != undefined && value.trim() !== '') record[field] = value; + } + const member = memberSchema.parse(record); + const rfidId = record.rfidId?.trim().toLocaleLowerCase(); + let card = undefined; + if (rfidId) { + card = rfidCardSchema.parse({ rfidId }); + } + parsed.push([member, card]); + } catch (error) { + errors.push({ + row: i + 1, + error: error instanceof Error ? error.message : 'Validation failed' + }); + } + } + + importSessionStore.update(key, { + hasHeader, + fieldMapping, + parsed, + errors, + step: 4 + }); + await getImportSession(key).refresh(); + return { + parsedCount: parsed.length, + errorCount: errors.length + }; + } +); + +// Step 3: Review & handle duplicates +// Step 4: Confirm import +export const checkDuplicates = command(z.string('Key is required'), async (key) => { + await checkAdminAuth(); + const session = importSessionStore.get(key); + if (!session) { + return { + type: 'error', + error: 'Session not found' + }; + } + + const duplicates: ImportSession['duplicates'] = []; + + // Check for duplicates within new data + const seenMemberships = new Set(); + const seenEmails = new Set(); + const seenRfids = new Set(); + + session.parsed.forEach(([record, rfid], index) => { + // Check membership number duplicates in new data + if (record.membershipNumber) { + if (seenMemberships.has(record.membershipNumber)) { + duplicates.push({ type: 'new_membership', newRecord: record, index }); + } else { + seenMemberships.add(record.membershipNumber); + } + } + + // Check email duplicates in new data + if (record.email) { + if (seenEmails.has(record.email)) { + duplicates.push({ type: 'new_email', newRecord: record, index }); + } else { + seenEmails.add(record.email); + } + } + + // Check RFID duplicates in new data + const rfidId = rfid?.rfidId; + if (rfidId) { + if (seenRfids.has(rfidId)) { + duplicates.push({ type: 'new_rfid', newRecord: record, index }); + } else { + seenRfids.add(rfidId); + } + } + }); + + // Check against existing data + for (const record of session.parsed) { + try { + // Check membership number against existing + if (record[0].membershipNumber) { + const existing = await memberRepository.findByMembershipNumber(record[0].membershipNumber); + if (existing) { + duplicates.push({ + type: 'existing_membership', + newRecord: record[0], + existingRecord: existing, + index: session.parsed.indexOf(record) + }); + } + } + + // Check email against existing + if (record[0].email) { + const existing = await memberRepository.findByEmail(record[0].email); + if (existing) { + duplicates.push({ + type: 'existing_email', + newRecord: record[0], + existingRecord: existing, + index: session.parsed.indexOf(record) + }); + } + } + + // Check RFID against existing + if (record[1]?.rfidId) { + const existing = await rfidCardRepository.findByRfidId(record[1].rfidId); + if (existing) { + duplicates.push({ + type: 'existing_rfid', + newRecord: record[1], + existingRecord: existing, + index: session.parsed.indexOf(record) + }); + } + } + } catch (err) { + console.error('Error checking duplicates for record:', record, err); + } + } + + logger.info('Duplicate check completed', { payload: { key, duplicateCount: duplicates.length } }); + + // parse final records excluding duplicates + const final: ImportSession['final'] = []; + session.parsed.forEach((entry, index) => { + if (!duplicates.find((dup) => dup.index === index)) { + try { + const member = memberSchema.parse(entry[0]); + const card = entry[1] != undefined ? rfidCardSchema.parse(entry[1]) : undefined; + final.push([member, card]); + } catch (err) { + console.error('Error parsing final record:', err); + } + } + }); + + importSessionStore.update(key, { duplicates, final, step: 5 }); + await getImportSession(key).refresh(); + return { type: 'success', duplicates }; +}); + +// Step 5: Import complete +export const completeImportSession = command(z.string('Key is required'), async (key) => { + await checkAdminAuth(); + const session = importSessionStore.get(key); + if (!session) { + return { + type: 'error', + error: 'Session not found' + }; + } + if (session.createdMemberIds.size > 0) { + // Already completed + return { + type: 'error', + error: 'Import session already completed' + }; + } + const { final, completed } = session; + if (completed) { + return { + type: 'error', + error: 'Import session already completed' + }; + } + if (final.length === 0) { + return { + type: 'error', + error: 'No records to import' + }; + } + const memberIds = new Set(); + let totalRfidCardsCreated = 0; + console.log('Final records to import:', final.length); + for (const [memberData, rfidData] of final) { + console.log('Importing member:', memberData, rfidData); + try { + // Create member + const { id: memberId } = await memberRepository.create(memberData); + memberIds.add(memberId); + + // Create RFID card if present + if (rfidData) { + const { id: cardId } = await rfidCardRepository.create(rfidData); + totalRfidCardsCreated++; + try { + await memberRfidCardRepository.create({ + memberId, + cardId + }); + } catch (err) { + console.error('Error assigning RFID card to member:', err); + } + } + } catch (err) { + console.error('Error importing record:', err); + } + } + + importSessionStore.update(key, { step: 5, createdMemberIds: memberIds, completed: true }); + await getImportSession(key).refresh(); + return { + type: 'success', + totalMembersCreated: memberIds.size, + totalCreatedRfidCards: totalRfidCardsCreated, + memberIds: Array.from(memberIds) + }; +}); + +export const removeDuplicate = command( + z.object({ + key: z.string(), + index: z.number().min(0) + }), + async ({ key, index }) => { + await checkAdminAuth(); + const session = importSessionStore.get(key); + if (!session) { + return error(404, { message: 'Session not found' }); + } + const duplicates = session.duplicates.filter((dup) => dup.index !== index); + const final = session.final.filter((_, i) => { + return !session.duplicates.find((dup) => dup.index === i); + }); + importSessionStore.update(key, { duplicates, final }); + getImportSession(key).refresh(); + return { success: true }; + } +); diff --git a/src/routes/admin/(authorized)/members/+page.server.ts b/src/routes/admin/(authorized)/members/+page.server.ts new file mode 100644 index 0000000..6367055 --- /dev/null +++ b/src/routes/admin/(authorized)/members/+page.server.ts @@ -0,0 +1,264 @@ +import { logger } from '$lib/server/logger'; +import { memberRepository } from '$lib/server/repositories'; +import { postgresErrorToFieldErrorCode } from '$lib/utils/handlePostgresError'; +import { m } from '$paraglide/messages'; +import { + MemberInsertSchema, + MemberSetPasswordSchema, + MembersListQuerySchema, + MemberUpdateSchema +} from '$schemas'; +import { fail } from '@sveltejs/kit'; +import { message, superValidate } from 'sveltekit-superforms'; +import { zod4 } from 'sveltekit-superforms/adapters'; +import type { Actions, PageServerLoad } from './$types'; +import { cleanQueryObject } from '$lib/utils/clean'; + +export const load: PageServerLoad = async ({ url }) => { + // Parse URL search params for filters + const searchParams = cleanQueryObject(Object.fromEntries(url.searchParams)); + + const filters = MembersListQuerySchema.parse(searchParams); + + const { members, pagination } = await memberRepository.list(filters); + + const formInsert = await superValidate(zod4(MemberInsertSchema)); + const formUpdate = await superValidate(zod4(MemberUpdateSchema)); + + const passwordReset = await superValidate(zod4(MemberSetPasswordSchema)); + + return { + members, + formInsert, + formUpdate, + passwordReset, + filters, + pagination + }; +}; + +export const actions: Actions = { + create: async ({ request, locals }) => { + const form = await superValidate(request, zod4(MemberInsertSchema)); + + if (!form.valid) { + return fail(400, { + form, + message: m.form_check_errors() + }); + } + + try { + const member = await memberRepository.create(form.data); + + // Log member creation + await logger.action('CREATE_MEMBER_PAGE', { + userId: locals.auth?.admin?.userId, + payload: { + memberId: member.id, + memberName: `${member.firstName} ${member.lastName}`, + guestAccount: member.guestAccount + } + }); + + return message(form, m.member_created_success()); + } catch (err) { + await logger.error('MEMBER_CREATE_FAILED', { + userId: locals.auth?.admin?.userId, + payload: { + error: err instanceof Error ? err.message : 'Unknown error', + memberData: form.data + } + }); + + if (err instanceof Error) { + postgresErrorToFieldErrorCode(err, form, { + members_membership_number_unique: { + field: 'membershipNumber', + i18nKey: 'zod.duplicate_membership_number' + }, + email: { field: 'email', i18nKey: 'zod.duplicate_email' } + }); + } + + return message(form, m.member_create_failed(), { status: 500 }); + } + }, + + update: async ({ request, locals }) => { + const form = await superValidate(request, zod4(MemberUpdateSchema)); + + if (!form.valid) { + return fail(400, { + form, + message: m.form_check_errors() + }); + } + + const id = form.data.id; + if (!id) { + return fail(400, { + form, + message: m.member_id_required() + }); + } + + try { + const existingMember = await memberRepository.getById(id); + if (!existingMember) { + return message(form, m.member_not_found(), { status: 404 }); + } + + const updatedMember = await memberRepository.update(id, form.data); + + // Log member update + await logger.action('UPDATE_MEMBER_PAGE', { + userId: locals.auth?.admin?.userId, + payload: { + memberId: id, + memberName: updatedMember + ? `${updatedMember.firstName} ${updatedMember.lastName}` + : `${existingMember.firstName} ${existingMember.lastName}`, + changes: form.data + } + }); + + return message(form, m.member_updated_success()); + } catch (err) { + await logger.error('MEMBER_UPDATE_FAILED', { + userId: locals.auth?.admin?.userId, + payload: { + error: err instanceof Error ? err.message : 'Unknown error', + memberId: id, + updateData: form.data + } + }); + + // Map database column names to form field names and i18n keys + postgresErrorToFieldErrorCode(err, form, { + members_membership_number_unique: { + field: 'membershipNumber', + i18nKey: 'zod.duplicate_membership_number' + }, + email: { field: 'email', i18nKey: 'duplicate_email' } + }); + + return message(form, m.member_update_failed(), { status: 500 }); + } + }, + + delete: async ({ request, locals }) => { + const formData = await request.formData(); + const idRaw = formData.get('id'); + + if (!idRaw) { + return fail(400, { + error: m.member_id_required() + }); + } + + const id = idRaw.toString(); + + try { + // Check if member exists + const existingMember = await memberRepository.getById(id); + if (!existingMember) { + return fail(404, { + error: m.member_not_found() + }); + } + + const success = await memberRepository.delete(id); + if (!success) { + return fail(500, { + error: m.delete_member_failed() + }); + } + + // Log member deletion + await logger.action('DELETE_MEMBER_PAGE', { + userId: locals.auth?.admin?.userId, + payload: { + memberId: id, + memberName: `${existingMember.firstName} ${existingMember.lastName}`, + guestAccount: existingMember.guestAccount + } + }); + + return { + success: true, + message: m.member_deleted_success() + }; + } catch (err) { + await logger.error('MEMBER_DELETE_FAILED', { + userId: locals.auth?.admin?.userId, + payload: { + error: err instanceof Error ? err.message : 'Unknown error', + memberId: id + } + }); + return fail(500, { + error: m.member_delete_failed_retry() + }); + } + }, + + updatePassword: async ({ request, locals }) => { + const form = await superValidate(request, zod4(MemberSetPasswordSchema)); + console.log('Password reset form data:', form.data); + const { memberId, password, sendNotification, allowSelfService } = form.data; + + try { + // Check if member exists + const existingMember = await memberRepository.getById(memberId); + if (!existingMember) { + return fail(404, { + error: m.member_not_found() + }); + } + + // Hash the provided password + const { hashPassword } = await import('$lib/utils/passwordHash'); + const passwordHash = await hashPassword(password); + + // Update password in database + const success = await memberRepository.updatePassword(memberId, passwordHash); + + if (!success) { + return fail(500, { + error: 'Failed to update password' + }); + } + + // Update self-service access if provided + if (allowSelfService !== undefined) { + await memberRepository.updateSelfServiceAccess(memberId, allowSelfService); + } + + // Log password update + await logger.action('ADMIN_UPDATE_MEMBER_PASSWORD', { + userId: locals.auth?.admin?.userId, + payload: { + memberId: memberId, + memberName: `${existingMember.firstName} ${existingMember.lastName}`, + sendNotification + } + }); + + console.log('Password updated successfully'); + + return message(form, m.member_updated_success()); + } catch (err) { + await logger.error('MEMBER_PASSWORD_UPDATE_FAILED', { + userId: locals.auth?.admin?.userId, + payload: { + error: err instanceof Error ? err.message : 'Unknown error', + memberId: memberId + } + }); + return fail(500, { + error: 'Failed to update password' + }); + } + } +}; diff --git a/src/routes/admin/(authorized)/members/+page.svelte b/src/routes/admin/(authorized)/members/+page.svelte new file mode 100644 index 0000000..d50e501 --- /dev/null +++ b/src/routes/admin/(authorized)/members/+page.svelte @@ -0,0 +1,285 @@ + + +
    + +
    +
    +

    {m.members_title()}

    +

    {m.members_description()}

    +
    +
    + + + +
    +
    + + + + + + + + + {#if data.pagination} + + {#snippet children({ pages, currentPage })} + + + + + {#each pages as page (page.key)} + {#if page.type === 'ellipsis'} + + + + {:else} + + + {page.value} + + + {/if} + {/each} + + + + + {/snippet} + + {/if} + + + (showFilters = false)} + /> + + + + + + +
    diff --git a/src/routes/admin/(authorized)/members/[id]/+page.server.ts b/src/routes/admin/(authorized)/members/[id]/+page.server.ts new file mode 100644 index 0000000..6bfd09b --- /dev/null +++ b/src/routes/admin/(authorized)/members/[id]/+page.server.ts @@ -0,0 +1,111 @@ +import { memberRepository } from '$lib/server/repositories'; +import { memberRfidCardRepository } from '$lib/server/repositories'; +import { rfidCardRepository } from '$lib/server/repositories'; +import { fail, redirect } from '@sveltejs/kit'; +import { m } from '$paraglide/messages'; +import { eq, and } from 'drizzle-orm'; +import { db } from '$lib/server/db'; +import { memberRfidCards, rfidCards } from '$schemas/database/schema'; +import type { PageServerLoad, Actions } from './$types'; + +export const load: PageServerLoad = async ({ params }) => { + const { id } = params; + + try { + const member = await memberRepository.getById(id); + if (!member) { + return fail(404, { + error: m.member_not_found() + }); + } + + // Fetch assigned RFID cards + const assignedCards = await db + .select({ + assignment: memberRfidCards, + rfidCard: rfidCards + }) + .from(memberRfidCards) + .leftJoin(rfidCards, eq(memberRfidCards.cardId, rfidCards.id)) + .where(and(eq(memberRfidCards.memberId, id), eq(memberRfidCards.status, 'ASSIGNED'))); + + return { + member, + assignedCards + }; + } catch (err) { + console.error('Failed to load member:', err); + return fail(500, { + error: 'Failed to load member' + }); + } +}; + +export const actions: Actions = { + returnCard: async ({ request, params }) => { + const { id } = params; + const formData = await request.formData(); + const assignmentId = formData.get('assignmentId') as string; + + if (!assignmentId) { + return fail(400, { error: 'Assignment ID is required' }); + } + + try { + // Update the assignment to returned + await memberRfidCardRepository.update(assignmentId, { + status: 'RETURNED', + returnedAt: new Date() + }); + + // Redirect back to the member page to refresh the data + throw redirect(303, `/admin/members/${id}`); + } catch (err) { + console.error('Failed to return card:', err); + return fail(500, { + error: 'Failed to return card' + }); + } + }, + + quickAssign: async ({ request, params }) => { + const { id } = params; + const formData = await request.formData(); + const rfidId = formData.get('rfidId')?.toString(); + + if (!rfidId) { + return fail(400, { error: 'RFID ID is required' }); + } + + try { + // Ensure member exists + const member = await memberRepository.getById(id); + if (!member) { + return fail(404, { error: m.member_not_found() }); + } + + // Create card if it doesn't exist + let card = await rfidCardRepository.findByRfidId(rfidId); + if (!card) { + card = await rfidCardRepository.create({ + rfidId, + status: 'NEW' + }); + } + + // Create assignment + await memberRfidCardRepository.create({ + memberId: id, + cardId: card.id, + status: 'ASSIGNED', + issuedAt: new Date() + }); + + // Redirect back to the member page to refresh the data + throw redirect(303, `/admin/members/${id}`); + } catch (err) { + console.error('Failed to quick assign card:', err); + return fail(500, { error: 'Failed to quick assign card' }); + } + } +}; diff --git a/src/routes/admin/(authorized)/members/[id]/+page.svelte b/src/routes/admin/(authorized)/members/[id]/+page.svelte new file mode 100644 index 0000000..a29803f --- /dev/null +++ b/src/routes/admin/(authorized)/members/[id]/+page.svelte @@ -0,0 +1,250 @@ + + +
    + +
    +
    + +
    +

    {m.member_details_title()}

    +

    {m.member_information()}

    +
    +
    +
    + + +
    +
    + + + + + {member.firstName} {member.lastName} + {#if member.membershipNumber} + {m.membership_number_label()}: {member.membershipNumber} + {/if} + + +
    +
    +
    {m.first_name_label()}
    +

    {member.firstName}

    +
    +
    +
    {m.last_name_label()}
    +

    {member.lastName}

    +
    +
    + + {#if member.title} +
    +
    {m.title_label()}
    +

    {member.title}

    +
    + {/if} + + {#if member.email} +
    +
    {m.email_column()}
    +

    {member.email}

    +
    + {/if} + + {#if member.birthDate} +
    +
    {m.birth_date_label()}
    +

    + {new Date(member.birthDate).toLocaleDateString()} +

    +
    + {/if} + + {#if member.occupation} +
    +
    {m.occupation_label()}
    +

    {member.occupation}

    +
    + {/if} + + + {#if member.street || member.houseNumber || member.postalCode || member.city} +
    +
    Address
    +

    + {member.street} + {member.houseNumber}
    + {member.postalCode} + {member.city} +

    +
    + {/if} + + + {#if member.phoneHome || member.phoneWork || member.phoneMobile} +
    +
    Phone
    +
    + {#if member.phoneHome} +
    {m.phone_home_label()}: {member.phoneHome}
    + {/if} + {#if member.phoneWork} +
    {m.phone_work_label()}: {member.phoneWork}
    + {/if} + {#if member.phoneMobile} +
    {m.phone_mobile_label()}: {member.phoneMobile}
    + {/if} +
    +
    + {/if} + +
    +
    +
    {m.guest_account_label()}
    +

    {member.guestAccount ? 'Yes' : 'No'}

    +
    + {#if member.joinedAt} +
    +
    {m.joined_at_label()}
    +

    + {new Date(member.joinedAt).toLocaleDateString()} +

    +
    + {/if} +
    + + {#if member.freeTextFunction} +
    +
    {m.free_text_function_label()}
    +

    {member.freeTextFunction}

    +
    + {/if} + + {#if member.freeTextComment} +
    +
    {m.free_text_comment_label()}
    +

    {member.freeTextComment}

    +
    + {/if} + +
    +
    +
    {m.created_column()}
    +

    {member.createdAt.toLocaleDateString()}

    +
    +
    +
    {m.last_updated()}
    +

    {member.updatedAt.toLocaleDateString()}

    +
    +
    +
    +
    + + + + +
    +
    + {m.rfid_cards_title()} + {m.rfid_cards_description()} +
    + +
    +
    + + + {#if data.assignedCards && data.assignedCards.length > 0} +
    + {#each data.assignedCards as { assignment, rfidCard }} + {#if rfidCard} +
    +
    +
    +
    {rfidCard.rfidId}
    +
    + {m.issued_at_column()} + + {assignment.issuedAt + ? new Date(assignment.issuedAt).toLocaleDateString() + : m.unknown_card()} + +
    +
    +
    + +
    + {assignment.status} +
    + + +
    +
    +
    + {/if} + {/each} +
    + {:else} +
    {m.no_assignments_found()}
    + {/if} +
    +
    + + + +
    diff --git a/src/routes/admin/(authorized)/members/[id]/edit/+page.server.ts b/src/routes/admin/(authorized)/members/[id]/edit/+page.server.ts new file mode 100644 index 0000000..ede5eb1 --- /dev/null +++ b/src/routes/admin/(authorized)/members/[id]/edit/+page.server.ts @@ -0,0 +1,68 @@ +import { memberRepository } from '$lib/server/repositories'; +import { postgresErrorToFieldErrorCode } from '$lib/utils/handlePostgresError'; +import { MemberUpdateSchema } from '$schemas'; +import { fail } from '@sveltejs/kit'; +import { message, superValidate } from 'sveltekit-superforms'; +import { zod4 } from 'sveltekit-superforms/adapters'; +import { m } from '$paraglide/messages'; +import type { Actions, PageServerLoad } from './$types'; + +export const load: PageServerLoad = async ({ params }) => { + const { id } = params; + + const member = await memberRepository.getById(id); + + const form = await superValidate( + { + ...member, + birthDate: member?.birthDate ? member?.birthDate.toISOString().split('T')[0] : '', + joinedAt: member?.joinedAt ? member?.joinedAt.toISOString().split('T')[0] : '' + }, + zod4(MemberUpdateSchema) + ); + return { + member, + form + }; +}; + +export const actions: Actions = { + update: async ({ request, params }) => { + const { id } = params; + const form = await superValidate(request, zod4(MemberUpdateSchema)); + + if (!form.valid) { + return fail(400, { + form, + message: m.form_check_errors() + }); + } + + if (form.data.id !== id) { + return fail(400, { + form, + message: 'Member ID mismatch' + }); + } + + try { + const existingMember = await memberRepository.getById(id); + if (!existingMember) { + return message(form, m.member_not_found(), { status: 404 }); + } + await memberRepository.update(id, form.data); + return message(form, m.member_updated_success()); + } catch (err) { + // Map database column names to form field names and i18n keys + postgresErrorToFieldErrorCode(err, form, { + members_membership_number_unique: { + field: 'membershipNumber', + i18nKey: 'zod.duplicate_membership_number' + }, + email: { field: 'email', i18nKey: 'duplicate_email' } + }); + + return message(form, m.member_update_failed(), { status: 500 }); + } + } +}; diff --git a/src/routes/admin/(authorized)/members/[id]/edit/+page.svelte b/src/routes/admin/(authorized)/members/[id]/edit/+page.svelte new file mode 100644 index 0000000..ee0dd64 --- /dev/null +++ b/src/routes/admin/(authorized)/members/[id]/edit/+page.svelte @@ -0,0 +1,78 @@ + + +
    + +
    + +
    +

    {m.edit_member_title()}

    +

    {m.modify_member_description()}

    +
    +
    + + + + + Edit Member Details + Update the member's information below + + + {#if updateForm} + {#if member} + + + + + {:else} +

    Member data not found.

    + {/if} + {:else} +

    Loading form...

    + {/if} +
    + +
    + + + {m.save_button()} + +
    +
    +
    +
    diff --git a/src/routes/admin/(authorized)/members/components/MemberDeleteDialog.svelte b/src/routes/admin/(authorized)/members/components/MemberDeleteDialog.svelte new file mode 100644 index 0000000..1f5a45c --- /dev/null +++ b/src/routes/admin/(authorized)/members/components/MemberDeleteDialog.svelte @@ -0,0 +1,43 @@ + + + + + + {m.confirm_delete()} + + {member + ? m.confirm_delete_member_description({ name: `${member.firstName} ${member.lastName}` }) + : m.confirm_delete_generic()} + + + + {m.cancel_button()} + {m.delete_member_button()} + + + diff --git a/src/routes/admin/(authorized)/members/components/MemberDetails.svelte b/src/routes/admin/(authorized)/members/components/MemberDetails.svelte new file mode 100644 index 0000000..6a6677c --- /dev/null +++ b/src/routes/admin/(authorized)/members/components/MemberDetails.svelte @@ -0,0 +1,110 @@ + + + + + + {m.member_details_title()} + {m.member_information()} + + + {#if member} +
    +
    +
    +
    {m.first_name_label()}
    +

    {member.firstName}

    +
    +
    +
    {m.last_name_label()}
    +

    {member.lastName}

    +
    +
    + + {#if member.membershipNumber} +
    +
    {m.membership_number_label()}
    +

    {member.membershipNumber}

    +
    + {/if} + + {#if member.email} +
    +
    {m.email_column()}
    +

    {member.email}

    +
    + {/if} + +
    +
    +
    {m.created_column()}
    +

    {member.createdAt.toLocaleDateString()}

    +
    +
    +
    {m.last_updated()}
    +

    {member.updatedAt.toLocaleDateString()}

    +
    +
    +
    + {:else} +
    +

    {m.no_member_selected()}

    +
    + {/if} + + +
    + + {#if member} +
    + + +
    + {/if} +
    +
    +
    +
    diff --git a/src/routes/admin/(authorized)/members/components/MemberFilters.svelte b/src/routes/admin/(authorized)/members/components/MemberFilters.svelte new file mode 100644 index 0000000..4ac1905 --- /dev/null +++ b/src/routes/admin/(authorized)/members/components/MemberFilters.svelte @@ -0,0 +1,251 @@ + + + + + + {m.advanced_filters_title()} + {m.advanced_filters_member_description()} + + +
    + +
    + + + + {guestAccountString === 'all' + ? m.all_account_types() + : guestAccountString === 'guest' + ? m.guest_account() + : m.member_account()} + + + {m.all_account_types()} + {m.member_account()} + {m.guest_account()} + + +
    + + +
    + +
    +
    + + +
    +
    + + +
    +
    +
    + + +
    + +
    +
    + + +
    +
    + + +
    +
    +
    + + +
    + +
    + + + {localFilters.orderBy === 'firstName' + ? m.first_name() + : localFilters.orderBy === 'lastName' + ? m.last_name() + : localFilters.orderBy === 'email' + ? m.email() + : localFilters.orderBy === 'membershipNumber' + ? m.membership_number() + : m.created_date()} + + + {m.first_name()} + {m.last_name()} + {m.email()} + {m.membership_number()} + {m.created_date()} + + + + + + {localFilters.sortOrder === 'asc' ? m.ascending() : m.descending()} + + + {m.ascending()} + {m.descending()} + + +
    +
    +
    + + +
    + + +
    +
    +
    +
    diff --git a/src/routes/admin/(authorized)/members/components/MemberForm.svelte b/src/routes/admin/(authorized)/members/components/MemberForm.svelte new file mode 100644 index 0000000..74616aa --- /dev/null +++ b/src/routes/admin/(authorized)/members/components/MemberForm.svelte @@ -0,0 +1,253 @@ + + +
    +
    + {#if mode === 'edit'} + + {/if} + + + +
    + + + {#snippet children({ props })} + {m.first_name_label()} + + {/snippet} + + + + + + + {#snippet children({ props })} + {m.last_name_label()} + + {/snippet} + + + +
    + +
    + + + {#snippet children({ props })} + {m.title_label()} + + {/snippet} + + + + + + + {#snippet children({ props })} + {m.membership_number_label()} + + {/snippet} + + + +
    + + + + {#snippet children({ props })} + {m.email_column()} + + {/snippet} + + + + +
    + + + {#snippet children({ props })} + {m.phone_home_label()} + + {/snippet} + + + + + + + {#snippet children({ props })} + {m.phone_work_label()} + + {/snippet} + + + + + + + {#snippet children({ props })} + {m.phone_mobile_label()} + + {/snippet} + + + +
    + +
    + + + {#snippet children({ props })} + {m.street_label()} + + {/snippet} + + + + +
    + + + {#snippet children({ props })} + {m.house_number_label()} + + {/snippet} + + + + + + + {#snippet children({ props })} + {m.postal_code_label()} + + {/snippet} + + + +
    +
    + + + + {#snippet children({ props })} + {m.city_label()} + + {/snippet} + + + + +
    + + + {#snippet children({ props })} + {m.birth_date_label()} + + {/snippet} + + + + + + + {#snippet children({ props })} + {m.occupation_label()} + + {/snippet} + + + +
    + +
    + + + {#snippet children({ props })} + {m.guest_account_label()} + + {/snippet} + + + + + + + {#snippet children({ props })} + Allow Self Service + + {/snippet} + + + + + + + {#snippet children({ props })} + {m.joined_at_label()} + + {/snippet} + + + +
    + + + + {#snippet children({ props })} + {m.free_text_function_label()} + + {/snippet} + + + + + + + {#snippet children({ props })} + {m.free_text_comment_label()} +