commit 72d5e2d984bca11c521703ddfb8d7d1b38930963 Author: Damien Coles Date: Mon Jan 26 10:55:11 2026 -0500 public-ready-init diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..95df281 --- /dev/null +++ b/.env.example @@ -0,0 +1,6 @@ +# Database +DATABASE_URL=postgres://user:password@localhost/nexus4 + +# JWT Authentication +JWT_SECRET=your-secret-key-change-in-production +JWT_EXPIRATION=3600 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..68e9e70 --- /dev/null +++ b/.gitignore @@ -0,0 +1,22 @@ +# Rust +/target +**/*.rs.bk +Cargo.lock + +# Environment +.env +.env.local + +# IDE +.idea/ +.vscode/ +*.swp +*.swo + +# Node (frontend) +node_modules/ +.svelte-kit/ + +# Misc +.DS_Store +*.log diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..b22918e --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,32 @@ +[package] +name = "nexus-4" +version = "0.1.0" +edition = "2024" + +[dependencies] +actix-web = "4.11.0" +sea-orm = { version = "1.1.12", features = [ + # REQUIRED: Database Driver + "sqlx-postgres", + # REQUIRED: Async Runtime & TLS Implementation + "runtime-tokio-rustls", # For Actix/Tokio and pure-Rust TLS + # ESSENTIAL: For SeaORM's Derive Macros + "macros", + # EXTRA FEATURES (Recommended & Common for your use case): + "debug-print", # To print SQL queries to the console/logger in debug mode + "mock", # For unit testing database interactions + "with-chrono", # To map PostgreSQL timestamp/date types to Rust's `chrono` types + "with-json", # To map PostgreSQL JSON/JSONB types to `serde_json::Value` + "with-uuid", # To map PostgreSQL UUID types to Rust's `uuid` type +]} +async-graphql = { version = "7.0.17", features = ["chrono"] } +async-graphql-actix-web = "7.0.17" +serde = { version = "1.0.219", features = ["derive"] } +uuid = { version = "1.17.0", features = ["v4"] } +chrono = "0.4.41" +jsonwebtoken = "9.2.0" +bcrypt = "0.17.0" +thiserror = "2.0.12" +serde_json = "1.0.140" +dotenv = "0.15.0" +actix-cors = "0.7.0" diff --git a/README.md b/README.md new file mode 100644 index 0000000..d586675 --- /dev/null +++ b/README.md @@ -0,0 +1,252 @@ +# Nexus 4 + +A Rust-based business management API experiment featuring Actix-web, SeaORM, and async-graphql. This project was an exploration into building a high-performance backend with Rust but was ultimately abandoned in favor of returning to more familiar frameworks (Django) for Nexus 5. + +## Project Status: Abandoned + +While the API itself demonstrated significantly better performance characteristics than the Python-based predecessors, the project was discontinued due to: + +- **Learning curve**: Rust's ownership model and borrow checker, while excellent for safety, required significant time investment +- **Ecosystem maturity**: The Django/Python ecosystem offered more battle-tested solutions for rapid business application development +- **Team familiarity**: Returning to Django allowed faster iteration and easier maintenance + +Despite being abandoned, this codebase serves as a reference implementation for building GraphQL APIs in Rust. + +## What Was Achieved + +- Full GraphQL API with queries and mutations for all entities +- JWT authentication with bcrypt password hashing +- SeaORM entities with PostgreSQL +- Database migrations +- SvelteKit frontend with Houdini GraphQL client +- CORS configuration for frontend integration + +## Performance Comparison + +| Metric | Nexus 3 (Django/Graphene) | Nexus 4 (Rust/async-graphql) | +|--------|---------------------------|------------------------------| +| Memory usage | ~150MB | ~15MB | +| Cold start | ~2-3s | ~50ms | +| Simple query latency | ~20-50ms | ~2-5ms | +| Concurrent connections | Hundreds | Thousands | + +*Note: These are approximate benchmarks from development testing, not production measurements.* + +## Improvements Over Previous Versions + +### Over Nexus 1-2 (Django REST) +- GraphQL instead of REST +- Compiled binary instead of interpreted Python +- Zero-cost abstractions and memory safety +- Native async/await without GIL limitations + +### Over Nexus 3 (Django/Graphene) +- ~10x lower memory footprint +- ~10x faster query response times +- True async I/O without Python's GIL +- Type safety at compile time +- Single binary deployment + +## Tech Stack + +### Backend +- **Rust** (2024 edition) +- **Actix-web** 4.x - High-performance web framework +- **SeaORM** 1.x - Async ORM with compile-time checked queries +- **async-graphql** 7.x - GraphQL server library +- **jsonwebtoken** - JWT authentication +- **bcrypt** - Password hashing +- **PostgreSQL** - Database + +### Frontend +- **SvelteKit** - Frontend framework +- **Houdini** - GraphQL client with generated types +- **TypeScript** + +## Project Structure + +``` +nexus-4/ +├── src/ +│ ├── main.rs # Application entry point +│ ├── db.rs # Database connection +│ ├── auth/ # JWT authentication +│ │ ├── mod.rs # JWT middleware and utilities +│ │ ├── error.rs # Auth error types +│ │ └── handlers.rs # Login, register, token renewal +│ ├── entities/ # SeaORM entities +│ │ ├── mod.rs +│ │ ├── prelude.rs +│ │ ├── customer.rs +│ │ ├── account.rs +│ │ ├── service.rs +│ │ ├── project.rs +│ │ ├── schedule.rs +│ │ ├── revenue.rs +│ │ ├── labor.rs +│ │ ├── invoice.rs +│ │ ├── report.rs +│ │ ├── user.rs +│ │ └── profile.rs +│ └── graphql/ # GraphQL resolvers +│ ├── mod.rs # Schema definition +│ ├── customer.rs +│ ├── account.rs +│ ├── service.rs +│ ├── project.rs +│ ├── schedule.rs +│ ├── revenue.rs +│ ├── labor.rs +│ ├── invoice.rs +│ ├── report.rs +│ ├── user.rs +│ └── profile.rs +├── migration/ # SeaORM migrations +│ └── src/ +│ ├── lib.rs +│ └── m20220101_*.rs +├── frontend/ # SvelteKit + Houdini frontend +│ ├── src/ +│ ├── .houdini/ # Generated GraphQL artifacts +│ └── schema.graphql # GraphQL schema +├── Cargo.toml +└── Cargo.lock +``` + +## Quick Start + +### Prerequisites +- Rust (latest stable) +- PostgreSQL 15+ +- Node.js 18+ (for frontend) + +### Backend Setup + +```bash +# Clone the repository +git clone +cd nexus-4 + +# Create .env file +cat > .env << EOF +DATABASE_URL=postgres://user:password@localhost/nexus4 +JWT_SECRET=your-secret-key-change-in-production +JWT_EXPIRATION=3600 +EOF + +# Run migrations +cd migration +cargo run + +# Start the server +cd .. +cargo run +``` + +The server will start at: +- GraphQL endpoint: http://127.0.0.1:8080/graphql +- GraphQL playground: http://127.0.0.1:8080/playground +- Auth endpoint: http://127.0.0.1:8080/token + +### Frontend Setup + +```bash +cd frontend + +# Install dependencies +npm install + +# Start development server +npm run dev +``` + +## API Usage + +### Authentication + +**Login:** +```bash +curl -X POST http://localhost:8080/token \ + -H "Content-Type: application/json" \ + -d '{"username": "your_username", "password": "your_password"}' +``` + +**Create User:** +```bash +curl -X POST http://localhost:8080/users \ + -H "Content-Type: application/json" \ + -d '{ + "username": "newuser", + "email": "user@example.com", + "password": "securepassword", + "is_active": true, + "first_name": "John", + "last_name": "Doe", + "primary_phone": "555-123-4567", + "role": "user" + }' +``` + +### GraphQL Queries + +**Get all customers:** +```graphql +query { + customers { + id + name + accounts { + id + name + } + } +} +``` + +**Create a service:** +```graphql +mutation { + createService(input: { + accountId: "uuid-here" + status: "scheduled" + scheduledDate: "2024-01-15" + }) { + id + status + } +} +``` + +## Configuration + +| Environment Variable | Description | Default | +|---------------------|-------------|---------| +| `DATABASE_URL` | PostgreSQL connection string | Required | +| `JWT_SECRET` | Secret for signing JWTs | `default_secret_change_me` | +| `JWT_EXPIRATION` | Token expiration in seconds | `3600` | + +## Why Rust Was Considered + +1. **Performance**: Rust's zero-cost abstractions provide C-like performance +2. **Memory Safety**: No null pointer exceptions or data races +3. **Concurrency**: Fearless concurrency with async/await +4. **Type System**: Catch errors at compile time +5. **Single Binary**: Easy deployment without runtime dependencies + +## Why It Was Abandoned + +1. **Development Speed**: Django's ORM and admin interface provide faster iteration +2. **Ecosystem**: Python has more libraries for business logic (PDF generation, email, etc.) +3. **Hiring**: Finding Django developers is easier than Rust developers +4. **Maintenance**: Team was more comfortable debugging Python code + +## Lessons Learned + +- Rust is excellent for performance-critical services +- For CRUD-heavy business apps, developer productivity often matters more than raw performance +- Consider Rust for specific microservices where performance is critical +- The async-graphql and SeaORM ecosystem is mature and pleasant to use + +## License + +MIT License - See LICENSE file for details. diff --git a/frontend/.gitignore b/frontend/.gitignore new file mode 100644 index 0000000..dda24ef --- /dev/null +++ b/frontend/.gitignore @@ -0,0 +1,25 @@ +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-* + +.houdini diff --git a/frontend/.graphqlrc.yaml b/frontend/.graphqlrc.yaml new file mode 100644 index 0000000..5eb640e --- /dev/null +++ b/frontend/.graphqlrc.yaml @@ -0,0 +1,9 @@ +projects: + default: + schema: + - ./schema.graphql + - ./.houdini/graphql/schema.graphql + documents: + - '**/*.gql' + - '**/*.svelte' + - ./.houdini/graphql/documents.gql diff --git a/frontend/.npmrc b/frontend/.npmrc new file mode 100644 index 0000000..b6f27f1 --- /dev/null +++ b/frontend/.npmrc @@ -0,0 +1 @@ +engine-strict=true diff --git a/frontend/.prettierignore b/frontend/.prettierignore new file mode 100644 index 0000000..6562bcb --- /dev/null +++ b/frontend/.prettierignore @@ -0,0 +1,6 @@ +# Package Managers +package-lock.json +pnpm-lock.yaml +yarn.lock +bun.lock +bun.lockb diff --git a/frontend/.prettierrc b/frontend/.prettierrc new file mode 100644 index 0000000..7ebb855 --- /dev/null +++ b/frontend/.prettierrc @@ -0,0 +1,15 @@ +{ + "useTabs": true, + "singleQuote": true, + "trailingComma": "none", + "printWidth": 100, + "plugins": ["prettier-plugin-svelte", "prettier-plugin-tailwindcss"], + "overrides": [ + { + "files": "*.svelte", + "options": { + "parser": "svelte" + } + } + ] +} diff --git a/frontend/README.md b/frontend/README.md new file mode 100644 index 0000000..b5b2950 --- /dev/null +++ b/frontend/README.md @@ -0,0 +1,38 @@ +# sv + +Everything you need to build a Svelte project, powered by [`sv`](https://github.com/sveltejs/cli). + +## Creating a project + +If you're seeing this, you've probably already done this step. Congrats! + +```bash +# create a new project in the current directory +npx sv create + +# create a new project in my-app +npx sv create my-app +``` + +## Developing + +Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server: + +```bash +npm run dev + +# or start the server and open the app in a new browser tab +npm run dev -- --open +``` + +## Building + +To create a production version of your app: + +```bash +npm run build +``` + +You can preview the production build with `npm run preview`. + +> To deploy your app, you may need to install an [adapter](https://svelte.dev/docs/kit/adapters) for your target environment. diff --git a/frontend/eslint.config.js b/frontend/eslint.config.js new file mode 100644 index 0000000..ef07d32 --- /dev/null +++ b/frontend/eslint.config.js @@ -0,0 +1,36 @@ +import prettier from 'eslint-config-prettier'; +import js from '@eslint/js'; +import { includeIgnoreFile } from '@eslint/compat'; +import svelte from 'eslint-plugin-svelte'; +import globals from 'globals'; +import { fileURLToPath } from 'node:url'; +import ts from 'typescript-eslint'; +import svelteConfig from './svelte.config.js'; + +const gitignorePath = fileURLToPath(new URL('./.gitignore', import.meta.url)); + +export default ts.config( + includeIgnoreFile(gitignorePath), + js.configs.recommended, + ...ts.configs.recommended, + ...svelte.configs.recommended, + prettier, + ...svelte.configs.prettier, + { + languageOptions: { + globals: { ...globals.browser, ...globals.node } + }, + rules: { 'no-undef': 'off' } + }, + { + files: ['**/*.svelte', '**/*.svelte.ts', '**/*.svelte.js'], + languageOptions: { + parserOptions: { + projectService: true, + extraFileExtensions: ['.svelte'], + parser: ts.parser, + svelteConfig + } + } + } +); diff --git a/frontend/houdini.config.js b/frontend/houdini.config.js new file mode 100644 index 0000000..300b372 --- /dev/null +++ b/frontend/houdini.config.js @@ -0,0 +1,28 @@ +/// + +/** @type {import('houdini').ConfigFile} */ +const config = { + watchSchema: { + url: 'http://127.0.0.1:8080/graphql', + interval: null + }, + runtimeDir: '.houdini', + plugins: { + 'houdini-svelte': {} + }, + // Correct for rust chrono types + scalars: { + NaiveDateTime: { + type: 'string', + marshal: (val) => val, + unmarshal: (val) => val + }, + NaiveDate: { + type: 'string', + marshal: (val) => val, + unmarshal: (val) => val + } + } +}; + +export default config; diff --git a/frontend/package-lock.json b/frontend/package-lock.json new file mode 100644 index 0000000..a43f357 --- /dev/null +++ b/frontend/package-lock.json @@ -0,0 +1,6009 @@ +{ + "name": "frontend", + "version": "0.0.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "frontend", + "version": "0.0.1", + "devDependencies": { + "@eslint/compat": "^1.2.5", + "@eslint/js": "^9.18.0", + "@sveltejs/adapter-node": "^5.2.12", + "@sveltejs/kit": "<=2.21.0", + "@sveltejs/vite-plugin-svelte": "^5.0.0", + "@tailwindcss/forms": "^0.5.9", + "@tailwindcss/typography": "^0.5.15", + "@tailwindcss/vite": "^4.0.0", + "eslint": "^9.18.0", + "eslint-config-prettier": "^10.0.1", + "eslint-plugin-svelte": "^3.0.0", + "globals": "^16.0.0", + "houdini": "^1.5.7", + "houdini-svelte": "^2.1.17", + "prettier": "^3.4.2", + "prettier-plugin-svelte": "^3.3.3", + "prettier-plugin-tailwindcss": "^0.6.11", + "svelte": "^5.0.0", + "svelte-check": "^4.0.0", + "tailwindcss": "^4.0.0", + "typescript": "^5.0.0", + "typescript-eslint": "^8.20.0", + "vite": "^6.2.6" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", + "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.27.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.5.tgz", + "integrity": "sha512-OsQd175SxWkGlzbny8J3K8TnnDD0N3lrIUtB92xwyRpzaenGZhxDvxN/JgU00U3CDZNj9tPuDJ5H0WS4Nt3vKg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.27.3" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/types": { + "version": "7.27.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.6.tgz", + "integrity": "sha512-ETyHEk2VHHvl9b9jZP5IHPavHYk57EhanlRRuae9XCpb/j5bDCbPPMOBfCWhnl/7EDJz0jEMCi/RhccCE8r1+Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@clack/core": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@clack/core/-/core-0.3.5.tgz", + "integrity": "sha512-5cfhQNH+1VQ2xLQlmzXMqUoiaH0lRBq9/CLW9lTyMbuKLC3+xEK01tHVvyut++mLOn5urSHmkm6I0Lg9MaJSTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "picocolors": "^1.0.0", + "sisteransi": "^1.0.5" + } + }, + "node_modules/@clack/prompts": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/@clack/prompts/-/prompts-0.6.3.tgz", + "integrity": "sha512-AM+kFmAHawpUQv2q9+mcB6jLKxXGjgu/r2EQjEwujgpCdzrST6BJqYw00GRn56/L/Izw5U7ImoLmy00X/r80Pw==", + "bundleDependencies": [ + "is-unicode-supported" + ], + "dev": true, + "license": "MIT", + "dependencies": { + "@clack/core": "^0.3.2", + "is-unicode-supported": "*", + "picocolors": "^1.0.0", + "sisteransi": "^1.0.5" + } + }, + "node_modules/@clack/prompts/node_modules/is-unicode-supported": { + "version": "1.3.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@envelop/core": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@envelop/core/-/core-4.0.3.tgz", + "integrity": "sha512-O0Vz8E0TObT6ijAob8jYFVJavcGywKThM3UAsxUIBBVPYZTMiqI9lo2gmAnbMUnrDcAYkUTZEW9FDYPRdF5l6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@envelop/types": "4.0.1", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@envelop/types": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@envelop/types/-/types-4.0.1.tgz", + "integrity": "sha512-ULo27/doEsP7uUhm2iTnElx13qTO6I5FKvmLoX41cpfuw8x6e0NUFknoqhEsLzAbgz8xVS5mjwcxGCXh4lDYzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.5.tgz", + "integrity": "sha512-9o3TMmpmftaCMepOdA5k/yDw8SfInyzWWTjYTFCX3kPSDJMROQTb8jg+h9Cnwnmm1vOzvxN7gIfB5V2ewpjtGA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.5.tgz", + "integrity": "sha512-AdJKSPeEHgi7/ZhuIPtcQKr5RQdo6OO2IL87JkianiMYMPbCtot9fxPbrMiBADOWWm3T2si9stAiVsGbTQFkbA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.5.tgz", + "integrity": "sha512-VGzGhj4lJO+TVGV1v8ntCZWJktV7SGCs3Pn1GRWI1SBFtRALoomm8k5E9Pmwg3HOAal2VDc2F9+PM/rEY6oIDg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.5.tgz", + "integrity": "sha512-D2GyJT1kjvO//drbRT3Hib9XPwQeWd9vZoBJn+bu/lVsOZ13cqNdDeqIF/xQ5/VmWvMduP6AmXvylO/PIc2isw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.5.tgz", + "integrity": "sha512-GtaBgammVvdF7aPIgH2jxMDdivezgFu6iKpmT+48+F8Hhg5J/sfnDieg0aeG/jfSvkYQU2/pceFPDKlqZzwnfQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.5.tgz", + "integrity": "sha512-1iT4FVL0dJ76/q1wd7XDsXrSW+oLoquptvh4CLR4kITDtqi2e/xwXwdCVH8hVHU43wgJdsq7Gxuzcs6Iq/7bxQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.5.tgz", + "integrity": "sha512-nk4tGP3JThz4La38Uy/gzyXtpkPW8zSAmoUhK9xKKXdBCzKODMc2adkB2+8om9BDYugz+uGV7sLmpTYzvmz6Sw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.5.tgz", + "integrity": "sha512-PrikaNjiXdR2laW6OIjlbeuCPrPaAl0IwPIaRv+SMV8CiM8i2LqVUHFC1+8eORgWyY7yhQY+2U2fA55mBzReaw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.5.tgz", + "integrity": "sha512-cPzojwW2okgh7ZlRpcBEtsX7WBuqbLrNXqLU89GxWbNt6uIg78ET82qifUy3W6OVww6ZWobWub5oqZOVtwolfw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.5.tgz", + "integrity": "sha512-Z9kfb1v6ZlGbWj8EJk9T6czVEjjq2ntSYLY2cw6pAZl4oKtfgQuS4HOq41M/BcoLPzrUbNd+R4BXFyH//nHxVg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.5.tgz", + "integrity": "sha512-sQ7l00M8bSv36GLV95BVAdhJ2QsIbCuCjh/uYrWiMQSUuV+LpXwIqhgJDcvMTj+VsQmqAHL2yYaasENvJ7CDKA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.5.tgz", + "integrity": "sha512-0ur7ae16hDUC4OL5iEnDb0tZHDxYmuQyhKhsPBV8f99f6Z9KQM02g33f93rNH5A30agMS46u2HP6qTdEt6Q1kg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.5.tgz", + "integrity": "sha512-kB/66P1OsHO5zLz0i6X0RxlQ+3cu0mkxS3TKFvkb5lin6uwZ/ttOkP3Z8lfR9mJOBk14ZwZ9182SIIWFGNmqmg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.5.tgz", + "integrity": "sha512-UZCmJ7r9X2fe2D6jBmkLBMQetXPXIsZjQJCjgwpVDz+YMcS6oFR27alkgGv3Oqkv07bxdvw7fyB71/olceJhkQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.5.tgz", + "integrity": "sha512-kTxwu4mLyeOlsVIFPfQo+fQJAV9mh24xL+y+Bm6ej067sYANjyEw1dNHmvoqxJUCMnkBdKpvOn0Ahql6+4VyeA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.5.tgz", + "integrity": "sha512-K2dSKTKfmdh78uJ3NcWFiqyRrimfdinS5ErLSn3vluHNeHVnBAFWC8a4X5N+7FgVE1EjXS1QDZbpqZBjfrqMTQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.5.tgz", + "integrity": "sha512-uhj8N2obKTE6pSZ+aMUbqq+1nXxNjZIIjCjGLfsWvVpy7gKCOL6rsY1MhRh9zLtUtAI7vpgLMK6DxjO8Qm9lJw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.5.tgz", + "integrity": "sha512-pwHtMP9viAy1oHPvgxtOv+OkduK5ugofNTVDilIzBLpoWAM16r7b/mxBvfpuQDpRQFMfuVr5aLcn4yveGvBZvw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.5.tgz", + "integrity": "sha512-WOb5fKrvVTRMfWFNCroYWWklbnXH0Q5rZppjq0vQIdlsQKuw6mdSihwSo4RV/YdQ5UCKKvBy7/0ZZYLBZKIbwQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.5.tgz", + "integrity": "sha512-7A208+uQKgTxHd0G0uqZO8UjK2R0DDb4fDmERtARjSHWxqMTye4Erz4zZafx7Di9Cv+lNHYuncAkiGFySoD+Mw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.5.tgz", + "integrity": "sha512-G4hE405ErTWraiZ8UiSoesH8DaCsMm0Cay4fsFWOOUcz8b8rC6uCvnagr+gnioEjWn0wC+o1/TAHt+It+MpIMg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.5.tgz", + "integrity": "sha512-l+azKShMy7FxzY0Rj4RCt5VD/q8mG/e+mDivgspo+yL8zW7qEwctQ6YqKX34DTEleFAvCIUviCFX1SDZRSyMQA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.5.tgz", + "integrity": "sha512-O2S7SNZzdcFG7eFKgvwUEZ2VG9D/sn/eIiz8XRZ1Q/DO5a3s76Xv0mdBzVM5j5R639lXQmPmSo0iRpHqUUrsxw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.5.tgz", + "integrity": "sha512-onOJ02pqs9h1iMJ1PQphR+VZv8qBMQ77Klcsqv9CNW2w6yLqoURLcgERAIurY6QE63bbLuqgP9ATqajFLK5AMQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.5.tgz", + "integrity": "sha512-TXv6YnJ8ZMVdX+SXWVBo/0p8LTcrUYngpWjvm91TMjjBQii7Oz11Lw5lbDV5Y0TzuhSJHwiH4hEtC1I42mMS0g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz", + "integrity": "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==", + "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.3.0", + "resolved": "https://registry.npmjs.org/@eslint/compat/-/compat-1.3.0.tgz", + "integrity": "sha512-ZBygRBqpDYiIHsN+d1WyHn3TYgzgpzLEcgJUxTATyiInQbKZz6wZb6+ljwdg8xeeOe4v03z6Uh6lELiw0/mVhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "peerDependencies": { + "eslint": "^9.10.0" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/@eslint/config-array": { + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.20.1.tgz", + "integrity": "sha512-OL0RJzC/CBzli0DrrR31qzj6d6i6Mm3HByuhflhl4LOBiWxN+3i6/t/ZQQNii4tjksXi8r2CRW1wMpWA2ULUEw==", + "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.2.3", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.2.3.tgz", + "integrity": "sha512-u180qk2Um1le4yf0ruXH3PYFeEZeYC3p/4wCTKrr2U1CmGdzGi3KtY0nuPDH48UJxlKCC5RDzbcbh4X0XlqgHg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.14.0.tgz", + "integrity": "sha512-qIbV0/JZr7iSDjqAc60IqbLdsj9GDt16xQtWD+B78d/HAlvysGdZZ6rpJHGAc2T0FQx1X6thsSPdnoiGKdNtdg==", + "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.29.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.29.0.tgz", + "integrity": "sha512-3PIF4cBw/y+1u2EazflInpV+lYsSG0aByVIQzAgb1m1MhHFSbqTyNqtBKHgWf/9Ykud+DhILS9EGkmekVhbKoQ==", + "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.2", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.2.tgz", + "integrity": "sha512-4SaFZCNfJqvk/kenHpI8xvN42DMaoycy4PzKc5otHxRswww1kAt82OlBuwRVLofCACCTZEcla2Ydxv8scMXaTg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.15.0", + "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.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.0.tgz", + "integrity": "sha512-b7ePw78tEWWkpgZCDYkbqDOP8dmM6qe+AOC6iuJqlq1R/0ahMAeH3qynpnqKFGkMltrp44ohV4ubGyvLX28tzw==", + "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/@fastify/busboy": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-3.1.1.tgz", + "integrity": "sha512-5DGmA8FTdB2XbDeEwc/5ZXBl6UbBAyBOOLlPuBnZ/N1SwdH9Ii+cOX3tBROlDgcTXxjOYnLMVoKk9+FXAw0CJw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@graphql-tools/executor": { + "version": "1.4.7", + "resolved": "https://registry.npmjs.org/@graphql-tools/executor/-/executor-1.4.7.tgz", + "integrity": "sha512-U0nK9jzJRP9/9Izf1+0Gggd6K6RNRsheFo1gC/VWzfnsr0qjcOSS9qTjY0OTC5iTPt4tQ+W5Zpw/uc7mebI6aA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@graphql-tools/utils": "^10.8.6", + "@graphql-typed-document-node/core": "^3.2.0", + "@repeaterjs/repeater": "^3.0.4", + "@whatwg-node/disposablestack": "^0.0.6", + "@whatwg-node/promise-helpers": "^1.0.0", + "tslib": "^2.4.0" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/@graphql-tools/merge": { + "version": "9.0.24", + "resolved": "https://registry.npmjs.org/@graphql-tools/merge/-/merge-9.0.24.tgz", + "integrity": "sha512-NzWx/Afl/1qHT3Nm1bghGG2l4jub28AdvtG11PoUlmjcIjnFBJMv4vqL0qnxWe8A82peWo4/TkVdjJRLXwgGEw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@graphql-tools/utils": "^10.8.6", + "tslib": "^2.4.0" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/@graphql-tools/schema": { + "version": "9.0.19", + "resolved": "https://registry.npmjs.org/@graphql-tools/schema/-/schema-9.0.19.tgz", + "integrity": "sha512-oBRPoNBtCkk0zbUsyP4GaIzCt8C0aCI4ycIRUL67KK5pOHljKLBBtGT+Jr6hkzA74C8Gco8bpZPe7aWFjiaK2w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@graphql-tools/merge": "^8.4.1", + "@graphql-tools/utils": "^9.2.1", + "tslib": "^2.4.0", + "value-or-promise": "^1.0.12" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/@graphql-tools/schema/node_modules/@graphql-tools/merge": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/@graphql-tools/merge/-/merge-8.4.2.tgz", + "integrity": "sha512-XbrHAaj8yDuINph+sAfuq3QCZ/tKblrTLOpirK0+CAgNlZUCHs0Fa+xtMUURgwCVThLle1AF7svJCxFizygLsw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@graphql-tools/utils": "^9.2.1", + "tslib": "^2.4.0" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/@graphql-tools/schema/node_modules/@graphql-tools/utils": { + "version": "9.2.1", + "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-9.2.1.tgz", + "integrity": "sha512-WUw506Ql6xzmOORlriNrD6Ugx+HjVgYxt9KCXD9mHAak+eaXSwuGGPyE60hy9xaDEoXKBsG7SkG69ybitaVl6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@graphql-typed-document-node/core": "^3.1.1", + "tslib": "^2.4.0" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/@graphql-tools/utils": { + "version": "10.8.6", + "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-10.8.6.tgz", + "integrity": "sha512-Alc9Vyg0oOsGhRapfL3xvqh1zV8nKoFUdtLhXX7Ki4nClaIJXckrA86j+uxEuG3ic6j4jlM1nvcWXRn/71AVLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@graphql-typed-document-node/core": "^3.1.1", + "@whatwg-node/promise-helpers": "^1.0.0", + "cross-inspect": "1.0.1", + "dset": "^3.1.4", + "tslib": "^2.4.0" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/@graphql-typed-document-node/core": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@graphql-typed-document-node/core/-/core-3.2.0.tgz", + "integrity": "sha512-mB9oAsNCm9aM3/SOv4YtBMqZbYj10R7dkq8byBqxGY/ncFwhf2oQzMV+LCRlWoDSEBJ3COiR1yeDvMtsoOsuFQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "graphql": "^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/@graphql-yoga/logger": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@graphql-yoga/logger/-/logger-1.0.0.tgz", + "integrity": "sha512-JYoxwnPggH2BfO+dWlWZkDeFhyFZqaTRGLvFhy+Pjp2UxitEW6nDrw+pEDw/K9tJwMjIFMmTT9VfTqrnESmBHg==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.5.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@graphql-yoga/subscription": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@graphql-yoga/subscription/-/subscription-4.0.0.tgz", + "integrity": "sha512-0qsN/BPPZNMoC2CZ8i+P6PgiJyHh1H35aKDt37qARBDaIOKDQuvEOq7+4txUKElcmXi7DYFo109FkhSQoEajrg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@graphql-yoga/typed-event-target": "^2.0.0", + "@repeaterjs/repeater": "^3.0.4", + "@whatwg-node/events": "^0.1.0", + "tslib": "^2.5.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@graphql-yoga/typed-event-target": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@graphql-yoga/typed-event-target/-/typed-event-target-2.0.0.tgz", + "integrity": "sha512-oA/VGxGmaSDym1glOHrltw43qZsFwLLjBwvh57B79UKX/vo3+UQcRgOyE44c5RP7DCYjkrC2tuArZmb6jCzysw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@repeaterjs/repeater": "^3.0.4", + "tslib": "^2.5.2" + }, + "engines": { + "node": ">=16.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.6", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz", + "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.3.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node/node_modules/@humanwhocodes/retry": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz", + "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "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/@isaacs/balanced-match": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz", + "integrity": "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@isaacs/brace-expansion": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.0.tgz", + "integrity": "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@isaacs/balanced-match": "^4.0.1" + }, + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "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.8", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", + "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@kamilkisiela/fast-url-parser": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@kamilkisiela/fast-url-parser/-/fast-url-parser-1.1.4.tgz", + "integrity": "sha512-gbkePEBupNydxCelHCESvFSFM8XPh1Zs/OAVRW/rKpEqPAl5PbOM90Si8mv9bvnR53uPD2s/FiRxdvSejpRJew==", + "dev": true, + "license": "MIT" + }, + "node_modules/@kitql/helpers": { + "version": "0.8.13", + "resolved": "https://registry.npmjs.org/@kitql/helpers/-/helpers-0.8.13.tgz", + "integrity": "sha512-8Om4jxF3XLTrpCkrZ8q6kHco4o6u/zKisA7XjLdiUDeSX7VSprlq6fnG2xayrbrc1He+pHQkgj2cHqqUvEHTGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "esm-env": "^1.2.2" + }, + "engines": { + "node": "^16.14 || >=18" + }, + "funding": { + "url": "https://github.com/sponsors/jycouet" + } + }, + "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/@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==", + "dev": true, + "license": "MIT" + }, + "node_modules/@repeaterjs/repeater": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@repeaterjs/repeater/-/repeater-3.0.6.tgz", + "integrity": "sha512-Javneu5lsuhwNCryN+pXH93VPQ8g0dBX7wItHFgYiwQmzE1sVdg5tWHiOgHywzL2W21XQopa7IwIEnNbmeUJYA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/plugin-commonjs": { + "version": "28.0.5", + "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-28.0.5.tgz", + "integrity": "sha512-lytLp2JgAMwqJY6ve3OSROXr2XuEYHjnsQN3hmnxC+w11dI91LuUw4Yc1kk2FqKXeMG8psoFejFgK+znoij0cg==", + "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.1.4", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.4.tgz", + "integrity": "sha512-USm05zrsFxYLPdWWq+K3STlWiT/3ELn3RcV5hJMghpeAIhxfsUIg6mt12CBJBInWMV4VneoV7SfGv8xIwo2qNQ==", + "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.43.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.43.0.tgz", + "integrity": "sha512-Krjy9awJl6rKbruhQDgivNbD1WuLb8xAclM4IR4cN5pHGAs2oIMMQJEiC3IC/9TZJ+QZkmZhlMO/6MBGxPidpw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.43.0.tgz", + "integrity": "sha512-ss4YJwRt5I63454Rpj+mXCXicakdFmKnUNxr1dLK+5rv5FJgAxnN7s31a5VchRYxCFWdmnDWKd0wbAdTr0J5EA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.43.0.tgz", + "integrity": "sha512-eKoL8ykZ7zz8MjgBenEF2OoTNFAPFz1/lyJ5UmmFSz5jW+7XbH1+MAgCVHy72aG59rbuQLcJeiMrP8qP5d/N0A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.43.0.tgz", + "integrity": "sha512-SYwXJgaBYW33Wi/q4ubN+ldWC4DzQY62S4Ll2dgfr/dbPoF50dlQwEaEHSKrQdSjC6oIe1WgzosoaNoHCdNuMg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.43.0.tgz", + "integrity": "sha512-SV+U5sSo0yujrjzBF7/YidieK2iF6E7MdF6EbYxNz94lA+R0wKl3SiixGyG/9Klab6uNBIqsN7j4Y/Fya7wAjQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.43.0.tgz", + "integrity": "sha512-J7uCsiV13L/VOeHJBo5SjasKiGxJ0g+nQTrBkAsmQBIdil3KhPnSE9GnRon4ejX1XDdsmK/l30IYLiAaQEO0Cg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.43.0.tgz", + "integrity": "sha512-gTJ/JnnjCMc15uwB10TTATBEhK9meBIY+gXP4s0sHD1zHOaIh4Dmy1X9wup18IiY9tTNk5gJc4yx9ctj/fjrIw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.43.0.tgz", + "integrity": "sha512-ZJ3gZynL1LDSIvRfz0qXtTNs56n5DI2Mq+WACWZ7yGHFUEirHBRt7fyIk0NsCKhmRhn7WAcjgSkSVVxKlPNFFw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.43.0.tgz", + "integrity": "sha512-8FnkipasmOOSSlfucGYEu58U8cxEdhziKjPD2FIa0ONVMxvl/hmONtX/7y4vGjdUhjcTHlKlDhw3H9t98fPvyA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.43.0.tgz", + "integrity": "sha512-KPPyAdlcIZ6S9C3S2cndXDkV0Bb1OSMsX0Eelr2Bay4EsF9yi9u9uzc9RniK3mcUGCLhWY9oLr6er80P5DE6XA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loongarch64-gnu": { + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.43.0.tgz", + "integrity": "sha512-HPGDIH0/ZzAZjvtlXj6g+KDQ9ZMHfSP553za7o2Odegb/BEfwJcR0Sw0RLNpQ9nC6Gy8s+3mSS9xjZ0n3rhcYg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.43.0.tgz", + "integrity": "sha512-gEmwbOws4U4GLAJDhhtSPWPXUzDfMRedT3hFMyRAvM9Mrnj+dJIFIeL7otsv2WF3D7GrV0GIewW0y28dOYWkmw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.43.0.tgz", + "integrity": "sha512-XXKvo2e+wFtXZF/9xoWohHg+MuRnvO29TI5Hqe9xwN5uN8NKUYy7tXUG3EZAlfchufNCTHNGjEx7uN78KsBo0g==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.43.0.tgz", + "integrity": "sha512-ruf3hPWhjw6uDFsOAzmbNIvlXFXlBQ4nk57Sec8E8rUxs/AI4HD6xmiiasOOx/3QxS2f5eQMKTAwk7KHwpzr/Q==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.43.0.tgz", + "integrity": "sha512-QmNIAqDiEMEvFV15rsSnjoSmO0+eJLoKRD9EAa9rrYNwO/XRCtOGM3A5A0X+wmG+XRrw9Fxdsw+LnyYiZWWcVw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.43.0.tgz", + "integrity": "sha512-jAHr/S0iiBtFyzjhOkAics/2SrXE092qyqEg96e90L3t9Op8OTzS6+IX0Fy5wCt2+KqeHAkti+eitV0wvblEoQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.43.0.tgz", + "integrity": "sha512-3yATWgdeXyuHtBhrLt98w+5fKurdqvs8B53LaoKD7P7H7FKOONLsBVMNl9ghPQZQuYcceV5CDyPfyfGpMWD9mQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.43.0.tgz", + "integrity": "sha512-wVzXp2qDSCOpcBCT5WRWLmpJRIzv23valvcTwMHEobkjippNf+C3ys/+wf07poPkeNix0paTNemB2XrHr2TnGw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.43.0.tgz", + "integrity": "sha512-fYCTEyzf8d+7diCw8b+asvWDCLMjsCEA8alvtAutqJOJp/wL5hs1rWSqJ1vkjgW0L2NB4bsYJrpKkiIPRR9dvw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.43.0.tgz", + "integrity": "sha512-SnGhLiE5rlK0ofq8kzuDkM0g7FN1s5VYY+YSMTibP7CqShxCQvqtNxTARS4xX4PFJfHjG0ZQYX9iGzI3FQh5Aw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "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==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^8.9.0" + } + }, + "node_modules/@sveltejs/adapter-node": { + "version": "5.2.12", + "resolved": "https://registry.npmjs.org/@sveltejs/adapter-node/-/adapter-node-5.2.12.tgz", + "integrity": "sha512-0bp4Yb3jKIEcZWVcJC/L1xXp9zzJS4hDwfb4VITAkfT4OVdkspSHsx7YhqJDbb2hgLl6R9Vs7VQR+fqIVOxPUQ==", + "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.21.0", + "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.21.0.tgz", + "integrity": "sha512-kvu4h9qXduiPk1Q1oqFKDLFGu/7mslEYbVaqpbBcBxjlRJnvNCFwEvEwKt0Mx9TtSi8J77xRelvJobrGlst4nQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sveltejs/acorn-typescript": "^1.0.5", + "@types/cookie": "^0.6.0", + "acorn": "^8.14.1", + "cookie": "^0.6.0", + "devalue": "^5.1.0", + "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": { + "@sveltejs/vite-plugin-svelte": "^3.0.0 || ^4.0.0-next.1 || ^5.0.0", + "svelte": "^4.0.0 || ^5.0.0-next.0", + "vite": "^5.0.3 || ^6.0.0" + } + }, + "node_modules/@sveltejs/vite-plugin-svelte": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-5.1.0.tgz", + "integrity": "sha512-wojIS/7GYnJDYIg1higWj2ROA6sSRWvcR1PO/bqEyFr/5UZah26c8Cz4u0NaqjPeVltzsVpt2Tm8d2io0V+4Tw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sveltejs/vite-plugin-svelte-inspector": "^4.0.1", + "debug": "^4.4.1", + "deepmerge": "^4.3.1", + "kleur": "^4.1.5", + "magic-string": "^0.30.17", + "vitefu": "^1.0.6" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22" + }, + "peerDependencies": { + "svelte": "^5.0.0", + "vite": "^6.0.0" + } + }, + "node_modules/@sveltejs/vite-plugin-svelte-inspector": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte-inspector/-/vite-plugin-svelte-inspector-4.0.1.tgz", + "integrity": "sha512-J/Nmb2Q2y7mck2hyCX4ckVHcR5tu2J+MtBEQqpDrrgELZ2uvraQcK/ioCV61AqkdXFgriksOKIceDcQmqnGhVw==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.3.7" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22" + }, + "peerDependencies": { + "@sveltejs/vite-plugin-svelte": "^5.0.0", + "svelte": "^5.0.0", + "vite": "^6.0.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.10", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.10.tgz", + "integrity": "sha512-2ACf1znY5fpRBwRhMgj9ZXvb2XZW8qs+oTfotJ2C5xR0/WNL7UHZ7zXl6s+rUqedL1mNi+0O+WQr5awGowS3PQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ampproject/remapping": "^2.3.0", + "enhanced-resolve": "^5.18.1", + "jiti": "^2.4.2", + "lightningcss": "1.30.1", + "magic-string": "^0.30.17", + "source-map-js": "^1.2.1", + "tailwindcss": "4.1.10" + } + }, + "node_modules/@tailwindcss/oxide": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.10.tgz", + "integrity": "sha512-v0C43s7Pjw+B9w21htrQwuFObSkio2aV/qPx/mhrRldbqxbWJK6KizM+q7BF1/1CmuLqZqX3CeYF7s7P9fbA8Q==", + "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.10", + "@tailwindcss/oxide-darwin-arm64": "4.1.10", + "@tailwindcss/oxide-darwin-x64": "4.1.10", + "@tailwindcss/oxide-freebsd-x64": "4.1.10", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.10", + "@tailwindcss/oxide-linux-arm64-gnu": "4.1.10", + "@tailwindcss/oxide-linux-arm64-musl": "4.1.10", + "@tailwindcss/oxide-linux-x64-gnu": "4.1.10", + "@tailwindcss/oxide-linux-x64-musl": "4.1.10", + "@tailwindcss/oxide-wasm32-wasi": "4.1.10", + "@tailwindcss/oxide-win32-arm64-msvc": "4.1.10", + "@tailwindcss/oxide-win32-x64-msvc": "4.1.10" + } + }, + "node_modules/@tailwindcss/oxide-android-arm64": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.10.tgz", + "integrity": "sha512-VGLazCoRQ7rtsCzThaI1UyDu/XRYVyH4/EWiaSX6tFglE+xZB5cvtC5Omt0OQ+FfiIVP98su16jDVHDEIuH4iQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-darwin-arm64": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.10.tgz", + "integrity": "sha512-ZIFqvR1irX2yNjWJzKCqTCcHZbgkSkSkZKbRM3BPzhDL/18idA8uWCoopYA2CSDdSGFlDAxYdU2yBHwAwx8euQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-darwin-x64": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.10.tgz", + "integrity": "sha512-eCA4zbIhWUFDXoamNztmS0MjXHSEJYlvATzWnRiTqJkcUteSjO94PoRHJy1Xbwp9bptjeIxxBHh+zBWFhttbrQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-freebsd-x64": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.10.tgz", + "integrity": "sha512-8/392Xu12R0cc93DpiJvNpJ4wYVSiciUlkiOHOSOQNH3adq9Gi/dtySK7dVQjXIOzlpSHjeCL89RUUI8/GTI6g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.10.tgz", + "integrity": "sha512-t9rhmLT6EqeuPT+MXhWhlRYIMSfh5LZ6kBrC4FS6/+M1yXwfCtp24UumgCWOAJVyjQwG+lYva6wWZxrfvB+NhQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.10.tgz", + "integrity": "sha512-3oWrlNlxLRxXejQ8zImzrVLuZ/9Z2SeKoLhtCu0hpo38hTO2iL86eFOu4sVR8cZc6n3z7eRXXqtHJECa6mFOvA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-musl": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.10.tgz", + "integrity": "sha512-saScU0cmWvg/Ez4gUmQWr9pvY9Kssxt+Xenfx1LG7LmqjcrvBnw4r9VjkFcqmbBb7GCBwYNcZi9X3/oMda9sqQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-gnu": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.10.tgz", + "integrity": "sha512-/G3ao/ybV9YEEgAXeEg28dyH6gs1QG8tvdN9c2MNZdUXYBaIY/Gx0N6RlJzfLy/7Nkdok4kaxKPHKJUlAaoTdA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-musl": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.10.tgz", + "integrity": "sha512-LNr7X8fTiKGRtQGOerSayc2pWJp/9ptRYAa4G+U+cjw9kJZvkopav1AQc5HHD+U364f71tZv6XamaHKgrIoVzA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.10.tgz", + "integrity": "sha512-d6ekQpopFQJAcIK2i7ZzWOYGZ+A6NzzvQ3ozBvWFdeyqfOZdYHU66g5yr+/HC4ipP1ZgWsqa80+ISNILk+ae/Q==", + "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.3", + "@emnapi/runtime": "^1.4.3", + "@emnapi/wasi-threads": "^1.0.2", + "@napi-rs/wasm-runtime": "^0.2.10", + "@tybys/wasm-util": "^0.9.0", + "tslib": "^2.8.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.10.tgz", + "integrity": "sha512-i1Iwg9gRbwNVOCYmnigWCCgow8nDWSFmeTUU5nbNx3rqbe4p0kRbEqLwLJbYZKmSSp23g4N6rCDmm7OuPBXhDA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-win32-x64-msvc": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.10.tgz", + "integrity": "sha512-sGiJTjcBSfGq2DVRtaSljq5ZgZS2SDHSIfhOylkBvHVjwOsodBhnb3HdmiKkVuUGKD0I7G63abMOVaskj1KpOA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/typography": { + "version": "0.5.16", + "resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.16.tgz", + "integrity": "sha512-0wDLwCVF5V3x3b1SGXPCDcdsbDHMBe+lkFzBRaHeLvNi+nrrnZ1lA18u+OTWO8iSWU2GxUOCvlXtDuqftc1oiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "lodash.castarray": "^4.4.0", + "lodash.isplainobject": "^4.0.6", + "lodash.merge": "^4.6.2", + "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.10", + "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.1.10.tgz", + "integrity": "sha512-QWnD5HDY2IADv+vYR82lOhqOlS1jSCUUAmfem52cXAhRTKxpDh3ARX8TTXJTCCO7Rv7cD2Nlekabv02bwP3a2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tailwindcss/node": "4.1.10", + "@tailwindcss/oxide": "4.1.10", + "tailwindcss": "4.1.10" + }, + "peerDependencies": { + "vite": "^5.2.0 || ^6" + } + }, + "node_modules/@types/braces": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@types/braces/-/braces-3.0.5.tgz", + "integrity": "sha512-SQFof9H+LXeWNz8wDe7oN5zu7ket0qwMu5vZubW4GCJ8Kkeh6nBWUz87+KTz/G3Kqsrp0j/W253XJb3KMEeg3w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/cookie": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==", + "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==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/fs-extra": { + "version": "9.0.13", + "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-9.0.13.tgz", + "integrity": "sha512-nEnwB++1u5lVDM2UI4c1+5R+FYaKfaAzS4OococimjVm3nQw3TuzH5UNsocrcTBbhnerblyHj4A49qXbIiZdpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "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==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/micromatch": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@types/micromatch/-/micromatch-4.0.9.tgz", + "integrity": "sha512-7V+8ncr22h4UoYRLnLXSpTxjQrNUXtWHGeMPRJt1nULXI57G9bIcpyrHlmrQ7QK24EyyuXvYcSSWAM8GA9nqCg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/braces": "*" + } + }, + "node_modules/@types/node": { + "version": "24.0.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.0.1.tgz", + "integrity": "sha512-MX4Zioh39chHlDJbKmEgydJDS3tspMP/lnQC67G3SWsTnb9NeYVWOjkxpOSy4oMfPs4StcWHwBrvUb4ybfnuaw==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.8.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/@typescript-eslint/eslint-plugin": { + "version": "8.34.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.34.0.tgz", + "integrity": "sha512-QXwAlHlbcAwNlEEMKQS2RCgJsgXrTJdjXT08xEgbPFa2yYQgVjBymxP5DrfrE7X7iodSzd9qBUHUycdyVJTW1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "8.34.0", + "@typescript-eslint/type-utils": "8.34.0", + "@typescript-eslint/utils": "8.34.0", + "@typescript-eslint/visitor-keys": "8.34.0", + "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.34.0", + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.9.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.34.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.34.0.tgz", + "integrity": "sha512-vxXJV1hVFx3IXz/oy2sICsJukaBrtDEQSBiV48/YIV5KWjX1dO+bcIr/kCPrW6weKXvsaGKFNlwH0v2eYdRRbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.34.0", + "@typescript-eslint/types": "8.34.0", + "@typescript-eslint/typescript-estree": "8.34.0", + "@typescript-eslint/visitor-keys": "8.34.0", + "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 <5.9.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.34.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.34.0.tgz", + "integrity": "sha512-iEgDALRf970/B2YExmtPMPF54NenZUf4xpL3wsCRx/lgjz6ul/l13R81ozP/ZNuXfnLCS+oPmG7JIxfdNYKELw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.34.0", + "@typescript-eslint/types": "^8.34.0", + "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 <5.9.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.34.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.34.0.tgz", + "integrity": "sha512-9Ac0X8WiLykl0aj1oYQNcLZjHgBojT6cW68yAgZ19letYu+Hxd0rE0veI1XznSSst1X5lwnxhPbVdwjDRIomRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.34.0", + "@typescript-eslint/visitor-keys": "8.34.0" + }, + "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.34.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.34.0.tgz", + "integrity": "sha512-+W9VYHKFIzA5cBeooqQxqNriAP0QeQ7xTiDuIOr71hzgffm3EL2hxwWBIIj4GuofIbKxGNarpKqIq6Q6YrShOA==", + "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 <5.9.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.34.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.34.0.tgz", + "integrity": "sha512-n7zSmOcUVhcRYC75W2pnPpbO1iwhJY3NLoHEtbJwJSNlVAZuwqu05zY3f3s2SDWWDSo9FdN5szqc73DCtDObAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/typescript-estree": "8.34.0", + "@typescript-eslint/utils": "8.34.0", + "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 <5.9.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.34.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.34.0.tgz", + "integrity": "sha512-9V24k/paICYPniajHfJ4cuAWETnt7Ssy+R0Rbcqo5sSFr3QEZ/8TSoUi9XeXVBGXCaLtwTOKSLGcInCAvyZeMA==", + "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.34.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.34.0.tgz", + "integrity": "sha512-rOi4KZxI7E0+BMqG7emPSK1bB4RICCpF7QD3KCLXn9ZvWoESsOMlHyZPAHyG04ujVplPaHbmEvs34m+wjgtVtg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.34.0", + "@typescript-eslint/tsconfig-utils": "8.34.0", + "@typescript-eslint/types": "8.34.0", + "@typescript-eslint/visitor-keys": "8.34.0", + "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 <5.9.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.34.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.34.0.tgz", + "integrity": "sha512-8L4tWatGchV9A1cKbjaavS6mwYwp39jql8xUmIIKJdm+qiaeHy5KMKlBrf30akXAWBzn2SqKsNOtSENWUwg7XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.7.0", + "@typescript-eslint/scope-manager": "8.34.0", + "@typescript-eslint/types": "8.34.0", + "@typescript-eslint/typescript-estree": "8.34.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 <5.9.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.34.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.34.0.tgz", + "integrity": "sha512-qHV7pW7E85A0x6qyrFn+O+q1k1p3tQCsqIZ1KZ5ESLXY57aTvUd3/a4rdPTeXisvhXn2VQG0VSKUqs8KHF2zcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.34.0", + "eslint-visitor-keys": "^4.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@ungap/structured-clone": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", + "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", + "dev": true, + "license": "ISC" + }, + "node_modules/@whatwg-node/disposablestack": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/@whatwg-node/disposablestack/-/disposablestack-0.0.6.tgz", + "integrity": "sha512-LOtTn+JgJvX8WfBVJtF08TGrdjuFzGJc4mkP8EdDI8ADbvO7kiexYep1o8dwnt0okb0jYclCDXF13xU7Ge4zSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@whatwg-node/promise-helpers": "^1.0.0", + "tslib": "^2.6.3" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@whatwg-node/events": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@whatwg-node/events/-/events-0.1.2.tgz", + "integrity": "sha512-ApcWxkrs1WmEMS2CaLLFUEem/49erT3sxIVjpzU5f6zmVcnijtDSrhoK2zVobOIikZJdH63jdAXOrvjf6eOUNQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.6.3" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@whatwg-node/fetch": { + "version": "0.10.8", + "resolved": "https://registry.npmjs.org/@whatwg-node/fetch/-/fetch-0.10.8.tgz", + "integrity": "sha512-Rw9z3ctmeEj8QIB9MavkNJqekiu9usBCSMZa+uuAvM0lF3v70oQVCXNppMIqaV6OTZbdaHF1M2HLow58DEw+wg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@whatwg-node/node-fetch": "^0.7.21", + "urlpattern-polyfill": "^10.0.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@whatwg-node/node-fetch": { + "version": "0.7.21", + "resolved": "https://registry.npmjs.org/@whatwg-node/node-fetch/-/node-fetch-0.7.21.tgz", + "integrity": "sha512-QC16IdsEyIW7kZd77aodrMO7zAoDyyqRCTLg+qG4wqtP4JV9AA+p7/lgqMdD29XyiYdVvIdFrfI9yh7B1QvRvw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@fastify/busboy": "^3.1.1", + "@whatwg-node/disposablestack": "^0.0.6", + "@whatwg-node/promise-helpers": "^1.3.2", + "tslib": "^2.6.3" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@whatwg-node/promise-helpers": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@whatwg-node/promise-helpers/-/promise-helpers-1.3.2.tgz", + "integrity": "sha512-Nst5JdK47VIl9UcGwtv2Rcgyn5lWtZ0/mhRQ4G8NN2isxpq2TO30iqHzmwoJycjWuyUfg3GFXqP/gFHXeV57IA==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.6.3" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@whatwg-node/server": { + "version": "0.9.71", + "resolved": "https://registry.npmjs.org/@whatwg-node/server/-/server-0.9.71.tgz", + "integrity": "sha512-ueFCcIPaMgtuYDS9u0qlUoEvj6GiSsKrwnOLPp9SshqjtcRaR1IEHRjoReq3sXNydsF5i0ZnmuYgXq9dV53t0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@whatwg-node/disposablestack": "^0.0.6", + "@whatwg-node/fetch": "^0.10.5", + "@whatwg-node/promise-helpers": "^1.2.2", + "tslib": "^2.6.3" + }, + "engines": { + "node": ">=18.0.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==", + "dev": true, + "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-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "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/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==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ast-types": { + "version": "0.16.1", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.16.1.tgz", + "integrity": "sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "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==", + "dev": true, + "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/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/builtins": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/builtins/-/builtins-5.1.0.tgz", + "integrity": "sha512-SW9lzGTLvWTP1AY8xeAMZimqDrIaSdLQUcVr9DMef51niJ022Ri87SwRRKYm4A6iHfkPaiVUu/Duw2Wc4J7kKg==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.0.0" + } + }, + "node_modules/busboy": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", + "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", + "dev": true, + "dependencies": { + "streamsearch": "^1.1.0" + }, + "engines": { + "node": ">=10.16.0" + } + }, + "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/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/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "dev": true, + "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": "9.5.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", + "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || >=14" + } + }, + "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/cookie": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cross-inspect": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cross-inspect/-/cross-inspect-1.0.1.tgz", + "integrity": "sha512-Pcw1JTvZLSJH83iiGWt6fRcT+BjZlCDRVwYLbUcHzv/CRpB7r0MlSrGbIyQvVSNyGnbt7G4AXuyCiDR3POvZ1A==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.4.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "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/data-uri-to-buffer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/detect-libc": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz", + "integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/devalue": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.1.1.tgz", + "integrity": "sha512-maua5KUiapvEwiEAe+XnlZ3Rh0GD+qI1J/nb9vrJc3muPXvcF/8gXYTWF76+5DAqHyDUtOIImEuo0YKE9mshVw==", + "dev": true, + "license": "MIT" + }, + "node_modules/dset": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/dset/-/dset-3.1.4.tgz", + "integrity": "sha512-2QF/g9/zTaPDc3BjNcVTGoBbXBgYfMTTceLaYcFJ/W9kggFUkhxD/hMEeuLKbugyef9SqAx8cpgwlIP/jinUTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, + "license": "MIT" + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/enhanced-resolve": { + "version": "5.18.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.1.tgz", + "integrity": "sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/esbuild": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.5.tgz", + "integrity": "sha512-P8OtKZRv/5J5hhz0cUAdu/cLuPIKXpQl1R9pZtvmHWQvrAUVd0UNIPT4IB4W3rNOqVO0rlqHmCIbSwxh/c9yUQ==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.5", + "@esbuild/android-arm": "0.25.5", + "@esbuild/android-arm64": "0.25.5", + "@esbuild/android-x64": "0.25.5", + "@esbuild/darwin-arm64": "0.25.5", + "@esbuild/darwin-x64": "0.25.5", + "@esbuild/freebsd-arm64": "0.25.5", + "@esbuild/freebsd-x64": "0.25.5", + "@esbuild/linux-arm": "0.25.5", + "@esbuild/linux-arm64": "0.25.5", + "@esbuild/linux-ia32": "0.25.5", + "@esbuild/linux-loong64": "0.25.5", + "@esbuild/linux-mips64el": "0.25.5", + "@esbuild/linux-ppc64": "0.25.5", + "@esbuild/linux-riscv64": "0.25.5", + "@esbuild/linux-s390x": "0.25.5", + "@esbuild/linux-x64": "0.25.5", + "@esbuild/netbsd-arm64": "0.25.5", + "@esbuild/netbsd-x64": "0.25.5", + "@esbuild/openbsd-arm64": "0.25.5", + "@esbuild/openbsd-x64": "0.25.5", + "@esbuild/sunos-x64": "0.25.5", + "@esbuild/win32-arm64": "0.25.5", + "@esbuild/win32-ia32": "0.25.5", + "@esbuild/win32-x64": "0.25.5" + } + }, + "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/eslint": { + "version": "9.29.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.29.0.tgz", + "integrity": "sha512-GsGizj2Y1rCWDu6XoEekL3RLilp0voSePurjZIkxL3wlm5o5EC9VpgaP7lrCvjnkuLvzFBQWB3vWB3K5KQTveQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.20.1", + "@eslint/config-helpers": "^0.2.1", + "@eslint/core": "^0.14.0", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.29.0", + "@eslint/plugin-kit": "^0.3.1", + "@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.5", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.5.tgz", + "integrity": "sha512-zc1UmCpNltmVY34vuLRV61r1K27sWuX39E+uyUnY8xS2Bex88VV9cugG+UZbRSRGtGyFboj+D8JODyme1plMpw==", + "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.9.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-svelte/-/eslint-plugin-svelte-3.9.2.tgz", + "integrity": "sha512-aqzfHtG9RPaFhCUFm5QFC6eFY/yHFQIT8VYYFe7/mT2A9mbgVR3XV2keCqU19LN8iVD9mdvRvqHU+4+CzJImvg==", + "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.36.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.2.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-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/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==", + "dev": true, + "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": "1.4.9", + "resolved": "https://registry.npmjs.org/esrap/-/esrap-1.4.9.tgz", + "integrity": "sha512-3OMlcd0a03UGuZpPeUC1HxR3nA23l+HEyCiZw3b3FumJIN9KphoGzDJKMXI1S72jVS1dsenDyQC0kJlO1U9E1g==", + "dev": true, + "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/execa": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-6.1.0.tgz", + "integrity": "sha512-QVWlX2e50heYJcCPG0iWtf8r0xjEYfz/OYLGDYH+IyjWezzPNxz63qNFOu0l4YftGWuizFVZHHs8PrLU5p2IDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.1", + "human-signals": "^3.0.1", + "is-stream": "^3.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^5.1.0", + "onetime": "^6.0.0", + "signal-exit": "^3.0.7", + "strip-final-newline": "^3.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/execa/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/fast-decode-uri-component": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/fast-decode-uri-component/-/fast-decode-uri-component-1.0.1.tgz", + "integrity": "sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg==", + "dev": true, + "license": "MIT" + }, + "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/fast-querystring": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/fast-querystring/-/fast-querystring-1.1.2.tgz", + "integrity": "sha512-g6KuKWmFXc0fID8WWH0jit4g0AGBoJhCkJMb1RmbsSEUNvQ+ZC8D6CUZ+GtF8nMzSPXnhiePyyqqipzNNEnHjg==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-decode-uri-component": "^1.0.1" + } + }, + "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.4.6", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz", + "integrity": "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fetch-blob": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "paypal", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "dependencies": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + }, + "engines": { + "node": "^12.20 || >= 14.13" + } + }, + "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/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fetch-blob": "^3.1.2" + }, + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/fs-monkey": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.0.6.tgz", + "integrity": "sha512-b1FMfwetIKymC0eioW7mTywihSQE4oLzQn1dB6rZB5fx/3NpNEdAWeCSMB+60/AeT0TCXsxzAlcYVEFCTAksWg==", + "dev": true, + "license": "Unlicense" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "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/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/glob": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-11.0.3.tgz", + "integrity": "sha512-2Nim7dha1KVkaiF4q6Dj+ngPPMdfvLJEOpZk/jKiUAkqKebpGAWQXAq9z1xu9HKu5lWfqw/FASuccEjyznjPaA==", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.3.1", + "jackspeak": "^4.1.1", + "minimatch": "^10.0.3", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^2.0.0" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "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/glob/node_modules/minimatch": { + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.3.tgz", + "integrity": "sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw==", + "dev": true, + "license": "ISC", + "dependencies": { + "@isaacs/brace-expansion": "^5.0.0" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/globals": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-16.2.0.tgz", + "integrity": "sha512-O+7l9tPdHCU320IigZZPj5zmRCFG9xHmx9cU8FqU2Rp+JN714seHV+2S9+JslCpY4gJwU2vOGox0wzgae/MCEg==", + "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/graphql": { + "version": "15.10.1", + "resolved": "https://registry.npmjs.org/graphql/-/graphql-15.10.1.tgz", + "integrity": "sha512-BL/Xd/T9baO6NFzoMpiMD7YUZ62R6viR5tp/MULVEnbYJXZA//kRNW7J0j1w/wXArgL0sCxhDfK5dczSKn3+cg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.x" + } + }, + "node_modules/graphql-yoga": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/graphql-yoga/-/graphql-yoga-4.0.5.tgz", + "integrity": "sha512-vIbJU9QX5RP4PoxbMCHcfOlt/3EsC/0uLdAOlKaiUvlwJDTFCaIHo2X10vL4i/27Gw8g90ECIwm2YbmeLDwcqg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@envelop/core": "^4.0.0", + "@graphql-tools/executor": "^1.0.0", + "@graphql-tools/schema": "^10.0.0", + "@graphql-tools/utils": "^10.0.0", + "@graphql-yoga/logger": "^1.0.0", + "@graphql-yoga/subscription": "^4.0.0", + "@whatwg-node/fetch": "^0.9.7", + "@whatwg-node/server": "^0.9.1", + "dset": "^3.1.1", + "lru-cache": "^10.0.0", + "tslib": "^2.5.2" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "graphql": "^15.2.0 || ^16.0.0" + } + }, + "node_modules/graphql-yoga/node_modules/@graphql-tools/schema": { + "version": "10.0.23", + "resolved": "https://registry.npmjs.org/@graphql-tools/schema/-/schema-10.0.23.tgz", + "integrity": "sha512-aEGVpd1PCuGEwqTXCStpEkmheTHNdMayiIKH1xDWqYp9i8yKv9FRDgkGrY4RD8TNxnf7iII+6KOBGaJ3ygH95A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@graphql-tools/merge": "^9.0.24", + "@graphql-tools/utils": "^10.8.6", + "tslib": "^2.4.0" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/graphql-yoga/node_modules/@whatwg-node/fetch": { + "version": "0.9.23", + "resolved": "https://registry.npmjs.org/@whatwg-node/fetch/-/fetch-0.9.23.tgz", + "integrity": "sha512-7xlqWel9JsmxahJnYVUj/LLxWcnA93DR4c9xlw3U814jWTiYalryiH1qToik1hOxweKKRLi4haXHM5ycRksPBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@whatwg-node/node-fetch": "^0.6.0", + "urlpattern-polyfill": "^10.0.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/graphql-yoga/node_modules/@whatwg-node/node-fetch": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@whatwg-node/node-fetch/-/node-fetch-0.6.0.tgz", + "integrity": "sha512-tcZAhrpx6oVlkEsRngeTEEE7I5/QdLjeEz4IlekabGaESP7+Dkm/6a9KcF1KdCBB7mO9PXtBkwCuTCt8+UPg8Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@kamilkisiela/fast-url-parser": "^1.1.4", + "busboy": "^1.6.0", + "fast-querystring": "^1.1.1", + "tslib": "^2.6.3" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "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/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/houdini": { + "version": "1.5.7", + "resolved": "https://registry.npmjs.org/houdini/-/houdini-1.5.7.tgz", + "integrity": "sha512-3VfFKgsYICJikuQ1EsL+tb8jdVQtHvv4BI4FHv7lfPkV+XsgWJirOfTTUCVC7EuWb19XRg1v7ZxJ+palZjM83w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.24.6", + "@clack/prompts": "^0.6.3", + "@graphql-tools/merge": "^9.0.0", + "@graphql-tools/schema": "^9.0.4", + "@kitql/helpers": "^0.8.2", + "@types/estree": "^1.0.0", + "@types/fs-extra": "^9.0.13", + "@types/micromatch": "^4.0.2", + "@ungap/structured-clone": "^1.0.2", + "@whatwg-node/server": "^0.9.14", + "ast-types": "^0.16.1", + "commander": "^9.4.0", + "deepmerge": "^4.2.2", + "estree-walker": "^3.0.1", + "fs-extra": "^10.1.0", + "glob": "^11.0.1", + "graphql": "^15.8.0", + "graphql-yoga": "^4.0.4", + "memfs": "^3.4.7", + "micromatch": "^4.0.5", + "minimatch": "^5.1.0", + "node-fetch": "^3.2.10", + "npx-import": "^1.1.3", + "recast": "^0.23.1", + "vite-plugin-watch-and-run": "^1.7.0" + }, + "bin": { + "houdini": "build/cmd-esm/index.js" + }, + "funding": { + "url": "https://github.com/sponsors/HoudiniGraphql" + }, + "peerDependencies": { + "vite": "^5.3.3 || ^6.0.3" + } + }, + "node_modules/houdini-svelte": { + "version": "2.1.17", + "resolved": "https://registry.npmjs.org/houdini-svelte/-/houdini-svelte-2.1.17.tgz", + "integrity": "sha512-uvoLX6MvE9nuxVRr9fxOAqNf/iu55Gj1tpYKRMNcU3HAmnZBZamlQIHgK4wSKIV6mb+1pAiyfqSCSQmrryM2sA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@kitql/helpers": "^0.8.2", + "ast-types": "^0.16.1", + "estree-walker": "^3.0.1", + "graphql": "^15.8.0", + "houdini": "^1.5.7", + "recast": "^0.23.1", + "rollup": "^4.28.1" + }, + "funding": { + "url": "https://github.com/sponsors/HoudiniGraphql" + }, + "peerDependencies": { + "@sveltejs/kit": "<=2.21.0", + "svelte": "^5.0.0", + "vite": "^5.3.3 || ^6.0.3" + } + }, + "node_modules/houdini-svelte/node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/houdini/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/houdini/node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/houdini/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/human-signals": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-3.0.1.tgz", + "integrity": "sha512-rQLskxnM/5OCldHo+wNXbpVgDn5A17CUoKX+7Sokwaknlq7CdSnphy0W39GU8dw59XiCXmFXDg4fRuckQRKewQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.20.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/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-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "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/is-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", + "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/jackspeak": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.1.1.tgz", + "integrity": "sha512-zptv57P3GpL+O0I7VdMJNBZCu+BPHVQUk55Ft8/QCJjTVxrnJHuVuX/0Bl2A6/+2oyR/ZMEuFKwmzqqZ/U5nPQ==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/jiti": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.4.2.tgz", + "integrity": "sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "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-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/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/known-css-properties": { + "version": "0.36.0", + "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.36.0.tgz", + "integrity": "sha512-A+9jP+IUmuQsNdsLdcg6Yt7voiMF/D4K83ew0OpJtpu+l34ef7LaohWV0Rc6KNvzw6ZDizkqfyB5JznZnzuKQA==", + "dev": true, + "license": "MIT" + }, + "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/lightningcss": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.1.tgz", + "integrity": "sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg==", + "dev": 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" + ], + "dev": true, + "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" + ], + "dev": true, + "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" + ], + "dev": true, + "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" + ], + "dev": true, + "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" + ], + "dev": true, + "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" + ], + "dev": true, + "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" + ], + "dev": true, + "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" + ], + "dev": true, + "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" + ], + "dev": true, + "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" + ], + "dev": true, + "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==", + "dev": true, + "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.castarray": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.castarray/-/lodash.castarray-4.4.0.tgz", + "integrity": "sha512-aVx8ztPv7/2ULbArGJ2Y42bG1mEQ5mGjpdvrbJcJFU3TbYybe+QlLS4pst9zV52ymy2in1KpFPiZnAOATxD4+Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "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/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/magic-string": { + "version": "0.30.17", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", + "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0" + } + }, + "node_modules/memfs": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.5.3.tgz", + "integrity": "sha512-UERzLsxzllchadvbPs5aolHh65ISpKpM+ccLbOJ8/vvpBKmAWf+la7dXFy7Mr0ySHbdHrFv5kGFCUHHe6GFEmw==", + "dev": true, + "license": "Unlicense", + "dependencies": { + "fs-monkey": "^1.0.4" + }, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true, + "license": "MIT" + }, + "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-fn": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", + "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "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.0.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.0.2.tgz", + "integrity": "sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA==", + "dev": true, + "license": "MIT", + "dependencies": { + "minipass": "^7.1.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/mkdirp": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz", + "integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==", + "dev": true, + "license": "MIT", + "bin": { + "mkdirp": "dist/cjs/src/bin.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/mri": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", + "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", + "dev": true, + "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==", + "dev": true, + "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==", + "dev": true, + "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==", + "dev": true, + "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-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "deprecated": "Use your platform's native DOMException instead", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "engines": { + "node": ">=10.5.0" + } + }, + "node_modules/node-fetch": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" + } + }, + "node_modules/npm-run-path": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz", + "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm-run-path/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npx-import": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/npx-import/-/npx-import-1.1.4.tgz", + "integrity": "sha512-3ShymTWOgqGyNlh5lMJAejLuIv3W1K3fbI5Ewc6YErZU3Sp0PqsNs8UIU1O8z5+KVl/Du5ag56Gza9vdorGEoA==", + "dev": true, + "license": "ISC", + "dependencies": { + "execa": "^6.1.0", + "parse-package-name": "^1.0.0", + "semver": "^7.3.7", + "validate-npm-package-name": "^4.0.0" + } + }, + "node_modules/onetime": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", + "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true, + "license": "BlueOak-1.0.0" + }, + "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/parse-package-name": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/parse-package-name/-/parse-package-name-1.0.0.tgz", + "integrity": "sha512-kBeTUtcj+SkyfaW4+KBe0HtsloBJ/mKTPoxpVdA57GZiPerREsUWJOhVj9anXweFiJkm5y8FG1sxFZkZ0SN6wg==", + "dev": true, + "license": "MIT" + }, + "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==", + "dev": true, + "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/path-scurry": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.0.tgz", + "integrity": "sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.1.0.tgz", + "integrity": "sha512-QIXZUBJUx+2zHUdQujWejBkcD9+cs94tLn0+YL8UrCh+D5sCXZ4c7LaEH48pNwRY3MLDgqUFyhlCyjJPf1WP0A==", + "dev": true, + "license": "ISC", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.5", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.5.tgz", + "integrity": "sha512-d/jtm+rdNT8tpXuHY5MMtcbJFBkhXE6593XVR9UoGCH8jSFGci7jGvMGH5RYd5PBJW+00NZQt6gf7CbagJCrhg==", + "dev": true, + "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/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.5.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.3.tgz", + "integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==", + "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.12", + "resolved": "https://registry.npmjs.org/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.6.12.tgz", + "integrity": "sha512-OuTQKoqNwV7RnxTPwXWzOFXy6Jc4z8oeRZYGuMpRyG3WbuR3jjXdQFK8qFBMBx8UHWdHrddARz2fgUenild6aw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.21.3" + }, + "peerDependencies": { + "@ianvs/prettier-plugin-sort-imports": "*", + "@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-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/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/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/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/recast": { + "version": "0.23.11", + "resolved": "https://registry.npmjs.org/recast/-/recast-0.23.11.tgz", + "integrity": "sha512-YTUo+Flmw4ZXiWfQKGcwwc11KnoRAYgzAE2E7mXKCjSviTKShtxBsN6YUUBB2gtaBzKzeKunxhUwNHQuRryhWA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ast-types": "^0.16.1", + "esprima": "~4.0.0", + "source-map": "~0.6.1", + "tiny-invariant": "^1.3.3", + "tslib": "^2.0.1" + }, + "engines": { + "node": ">= 4" + } + }, + "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/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/rollup": { + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.43.0.tgz", + "integrity": "sha512-wdN2Kd3Twh8MAEOEJZsuxuLKCsBEo4PVNLK6tQWAn10VhsVewQLzcucMgLolRlhFybGxfclbPeEYBaP6RvUFGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.7" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.43.0", + "@rollup/rollup-android-arm64": "4.43.0", + "@rollup/rollup-darwin-arm64": "4.43.0", + "@rollup/rollup-darwin-x64": "4.43.0", + "@rollup/rollup-freebsd-arm64": "4.43.0", + "@rollup/rollup-freebsd-x64": "4.43.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.43.0", + "@rollup/rollup-linux-arm-musleabihf": "4.43.0", + "@rollup/rollup-linux-arm64-gnu": "4.43.0", + "@rollup/rollup-linux-arm64-musl": "4.43.0", + "@rollup/rollup-linux-loongarch64-gnu": "4.43.0", + "@rollup/rollup-linux-powerpc64le-gnu": "4.43.0", + "@rollup/rollup-linux-riscv64-gnu": "4.43.0", + "@rollup/rollup-linux-riscv64-musl": "4.43.0", + "@rollup/rollup-linux-s390x-gnu": "4.43.0", + "@rollup/rollup-linux-x64-gnu": "4.43.0", + "@rollup/rollup-linux-x64-musl": "4.43.0", + "@rollup/rollup-win32-arm64-msvc": "4.43.0", + "@rollup/rollup-win32-ia32-msvc": "4.43.0", + "@rollup/rollup-win32-x64-msvc": "4.43.0", + "fsevents": "~2.3.2" + } + }, + "node_modules/rollup/node_modules/@types/estree": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", + "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==", + "dev": true, + "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/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==", + "dev": true, + "license": "MIT", + "dependencies": { + "mri": "^1.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "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==", + "dev": 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==", + "dev": true, + "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==", + "dev": true, + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/sirv": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/sirv/-/sirv-3.0.1.tgz", + "integrity": "sha512-FoqMu0NCGBLCcAkS1qA+XJIQTR6/JHfQXl+uGteNCQ76T91DMUjPa9xfmeqMY3z80nLSg9yQmNjK0Px6RWsH/A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@polka/url": "^1.0.0-next.24", + "mrmime": "^2.0.0", + "totalist": "^3.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true, + "license": "MIT" + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=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==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/streamsearch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", + "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", + "dev": true, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/string-width-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-final-newline": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", + "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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/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.34.3", + "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.34.3.tgz", + "integrity": "sha512-Y0QKP2rfWD+ARKe91c4JgZgc/nXa2BfOnVBUjYUMB819m7VyPszihkjdzXPIV0qlGRZYEukpgNq7hgbzTbopJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ampproject/remapping": "^2.3.0", + "@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": "^1.4.8", + "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.2.1", + "resolved": "https://registry.npmjs.org/svelte-check/-/svelte-check-4.2.1.tgz", + "integrity": "sha512-e49SU1RStvQhoipkQ/aonDhHnG3qxHSBtNfBRb9pxVXoa+N7qybAo32KgA9wEb2PCYFNaDg7bZCdhLD1vHpdYA==", + "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.2.0", + "resolved": "https://registry.npmjs.org/svelte-eslint-parser/-/svelte-eslint-parser-1.2.0.tgz", + "integrity": "sha512-mbPtajIeuiyU80BEyGvwAktBeTX7KCr5/0l+uRGLq1dafwRNrjfM5kHGJScEBlPG3ipu6dJqfW/k0/fujvIEVw==", + "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/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==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.6" + } + }, + "node_modules/tailwindcss": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.10.tgz", + "integrity": "sha512-P3nr6WkvKV/ONsTzj6Gb57sWPMX29EPNPopo7+FcpkQaNsrNpZ1pv8QmrYI2RqEKD7mlGqLnGovlcYnBK0IqUA==", + "dev": true, + "license": "MIT" + }, + "node_modules/tapable": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.2.tgz", + "integrity": "sha512-Re10+NauLTMCudc7T5WLFLAwDhQ0JWdrMK+9B2M8zR5hRExKmsRDCBA7/aV/pNJFltmBFO5BAMlQFi/vq3nKOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/tar": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.4.3.tgz", + "integrity": "sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==", + "dev": true, + "license": "ISC", + "dependencies": { + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.0.1", + "mkdirp": "^3.0.1", + "yallist": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/tiny-invariant": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", + "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyglobby": { + "version": "0.2.14", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz", + "integrity": "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.4.4", + "picomatch": "^4.0.2" + }, + "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/totalist": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", + "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "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/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/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/typescript": { + "version": "5.8.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", + "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/typescript-eslint": { + "version": "8.34.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.34.0.tgz", + "integrity": "sha512-MRpfN7uYjTrTGigFCt8sRyNqJFhjN0WwZecldaqhWm+wy0gaRt8Edb/3cuUy0zdq2opJWT6iXINKAtewnDOltQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.34.0", + "@typescript-eslint/parser": "8.34.0", + "@typescript-eslint/utils": "8.34.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 <5.9.0" + } + }, + "node_modules/undici-types": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.8.0.tgz", + "integrity": "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==", + "dev": true, + "license": "MIT" + }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.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/validate-npm-package-name": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-4.0.0.tgz", + "integrity": "sha512-mzR0L8ZDktZjpX4OB46KT+56MAhl4EIazWP/+G/HPGuvfdaqg4YsCdtOm6U9+LOFyYDoh4dpnpxZRB9MQQns5Q==", + "dev": true, + "license": "ISC", + "dependencies": { + "builtins": "^5.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/value-or-promise": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/value-or-promise/-/value-or-promise-1.0.12.tgz", + "integrity": "sha512-Z6Uz+TYwEqE7ZN50gwn+1LCVo9ZVrpxRPOhOLnncYkY1ZzOYtrX8Fwf/rFktZ8R5mJms6EZf5TqNOMeZmnPq9Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/vite": { + "version": "6.3.5", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.5.tgz", + "integrity": "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "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-plugin-watch-and-run": { + "version": "1.7.5", + "resolved": "https://registry.npmjs.org/vite-plugin-watch-and-run/-/vite-plugin-watch-and-run-1.7.5.tgz", + "integrity": "sha512-IISx6Cw+w/9HhxaovnJlXaEAnPlA5wLY8lk0CMrFkBgdJdhKElH0jGSjf6gCPti/Z9F4kMM8WUMGx8CQm1BpHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@kitql/helpers": "0.8.13", + "micromatch": "4.0.8" + }, + "engines": { + "node": "^16.14 || >=18" + }, + "funding": { + "url": "https://github.com/sponsors/jycouet" + } + }, + "node_modules/vitefu": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-1.0.6.tgz", + "integrity": "sha512-+Rex1GlappUyNN6UfwbVZne/9cYC4+R2XDk9xkNXBKMw6HQagdX9PgZ8V2v1WUSK1wfBLp7qbI1+XSNIlB1xmA==", + "dev": true, + "license": "MIT", + "workspaces": [ + "tests/deps/*", + "tests/projects/*" + ], + "peerDependencies": { + "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0" + }, + "peerDependenciesMeta": { + "vite": { + "optional": true + } + } + }, + "node_modules/web-streams-polyfill": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", + "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "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/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "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/yaml": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.0.tgz", + "integrity": "sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ==", + "dev": true, + "license": "ISC", + "optional": true, + "peer": true, + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + } + }, + "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/zimmerframe": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/zimmerframe/-/zimmerframe-1.1.2.tgz", + "integrity": "sha512-rAbqEGa8ovJy4pyBxZM70hg4pE6gDgaQ0Sl9M3enG3I0d6H4XSAM3GeNGLKnsBpuijUow064sf7ww1nutC5/3w==", + "dev": true, + "license": "MIT" + } + } +} diff --git a/frontend/package.json b/frontend/package.json new file mode 100644 index 0000000..87fa210 --- /dev/null +++ b/frontend/package.json @@ -0,0 +1,41 @@ +{ + "name": "frontend", + "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 ." + }, + "devDependencies": { + "@eslint/compat": "^1.2.5", + "@eslint/js": "^9.18.0", + "@sveltejs/adapter-node": "^5.2.12", + "@sveltejs/kit": "<=2.21.0", + "@sveltejs/vite-plugin-svelte": "^5.0.0", + "@tailwindcss/forms": "^0.5.9", + "@tailwindcss/typography": "^0.5.15", + "@tailwindcss/vite": "^4.0.0", + "eslint": "^9.18.0", + "eslint-config-prettier": "^10.0.1", + "eslint-plugin-svelte": "^3.0.0", + "globals": "^16.0.0", + "prettier": "^3.4.2", + "prettier-plugin-svelte": "^3.3.3", + "prettier-plugin-tailwindcss": "^0.6.11", + "svelte": "^5.0.0", + "svelte-check": "^4.0.0", + "tailwindcss": "^4.0.0", + "typescript": "^5.0.0", + "typescript-eslint": "^8.20.0", + "vite": "^6.2.6", + "houdini": "^1.5.7", + "houdini-svelte": "^2.1.17" + } +} \ No newline at end of file diff --git a/frontend/schema.graphql b/frontend/schema.graphql new file mode 100644 index 0000000..2a5ed9e --- /dev/null +++ b/frontend/schema.graphql @@ -0,0 +1,574 @@ +""" +Indicates that an Input Object is a OneOf Input Object (and thus requires exactly one of its field be provided) +""" +directive @oneOf on INPUT_OBJECT + +type Account { + city: String! + createdAt: NaiveDateTime! + customerId: ID! + endDate: NaiveDate + id: ID! + name: String! + primaryContactEmail: String! + primaryContactFirstName: String! + primaryContactLastName: String! + primaryContactPhone: String! + secondaryContactEmail: String + secondaryContactFirstName: String + secondaryContactLastName: String + secondaryContactPhone: String + startDate: NaiveDate! + state: String! + streetAddress: String! + updatedAt: NaiveDateTime! + zipCode: String! +} + +input CreateAccountInput { + city: String! + customerId: ID! + endDate: NaiveDate + name: String! + primaryContactEmail: String! + primaryContactFirstName: String! + primaryContactLastName: String! + primaryContactPhone: String! + secondaryContactEmail: String + secondaryContactFirstName: String + secondaryContactLastName: String + secondaryContactPhone: String + startDate: NaiveDate! + state: String! + streetAddress: String! + zipCode: String! +} + +input CreateCustomerInput { + billingCity: String! + billingContactFirstName: String! + billingContactLastName: String! + billingEmail: String! + billingState: String! + billingStreetAddress: String! + billingTerms: String! + billingZipCode: String! + endDate: NaiveDate + name: String! + primaryContactEmail: String! + primaryContactFirstName: String! + primaryContactLastName: String! + primaryContactPhone: String! + secondaryContactEmail: String + secondaryContactFirstName: String + secondaryContactLastName: String + secondaryContactPhone: String + startDate: NaiveDate! +} + +input CreateInvoiceInput { + customerId: ID! + date: NaiveDate! + datePaid: NaiveDate + paymentType: String + sentAt: NaiveDateTime + status: String! + totalAmount: String! +} + +input CreateLaborInput { + accountId: ID! + amount: String! + endDate: NaiveDate + startDate: NaiveDate! +} + +input CreateProfileInput { + email: String! + firstName: String! + lastName: String! + primaryPhone: String! + role: String! + secondaryPhone: String + userId: ID! +} + +input CreateProjectInput { + accountId: ID + amount: String! + completedAt: NaiveDateTime + customerId: ID! + date: NaiveDate! + labor: String! + notes: String + status: String! +} + +input CreateReportInput { + date: NaiveDate! + notes: String + teamMemberId: ID! +} + +input CreateRevenueInput { + accountId: ID! + amount: String! + endDate: NaiveDate + startDate: NaiveDate! +} + +input CreateScheduleInput { + accountId: ID! + endDate: NaiveDate + fridayService: Boolean! + mondayService: Boolean! + saturdayService: Boolean! + scheduleException: String + startDate: NaiveDate! + sundayService: Boolean! + thursdayService: Boolean! + tuesdayService: Boolean! + wednesdayService: Boolean! + weekendService: Boolean! +} + +input CreateServiceInput { + accountId: ID! + completedAt: NaiveDateTime + date: NaiveDate! + deadlineEnd: NaiveDateTime! + deadlineStart: NaiveDateTime! + notes: String + status: String! +} + +input CreateUserInput { + email: String! + isActive: Boolean! + password: String! + username: String! +} + +type Customer { + billingCity: String! + billingContactFirstName: String! + billingContactLastName: String! + billingEmail: String! + billingState: String! + billingStreetAddress: String! + billingTerms: String! + billingZipCode: String! + createdAt: NaiveDateTime! + endDate: NaiveDate + id: ID! + name: String! + primaryContactEmail: String! + primaryContactFirstName: String! + primaryContactLastName: String! + primaryContactPhone: String! + secondaryContactEmail: String + secondaryContactFirstName: String + secondaryContactLastName: String + secondaryContactPhone: String + startDate: NaiveDate! + updatedAt: NaiveDateTime! +} + +type Invoice { + createdAt: NaiveDateTime! + customerId: ID! + date: NaiveDate! + datePaid: NaiveDate + id: ID! + paymentType: String + sentAt: NaiveDateTime + status: String! + totalAmount: String! + updatedAt: NaiveDateTime! +} + +type Labor { + accountId: ID! + amount: String! + createdAt: NaiveDateTime! + endDate: NaiveDate + id: ID! + startDate: NaiveDate! + updatedAt: NaiveDateTime! +} + +type Mutation { + createAccount(input: CreateAccountInput!): Account! + createCustomer(input: CreateCustomerInput!): Customer! + createInvoice(input: CreateInvoiceInput!): Invoice! + createLabor(input: CreateLaborInput!): Labor! + createProfile(input: CreateProfileInput!): Profile! + createProject(input: CreateProjectInput!): Project! + createReport(input: CreateReportInput!): Report! + createRevenue(input: CreateRevenueInput!): Revenue! + createSchedule(input: CreateScheduleInput!): Schedule! + createService(input: CreateServiceInput!): Service! + createUser(input: CreateUserInput!): User! + deleteAccount(id: ID!): Boolean! + deleteCustomer(id: ID!): Boolean! + deleteInvoice(id: ID!): Boolean! + deleteLabor(id: ID!): Boolean! + deleteProfile(id: ID!): Boolean! + deleteProject(id: ID!): Boolean! + deleteReport(id: ID!): Boolean! + deleteRevenue(id: ID!): Boolean! + deleteSchedule(id: ID!): Boolean! + deleteService(id: ID!): Boolean! + deleteUser(id: ID!): Boolean! + updateAccount(id: ID!, input: UpdateAccountInput!): Account! + updateCustomer(id: ID!, input: UpdateCustomerInput!): Customer! + updateInvoice(id: ID!, input: UpdateInvoiceInput!): Invoice! + updateLabor(id: ID!, input: UpdateLaborInput!): Labor! + updateProfile(id: ID!, input: UpdateProfileInput!): Profile! + updateProject(id: ID!, input: UpdateProjectInput!): Project! + updateReport(id: ID!, input: UpdateReportInput!): Report! + updateRevenue(id: ID!, input: UpdateRevenueInput!): Revenue! + updateSchedule(id: ID!, input: UpdateScheduleInput!): Schedule! + updateService(id: ID!, input: UpdateServiceInput!): Service! + updateUser(id: ID!, input: UpdateUserInput!): User! +} + +""" +ISO 8601 calendar date without timezone. +Format: %Y-%m-%d + +# Examples + +* `1994-11-13` +* `2000-02-24` +""" +scalar NaiveDate + +""" +ISO 8601 combined date and time without timezone. + +# Examples + +* `2015-07-01T08:59:60.123`, +""" +scalar NaiveDateTime + +type Profile { + createdAt: NaiveDateTime! + email: String! + firstName: String! + id: ID! + lastName: String! + primaryPhone: String! + role: String! + secondaryPhone: String + updatedAt: NaiveDateTime! + userId: ID! +} + +type Project { + accountId: ID + amount: String! + completedAt: NaiveDateTime + createdAt: NaiveDateTime! + customerId: ID! + date: NaiveDate! + id: ID! + labor: String! + notes: String + status: String! + updatedAt: NaiveDateTime! +} + +type Query { + account(id: ID!): Account + accounts( + """Number of items to return""" + limit: Int! = 10 + + """Number of items to skip""" + offset: Int! = 0 + ): [Account!]! + accountsByCustomer(customerId: ID!): [Account!]! + customer(id: ID!): Customer + customers( + """Number of items to return""" + limit: Int! = 10 + + """Number of items to skip""" + offset: Int! = 0 + ): [Customer!]! + invoice(id: ID!): Invoice + invoices( + """Number of items to return""" + limit: Int! = 10 + + """Number of items to skip""" + offset: Int! = 0 + ): [Invoice!]! + invoicesByCustomer(customerId: ID!): [Invoice!]! + invoicesByStatus(status: String!): [Invoice!]! + labor(id: ID!): Labor + labors( + """Number of items to return""" + limit: Int! = 10 + + """Number of items to skip""" + offset: Int! = 0 + ): [Labor!]! + laborsByAccount(accountId: ID!): [Labor!]! + profile(id: ID!): Profile + profileByUser(userId: ID!): Profile + profiles( + """Number of items to return""" + limit: Int! = 10 + + """Number of items to skip""" + offset: Int! = 0 + ): [Profile!]! + profilesByRole(role: String!): [Profile!]! + project(id: ID!): Project + projects( + """Number of items to return""" + limit: Int! = 10 + + """Number of items to skip""" + offset: Int! = 0 + ): [Project!]! + projectsByAccount(accountId: ID!): [Project!]! + projectsByCustomer(customerId: ID!): [Project!]! + projectsByStatus(status: String!): [Project!]! + report(id: ID!): Report + reports( + """Number of items to return""" + limit: Int! = 10 + + """Number of items to skip""" + offset: Int! = 0 + ): [Report!]! + reportsByDate(date: NaiveDate!): [Report!]! + reportsByTeamMember(teamMemberId: ID!): [Report!]! + revenue(id: ID!): Revenue + revenues( + """Number of items to return""" + limit: Int! = 10 + + """Number of items to skip""" + offset: Int! = 0 + ): [Revenue!]! + revenuesByAccount(accountId: ID!): [Revenue!]! + schedule(id: ID!): Schedule + schedules( + """Number of items to return""" + limit: Int! = 10 + + """Number of items to skip""" + offset: Int! = 0 + ): [Schedule!]! + schedulesByAccount(accountId: ID!): [Schedule!]! + searchAccounts(name: String!): [Account!]! + searchCustomers(name: String!): [Customer!]! + service(id: ID!): Service + services( + """Number of items to return""" + limit: Int! = 10 + + """Number of items to skip""" + offset: Int! = 0 + ): [Service!]! + servicesByAccount(accountId: ID!): [Service!]! + servicesByStatus(status: String!): [Service!]! + user(id: ID!): User + userByEmail(email: String!): User + userByUsername(username: String!): User + users( + """Number of items to return""" + limit: Int! = 10 + + """Number of items to skip""" + offset: Int! = 0 + ): [User!]! +} + +type Report { + createdAt: NaiveDateTime! + date: NaiveDate! + id: ID! + notes: String + teamMemberId: ID! + updatedAt: NaiveDateTime! +} + +type Revenue { + accountId: ID! + amount: String! + createdAt: NaiveDateTime! + endDate: NaiveDate + id: ID! + startDate: NaiveDate! + updatedAt: NaiveDateTime! +} + +type Schedule { + accountId: ID! + createdAt: NaiveDateTime! + endDate: NaiveDate + fridayService: Boolean! + id: ID! + mondayService: Boolean! + saturdayService: Boolean! + scheduleException: String + startDate: NaiveDate! + sundayService: Boolean! + thursdayService: Boolean! + tuesdayService: Boolean! + updatedAt: NaiveDateTime! + wednesdayService: Boolean! + weekendService: Boolean! +} + +type Service { + accountId: ID! + completedAt: NaiveDateTime + createdAt: NaiveDateTime! + date: NaiveDate! + deadlineEnd: NaiveDateTime! + deadlineStart: NaiveDateTime! + id: ID! + notes: String + status: String! + updatedAt: NaiveDateTime! +} + +input UpdateAccountInput { + city: String + customerId: ID + endDate: NaiveDate + name: String + primaryContactEmail: String + primaryContactFirstName: String + primaryContactLastName: String + primaryContactPhone: String + secondaryContactEmail: String + secondaryContactFirstName: String + secondaryContactLastName: String + secondaryContactPhone: String + startDate: NaiveDate + state: String + streetAddress: String + zipCode: String +} + +input UpdateCustomerInput { + billingCity: String + billingContactFirstName: String + billingContactLastName: String + billingEmail: String + billingState: String + billingStreetAddress: String + billingTerms: String + billingZipCode: String + endDate: NaiveDate + name: String + primaryContactEmail: String + primaryContactFirstName: String + primaryContactLastName: String + primaryContactPhone: String + secondaryContactEmail: String + secondaryContactFirstName: String + secondaryContactLastName: String + secondaryContactPhone: String + startDate: NaiveDate +} + +input UpdateInvoiceInput { + customerId: ID + date: NaiveDate + datePaid: NaiveDate + paymentType: String + sentAt: NaiveDateTime + status: String + totalAmount: String +} + +input UpdateLaborInput { + accountId: ID + amount: String + endDate: NaiveDate + startDate: NaiveDate +} + +input UpdateProfileInput { + email: String + firstName: String + lastName: String + primaryPhone: String + role: String + secondaryPhone: String + userId: ID +} + +input UpdateProjectInput { + accountId: ID + amount: String + completedAt: NaiveDateTime + customerId: ID + date: NaiveDate + labor: String + notes: String + status: String +} + +input UpdateReportInput { + date: NaiveDate + notes: String + teamMemberId: ID +} + +input UpdateRevenueInput { + accountId: ID + amount: String + endDate: NaiveDate + startDate: NaiveDate +} + +input UpdateScheduleInput { + accountId: ID + endDate: NaiveDate + fridayService: Boolean + mondayService: Boolean + saturdayService: Boolean + scheduleException: String + startDate: NaiveDate + sundayService: Boolean + thursdayService: Boolean + tuesdayService: Boolean + wednesdayService: Boolean + weekendService: Boolean +} + +input UpdateServiceInput { + accountId: ID + completedAt: NaiveDateTime + date: NaiveDate + deadlineEnd: NaiveDateTime + deadlineStart: NaiveDateTime + notes: String + status: String +} + +input UpdateUserInput { + email: String + isActive: Boolean + password: String + username: String +} + +type User { + createdAt: NaiveDateTime! + email: String! + id: ID! + isActive: Boolean! + updatedAt: NaiveDateTime! + username: String! +} diff --git a/frontend/src/app.css b/frontend/src/app.css new file mode 100644 index 0000000..0351681 --- /dev/null +++ b/frontend/src/app.css @@ -0,0 +1,12 @@ +@import 'tailwindcss'; +@plugin '@tailwindcss/forms'; +@plugin '@tailwindcss/typography'; + +@theme { + --color-primary: #2563eb; /* Darker green */ + --color-secondary: #059669; /* Darker blue */ + --color-accent: #ea580c; /* Orange accent */ + --color-primary-light: #93c5fd; + --color-secondary-light: #6ee7b7; + --color-accent-light: #fed7aa; +} diff --git a/frontend/src/app.d.ts b/frontend/src/app.d.ts new file mode 100644 index 0000000..da08e6d --- /dev/null +++ b/frontend/src/app.d.ts @@ -0,0 +1,13 @@ +// See https://svelte.dev/docs/kit/types#app.d.ts +// for information about these interfaces +declare global { + namespace App { + // interface Error {} + // interface Locals {} + // interface PageData {} + // interface PageState {} + // interface Platform {} + } +} + +export {}; diff --git a/frontend/src/app.html b/frontend/src/app.html new file mode 100644 index 0000000..817c086 --- /dev/null +++ b/frontend/src/app.html @@ -0,0 +1,12 @@ + + + + + + Nexus + %sveltekit.head% + + +
%sveltekit.body%
+ + diff --git a/frontend/src/client.ts b/frontend/src/client.ts new file mode 100644 index 0000000..de2160c --- /dev/null +++ b/frontend/src/client.ts @@ -0,0 +1,20 @@ +import { HoudiniClient } from '$houdini'; + +export default new HoudiniClient({ + url: 'http://127.0.0.1:8080/graphql', + // Configure the network call with authentication + fetchParams() { + // Get the token from localStorage + const token = typeof localStorage !== 'undefined' ? localStorage.getItem('accessToken') : null; + // If a token exists, include it in the Authorization header + if (token) { + return { + headers: { + Authorization: `Bearer ${token}` + } + }; + } + // Otherwise, return empty headers + return {}; + } +}) diff --git a/frontend/src/lib/components/Sidebar.svelte b/frontend/src/lib/components/Sidebar.svelte new file mode 100644 index 0000000..7b14aff --- /dev/null +++ b/frontend/src/lib/components/Sidebar.svelte @@ -0,0 +1,107 @@ + + +{#if isAuthenticated} +
+ + + + +
+ +
+
+{/if} + + \ No newline at end of file diff --git a/frontend/src/lib/components/accounts/AccountForm.svelte b/frontend/src/lib/components/accounts/AccountForm.svelte new file mode 100644 index 0000000..34daf29 --- /dev/null +++ b/frontend/src/lib/components/accounts/AccountForm.svelte @@ -0,0 +1,332 @@ + + +
+ {#if isEditing} +
+ + +
+ {/if} + +
+ + + {#if errors.name} +

{errors.name}

+ {/if} +
+ +
+ + + {#if errors.customerId} +

{errors.customerId}

+ {/if} +
+ +
+
+ + + {#if errors.startDate} +

{errors.startDate}

+ {/if} +
+ +
+ + +
+
+ +
+ + + {#if errors.streetAddress} +

{errors.streetAddress}

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

{errors.city}

+ {/if} +
+ +
+ + + {#if errors.state} +

{errors.state}

+ {/if} +
+ +
+ + + {#if errors.zipCode} +

{errors.zipCode}

+ {/if} +
+
+ +

Primary Contact Information

+ +
+
+ + + {#if errors.primaryContactFirstName} +

{errors.primaryContactFirstName}

+ {/if} +
+ +
+ + + {#if errors.primaryContactLastName} +

{errors.primaryContactLastName}

+ {/if} +
+
+ +
+
+ + + {#if errors.primaryContactEmail} +

{errors.primaryContactEmail}

+ {/if} +
+ +
+ + + {#if errors.primaryContactPhone} +

{errors.primaryContactPhone}

+ {/if} +
+
+ +

Secondary Contact Information (Optional)

+ +
+
+ + +
+ +
+ + +
+
+ +
+
+ + + {#if errors.secondaryContactEmail} +

{errors.secondaryContactEmail}

+ {/if} +
+ +
+ + +
+
+ +
+ + +
+
diff --git a/frontend/src/lib/components/customers/CustomerForm.svelte b/frontend/src/lib/components/customers/CustomerForm.svelte new file mode 100644 index 0000000..d86dc7e --- /dev/null +++ b/frontend/src/lib/components/customers/CustomerForm.svelte @@ -0,0 +1,387 @@ + + +
+ {#if isEditing} +
+ + +
+ {/if} + +
+ + + {#if errors.name} +

{errors.name}

+ {/if} +
+ +
+
+ + + {#if errors.startDate} +

{errors.startDate}

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

Primary Contact Information

+ +
+
+ + + {#if errors.primaryContactFirstName} +

{errors.primaryContactFirstName}

+ {/if} +
+ +
+ + + {#if errors.primaryContactLastName} +

{errors.primaryContactLastName}

+ {/if} +
+
+ +
+
+ + + {#if errors.primaryContactEmail} +

{errors.primaryContactEmail}

+ {/if} +
+ +
+ + + {#if errors.primaryContactPhone} +

{errors.primaryContactPhone}

+ {/if} +
+
+ +

Secondary Contact Information (Optional)

+ +
+
+ + +
+ +
+ + +
+
+ +
+
+ + + {#if errors.secondaryContactEmail} +

{errors.secondaryContactEmail}

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

Billing Information

+ +
+
+ + + {#if errors.billingContactFirstName} +

{errors.billingContactFirstName}

+ {/if} +
+ +
+ + + {#if errors.billingContactLastName} +

{errors.billingContactLastName}

+ {/if} +
+
+ +
+ + + {#if errors.billingEmail} +

{errors.billingEmail}

+ {/if} +
+ +
+ + + {#if errors.billingStreetAddress} +

{errors.billingStreetAddress}

+ {/if} +
+ +
+
+ + + {#if errors.billingCity} +

{errors.billingCity}

+ {/if} +
+ +
+ + + {#if errors.billingState} +

{errors.billingState}

+ {/if} +
+ +
+ + + {#if errors.billingZipCode} +

{errors.billingZipCode}

+ {/if} +
+
+ +
+ + + {#if errors.billingTerms} +

{errors.billingTerms}

+ {/if} +
+ +
+ + +
+
diff --git a/frontend/src/lib/components/projects/ProjectForm.svelte b/frontend/src/lib/components/projects/ProjectForm.svelte new file mode 100644 index 0000000..2c220a9 --- /dev/null +++ b/frontend/src/lib/components/projects/ProjectForm.svelte @@ -0,0 +1,209 @@ + + +
+ {#if isEditing} +
+ + +
+ {/if} + +
+ + + {#if errors.customerId} +

{errors.customerId}

+ {/if} +
+ +
+ + +
+ +
+ + + {#if errors.date} +

{errors.date}

+ {/if} +
+ +
+
+ + + {#if errors.amount} +

{errors.amount}

+ {/if} +
+ +
+ + + {#if errors.labor} +

{errors.labor}

+ {/if} +
+
+ +
+ + + {#if errors.status} +

{errors.status}

+ {/if} +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+
diff --git a/frontend/src/lib/components/services/ServiceForm.svelte b/frontend/src/lib/components/services/ServiceForm.svelte new file mode 100644 index 0000000..c71248b --- /dev/null +++ b/frontend/src/lib/components/services/ServiceForm.svelte @@ -0,0 +1,199 @@ + + +
+ {#if isEditing} +
+ + +
+ {/if} + +
+ + + {#if errors.accountId} +

{errors.accountId}

+ {/if} +
+ +
+ + + {#if errors.date} +

{errors.date}

+ {/if} +
+ +
+
+ + + {#if errors.deadlineStart} +

{errors.deadlineStart}

+ {/if} +
+ +
+ + + {#if errors.deadlineEnd} +

{errors.deadlineEnd}

+ {/if} +
+
+ +
+ + + {#if errors.status} +

{errors.status}

+ {/if} +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+
\ No newline at end of file diff --git a/frontend/src/lib/graphql/account/createAccount.graphql b/frontend/src/lib/graphql/account/createAccount.graphql new file mode 100644 index 0000000..c0e0d7d --- /dev/null +++ b/frontend/src/lib/graphql/account/createAccount.graphql @@ -0,0 +1,23 @@ +mutation CreateAccount($input: CreateAccountInput!) { + createAccount(input: $input) { + id + name + customerId + startDate + endDate + streetAddress + city + state + zipCode + primaryContactFirstName + primaryContactLastName + primaryContactEmail + primaryContactPhone + secondaryContactFirstName + secondaryContactLastName + secondaryContactEmail + secondaryContactPhone + createdAt + updatedAt + } +} \ No newline at end of file diff --git a/frontend/src/lib/graphql/account/deleteAccount.graphql b/frontend/src/lib/graphql/account/deleteAccount.graphql new file mode 100644 index 0000000..c10e0c9 --- /dev/null +++ b/frontend/src/lib/graphql/account/deleteAccount.graphql @@ -0,0 +1,3 @@ +mutation DeleteAccount($id: ID!) { + deleteAccount(id: $id) +} \ No newline at end of file diff --git a/frontend/src/lib/graphql/account/getAccount.graphql b/frontend/src/lib/graphql/account/getAccount.graphql new file mode 100644 index 0000000..3520f05 --- /dev/null +++ b/frontend/src/lib/graphql/account/getAccount.graphql @@ -0,0 +1,23 @@ +query GetAccount($id: ID!) { + account(id: $id) { + id + name + customerId + startDate + endDate + streetAddress + city + state + zipCode + primaryContactFirstName + primaryContactLastName + primaryContactEmail + primaryContactPhone + secondaryContactFirstName + secondaryContactLastName + secondaryContactEmail + secondaryContactPhone + createdAt + updatedAt + } +} \ No newline at end of file diff --git a/frontend/src/lib/graphql/account/getAccounts.graphql b/frontend/src/lib/graphql/account/getAccounts.graphql new file mode 100644 index 0000000..700367c --- /dev/null +++ b/frontend/src/lib/graphql/account/getAccounts.graphql @@ -0,0 +1,15 @@ +query GetAccounts { + accounts { + id + name + customerId + startDate + endDate + primaryContactFirstName + primaryContactLastName + primaryContactEmail + primaryContactPhone + createdAt + updatedAt + } +} \ No newline at end of file diff --git a/frontend/src/lib/graphql/account/updateAccount.graphql b/frontend/src/lib/graphql/account/updateAccount.graphql new file mode 100644 index 0000000..000bfa0 --- /dev/null +++ b/frontend/src/lib/graphql/account/updateAccount.graphql @@ -0,0 +1,23 @@ +mutation UpdateAccount($id: ID!, $input: UpdateAccountInput!) { + updateAccount(id: $id, input: $input) { + id + name + customerId + startDate + endDate + streetAddress + city + state + zipCode + primaryContactFirstName + primaryContactLastName + primaryContactEmail + primaryContactPhone + secondaryContactFirstName + secondaryContactLastName + secondaryContactEmail + secondaryContactPhone + createdAt + updatedAt + } +} \ No newline at end of file diff --git a/frontend/src/lib/graphql/customer/createCustomer.graphql b/frontend/src/lib/graphql/customer/createCustomer.graphql new file mode 100644 index 0000000..1e095b3 --- /dev/null +++ b/frontend/src/lib/graphql/customer/createCustomer.graphql @@ -0,0 +1,26 @@ +mutation CreateCustomer($input: CreateCustomerInput!) { + createCustomer(input: $input) { + id + name + startDate + endDate + primaryContactFirstName + primaryContactLastName + primaryContactEmail + primaryContactPhone + secondaryContactFirstName + secondaryContactLastName + secondaryContactEmail + secondaryContactPhone + billingContactFirstName + billingContactLastName + billingEmail + billingStreetAddress + billingCity + billingState + billingZipCode + billingTerms + createdAt + updatedAt + } +} \ No newline at end of file diff --git a/frontend/src/lib/graphql/customer/deleteCustomer.graphql b/frontend/src/lib/graphql/customer/deleteCustomer.graphql new file mode 100644 index 0000000..8054b27 --- /dev/null +++ b/frontend/src/lib/graphql/customer/deleteCustomer.graphql @@ -0,0 +1,3 @@ +mutation DeleteCustomer($id: ID!) { + deleteCustomer(id: $id) +} \ No newline at end of file diff --git a/frontend/src/lib/graphql/customer/getCustomer.graphql b/frontend/src/lib/graphql/customer/getCustomer.graphql new file mode 100644 index 0000000..a3aab79 --- /dev/null +++ b/frontend/src/lib/graphql/customer/getCustomer.graphql @@ -0,0 +1,26 @@ +query GetCustomer($id: ID!) { + customer(id: $id) { + id + name + startDate + endDate + primaryContactFirstName + primaryContactLastName + primaryContactEmail + primaryContactPhone + secondaryContactFirstName + secondaryContactLastName + secondaryContactEmail + secondaryContactPhone + billingContactFirstName + billingContactLastName + billingEmail + billingStreetAddress + billingCity + billingState + billingZipCode + billingTerms + createdAt + updatedAt + } +} \ No newline at end of file diff --git a/frontend/src/lib/graphql/customer/getCustomers.graphql b/frontend/src/lib/graphql/customer/getCustomers.graphql new file mode 100644 index 0000000..b89ea70 --- /dev/null +++ b/frontend/src/lib/graphql/customer/getCustomers.graphql @@ -0,0 +1,14 @@ +query GetCustomers { + customers { + id + name + startDate + endDate + primaryContactFirstName + primaryContactLastName + primaryContactEmail + primaryContactPhone + createdAt + updatedAt + } +} \ No newline at end of file diff --git a/frontend/src/lib/graphql/customer/updateCustomer.graphql b/frontend/src/lib/graphql/customer/updateCustomer.graphql new file mode 100644 index 0000000..7715263 --- /dev/null +++ b/frontend/src/lib/graphql/customer/updateCustomer.graphql @@ -0,0 +1,26 @@ +mutation UpdateCustomer($id: ID!, $input: UpdateCustomerInput!) { + updateCustomer(id: $id, input: $input) { + id + name + startDate + endDate + primaryContactFirstName + primaryContactLastName + primaryContactEmail + primaryContactPhone + secondaryContactFirstName + secondaryContactLastName + secondaryContactEmail + secondaryContactPhone + billingContactFirstName + billingContactLastName + billingEmail + billingStreetAddress + billingCity + billingState + billingZipCode + billingTerms + createdAt + updatedAt + } +} \ No newline at end of file diff --git a/frontend/src/lib/graphql/invoice/createInvoice.graphql b/frontend/src/lib/graphql/invoice/createInvoice.graphql new file mode 100644 index 0000000..2927a94 --- /dev/null +++ b/frontend/src/lib/graphql/invoice/createInvoice.graphql @@ -0,0 +1,14 @@ +mutation CreateInvoice($input: CreateInvoiceInput!) { + createInvoice(input: $input) { + id + customerId + date + datePaid + paymentType + sentAt + status + totalAmount + createdAt + updatedAt + } +} \ No newline at end of file diff --git a/frontend/src/lib/graphql/invoice/deleteInvoice.graphql b/frontend/src/lib/graphql/invoice/deleteInvoice.graphql new file mode 100644 index 0000000..32c186f --- /dev/null +++ b/frontend/src/lib/graphql/invoice/deleteInvoice.graphql @@ -0,0 +1,3 @@ +mutation DeleteInvoice($id: ID!) { + deleteInvoice(id: $id) +} \ No newline at end of file diff --git a/frontend/src/lib/graphql/invoice/getInvoice.graphql b/frontend/src/lib/graphql/invoice/getInvoice.graphql new file mode 100644 index 0000000..00087af --- /dev/null +++ b/frontend/src/lib/graphql/invoice/getInvoice.graphql @@ -0,0 +1,14 @@ +query GetInvoice($id: ID!) { + invoice(id: $id) { + id + customerId + date + datePaid + paymentType + sentAt + status + totalAmount + createdAt + updatedAt + } +} \ No newline at end of file diff --git a/frontend/src/lib/graphql/invoice/getInvoices.graphql b/frontend/src/lib/graphql/invoice/getInvoices.graphql new file mode 100644 index 0000000..d8e5735 --- /dev/null +++ b/frontend/src/lib/graphql/invoice/getInvoices.graphql @@ -0,0 +1,12 @@ +query GetInvoices { + invoices { + id + customerId + date + status + totalAmount + datePaid + createdAt + updatedAt + } +} \ No newline at end of file diff --git a/frontend/src/lib/graphql/invoice/updateInvoice.graphql b/frontend/src/lib/graphql/invoice/updateInvoice.graphql new file mode 100644 index 0000000..1221595 --- /dev/null +++ b/frontend/src/lib/graphql/invoice/updateInvoice.graphql @@ -0,0 +1,14 @@ +mutation UpdateInvoice($id: ID!, $input: UpdateInvoiceInput!) { + updateInvoice(id: $id, input: $input) { + id + customerId + date + datePaid + paymentType + sentAt + status + totalAmount + createdAt + updatedAt + } +} \ No newline at end of file diff --git a/frontend/src/lib/graphql/labor/createLabor.graphql b/frontend/src/lib/graphql/labor/createLabor.graphql new file mode 100644 index 0000000..a370f5d --- /dev/null +++ b/frontend/src/lib/graphql/labor/createLabor.graphql @@ -0,0 +1,11 @@ +mutation CreateLabor($input: CreateLaborInput!) { + createLabor(input: $input) { + id + accountId + amount + startDate + endDate + createdAt + updatedAt + } +} \ No newline at end of file diff --git a/frontend/src/lib/graphql/labor/deleteLabor.graphql b/frontend/src/lib/graphql/labor/deleteLabor.graphql new file mode 100644 index 0000000..5133d96 --- /dev/null +++ b/frontend/src/lib/graphql/labor/deleteLabor.graphql @@ -0,0 +1,3 @@ +mutation DeleteLabor($id: ID!) { + deleteLabor(id: $id) +} \ No newline at end of file diff --git a/frontend/src/lib/graphql/labor/getLabor.graphql b/frontend/src/lib/graphql/labor/getLabor.graphql new file mode 100644 index 0000000..9bcb8c2 --- /dev/null +++ b/frontend/src/lib/graphql/labor/getLabor.graphql @@ -0,0 +1,11 @@ +query GetLabor($id: ID!) { + labor(id: $id) { + id + accountId + amount + startDate + endDate + createdAt + updatedAt + } +} \ No newline at end of file diff --git a/frontend/src/lib/graphql/labor/getLabors.graphql b/frontend/src/lib/graphql/labor/getLabors.graphql new file mode 100644 index 0000000..c81a1d9 --- /dev/null +++ b/frontend/src/lib/graphql/labor/getLabors.graphql @@ -0,0 +1,11 @@ +query GetLabors { + labors { + id + accountId + amount + startDate + endDate + createdAt + updatedAt + } +} \ No newline at end of file diff --git a/frontend/src/lib/graphql/labor/laborsByAccount.graphql b/frontend/src/lib/graphql/labor/laborsByAccount.graphql new file mode 100644 index 0000000..50d2115 --- /dev/null +++ b/frontend/src/lib/graphql/labor/laborsByAccount.graphql @@ -0,0 +1,11 @@ +query LaborsByAccount($accountId: ID!) { + laborsByAccount(accountId: $accountId) { + id + accountId + amount + startDate + endDate + createdAt + updatedAt + } +} \ No newline at end of file diff --git a/frontend/src/lib/graphql/labor/updateLabor.graphql b/frontend/src/lib/graphql/labor/updateLabor.graphql new file mode 100644 index 0000000..5e28014 --- /dev/null +++ b/frontend/src/lib/graphql/labor/updateLabor.graphql @@ -0,0 +1,11 @@ +mutation UpdateLabor($id: ID!, $input: UpdateLaborInput!) { + updateLabor(id: $id, input: $input) { + id + accountId + amount + startDate + endDate + createdAt + updatedAt + } +} \ No newline at end of file diff --git a/frontend/src/lib/graphql/profile/createProfile.graphql b/frontend/src/lib/graphql/profile/createProfile.graphql new file mode 100644 index 0000000..5fbe84a --- /dev/null +++ b/frontend/src/lib/graphql/profile/createProfile.graphql @@ -0,0 +1,14 @@ +mutation CreateProfile($input: CreateProfileInput!) { + createProfile(input: $input) { + id + firstName + lastName + email + primaryPhone + secondaryPhone + role + userId + createdAt + updatedAt + } +} \ No newline at end of file diff --git a/frontend/src/lib/graphql/profile/deleteProfile.graphql b/frontend/src/lib/graphql/profile/deleteProfile.graphql new file mode 100644 index 0000000..045b8ef --- /dev/null +++ b/frontend/src/lib/graphql/profile/deleteProfile.graphql @@ -0,0 +1,3 @@ +mutation DeleteProfile($id: ID!) { + deleteProfile(id: $id) +} \ No newline at end of file diff --git a/frontend/src/lib/graphql/profile/getProfile.graphql b/frontend/src/lib/graphql/profile/getProfile.graphql new file mode 100644 index 0000000..1ca4ef2 --- /dev/null +++ b/frontend/src/lib/graphql/profile/getProfile.graphql @@ -0,0 +1,14 @@ +query GetProfile($id: ID!) { + profile(id: $id) { + id + firstName + lastName + email + primaryPhone + secondaryPhone + role + userId + createdAt + updatedAt + } +} \ No newline at end of file diff --git a/frontend/src/lib/graphql/profile/getProfiles.graphql b/frontend/src/lib/graphql/profile/getProfiles.graphql new file mode 100644 index 0000000..903b1fc --- /dev/null +++ b/frontend/src/lib/graphql/profile/getProfiles.graphql @@ -0,0 +1,14 @@ +query GetProfiles { + profiles { + id + firstName + lastName + email + primaryPhone + secondaryPhone + role + userId + createdAt + updatedAt + } +} \ No newline at end of file diff --git a/frontend/src/lib/graphql/profile/updateProfile.graphql b/frontend/src/lib/graphql/profile/updateProfile.graphql new file mode 100644 index 0000000..98b8adc --- /dev/null +++ b/frontend/src/lib/graphql/profile/updateProfile.graphql @@ -0,0 +1,14 @@ +mutation UpdateProfile($id: ID!, $input: UpdateProfileInput!) { + updateProfile(id: $id, input: $input) { + id + firstName + lastName + email + primaryPhone + secondaryPhone + role + userId + createdAt + updatedAt + } +} \ No newline at end of file diff --git a/frontend/src/lib/graphql/project/createProject.graphql b/frontend/src/lib/graphql/project/createProject.graphql new file mode 100644 index 0000000..6211a67 --- /dev/null +++ b/frontend/src/lib/graphql/project/createProject.graphql @@ -0,0 +1,15 @@ +mutation CreateProject($input: CreateProjectInput!) { + createProject(input: $input) { + id + customerId + accountId + date + status + notes + labor + amount + createdAt + updatedAt + completedAt + } +} \ No newline at end of file diff --git a/frontend/src/lib/graphql/project/deleteProject.graphql b/frontend/src/lib/graphql/project/deleteProject.graphql new file mode 100644 index 0000000..b84a850 --- /dev/null +++ b/frontend/src/lib/graphql/project/deleteProject.graphql @@ -0,0 +1,3 @@ +mutation DeleteProject($id: ID!) { + deleteProject(id: $id) +} \ No newline at end of file diff --git a/frontend/src/lib/graphql/project/getProject.graphql b/frontend/src/lib/graphql/project/getProject.graphql new file mode 100644 index 0000000..9b5d553 --- /dev/null +++ b/frontend/src/lib/graphql/project/getProject.graphql @@ -0,0 +1,15 @@ +query GetProject($id: ID!) { + project(id: $id) { + id + customerId + accountId + date + status + notes + labor + amount + createdAt + updatedAt + completedAt + } +} \ No newline at end of file diff --git a/frontend/src/lib/graphql/project/getProjects.graphql b/frontend/src/lib/graphql/project/getProjects.graphql new file mode 100644 index 0000000..19f9512 --- /dev/null +++ b/frontend/src/lib/graphql/project/getProjects.graphql @@ -0,0 +1,15 @@ +query GetProjects { + projects { + id + customerId + accountId + date + status + notes + labor + amount + createdAt + updatedAt + completedAt + } +} diff --git a/frontend/src/lib/graphql/project/projectsByAccount.graphql b/frontend/src/lib/graphql/project/projectsByAccount.graphql new file mode 100644 index 0000000..01080f3 --- /dev/null +++ b/frontend/src/lib/graphql/project/projectsByAccount.graphql @@ -0,0 +1,15 @@ +query ProjectsByAccount($accountId: ID!) { + projectsByAccount(accountId: $accountId) { + id + accountId + customerId + date + status + amount + labor + completedAt + notes + createdAt + updatedAt + } +} \ No newline at end of file diff --git a/frontend/src/lib/graphql/project/updateProject.graphql b/frontend/src/lib/graphql/project/updateProject.graphql new file mode 100644 index 0000000..460344a --- /dev/null +++ b/frontend/src/lib/graphql/project/updateProject.graphql @@ -0,0 +1,15 @@ +mutation UpdateProject($id: ID!, $input: UpdateProjectInput!) { + updateProject(id: $id, input: $input) { + id + customerId + accountId + date + status + notes + labor + amount + createdAt + updatedAt + completedAt + } +} \ No newline at end of file diff --git a/frontend/src/lib/graphql/report/createReport.graphql b/frontend/src/lib/graphql/report/createReport.graphql new file mode 100644 index 0000000..2cb4b7b --- /dev/null +++ b/frontend/src/lib/graphql/report/createReport.graphql @@ -0,0 +1,10 @@ +mutation CreateReport($input: CreateReportInput!) { + createReport(input: $input) { + id + teamMemberId + date + notes + createdAt + updatedAt + } +} \ No newline at end of file diff --git a/frontend/src/lib/graphql/report/deleteReport.graphql b/frontend/src/lib/graphql/report/deleteReport.graphql new file mode 100644 index 0000000..370d32a --- /dev/null +++ b/frontend/src/lib/graphql/report/deleteReport.graphql @@ -0,0 +1,3 @@ +mutation DeleteReport($id: ID!) { + deleteReport(id: $id) +} \ No newline at end of file diff --git a/frontend/src/lib/graphql/report/getReport.graphql b/frontend/src/lib/graphql/report/getReport.graphql new file mode 100644 index 0000000..6d4e94f --- /dev/null +++ b/frontend/src/lib/graphql/report/getReport.graphql @@ -0,0 +1,10 @@ +query GetReport($id: ID!) { + report(id: $id) { + id + teamMemberId + date + notes + createdAt + updatedAt + } +} \ No newline at end of file diff --git a/frontend/src/lib/graphql/report/getReports.graphql b/frontend/src/lib/graphql/report/getReports.graphql new file mode 100644 index 0000000..16acf18 --- /dev/null +++ b/frontend/src/lib/graphql/report/getReports.graphql @@ -0,0 +1,10 @@ +query GetReports { + reports { + id + teamMemberId + date + notes + createdAt + updatedAt + } +} \ No newline at end of file diff --git a/frontend/src/lib/graphql/report/updateReport.graphql b/frontend/src/lib/graphql/report/updateReport.graphql new file mode 100644 index 0000000..582b6bf --- /dev/null +++ b/frontend/src/lib/graphql/report/updateReport.graphql @@ -0,0 +1,10 @@ +mutation UpdateReport($id: ID!, $input: UpdateReportInput!) { + updateReport(id: $id, input: $input) { + id + teamMemberId + date + notes + createdAt + updatedAt + } +} \ No newline at end of file diff --git a/frontend/src/lib/graphql/revenue/createRevenue.graphql b/frontend/src/lib/graphql/revenue/createRevenue.graphql new file mode 100644 index 0000000..3b66dbe --- /dev/null +++ b/frontend/src/lib/graphql/revenue/createRevenue.graphql @@ -0,0 +1,11 @@ +mutation CreateRevenue($input: CreateRevenueInput!) { + createRevenue(input: $input) { + id + accountId + amount + startDate + endDate + createdAt + updatedAt + } +} \ No newline at end of file diff --git a/frontend/src/lib/graphql/revenue/deleteRevenue.graphql b/frontend/src/lib/graphql/revenue/deleteRevenue.graphql new file mode 100644 index 0000000..2a4af93 --- /dev/null +++ b/frontend/src/lib/graphql/revenue/deleteRevenue.graphql @@ -0,0 +1,3 @@ +mutation DeleteRevenue($id: ID!) { + deleteRevenue(id: $id) +} \ No newline at end of file diff --git a/frontend/src/lib/graphql/revenue/getRevenue.graphql b/frontend/src/lib/graphql/revenue/getRevenue.graphql new file mode 100644 index 0000000..aef5600 --- /dev/null +++ b/frontend/src/lib/graphql/revenue/getRevenue.graphql @@ -0,0 +1,11 @@ +query GetRevenue($id: ID!) { + revenue(id: $id) { + id + accountId + amount + startDate + endDate + createdAt + updatedAt + } +} \ No newline at end of file diff --git a/frontend/src/lib/graphql/revenue/getRevenues.graphql b/frontend/src/lib/graphql/revenue/getRevenues.graphql new file mode 100644 index 0000000..1fd31cc --- /dev/null +++ b/frontend/src/lib/graphql/revenue/getRevenues.graphql @@ -0,0 +1,11 @@ +query GetRevenues($limit: Int!, $offset: Int!) { + revenues(limit: $limit, offset: $offset) { + id + accountId + amount + startDate + endDate + createdAt + updatedAt + } +} \ No newline at end of file diff --git a/frontend/src/lib/graphql/revenue/revenuesByAccount.graphql b/frontend/src/lib/graphql/revenue/revenuesByAccount.graphql new file mode 100644 index 0000000..262624c --- /dev/null +++ b/frontend/src/lib/graphql/revenue/revenuesByAccount.graphql @@ -0,0 +1,11 @@ +query RevenuesByAccount($accountId: ID!) { + revenuesByAccount(accountId: $accountId) { + id + accountId + amount + startDate + endDate + createdAt + updatedAt + } +} \ No newline at end of file diff --git a/frontend/src/lib/graphql/revenue/updateRevenue.graphql b/frontend/src/lib/graphql/revenue/updateRevenue.graphql new file mode 100644 index 0000000..2939af2 --- /dev/null +++ b/frontend/src/lib/graphql/revenue/updateRevenue.graphql @@ -0,0 +1,11 @@ +mutation UpdateRevenue($id: ID!, $input: UpdateRevenueInput!) { + updateRevenue(id: $id, input: $input) { + id + accountId + amount + startDate + endDate + createdAt + updatedAt + } +} \ No newline at end of file diff --git a/frontend/src/lib/graphql/schedule/createSchedule.graphql b/frontend/src/lib/graphql/schedule/createSchedule.graphql new file mode 100644 index 0000000..de2b423 --- /dev/null +++ b/frontend/src/lib/graphql/schedule/createSchedule.graphql @@ -0,0 +1,19 @@ +mutation CreateSchedule($input: CreateScheduleInput!) { + createSchedule(input: $input) { + id + accountId + startDate + endDate + mondayService + tuesdayService + wednesdayService + thursdayService + fridayService + saturdayService + sundayService + weekendService + scheduleException + createdAt + updatedAt + } +} \ No newline at end of file diff --git a/frontend/src/lib/graphql/schedule/deleteSchedule.graphql b/frontend/src/lib/graphql/schedule/deleteSchedule.graphql new file mode 100644 index 0000000..9532c61 --- /dev/null +++ b/frontend/src/lib/graphql/schedule/deleteSchedule.graphql @@ -0,0 +1,3 @@ +mutation DeleteSchedule($id: ID!) { + deleteSchedule(id: $id) +} \ No newline at end of file diff --git a/frontend/src/lib/graphql/schedule/getSchedule.graphql b/frontend/src/lib/graphql/schedule/getSchedule.graphql new file mode 100644 index 0000000..6c1a01e --- /dev/null +++ b/frontend/src/lib/graphql/schedule/getSchedule.graphql @@ -0,0 +1,19 @@ +query GetSchedule($id: ID!) { + schedule(id: $id) { + id + accountId + startDate + endDate + mondayService + tuesdayService + wednesdayService + thursdayService + fridayService + saturdayService + sundayService + weekendService + scheduleException + createdAt + updatedAt + } +} \ No newline at end of file diff --git a/frontend/src/lib/graphql/schedule/getSchedules.graphql b/frontend/src/lib/graphql/schedule/getSchedules.graphql new file mode 100644 index 0000000..ccd2e4b --- /dev/null +++ b/frontend/src/lib/graphql/schedule/getSchedules.graphql @@ -0,0 +1,19 @@ +query GetSchedules($limit: Int!, $offset: Int!) { + schedules(limit: $limit, offset: $offset) { + id + accountId + startDate + endDate + mondayService + tuesdayService + wednesdayService + thursdayService + fridayService + saturdayService + sundayService + weekendService + scheduleException + createdAt + updatedAt + } +} \ No newline at end of file diff --git a/frontend/src/lib/graphql/schedule/schedulesByAccount.graphql b/frontend/src/lib/graphql/schedule/schedulesByAccount.graphql new file mode 100644 index 0000000..30dff1f --- /dev/null +++ b/frontend/src/lib/graphql/schedule/schedulesByAccount.graphql @@ -0,0 +1,19 @@ +query SchedulesByAccount($accountId: ID!) { + schedulesByAccount(accountId: $accountId) { + id + accountId + startDate + endDate + mondayService + tuesdayService + wednesdayService + thursdayService + fridayService + saturdayService + sundayService + weekendService + scheduleException + createdAt + updatedAt + } +} \ No newline at end of file diff --git a/frontend/src/lib/graphql/schedule/updateSchedule.graphql b/frontend/src/lib/graphql/schedule/updateSchedule.graphql new file mode 100644 index 0000000..9d6a7f7 --- /dev/null +++ b/frontend/src/lib/graphql/schedule/updateSchedule.graphql @@ -0,0 +1,19 @@ +mutation UpdateSchedule($id: ID!, $input: UpdateScheduleInput!) { + updateSchedule(id: $id, input: $input) { + id + accountId + startDate + endDate + mondayService + tuesdayService + wednesdayService + thursdayService + fridayService + saturdayService + sundayService + weekendService + scheduleException + createdAt + updatedAt + } +} \ No newline at end of file diff --git a/frontend/src/lib/graphql/service/createService.graphql b/frontend/src/lib/graphql/service/createService.graphql new file mode 100644 index 0000000..18ef03c --- /dev/null +++ b/frontend/src/lib/graphql/service/createService.graphql @@ -0,0 +1,14 @@ +mutation CreateService($input: CreateServiceInput!) { + createService(input: $input) { + id + accountId + date + deadlineStart + deadlineEnd + status + notes + completedAt + createdAt + updatedAt + } +} \ No newline at end of file diff --git a/frontend/src/lib/graphql/service/deleteService.graphql b/frontend/src/lib/graphql/service/deleteService.graphql new file mode 100644 index 0000000..30c0158 --- /dev/null +++ b/frontend/src/lib/graphql/service/deleteService.graphql @@ -0,0 +1,3 @@ +mutation DeleteService($id: ID!) { + deleteService(id: $id) +} \ No newline at end of file diff --git a/frontend/src/lib/graphql/service/getService.graphql b/frontend/src/lib/graphql/service/getService.graphql new file mode 100644 index 0000000..8bad169 --- /dev/null +++ b/frontend/src/lib/graphql/service/getService.graphql @@ -0,0 +1,14 @@ +query GetService($id: ID!) { + service(id: $id) { + id + accountId + date + deadlineStart + deadlineEnd + status + notes + completedAt + createdAt + updatedAt + } +} \ No newline at end of file diff --git a/frontend/src/lib/graphql/service/getServices.graphql b/frontend/src/lib/graphql/service/getServices.graphql new file mode 100644 index 0000000..e081da5 --- /dev/null +++ b/frontend/src/lib/graphql/service/getServices.graphql @@ -0,0 +1,14 @@ +query GetServices { + services { + id + accountId + date + deadlineStart + deadlineEnd + status + notes + completedAt + createdAt + updatedAt + } +} \ No newline at end of file diff --git a/frontend/src/lib/graphql/service/servicesByAccount.graphql b/frontend/src/lib/graphql/service/servicesByAccount.graphql new file mode 100644 index 0000000..cdb6d24 --- /dev/null +++ b/frontend/src/lib/graphql/service/servicesByAccount.graphql @@ -0,0 +1,14 @@ +query ServicesByAccount($accountId: ID!) { + servicesByAccount(accountId: $accountId) { + id + accountId + date + status + completedAt + deadlineStart + deadlineEnd + notes + createdAt + updatedAt + } +} \ No newline at end of file diff --git a/frontend/src/lib/graphql/service/updateService.graphql b/frontend/src/lib/graphql/service/updateService.graphql new file mode 100644 index 0000000..98aac32 --- /dev/null +++ b/frontend/src/lib/graphql/service/updateService.graphql @@ -0,0 +1,14 @@ +mutation UpdateService($id: ID!, $input: UpdateServiceInput!) { + updateService(id: $id, input: $input) { + id + accountId + date + deadlineStart + deadlineEnd + status + notes + completedAt + createdAt + updatedAt + } +} \ No newline at end of file diff --git a/frontend/src/lib/index.ts b/frontend/src/lib/index.ts new file mode 100644 index 0000000..856f2b6 --- /dev/null +++ b/frontend/src/lib/index.ts @@ -0,0 +1 @@ +// place files you want to import through the `$lib` alias in this folder. diff --git a/frontend/src/lib/types/accounts.ts b/frontend/src/lib/types/accounts.ts new file mode 100644 index 0000000..f27b548 --- /dev/null +++ b/frontend/src/lib/types/accounts.ts @@ -0,0 +1,59 @@ +export interface Account { + id: string; + name: string; + customerId: string; + startDate: string; + endDate: string | null; + streetAddress: string; + city: string; + state: string; + zipCode: string; + primaryContactFirstName: string; + primaryContactLastName: string; + primaryContactEmail: string; + primaryContactPhone: string; + secondaryContactFirstName: string | null; + secondaryContactLastName: string | null; + secondaryContactEmail: string | null; + secondaryContactPhone: string | null; + createdAt: string; + updatedAt: string; +} + +export interface CreateAccountInput { + name: string; + customerId: string; + startDate: string; + endDate?: string | null; + streetAddress: string; + city: string; + state: string; + zipCode: string; + primaryContactFirstName: string; + primaryContactLastName: string; + primaryContactEmail: string; + primaryContactPhone: string; + secondaryContactFirstName?: string | null; + secondaryContactLastName?: string | null; + secondaryContactEmail?: string | null; + secondaryContactPhone?: string | null; +} + +export interface UpdateAccountInput { + name?: string; + customerId?: string; + startDate?: string; + endDate?: string | null; + streetAddress?: string; + city?: string; + state?: string; + zipCode?: string; + primaryContactFirstName?: string; + primaryContactLastName?: string; + primaryContactEmail?: string; + primaryContactPhone?: string; + secondaryContactFirstName?: string | null; + secondaryContactLastName?: string | null; + secondaryContactEmail?: string | null; + secondaryContactPhone?: string | null; +} \ No newline at end of file diff --git a/frontend/src/lib/types/customers.ts b/frontend/src/lib/types/customers.ts new file mode 100644 index 0000000..47785ab --- /dev/null +++ b/frontend/src/lib/types/customers.ts @@ -0,0 +1,68 @@ +export interface Customer { + id: string; + name: string; + startDate: string; + endDate: string | null; + primaryContactFirstName: string; + primaryContactLastName: string; + primaryContactEmail: string; + primaryContactPhone: string; + secondaryContactFirstName: string | null; + secondaryContactLastName: string | null; + secondaryContactEmail: string | null; + secondaryContactPhone: string | null; + billingContactFirstName: string; + billingContactLastName: string; + billingEmail: string; + billingStreetAddress: string; + billingCity: string; + billingState: string; + billingZipCode: string; + billingTerms: string; + createdAt: string; + updatedAt: string; +} + +export interface CreateCustomerInput { + name: string; + startDate: string; + endDate?: string | null; + primaryContactFirstName: string; + primaryContactLastName: string; + primaryContactEmail: string; + primaryContactPhone: string; + secondaryContactFirstName?: string | null; + secondaryContactLastName?: string | null; + secondaryContactEmail?: string | null; + secondaryContactPhone?: string | null; + billingContactFirstName: string; + billingContactLastName: string; + billingEmail: string; + billingStreetAddress: string; + billingCity: string; + billingState: string; + billingZipCode: string; + billingTerms: string; +} + +export interface UpdateCustomerInput { + name?: string; + startDate?: string; + endDate?: string | null; + primaryContactFirstName?: string; + primaryContactLastName?: string; + primaryContactEmail?: string; + primaryContactPhone?: string; + secondaryContactFirstName?: string | null; + secondaryContactLastName?: string | null; + secondaryContactEmail?: string | null; + secondaryContactPhone?: string | null; + billingContactFirstName?: string; + billingContactLastName?: string; + billingEmail?: string; + billingStreetAddress?: string; + billingCity?: string; + billingState?: string; + billingZipCode?: string; + billingTerms?: string; +} diff --git a/frontend/src/lib/types/invoices.ts b/frontend/src/lib/types/invoices.ts new file mode 100644 index 0000000..69a0858 --- /dev/null +++ b/frontend/src/lib/types/invoices.ts @@ -0,0 +1,32 @@ +export interface Invoice { + id: string; + customerId: string; + date: string; + totalAmount: string; + status: string; + datePaid: string | null; + paymentType: string | null; + sentAt: string | null; + createdAt: string; + updatedAt: string; +} + +export interface CreateInvoiceInput { + customerId: string; + date: string; + totalAmount: string; + status: string; + datePaid?: string | null; + paymentType?: string | null; + sentAt?: string | null; +} + +export interface UpdateInvoiceInput { + customerId?: string; + date?: string; + totalAmount?: string; + status?: string; + datePaid?: string | null; + paymentType?: string | null; + sentAt?: string | null; +} \ No newline at end of file diff --git a/frontend/src/lib/types/labors.ts b/frontend/src/lib/types/labors.ts new file mode 100644 index 0000000..15af022 --- /dev/null +++ b/frontend/src/lib/types/labors.ts @@ -0,0 +1,23 @@ +export interface Labor { + id: string; + accountId: string; + amount: string; + startDate: string; + endDate: string | null; + createdAt: string; + updatedAt: string; +} + +export interface CreateLaborInput { + accountId: string; + amount: string; + startDate: string; + endDate?: string | null; +} + +export interface UpdateLaborInput { + accountId?: string; + amount?: string; + startDate?: string; + endDate?: string | null; +} \ No newline at end of file diff --git a/frontend/src/lib/types/profiles.ts b/frontend/src/lib/types/profiles.ts new file mode 100644 index 0000000..11d3219 --- /dev/null +++ b/frontend/src/lib/types/profiles.ts @@ -0,0 +1,32 @@ +export interface Profile { + id: string; + userId: string; + firstName: string; + lastName: string; + email: string; + primaryPhone: string; + secondaryPhone: string | null; + role: string; + createdAt: string; + updatedAt: string; +} + +export interface CreateProfileInput { + userId: string; + firstName: string; + lastName: string; + email: string; + primaryPhone: string; + secondaryPhone?: string | null; + role: string; +} + +export interface UpdateProfileInput { + userId?: string; + firstName?: string; + lastName?: string; + email?: string; + primaryPhone?: string; + secondaryPhone?: string | null; + role?: string; +} \ No newline at end of file diff --git a/frontend/src/lib/types/projects.ts b/frontend/src/lib/types/projects.ts new file mode 100644 index 0000000..8e57c18 --- /dev/null +++ b/frontend/src/lib/types/projects.ts @@ -0,0 +1,35 @@ +export interface Project { + id: string; + customerId: string; + accountId: string | null; + date: string; + amount: string; + labor: string; + status: string; + notes: string | null; + completedAt: string | null; + createdAt: string; + updatedAt: string; +} + +export interface CreateProjectInput { + customerId: string; + accountId?: string | null; + date: string; + amount: string; + labor: string; + status: string; + notes?: string | null; + completedAt?: string | null; +} + +export interface UpdateProjectInput { + customerId?: string; + accountId?: string | null; + date?: string; + amount?: string; + labor?: string; + status?: string; + notes?: string | null; + completedAt?: string | null; +} \ No newline at end of file diff --git a/frontend/src/lib/types/reports.ts b/frontend/src/lib/types/reports.ts new file mode 100644 index 0000000..74f594a --- /dev/null +++ b/frontend/src/lib/types/reports.ts @@ -0,0 +1,20 @@ +export interface Report { + id: string; + teamMemberId: string; + date: string; + notes: string | null; + createdAt: string; + updatedAt: string; +} + +export interface CreateReportInput { + teamMemberId: string; + date: string; + notes?: string | null; +} + +export interface UpdateReportInput { + teamMemberId?: string; + date?: string; + notes?: string | null; +} \ No newline at end of file diff --git a/frontend/src/lib/types/revenues.ts b/frontend/src/lib/types/revenues.ts new file mode 100644 index 0000000..57a4c24 --- /dev/null +++ b/frontend/src/lib/types/revenues.ts @@ -0,0 +1,23 @@ +export interface Revenue { + id: string; + accountId: string; + amount: string; + startDate: string; + endDate: string | null; + createdAt: string; + updatedAt: string; +} + +export interface CreateRevenueInput { + accountId: string; + amount: string; + startDate: string; + endDate?: string | null; +} + +export interface UpdateRevenueInput { + accountId?: string; + amount?: string; + startDate?: string; + endDate?: string | null; +} \ No newline at end of file diff --git a/frontend/src/lib/types/schedules.ts b/frontend/src/lib/types/schedules.ts new file mode 100644 index 0000000..c4c21d5 --- /dev/null +++ b/frontend/src/lib/types/schedules.ts @@ -0,0 +1,47 @@ +export interface Schedule { + id: string; + accountId: string; + startDate: string; + endDate: string | null; + mondayService: boolean; + tuesdayService: boolean; + wednesdayService: boolean; + thursdayService: boolean; + fridayService: boolean; + saturdayService: boolean; + sundayService: boolean; + weekendService: boolean; + scheduleException: string | null; + createdAt: string; + updatedAt: string; +} + +export interface CreateScheduleInput { + accountId: string; + startDate: string; + endDate?: string | null; + mondayService: boolean; + tuesdayService: boolean; + wednesdayService: boolean; + thursdayService: boolean; + fridayService: boolean; + saturdayService: boolean; + sundayService: boolean; + weekendService: boolean; + scheduleException?: string | null; +} + +export interface UpdateScheduleInput { + accountId?: string; + startDate?: string; + endDate?: string | null; + mondayService?: boolean; + tuesdayService?: boolean; + wednesdayService?: boolean; + thursdayService?: boolean; + fridayService?: boolean; + saturdayService?: boolean; + sundayService?: boolean; + weekendService?: boolean; + scheduleException?: string | null; +} \ No newline at end of file diff --git a/frontend/src/lib/types/services.ts b/frontend/src/lib/types/services.ts new file mode 100644 index 0000000..e9d760a --- /dev/null +++ b/frontend/src/lib/types/services.ts @@ -0,0 +1,32 @@ +export interface Service { + id: string; + accountId: string; + date: string; + deadlineStart: string; + deadlineEnd: string; + status: string; + notes: string | null; + completedAt: string | null; + createdAt: string; + updatedAt: string; +} + +export interface CreateServiceInput { + accountId: string; + date: string; + deadlineStart: string; + deadlineEnd: string; + status: string; + notes?: string | null; + completedAt?: string | null; +} + +export interface UpdateServiceInput { + accountId?: string; + date?: string; + deadlineStart?: string; + deadlineEnd?: string; + status?: string; + notes?: string | null; + completedAt?: string | null; +} \ No newline at end of file diff --git a/frontend/src/routes/+layout.svelte b/frontend/src/routes/+layout.svelte new file mode 100644 index 0000000..b955ba8 --- /dev/null +++ b/frontend/src/routes/+layout.svelte @@ -0,0 +1,16 @@ + + +
+ {#if page.url.pathname !== '/' && page.url.pathname !== '/login'} + + {/if} +
+ {@render children()} +
+
diff --git a/frontend/src/routes/+page.svelte b/frontend/src/routes/+page.svelte new file mode 100644 index 0000000..a2c7e0b --- /dev/null +++ b/frontend/src/routes/+page.svelte @@ -0,0 +1,32 @@ + + +
+
+

Welcome to Nexus

+

Your next-generation application platform

+ +

+ Nexus provides a powerful, scalable foundation for your applications with + integrated GraphQL, authentication, and more. +

+ + +
+ +
+

© 2025 Nexus Platform. All rights reserved.

+
+
\ No newline at end of file diff --git a/frontend/src/routes/accounts/+page.svelte b/frontend/src/routes/accounts/+page.svelte new file mode 100644 index 0000000..6a4e968 --- /dev/null +++ b/frontend/src/routes/accounts/+page.svelte @@ -0,0 +1,132 @@ + + +
+
+

Accounts

+ + Add New Account + +
+ + {#if isLoading} +
+
+
+ {:else if errorMessage} + + {:else if $accountsStore.data?.accounts && $accountsStore.data.accounts.length > 0} +
+ + + + + + + + + + + + + + {#each $accountsStore.data.accounts as account (account.id)} + viewAccount(account.id)}> + + + + + + + + + {/each} + +
Name + Customer ID + Primary + Contact + Email + Start + Date + End Date + Actions +
+
{account.name}
+
+
{account.customerId}
+
+
{account.primaryContactFirstName} {account.primaryContactLastName}
+
+
{account.primaryContactEmail}
+
+
{formatDate(account.startDate)}
+
+
{formatDate(account.endDate)}
+
+ e.stopPropagation()}> + Edit + +
+
+ {:else} + + {/if} + + +
\ No newline at end of file diff --git a/frontend/src/routes/accounts/[id]/+page.svelte b/frontend/src/routes/accounts/[id]/+page.svelte new file mode 100644 index 0000000..8057013 --- /dev/null +++ b/frontend/src/routes/accounts/[id]/+page.svelte @@ -0,0 +1,600 @@ + + +
+

Account Details

+ + {#if isLoading} +
+
+
+ {:else if errorMessage} + + {:else if successMessage} + + {:else if $accountStore.data?.account} +
+
+

{$accountStore.data.account.name}

+
+ + Edit Account + + +
+
+ +
+
+
+

Account Information

+
+
+

Customer ID

+

{$accountStore.data.account.customerId}

+
+
+

Start Date

+

{formatDate($accountStore.data.account.startDate)}

+
+ {#if $accountStore.data.account.endDate} +
+

End Date

+

{formatDate($accountStore.data.account.endDate)}

+
+ {/if} +
+
+ +
+

Primary Contact

+
+
+

Name

+

{$accountStore.data.account.primaryContactFirstName} {$accountStore.data.account.primaryContactLastName}

+
+
+

Email

+

{$accountStore.data.account.primaryContactEmail}

+
+
+

Phone

+

{$accountStore.data.account.primaryContactPhone}

+
+
+
+ + {#if $accountStore.data.account.secondaryContactFirstName} +
+

Secondary Contact

+
+
+

Name

+

{$accountStore.data.account.secondaryContactFirstName} {$accountStore.data.account.secondaryContactLastName}

+
+ {#if $accountStore.data.account.secondaryContactEmail} +
+

Email

+

{$accountStore.data.account.secondaryContactEmail}

+
+ {/if} + {#if $accountStore.data.account.secondaryContactPhone} +
+

Phone

+

{$accountStore.data.account.secondaryContactPhone}

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

Address Information

+
+
+

Street Address

+

{$accountStore.data.account.streetAddress}

+
+
+

City, State, Zip

+

{$accountStore.data.account.city}, {$accountStore.data.account.state} {$accountStore.data.account.zipCode}

+
+
+
+ +
+

System Information

+
+
+

Account ID

+

{$accountStore.data.account.id}

+
+
+

Created

+

{new Date($accountStore.data.account.createdAt).toLocaleString()}

+
+
+

Last Updated

+

{new Date($accountStore.data.account.updatedAt).toLocaleString()}

+
+
+
+
+
+ + +
+
+

Weekly Schedule

+ + Add Schedule + +
+ + {#if $schedulesStore.data?.schedulesByAccount && $schedulesStore.data.schedulesByAccount.length > 0} +
+ {#each $schedulesStore.data.schedulesByAccount as schedule (schedule.id)} + {@const active = isActive(schedule.startDate, schedule.endDate)} +
+ {#if active} + Active + {:else} + Inactive + {/if} + +
+

Date Range

+

{formatDate(schedule.startDate)} - {formatDate(schedule.endDate)}

+
+ +
+

Service Days

+
+ Mon + Tue + Wed + Thu + Fri + Sat + Sun +
+
+ + {#if schedule.scheduleException} +
+

Exceptions

+

{schedule.scheduleException}

+
+ {/if} + +
+ + Edit + + +
+
+ {/each} +
+ {:else} +
+

No schedules found for this account.

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

Labor

+ + Add Labor + +
+ + {#if $laborsStore.data?.laborsByAccount && $laborsStore.data.laborsByAccount.length > 0} +
+ {#each $laborsStore.data.laborsByAccount as labor (labor.id)} + {@const active = isActive(labor.startDate, labor.endDate)} +
+ {#if active} + Active + {:else} + Inactive + {/if} + +
+

Amount

+

${labor.amount}

+
+ +
+

Date Range

+

{formatDate(labor.startDate)} - {formatDate(labor.endDate)}

+
+ +
+ + Edit + + +
+
+ {/each} +
+ {:else} +
+

No labor records found for this account.

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

Monthly Revenue

+ + Add Revenue + +
+ + {#if $revenuesStore.data?.revenuesByAccount && $revenuesStore.data.revenuesByAccount.length > 0} +
+ {#each $revenuesStore.data.revenuesByAccount as revenue (revenue.id)} + {@const active = isActive(revenue.startDate, revenue.endDate)} +
+ {#if active} + Active + {:else} + Inactive + {/if} + +
+

Monthly Amount

+

${revenue.amount}

+
+ +
+

Date Range

+

{formatDate(revenue.startDate)} - {formatDate(revenue.endDate)}

+
+ +
+ + Edit + + +
+
+ {/each} +
+ {:else} +
+

No revenue records found for this account.

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

Upcoming Services

+ + Add Service + +
+ + {#if $servicesStore.data?.servicesByAccount && $servicesStore.data.servicesByAccount.length > 0} +
+ + + + + + + + + + + {#each $servicesStore.data.servicesByAccount.sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime()).slice(0, 5) as service (service.id)} + + + + + + + {/each} + +
DateStatusDeadlineActions
{formatDate(service.date)} + + {service.status} + + + {new Date(service.deadlineStart).toLocaleString()} - {new Date(service.deadlineEnd).toLocaleString()} + + + View + +
+
+ {#if $servicesStore.data.servicesByAccount.length > 5} + + {/if} + {:else} +
+

No services found for this account.

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

Recent Projects

+ + Add Project + +
+ + {#if $projectsStore.data?.projectsByAccount && $projectsStore.data.projectsByAccount.length > 0} +
+ + + + + + + + + + + + {#each $projectsStore.data.projectsByAccount.sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime()).slice(0, 5) as project (project.id)} + + + + + + + + {/each} + +
DateStatusAmountLaborActions
{formatDate(project.date)} + + {project.status} + + ${project.amount}${project.labor} + + View + +
+
+ {#if $projectsStore.data.projectsByAccount.length > 5} + + {/if} + {:else} +
+

No projects found for this account.

+
+ {/if} +
+
+ {:else} + + {/if} + + + + {#if showDeleteConfirm} +
+
+

Confirm Delete

+

Are you sure you want to delete this account? This action cannot be undone.

+
+ + +
+
+
+ {/if} +
diff --git a/frontend/src/routes/accounts/[id]/edit/+page.svelte b/frontend/src/routes/accounts/[id]/edit/+page.svelte new file mode 100644 index 0000000..4d4e7b6 --- /dev/null +++ b/frontend/src/routes/accounts/[id]/edit/+page.svelte @@ -0,0 +1,135 @@ + + +
+

Edit Account

+ + {#if isLoading} +
+
+
+ {:else if errorMessage} + + {:else if successMessage} + + {:else if account} +
+ +
+ {:else} + + {/if} + + +
\ No newline at end of file diff --git a/frontend/src/routes/accounts/new/+page.svelte b/frontend/src/routes/accounts/new/+page.svelte new file mode 100644 index 0000000..818edac --- /dev/null +++ b/frontend/src/routes/accounts/new/+page.svelte @@ -0,0 +1,97 @@ + + +
+

Create New Account

+ + {#if isLoading} +
+
+
+ {:else if errorMessage} + + {:else if successMessage} + + {:else} +
+ +
+ {/if} + + +
\ No newline at end of file diff --git a/frontend/src/routes/customers/+page.svelte b/frontend/src/routes/customers/+page.svelte new file mode 100644 index 0000000..0206513 --- /dev/null +++ b/frontend/src/routes/customers/+page.svelte @@ -0,0 +1,126 @@ + + +
+
+

Customers

+ + Add New Customer + +
+ + {#if isLoading} +
+
+
+ {:else if errorMessage} + + {:else if $customersStore.data?.customers && $customersStore.data.customers.length > 0} +
+ + + + + + + + + + + + + {#each $customersStore.data.customers as customer (customer.id)} + viewCustomer(customer.id)}> + + + + + + + + {/each} + +
Name + Primary + Contact + Email + Start + Date + End Date + Actions +
+
{customer.name}
+
+
{customer.primaryContactFirstName} {customer.primaryContactLastName}
+
+
{customer.primaryContactEmail}
+
+
{formatDate(customer.startDate)}
+
+
{formatDate(customer.endDate)}
+
+ e.stopPropagation()}> + Edit + +
+
+ {:else} + + {/if} + + +
diff --git a/frontend/src/routes/customers/[id]/+page.svelte b/frontend/src/routes/customers/[id]/+page.svelte new file mode 100644 index 0000000..6e6dd0c --- /dev/null +++ b/frontend/src/routes/customers/[id]/+page.svelte @@ -0,0 +1,258 @@ + + +
+

Customer Details

+ + {#if isLoading} +
+
+
+ {:else if errorMessage} + + {:else if successMessage} + + {:else if $customerStore.data?.customer} +
+
+

{$customerStore.data.customer.name}

+
+ + Edit Customer + + +
+
+ +
+
+
+

Customer Information

+
+
+

Start Date

+

{formatDate($customerStore.data.customer.startDate)}

+
+ {#if $customerStore.data.customer.endDate} +
+

End Date

+

{formatDate($customerStore.data.customer.endDate)}

+
+ {/if} +
+
+ +
+

Primary Contact

+
+
+

Name

+

{$customerStore.data.customer.primaryContactFirstName} {$customerStore.data.customer.primaryContactLastName}

+
+
+

Email

+

{$customerStore.data.customer.primaryContactEmail}

+
+
+

Phone

+

{$customerStore.data.customer.primaryContactPhone}

+
+
+
+ + {#if $customerStore.data.customer.secondaryContactFirstName} +
+

Secondary Contact

+
+
+

Name

+

{$customerStore.data.customer.secondaryContactFirstName} {$customerStore.data.customer.secondaryContactLastName}

+
+ {#if $customerStore.data.customer.secondaryContactEmail} +
+

Email

+

{$customerStore.data.customer.secondaryContactEmail}

+
+ {/if} + {#if $customerStore.data.customer.secondaryContactPhone} +
+

Phone

+

{$customerStore.data.customer.secondaryContactPhone}

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

Billing Information

+
+
+

Billing Contact

+

{$customerStore.data.customer.billingContactFirstName} {$customerStore.data.customer.billingContactLastName}

+
+
+

Billing Email

+

{$customerStore.data.customer.billingEmail}

+
+
+

Billing Address

+

{$customerStore.data.customer.billingStreetAddress}

+

{$customerStore.data.customer.billingCity} + , {$customerStore.data.customer.billingState} {$customerStore.data.customer.billingZipCode}

+
+
+

Billing Terms

+

{$customerStore.data.customer.billingTerms}

+
+
+
+ +
+

System Information

+
+
+

Customer ID

+

{$customerStore.data.customer.id}

+
+
+

Created

+

{new Date($customerStore.data.customer.createdAt).toLocaleString()}

+
+
+

Last Updated

+

{new Date($customerStore.data.customer.updatedAt).toLocaleString()}

+
+
+
+
+
+
+ {:else} + + {/if} + + + + {#if showDeleteConfirm} +
+
+

Confirm Delete

+

Are you sure you want to delete this customer? This action cannot be undone.

+
+ + +
+
+
+ {/if} +
diff --git a/frontend/src/routes/customers/[id]/edit/+page.svelte b/frontend/src/routes/customers/[id]/edit/+page.svelte new file mode 100644 index 0000000..fa898ca --- /dev/null +++ b/frontend/src/routes/customers/[id]/edit/+page.svelte @@ -0,0 +1,138 @@ + + +
+

Edit Customer

+ + {#if isLoading} +
+
+
+ {:else if errorMessage} + + {:else if successMessage} + + {:else if customer} +
+ +
+ {:else} + + {/if} + + +
diff --git a/frontend/src/routes/customers/new/+page.svelte b/frontend/src/routes/customers/new/+page.svelte new file mode 100644 index 0000000..26318f4 --- /dev/null +++ b/frontend/src/routes/customers/new/+page.svelte @@ -0,0 +1,97 @@ + + +
+

Create New Customer

+ + {#if isLoading} +
+
+
+ {:else if errorMessage} + + {:else if successMessage} + + {:else} +
+ +
+ {/if} + + +
diff --git a/frontend/src/routes/home/+page.svelte b/frontend/src/routes/home/+page.svelte new file mode 100644 index 0000000..edebe5d --- /dev/null +++ b/frontend/src/routes/home/+page.svelte @@ -0,0 +1,141 @@ + + +
+
+
+

Nexus Dashboard

+
+ Welcome, {username} + +
+
+
+ +
+ {#if isLoading} +
+
Loading...
+
+ {:else} +
+

Welcome to Your Dashboard

+ +
+ +
+

Projects

+

12

+

Active projects in your account

+
+ +
+

Tasks

+

34

+

Tasks assigned to you

+
+ +
+

Notifications

+

7

+

Unread notifications

+
+
+ +
+

Recent Activity

+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
+ Activity + + Project + + Date +
Updated task statusProject AlphaToday, 10:30 AM
Added new commentProject BetaYesterday, 3:45 PM
Created new taskProject GammaJun 14, 2025
+
+
+
+ {/if} +
+ +
+
+

© 2025 Nexus Platform. All rights reserved.

+
+
+
diff --git a/frontend/src/routes/login/+page.svelte b/frontend/src/routes/login/+page.svelte new file mode 100644 index 0000000..a5c4adb --- /dev/null +++ b/frontend/src/routes/login/+page.svelte @@ -0,0 +1,114 @@ + + +
+
+

Login to Nexus

+ + {#if errorMessage} + + {/if} + +
+
+ + +
+ +
+ + +
+ +
+ +
+
+ + +
+
diff --git a/frontend/src/routes/profile/+page.svelte b/frontend/src/routes/profile/+page.svelte new file mode 100644 index 0000000..20c0dd9 --- /dev/null +++ b/frontend/src/routes/profile/+page.svelte @@ -0,0 +1,127 @@ + + +
+

Profile

+ + {#if isLoading} +
+
+
+ {:else if errorMessage} + + {:else if $profileStore.data?.profile} +
+
+
+

{$profileStore.data.profile.firstName} {$profileStore.data.profile.lastName}

+ +
+
+

Role

+

{$profileStore.data.profile.role}

+
+ +
+

Email

+

{$profileStore.data.profile.email}

+
+ +
+

Primary Phone

+

{$profileStore.data.profile.primaryPhone}

+
+ + {#if $profileStore.data.profile.secondaryPhone} +
+

Secondary Phone

+

{$profileStore.data.profile.secondaryPhone}

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

Account Details

+
+
+

Profile ID

+

{$profileStore.data.profile.id}

+
+
+

User ID

+

{$profileStore.data.profile.userId}

+
+
+

Created

+

{new Date($profileStore.data.profile.createdAt).toLocaleString()}

+
+
+

Last Updated

+

{new Date($profileStore.data.profile.updatedAt).toLocaleString()}

+
+
+
+ +
+ +
+
+
+
+ {:else} + + {/if} + + +
\ No newline at end of file diff --git a/frontend/src/routes/projects/+page.svelte b/frontend/src/routes/projects/+page.svelte new file mode 100644 index 0000000..2b89394 --- /dev/null +++ b/frontend/src/routes/projects/+page.svelte @@ -0,0 +1,145 @@ + + +
+
+

Projects

+ + Add New Project + +
+ + {#if isLoading} +
+
+
+ {:else if errorMessage} + + {:else if $projectsStore.data?.projects && $projectsStore.data.projects.length > 0} +
+ + + + + + + + + + + + + + {#each $projectsStore.data.projects as project (project.id)} + viewProject(project.id)}> + + + + + + + + + {/each} + +
Customer ID + Account ID + Date + Labor + Amount + Status + Actions +
+
{project.customerId}
+
+
{project.accountId || 'N/A'}
+
+
{formatDate(project.date)}
+
+
{project.labor}
+
+
{formatCurrency(project.amount)}
+
+ + {project.status} + + + e.stopPropagation()}> + Edit + +
+
+ {:else} + + {/if} + + +
\ No newline at end of file diff --git a/frontend/src/routes/projects/[id]/+page.svelte b/frontend/src/routes/projects/[id]/+page.svelte new file mode 100644 index 0000000..efd3009 --- /dev/null +++ b/frontend/src/routes/projects/[id]/+page.svelte @@ -0,0 +1,256 @@ + + +
+

Project Details

+ + {#if isLoading} +
+
+
+ {:else if errorMessage} + + {:else if successMessage} + + {:else if $projectStore.data?.project} +
+
+

Project for Customer {$projectStore.data.project.customerId}

+
+ + Edit Project + + +
+
+ +
+
+
+

Project Information

+
+
+

Customer ID

+

{$projectStore.data.project.customerId}

+
+ {#if $projectStore.data.project.accountId} +
+

Account ID

+

{$projectStore.data.project.accountId}

+
+ {/if} +
+

Date

+

{formatDate($projectStore.data.project.date)}

+
+
+

Status

+

+ {$projectStore.data.project.status} +

+
+
+
+ +
+

Financial Information

+
+
+

Labor

+

{$projectStore.data.project.labor}

+
+
+

Amount

+

{formatCurrency($projectStore.data.project.amount)}

+
+
+
+ + {#if $projectStore.data.project.completedAt} +
+

Completion Information

+
+
+

Completed At

+

{formatDateTime($projectStore.data.project.completedAt)}

+
+
+
+ {/if} +
+ +
+ {#if $projectStore.data.project.notes} +
+

Notes

+
+

{$projectStore.data.project.notes}

+
+
+ {/if} + +
+

System Information

+
+
+

Project ID

+

{$projectStore.data.project.id}

+
+
+

Created

+

{new Date($projectStore.data.project.createdAt).toLocaleString()}

+
+
+

Last Updated

+

{new Date($projectStore.data.project.updatedAt).toLocaleString()}

+
+
+
+
+
+
+ {:else} + + {/if} + + + + {#if showDeleteConfirm} +
+
+

Confirm Delete

+

Are you sure you want to delete this project? This action cannot be undone.

+
+ + +
+
+
+ {/if} +
\ No newline at end of file diff --git a/frontend/src/routes/projects/[id]/edit/+page.svelte b/frontend/src/routes/projects/[id]/edit/+page.svelte new file mode 100644 index 0000000..d6d1c64 --- /dev/null +++ b/frontend/src/routes/projects/[id]/edit/+page.svelte @@ -0,0 +1,127 @@ + + +
+

Edit Project

+ + {#if isLoading} +
+
+
+ {:else if errorMessage} + + {:else if successMessage} + + {:else if project} +
+ +
+ {:else} + + {/if} + + +
\ No newline at end of file diff --git a/frontend/src/routes/projects/new/+page.svelte b/frontend/src/routes/projects/new/+page.svelte new file mode 100644 index 0000000..3e4b69f --- /dev/null +++ b/frontend/src/routes/projects/new/+page.svelte @@ -0,0 +1,97 @@ + + +
+

Create New Project

+ + {#if isLoading} +
+
+
+ {:else if errorMessage} + + {:else if successMessage} + + {:else} +
+ +
+ {/if} + + +
\ No newline at end of file diff --git a/frontend/src/routes/services/+page.svelte b/frontend/src/routes/services/+page.svelte new file mode 100644 index 0000000..dca3c75 --- /dev/null +++ b/frontend/src/routes/services/+page.svelte @@ -0,0 +1,136 @@ + + +
+
+

Services

+ + Add New Service + +
+ + {#if isLoading} +
+
+
+ {:else if errorMessage} + + {:else if $servicesStore.data?.services && $servicesStore.data.services.length > 0} +
+ + + + + + + + + + + + + {#each $servicesStore.data.services as service (service.id)} + viewService(service.id)}> + + + + + + + + {/each} + +
Account ID + Date + Deadline Start + Deadline End + Status + Actions +
+
{service.accountId}
+
+
{formatDate(service.date)}
+
+
{formatDateTime(service.deadlineStart)}
+
+
{formatDateTime(service.deadlineEnd)}
+
+ + {service.status} + + + e.stopPropagation()}> + Edit + +
+
+ {:else} + + {/if} + + +
\ No newline at end of file diff --git a/frontend/src/routes/services/[id]/+page.svelte b/frontend/src/routes/services/[id]/+page.svelte new file mode 100644 index 0000000..7015917 --- /dev/null +++ b/frontend/src/routes/services/[id]/+page.svelte @@ -0,0 +1,241 @@ + + +
+

Service Details

+ + {#if isLoading} +
+
+
+ {:else if errorMessage} + + {:else if successMessage} + + {:else if $serviceStore.data?.service} +
+
+

Service for Account {$serviceStore.data.service.accountId}

+
+ + Edit Service + + +
+
+ +
+
+
+

Service Information

+
+
+

Account ID

+

{$serviceStore.data.service.accountId}

+
+
+

Date

+

{formatDate($serviceStore.data.service.date)}

+
+
+

Status

+

+ {$serviceStore.data.service.status} +

+
+
+
+ +
+

Deadline Information

+
+
+

Start Deadline

+

{formatDateTime($serviceStore.data.service.deadlineStart)}

+
+
+

End Deadline

+

{formatDateTime($serviceStore.data.service.deadlineEnd)}

+
+
+
+ + {#if $serviceStore.data.service.completedAt} +
+

Completion Information

+
+
+

Completed At

+

{formatDateTime($serviceStore.data.service.completedAt)}

+
+
+
+ {/if} +
+ +
+ {#if $serviceStore.data.service.notes} +
+

Notes

+
+

{$serviceStore.data.service.notes}

+
+
+ {/if} + +
+

System Information

+
+
+

Service ID

+

{$serviceStore.data.service.id}

+
+
+

Created

+

{new Date($serviceStore.data.service.createdAt).toLocaleString()}

+
+
+

Last Updated

+

{new Date($serviceStore.data.service.updatedAt).toLocaleString()}

+
+
+
+
+
+
+ {:else} + + {/if} + + + + {#if showDeleteConfirm} +
+
+

Confirm Delete

+

Are you sure you want to delete this service? This action cannot be undone.

+
+ + +
+
+
+ {/if} +
\ No newline at end of file diff --git a/frontend/src/routes/services/[id]/edit/+page.svelte b/frontend/src/routes/services/[id]/edit/+page.svelte new file mode 100644 index 0000000..afaecbc --- /dev/null +++ b/frontend/src/routes/services/[id]/edit/+page.svelte @@ -0,0 +1,126 @@ + + +
+

Edit Service

+ + {#if isLoading} +
+
+
+ {:else if errorMessage} + + {:else if successMessage} + + {:else if service} +
+ +
+ {:else} + + {/if} + + +
\ No newline at end of file diff --git a/frontend/src/routes/services/new/+page.svelte b/frontend/src/routes/services/new/+page.svelte new file mode 100644 index 0000000..a359e22 --- /dev/null +++ b/frontend/src/routes/services/new/+page.svelte @@ -0,0 +1,97 @@ + + +
+

Create New Service

+ + {#if isLoading} +
+
+
+ {:else if errorMessage} + + {:else if successMessage} + + {:else} +
+ +
+ {/if} + + +
\ No newline at end of file diff --git a/frontend/static/favicon.png b/frontend/static/favicon.png new file mode 100644 index 0000000..825b9e6 Binary files /dev/null and b/frontend/static/favicon.png differ diff --git a/frontend/svelte.config.js b/frontend/svelte.config.js new file mode 100644 index 0000000..c1ac59f --- /dev/null +++ b/frontend/svelte.config.js @@ -0,0 +1,15 @@ +import adapter from '@sveltejs/adapter-node'; +import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'; + +const config = { + preprocess: vitePreprocess(), + kit: { + adapter: adapter(), + + alias: { + $houdini: ".houdini/" + } + } +}; + +export default config; diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json new file mode 100644 index 0000000..1228350 --- /dev/null +++ b/frontend/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "./.svelte-kit/tsconfig.json", + "compilerOptions": { + "allowJs": true, + "checkJs": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "skipLibCheck": true, + "sourceMap": true, + "strict": true, + "moduleResolution": "bundler", + "rootDirs": [ + ".", + "./.svelte-kit/types", + "./.houdini/types" + ] + } +} \ No newline at end of file diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts new file mode 100644 index 0000000..c8ed2df --- /dev/null +++ b/frontend/vite.config.ts @@ -0,0 +1,8 @@ +import houdini from "houdini/vite"; +import tailwindcss from '@tailwindcss/vite'; +import { sveltekit } from '@sveltejs/kit/vite'; +import { defineConfig } from 'vite'; + +export default defineConfig({ + plugins: [houdini(), tailwindcss(), sveltekit()] +}); diff --git a/migration/.gitignore b/migration/.gitignore new file mode 100644 index 0000000..a6f89c2 --- /dev/null +++ b/migration/.gitignore @@ -0,0 +1 @@ +/target/ \ No newline at end of file diff --git a/migration/Cargo.toml b/migration/Cargo.toml new file mode 100644 index 0000000..a5ad34e --- /dev/null +++ b/migration/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "migration" +version = "0.1.0" +edition = "2021" +publish = false + +[lib] +name = "migration" +path = "src/lib.rs" + +[dependencies] +async-std = { version = "1", features = ["attributes", "tokio1"] } +sea-orm = { version = "1.1.12", features = ["macros", "with-uuid", "with-chrono"] } + +[dependencies.sea-orm-migration] +version = "1.1.0" +features = [ + # Enable at least one `ASYNC_RUNTIME` and `DATABASE_DRIVER` feature if you want to run migration via CLI. + # View the list of supported features at https://www.sea-ql.org/SeaORM/docs/install-and-config/database-and-async-runtime. + # e.g. + "runtime-tokio-rustls", # `ASYNC_RUNTIME` feature + "sqlx-postgres", # `DATABASE_DRIVER` feature +] diff --git a/migration/README.md b/migration/README.md new file mode 100644 index 0000000..3b438d8 --- /dev/null +++ b/migration/README.md @@ -0,0 +1,41 @@ +# Running Migrator CLI + +- Generate a new migration file + ```sh + cargo run -- generate MIGRATION_NAME + ``` +- Apply all pending migrations + ```sh + cargo run + ``` + ```sh + cargo run -- up + ``` +- Apply first 10 pending migrations + ```sh + cargo run -- up -n 10 + ``` +- Rollback last applied migrations + ```sh + cargo run -- down + ``` +- Rollback last 10 applied migrations + ```sh + cargo run -- down -n 10 + ``` +- Drop all tables from the database, then reapply all migrations + ```sh + cargo run -- fresh + ``` +- Rollback all applied migrations, then reapply all migrations + ```sh + cargo run -- refresh + ``` +- Rollback all applied migrations + ```sh + cargo run -- reset + ``` +- Check the status of all migrations + ```sh + cargo run -- status + ``` diff --git a/migration/src/lib.rs b/migration/src/lib.rs new file mode 100644 index 0000000..a1de65f --- /dev/null +++ b/migration/src/lib.rs @@ -0,0 +1,10 @@ +pub use sea_orm_migration::prelude::*; +pub struct Migrator; + +#[async_trait::async_trait] +impl MigratorTrait for Migrator { + fn migrations() -> Vec> { + vec![ + ] + } +} diff --git a/migration/src/main.rs b/migration/src/main.rs new file mode 100644 index 0000000..c6b6e48 --- /dev/null +++ b/migration/src/main.rs @@ -0,0 +1,6 @@ +use sea_orm_migration::prelude::*; + +#[async_std::main] +async fn main() { + cli::run_cli(migration::Migrator).await; +} diff --git a/src/auth/error.rs b/src/auth/error.rs new file mode 100644 index 0000000..2cfec7a --- /dev/null +++ b/src/auth/error.rs @@ -0,0 +1,46 @@ +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum AuthError { + #[error("Invalid credentials")] + InvalidCredentials, + + #[error("Failed to create token")] + TokenCreationError, + + #[error("Invalid token")] + InvalidToken, + + #[error("Token has expired")] + TokenExpired, + + #[error("Missing authentication token")] + MissingToken, + + #[error("Password hashing error: {0}")] + PasswordHashError(String), + + #[error("Database error: {0}")] + DatabaseError(String), + + #[error("User not found")] + UserNotFound, + + #[error("Profile not found")] + ProfileNotFound, + + #[error("Internal server error")] + InternalError, +} + +impl From for AuthError { + fn from(err: sea_orm::DbErr) -> Self { + AuthError::DatabaseError(err.to_string()) + } +} + +impl From for AuthError { + fn from(err: bcrypt::BcryptError) -> Self { + AuthError::PasswordHashError(err.to_string()) + } +} \ No newline at end of file diff --git a/src/auth/handlers.rs b/src/auth/handlers.rs new file mode 100644 index 0000000..4a089dd --- /dev/null +++ b/src/auth/handlers.rs @@ -0,0 +1,249 @@ +use std::sync::Arc; + +use actix_web::{HttpResponse, Responder, web}; +use bcrypt::verify; +use chrono::Utc; +use sea_orm::{ + ActiveModelTrait, ColumnTrait, DatabaseConnection, EntityTrait, QueryFilter, Set, + TransactionTrait, +}; +use serde::{Deserialize, Serialize}; +use uuid::Uuid; + +use crate::auth::error::AuthError; +use crate::auth::{JwtConfig, generate_token, hash_password, validate_token}; +use crate::entities::{profile, user}; + +// Request and response structures +#[derive(Debug, Deserialize)] +pub struct LoginRequest { + pub username: String, + pub password: String, +} + +#[derive(Debug, Serialize)] +pub struct TokenResponse { + pub access_token: String, + pub token_type: String, + pub expires_in: i64, + pub profile_id: String, +} + +#[derive(Debug, Deserialize)] +pub struct RenewTokenRequest { + pub token: String, +} + +// User creation request structure +#[derive(Debug, Deserialize)] +pub struct CreateUserRequest { + // User fields + pub username: String, + pub email: String, + pub password: String, + pub is_active: Option, + + // Profile fields + pub first_name: String, + pub last_name: String, + pub primary_phone: String, + pub secondary_phone: Option, + pub profile_email: Option, // Optional, uses user email if not provided + pub role: String, +} + +#[derive(Debug, Serialize)] +pub struct CreateUserResponse { + pub user_id: String, + pub profile_id: String, + pub username: String, + pub message: String, +} + +// Login handler +pub async fn login( + db: web::Data>, + login_req: web::Json, +) -> Result { + // Find user by username + let user_result = user::Entity::find() + .filter(user::Column::Username.eq(&login_req.username)) + .one(db.as_ref().as_ref()) + .await?; + + let user = user_result.ok_or(AuthError::InvalidCredentials)?; + + // Verify password + let password_matches = verify(&login_req.password, &user.password_hash) + .map_err(|e| AuthError::PasswordHashError(e.to_string()))?; + + if !password_matches { + return Err(AuthError::InvalidCredentials); + } + + // Get user profile for role information + let profile_result = profile::Entity::find() + .filter(profile::Column::UserId.eq(user.id)) + .one(db.as_ref().as_ref()) + .await?; + + let profile = profile_result.ok_or(AuthError::ProfileNotFound)?; + + // Generate JWT token + let config = JwtConfig::default(); + let token = generate_token(user.id, &user.username, &profile.role, &config)?; + + // Create response + let response = TokenResponse { + profile_id: profile.id.to_string(), + access_token: token, + token_type: "Bearer".to_string(), + expires_in: config.token_expiration, + }; + + Ok(HttpResponse::Ok().json(response)) +} + +// Token renewal handler +pub async fn renew_token( + db: web::Data>, + token_req: web::Json, +) -> Result { + // Validate the existing token + let config = JwtConfig::default(); + let claims = validate_token(&token_req.token, &config)?; + + // Check if the user exists and is active + let user_id = Uuid::parse_str(&claims.sub).map_err(|_| AuthError::InvalidToken)?; + + let user_result = user::Entity::find_by_id(user_id) + .one(db.as_ref().as_ref()) + .await?; + + let user = user_result.ok_or(AuthError::UserNotFound)?; + + if !user.is_active { + return Err(AuthError::InvalidCredentials); + } + + // Get user profile for role information + let profile_result = profile::Entity::find() + .filter(profile::Column::UserId.eq(user.id)) + .one(db.as_ref().as_ref()) + .await?; + + let profile = profile_result.ok_or(AuthError::ProfileNotFound)?; + + // Generate new JWT token + let new_token = generate_token(user.id, &user.username, &profile.role, &config)?; + + // Create response + let response = TokenResponse { + profile_id: profile.id.to_string(), + access_token: new_token, + token_type: "Bearer".to_string(), + expires_in: config.token_expiration, + }; + + Ok(HttpResponse::Ok().json(response)) +} + +// User creation handler +pub async fn create_user( + db: web::Data>, + user_req: web::Json, +) -> Result { + // Start a transaction + let txn = db.as_ref().begin().await?; + + // Generate UUIDs for user and profile + let user_id = Uuid::new_v4(); + let profile_id = Uuid::new_v4(); + let now = Utc::now().naive_utc(); + + // Hash the password + let password_hash = hash_password(&user_req.password)?; + + // Create a user active model + let user = user::ActiveModel { + id: Set(user_id), + username: Set(user_req.username.clone()), + email: Set(user_req.email.clone()), + password_hash: Set(password_hash), + is_active: Set(user_req.is_active.unwrap_or(true)), + created_at: Set(now), + updated_at: Set(now), + }; + + // Insert user + let user_result = user.insert(&txn).await?; + + // Create a profile active model + let profile = profile::ActiveModel { + id: Set(profile_id), + user_id: Set(user_id), + first_name: Set(user_req.first_name.clone()), + last_name: Set(user_req.last_name.clone()), + primary_phone: Set(user_req.primary_phone.clone()), + secondary_phone: Set(user_req.secondary_phone.clone()), + email: Set(user_req + .profile_email + .clone() + .unwrap_or_else(|| user_req.email.clone())), + role: Set(user_req.role.clone()), + created_at: Set(now), + updated_at: Set(now), + }; + + // Insert profile + let profile_result = profile.insert(&txn).await?; + + // Commit the transaction + txn.commit().await?; + + // Create response + let response = CreateUserResponse { + user_id: user_result.id.to_string(), + profile_id: profile_result.id.to_string(), + username: user_result.username, + message: "User and profile created successfully".to_string(), + }; + + Ok(HttpResponse::Created().json(response)) +} + +// Error handling for auth errors +impl actix_web::ResponseError for AuthError { + fn error_response(&self) -> HttpResponse { + match self { + AuthError::InvalidCredentials => HttpResponse::Unauthorized().json(serde_json::json!({ + "error": "invalid_credentials", + "error_description": "The username or password is incorrect" + })), + AuthError::TokenExpired => HttpResponse::Unauthorized().json(serde_json::json!({ + "error": "token_expired", + "error_description": "The access token has expired" + })), + AuthError::InvalidToken => HttpResponse::Unauthorized().json(serde_json::json!({ + "error": "invalid_token", + "error_description": "The access token is invalid" + })), + AuthError::MissingToken => HttpResponse::Unauthorized().json(serde_json::json!({ + "error": "missing_token", + "error_description": "No authentication token provided" + })), + AuthError::UserNotFound => HttpResponse::Unauthorized().json(serde_json::json!({ + "error": "user_not_found", + "error_description": "User not found" + })), + AuthError::ProfileNotFound => HttpResponse::Unauthorized().json(serde_json::json!({ + "error": "profile_not_found", + "error_description": "User profile not found" + })), + _ => HttpResponse::InternalServerError().json(serde_json::json!({ + "error": "server_error", + "error_description": "An internal server error occurred" + })), + } + } +} diff --git a/src/auth/mod.rs b/src/auth/mod.rs new file mode 100644 index 0000000..374d0d0 --- /dev/null +++ b/src/auth/mod.rs @@ -0,0 +1,161 @@ +use dotenv::dotenv; + +use std::future::{Ready, ready}; + +use actix_web::{ + Error, HttpMessage, + dev::{Service, ServiceRequest, ServiceResponse, Transform, forward_ready}, +}; +use bcrypt::{DEFAULT_COST, hash}; +use chrono::{Duration, Utc}; +use jsonwebtoken::{DecodingKey, EncodingKey, Header, Validation, decode, encode}; +use serde::{Deserialize, Serialize}; +use uuid::Uuid; + +use crate::auth::error::AuthError; + +pub mod error; +pub mod handlers; + +// JWT Claims structure +#[derive(Debug, Serialize, Deserialize)] +pub struct Claims { + pub sub: String, // Subject (user ID) + pub username: String, // Username + pub role: String, // User role + pub exp: i64, // Expiration time + pub iat: i64, // Issued at +} + +// JWT configuration +pub struct JwtConfig { + pub secret: String, + pub token_expiration: i64, // in seconds +} + +impl Default for JwtConfig { + fn default() -> Self { + dotenv().ok(); + Self { + secret: std::env::var("JWT_SECRET") + .unwrap_or_else(|_| "default_secret_change_me".to_string()), + token_expiration: 3600, // 1 hour by default + } + } +} + +// Generate JWT token +pub fn generate_token( + user_id: Uuid, + username: &str, + role: &str, + config: &JwtConfig, +) -> Result { + let now = Utc::now(); + let expires_at = now + Duration::seconds(config.token_expiration); + + let claims = Claims { + sub: user_id.to_string(), + username: username.to_string(), + role: role.to_string(), + exp: expires_at.timestamp(), + iat: now.timestamp(), + }; + + encode( + &Header::default(), + &claims, + &EncodingKey::from_secret(config.secret.as_bytes()), + ) + .map_err(|_| AuthError::TokenCreationError) +} + +// Hash password +pub fn hash_password(password: &str) -> Result { + hash(password, DEFAULT_COST).map_err(|e| AuthError::PasswordHashError(e.to_string())) +} + +// Validate JWT token +pub fn validate_token(token: &str, config: &JwtConfig) -> Result { + decode::( + token, + &DecodingKey::from_secret(config.secret.as_bytes()), + &Validation::default(), + ) + .map(|data| data.claims) + .map_err(|e| match e.kind() { + jsonwebtoken::errors::ErrorKind::ExpiredSignature => AuthError::TokenExpired, + _ => AuthError::InvalidToken, + }) +} + +// Middleware for JWT authentication +pub struct JwtMiddleware; + +impl Transform for JwtMiddleware +where + S: Service, Error = Error>, + S::Future: 'static, + B: 'static, +{ + type Response = ServiceResponse; + type Error = Error; + type Transform = JwtMiddlewareService; + type InitError = (); + type Future = Ready>; + + fn new_transform(&self, service: S) -> Self::Future { + ready(Ok(JwtMiddlewareService { service })) + } +} + +pub struct JwtMiddlewareService { + service: S, +} + +impl Service for JwtMiddlewareService +where + S: Service, Error = Error>, + S::Future: 'static, + B: 'static, +{ + type Response = ServiceResponse; + type Error = Error; + type Future = std::pin::Pin>>>; + + forward_ready!(service); + + fn call(&self, req: ServiceRequest) -> Self::Future { + let auth_header = req.headers().get("Authorization"); + + let config = JwtConfig::default(); + + let result = match auth_header { + Some(header) => { + let auth_str = header.to_str().unwrap_or(""); + if !auth_str.starts_with("Bearer ") { + Err(AuthError::InvalidToken) + } else { + let token = &auth_str[7..]; // Remove "Bearer " prefix + validate_token(token, &config) + } + } + None => Err(AuthError::MissingToken), + }; + + match result { + Ok(claims) => { + // Add claims to request extensions for handlers to access + req.extensions_mut().insert(claims); + let fut = self.service.call(req); + Box::pin(async move { + let res = fut.await?; + Ok(res) + }) + } + Err(e) => { + Box::pin(async move { Err(actix_web::error::ErrorUnauthorized(e.to_string())) }) + } + } + } +} diff --git a/src/db.rs b/src/db.rs new file mode 100644 index 0000000..26dbe76 --- /dev/null +++ b/src/db.rs @@ -0,0 +1,11 @@ +// src/db.rs + +use dotenv::dotenv; +use sea_orm::{Database, DatabaseConnection, DbErr}; +use std::env; + +pub async fn establish_connection() -> Result { + dotenv().ok(); + let database_url = env::var("DATABASE_URL").expect("DATABASE_URL must be set"); + Database::connect(database_url).await +} \ No newline at end of file diff --git a/src/entities/account.rs b/src/entities/account.rs new file mode 100644 index 0000000..a950632 --- /dev/null +++ b/src/entities/account.rs @@ -0,0 +1,106 @@ +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.12 + +use sea_orm::entity::prelude::*; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)] +#[sea_orm(table_name = "account")] +pub struct Model { + #[sea_orm(primary_key, auto_increment = false)] + pub id: Uuid, + pub customer_id: Uuid, + pub name: String, + pub street_address: String, + pub city: String, + pub state: String, + pub zip_code: String, + pub primary_contact_first_name: String, + pub primary_contact_last_name: String, + pub primary_contact_phone: String, + pub primary_contact_email: String, + pub secondary_contact_first_name: Option, + pub secondary_contact_last_name: Option, + pub secondary_contact_phone: Option, + pub secondary_contact_email: Option, + pub start_date: Date, + pub end_date: Option, + pub created_at: DateTime, + pub updated_at: DateTime, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation { + #[sea_orm( + belongs_to = "super::customer::Entity", + from = "Column::CustomerId", + to = "super::customer::Column::Id", + on_update = "Cascade", + on_delete = "Cascade" + )] + Customer, + #[sea_orm(has_many = "super::invoice_account::Entity")] + InvoiceAccount, + #[sea_orm(has_many = "super::labor::Entity")] + Labor, + #[sea_orm(has_many = "super::project::Entity")] + Project, + #[sea_orm(has_many = "super::revenue::Entity")] + Revenue, + #[sea_orm(has_many = "super::schedule::Entity")] + Schedule, + #[sea_orm(has_many = "super::service::Entity")] + Service, +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::Customer.def() + } +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::InvoiceAccount.def() + } +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::Labor.def() + } +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::Project.def() + } +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::Revenue.def() + } +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::Schedule.def() + } +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::Service.def() + } +} + +impl Related for Entity { + fn to() -> RelationDef { + super::invoice_account::Relation::Invoice.def() + } + fn via() -> Option { + Some(super::invoice_account::Relation::Account.def().rev()) + } +} + +impl ActiveModelBehavior for ActiveModel {} diff --git a/src/entities/customer.rs b/src/entities/customer.rs new file mode 100644 index 0000000..3861235 --- /dev/null +++ b/src/entities/customer.rs @@ -0,0 +1,63 @@ +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.12 + +use sea_orm::entity::prelude::*; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)] +#[sea_orm(table_name = "customer")] +pub struct Model { + #[sea_orm(primary_key, auto_increment = false)] + pub id: Uuid, + pub name: String, + pub primary_contact_first_name: String, + pub primary_contact_last_name: String, + pub primary_contact_phone: String, + pub primary_contact_email: String, + pub secondary_contact_first_name: Option, + pub secondary_contact_last_name: Option, + pub secondary_contact_phone: Option, + pub secondary_contact_email: Option, + pub billing_contact_first_name: String, + pub billing_contact_last_name: String, + pub billing_street_address: String, + pub billing_city: String, + pub billing_state: String, + pub billing_zip_code: String, + pub billing_email: String, + #[sea_orm(column_type = "Text")] + pub billing_terms: String, + pub start_date: Date, + pub end_date: Option, + pub created_at: DateTime, + pub updated_at: DateTime, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation { + #[sea_orm(has_many = "super::account::Entity")] + Account, + #[sea_orm(has_many = "super::invoice::Entity")] + Invoice, + #[sea_orm(has_many = "super::project::Entity")] + Project, +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::Account.def() + } +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::Invoice.def() + } +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::Project.def() + } +} + +impl ActiveModelBehavior for ActiveModel {} diff --git a/src/entities/invoice.rs b/src/entities/invoice.rs new file mode 100644 index 0000000..158d141 --- /dev/null +++ b/src/entities/invoice.rs @@ -0,0 +1,75 @@ +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.12 + +use sea_orm::entity::prelude::*; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)] +#[sea_orm(table_name = "invoice")] +pub struct Model { + #[sea_orm(primary_key, auto_increment = false)] + pub id: Uuid, + pub customer_id: Uuid, + pub date: Date, + pub status: String, + pub date_paid: Option, + pub payment_type: Option, + #[sea_orm(column_type = "Decimal(Some((10, 2)))")] + pub total_amount: Decimal, + pub created_at: DateTime, + pub updated_at: DateTime, + pub sent_at: Option, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation { + #[sea_orm( + belongs_to = "super::customer::Entity", + from = "Column::CustomerId", + to = "super::customer::Column::Id", + on_update = "Cascade", + on_delete = "Cascade" + )] + Customer, + #[sea_orm(has_many = "super::invoice_account::Entity")] + InvoiceAccount, + #[sea_orm(has_many = "super::invoice_project::Entity")] + InvoiceProject, +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::Customer.def() + } +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::InvoiceAccount.def() + } +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::InvoiceProject.def() + } +} + +impl Related for Entity { + fn to() -> RelationDef { + super::invoice_account::Relation::Account.def() + } + fn via() -> Option { + Some(super::invoice_account::Relation::Invoice.def().rev()) + } +} + +impl Related for Entity { + fn to() -> RelationDef { + super::invoice_project::Relation::Project.def() + } + fn via() -> Option { + Some(super::invoice_project::Relation::Invoice.def().rev()) + } +} + +impl ActiveModelBehavior for ActiveModel {} diff --git a/src/entities/invoice_account.rs b/src/entities/invoice_account.rs new file mode 100644 index 0000000..64d580f --- /dev/null +++ b/src/entities/invoice_account.rs @@ -0,0 +1,47 @@ +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.12 + +use sea_orm::entity::prelude::*; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)] +#[sea_orm(table_name = "invoice_account")] +pub struct Model { + #[sea_orm(primary_key, auto_increment = false)] + pub invoice_id: Uuid, + #[sea_orm(primary_key, auto_increment = false)] + pub account_id: Uuid, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation { + #[sea_orm( + belongs_to = "super::account::Entity", + from = "Column::AccountId", + to = "super::account::Column::Id", + on_update = "Cascade", + on_delete = "Cascade" + )] + Account, + #[sea_orm( + belongs_to = "super::invoice::Entity", + from = "Column::InvoiceId", + to = "super::invoice::Column::Id", + on_update = "Cascade", + on_delete = "Cascade" + )] + Invoice, +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::Account.def() + } +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::Invoice.def() + } +} + +impl ActiveModelBehavior for ActiveModel {} diff --git a/src/entities/invoice_project.rs b/src/entities/invoice_project.rs new file mode 100644 index 0000000..ae0b92e --- /dev/null +++ b/src/entities/invoice_project.rs @@ -0,0 +1,47 @@ +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.12 + +use sea_orm::entity::prelude::*; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)] +#[sea_orm(table_name = "invoice_project")] +pub struct Model { + #[sea_orm(primary_key, auto_increment = false)] + pub invoice_id: Uuid, + #[sea_orm(primary_key, auto_increment = false)] + pub project_id: Uuid, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation { + #[sea_orm( + belongs_to = "super::invoice::Entity", + from = "Column::InvoiceId", + to = "super::invoice::Column::Id", + on_update = "Cascade", + on_delete = "Cascade" + )] + Invoice, + #[sea_orm( + belongs_to = "super::project::Entity", + from = "Column::ProjectId", + to = "super::project::Column::Id", + on_update = "Cascade", + on_delete = "Cascade" + )] + Project, +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::Invoice.def() + } +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::Project.def() + } +} + +impl ActiveModelBehavior for ActiveModel {} diff --git a/src/entities/labor.rs b/src/entities/labor.rs new file mode 100644 index 0000000..6d504dd --- /dev/null +++ b/src/entities/labor.rs @@ -0,0 +1,38 @@ +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.12 + +use sea_orm::entity::prelude::*; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)] +#[sea_orm(table_name = "labor")] +pub struct Model { + #[sea_orm(primary_key, auto_increment = false)] + pub id: Uuid, + pub account_id: Uuid, + #[sea_orm(column_type = "Decimal(Some((10, 2)))")] + pub amount: Decimal, + pub start_date: Date, + pub end_date: Option, + pub created_at: DateTime, + pub updated_at: DateTime, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation { + #[sea_orm( + belongs_to = "super::account::Entity", + from = "Column::AccountId", + to = "super::account::Column::Id", + on_update = "Cascade", + on_delete = "Cascade" + )] + Account, +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::Account.def() + } +} + +impl ActiveModelBehavior for ActiveModel {} diff --git a/src/entities/mod.rs b/src/entities/mod.rs new file mode 100644 index 0000000..63c18db --- /dev/null +++ b/src/entities/mod.rs @@ -0,0 +1,21 @@ +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.12 + +pub mod prelude; + +pub mod account; +pub mod customer; +pub mod invoice; +pub mod invoice_account; +pub mod invoice_project; +pub mod labor; +pub mod profile; +pub mod project; +pub mod project_profile; +pub mod report; +pub mod report_project; +pub mod report_service; +pub mod revenue; +pub mod schedule; +pub mod service; +pub mod service_profile; +pub mod user; diff --git a/src/entities/prelude.rs b/src/entities/prelude.rs new file mode 100644 index 0000000..28890cf --- /dev/null +++ b/src/entities/prelude.rs @@ -0,0 +1,19 @@ +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.12 + +pub use super::account::Entity as Account; +pub use super::customer::Entity as Customer; +pub use super::invoice::Entity as Invoice; +pub use super::invoice_account::Entity as InvoiceAccount; +pub use super::invoice_project::Entity as InvoiceProject; +pub use super::labor::Entity as Labor; +pub use super::profile::Entity as Profile; +pub use super::project::Entity as Project; +pub use super::project_profile::Entity as ProjectProfile; +pub use super::report::Entity as Report; +pub use super::report_project::Entity as ReportProject; +pub use super::report_service::Entity as ReportService; +pub use super::revenue::Entity as Revenue; +pub use super::schedule::Entity as Schedule; +pub use super::service::Entity as Service; +pub use super::service_profile::Entity as ServiceProfile; +pub use super::user::Entity as User; diff --git a/src/entities/profile.rs b/src/entities/profile.rs new file mode 100644 index 0000000..aa69bc2 --- /dev/null +++ b/src/entities/profile.rs @@ -0,0 +1,82 @@ +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.12 + +use sea_orm::entity::prelude::*; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)] +#[sea_orm(table_name = "profile")] +pub struct Model { + #[sea_orm(primary_key, auto_increment = false)] + pub id: Uuid, + pub user_id: Uuid, + pub first_name: String, + pub last_name: String, + pub primary_phone: String, + pub secondary_phone: Option, + pub email: String, + pub role: String, + pub created_at: DateTime, + pub updated_at: DateTime, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation { + #[sea_orm(has_many = "super::project_profile::Entity")] + ProjectProfile, + #[sea_orm(has_many = "super::report::Entity")] + Report, + #[sea_orm(has_many = "super::service_profile::Entity")] + ServiceProfile, + #[sea_orm( + belongs_to = "super::user::Entity", + from = "Column::UserId", + to = "super::user::Column::Id", + on_update = "Cascade", + on_delete = "Cascade" + )] + User, +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::ProjectProfile.def() + } +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::Report.def() + } +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::ServiceProfile.def() + } +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::User.def() + } +} + +impl Related for Entity { + fn to() -> RelationDef { + super::project_profile::Relation::Project.def() + } + fn via() -> Option { + Some(super::project_profile::Relation::Profile.def().rev()) + } +} + +impl Related for Entity { + fn to() -> RelationDef { + super::service_profile::Relation::Service.def() + } + fn via() -> Option { + Some(super::service_profile::Relation::Profile.def().rev()) + } +} + +impl ActiveModelBehavior for ActiveModel {} diff --git a/src/entities/project.rs b/src/entities/project.rs new file mode 100644 index 0000000..92bf79f --- /dev/null +++ b/src/entities/project.rs @@ -0,0 +1,109 @@ +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.12 + +use sea_orm::entity::prelude::*; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)] +#[sea_orm(table_name = "project")] +pub struct Model { + #[sea_orm(primary_key, auto_increment = false)] + pub id: Uuid, + pub customer_id: Uuid, + pub account_id: Option, + pub date: Date, + pub status: String, + #[sea_orm(column_type = "Text", nullable)] + pub notes: Option, + #[sea_orm(column_type = "Decimal(Some((10, 2)))")] + pub labor: Decimal, + #[sea_orm(column_type = "Decimal(Some((10, 2)))")] + pub amount: Decimal, + pub created_at: DateTime, + pub updated_at: DateTime, + pub completed_at: Option, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation { + #[sea_orm( + belongs_to = "super::account::Entity", + from = "Column::AccountId", + to = "super::account::Column::Id", + on_update = "Cascade", + on_delete = "SetNull" + )] + Account, + #[sea_orm( + belongs_to = "super::customer::Entity", + from = "Column::CustomerId", + to = "super::customer::Column::Id", + on_update = "Cascade", + on_delete = "Cascade" + )] + Customer, + #[sea_orm(has_many = "super::invoice_project::Entity")] + InvoiceProject, + #[sea_orm(has_many = "super::project_profile::Entity")] + ProjectProfile, + #[sea_orm(has_many = "super::report_project::Entity")] + ReportProject, +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::Account.def() + } +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::Customer.def() + } +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::InvoiceProject.def() + } +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::ProjectProfile.def() + } +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::ReportProject.def() + } +} + +impl Related for Entity { + fn to() -> RelationDef { + super::invoice_project::Relation::Invoice.def() + } + fn via() -> Option { + Some(super::invoice_project::Relation::Project.def().rev()) + } +} + +impl Related for Entity { + fn to() -> RelationDef { + super::project_profile::Relation::Profile.def() + } + fn via() -> Option { + Some(super::project_profile::Relation::Project.def().rev()) + } +} + +impl Related for Entity { + fn to() -> RelationDef { + super::report_project::Relation::Report.def() + } + fn via() -> Option { + Some(super::report_project::Relation::Project.def().rev()) + } +} + +impl ActiveModelBehavior for ActiveModel {} diff --git a/src/entities/project_profile.rs b/src/entities/project_profile.rs new file mode 100644 index 0000000..0101b21 --- /dev/null +++ b/src/entities/project_profile.rs @@ -0,0 +1,47 @@ +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.12 + +use sea_orm::entity::prelude::*; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)] +#[sea_orm(table_name = "project_profile")] +pub struct Model { + #[sea_orm(primary_key, auto_increment = false)] + pub project_id: Uuid, + #[sea_orm(primary_key, auto_increment = false)] + pub profile_id: Uuid, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation { + #[sea_orm( + belongs_to = "super::profile::Entity", + from = "Column::ProfileId", + to = "super::profile::Column::Id", + on_update = "Cascade", + on_delete = "Cascade" + )] + Profile, + #[sea_orm( + belongs_to = "super::project::Entity", + from = "Column::ProjectId", + to = "super::project::Column::Id", + on_update = "Cascade", + on_delete = "Cascade" + )] + Project, +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::Profile.def() + } +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::Project.def() + } +} + +impl ActiveModelBehavior for ActiveModel {} diff --git a/src/entities/report.rs b/src/entities/report.rs new file mode 100644 index 0000000..4830aa7 --- /dev/null +++ b/src/entities/report.rs @@ -0,0 +1,71 @@ +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.12 + +use sea_orm::entity::prelude::*; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)] +#[sea_orm(table_name = "report")] +pub struct Model { + #[sea_orm(primary_key, auto_increment = false)] + pub id: Uuid, + pub date: Date, + pub team_member_id: Uuid, + #[sea_orm(column_type = "Text", nullable)] + pub notes: Option, + pub created_at: DateTime, + pub updated_at: DateTime, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation { + #[sea_orm( + belongs_to = "super::profile::Entity", + from = "Column::TeamMemberId", + to = "super::profile::Column::Id", + on_update = "Cascade", + on_delete = "Cascade" + )] + Profile, + #[sea_orm(has_many = "super::report_project::Entity")] + ReportProject, + #[sea_orm(has_many = "super::report_service::Entity")] + ReportService, +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::Profile.def() + } +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::ReportProject.def() + } +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::ReportService.def() + } +} + +impl Related for Entity { + fn to() -> RelationDef { + super::report_project::Relation::Project.def() + } + fn via() -> Option { + Some(super::report_project::Relation::Report.def().rev()) + } +} + +impl Related for Entity { + fn to() -> RelationDef { + super::report_service::Relation::Service.def() + } + fn via() -> Option { + Some(super::report_service::Relation::Report.def().rev()) + } +} + +impl ActiveModelBehavior for ActiveModel {} diff --git a/src/entities/report_project.rs b/src/entities/report_project.rs new file mode 100644 index 0000000..9c28c51 --- /dev/null +++ b/src/entities/report_project.rs @@ -0,0 +1,47 @@ +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.12 + +use sea_orm::entity::prelude::*; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)] +#[sea_orm(table_name = "report_project")] +pub struct Model { + #[sea_orm(primary_key, auto_increment = false)] + pub report_id: Uuid, + #[sea_orm(primary_key, auto_increment = false)] + pub project_id: Uuid, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation { + #[sea_orm( + belongs_to = "super::project::Entity", + from = "Column::ProjectId", + to = "super::project::Column::Id", + on_update = "Cascade", + on_delete = "Cascade" + )] + Project, + #[sea_orm( + belongs_to = "super::report::Entity", + from = "Column::ReportId", + to = "super::report::Column::Id", + on_update = "Cascade", + on_delete = "Cascade" + )] + Report, +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::Project.def() + } +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::Report.def() + } +} + +impl ActiveModelBehavior for ActiveModel {} diff --git a/src/entities/report_service.rs b/src/entities/report_service.rs new file mode 100644 index 0000000..3780497 --- /dev/null +++ b/src/entities/report_service.rs @@ -0,0 +1,47 @@ +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.12 + +use sea_orm::entity::prelude::*; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)] +#[sea_orm(table_name = "report_service")] +pub struct Model { + #[sea_orm(primary_key, auto_increment = false)] + pub report_id: Uuid, + #[sea_orm(primary_key, auto_increment = false)] + pub service_id: Uuid, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation { + #[sea_orm( + belongs_to = "super::report::Entity", + from = "Column::ReportId", + to = "super::report::Column::Id", + on_update = "Cascade", + on_delete = "Cascade" + )] + Report, + #[sea_orm( + belongs_to = "super::service::Entity", + from = "Column::ServiceId", + to = "super::service::Column::Id", + on_update = "Cascade", + on_delete = "Cascade" + )] + Service, +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::Report.def() + } +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::Service.def() + } +} + +impl ActiveModelBehavior for ActiveModel {} diff --git a/src/entities/revenue.rs b/src/entities/revenue.rs new file mode 100644 index 0000000..9d4cdb1 --- /dev/null +++ b/src/entities/revenue.rs @@ -0,0 +1,38 @@ +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.12 + +use sea_orm::entity::prelude::*; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)] +#[sea_orm(table_name = "revenue")] +pub struct Model { + #[sea_orm(primary_key, auto_increment = false)] + pub id: Uuid, + pub account_id: Uuid, + #[sea_orm(column_type = "Decimal(Some((10, 2)))")] + pub amount: Decimal, + pub start_date: Date, + pub end_date: Option, + pub created_at: DateTime, + pub updated_at: DateTime, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation { + #[sea_orm( + belongs_to = "super::account::Entity", + from = "Column::AccountId", + to = "super::account::Column::Id", + on_update = "Cascade", + on_delete = "Cascade" + )] + Account, +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::Account.def() + } +} + +impl ActiveModelBehavior for ActiveModel {} diff --git a/src/entities/schedule.rs b/src/entities/schedule.rs new file mode 100644 index 0000000..c287849 --- /dev/null +++ b/src/entities/schedule.rs @@ -0,0 +1,46 @@ +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.12 + +use sea_orm::entity::prelude::*; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)] +#[sea_orm(table_name = "schedule")] +pub struct Model { + #[sea_orm(primary_key, auto_increment = false)] + pub id: Uuid, + pub account_id: Uuid, + pub monday_service: bool, + pub tuesday_service: bool, + pub wednesday_service: bool, + pub thursday_service: bool, + pub friday_service: bool, + pub saturday_service: bool, + pub sunday_service: bool, + pub weekend_service: bool, + #[sea_orm(column_type = "Text", nullable)] + pub schedule_exception: Option, + pub start_date: Date, + pub end_date: Option, + pub created_at: DateTime, + pub updated_at: DateTime, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation { + #[sea_orm( + belongs_to = "super::account::Entity", + from = "Column::AccountId", + to = "super::account::Column::Id", + on_update = "Cascade", + on_delete = "Cascade" + )] + Account, +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::Account.def() + } +} + +impl ActiveModelBehavior for ActiveModel {} diff --git a/src/entities/service.rs b/src/entities/service.rs new file mode 100644 index 0000000..5c2bae0 --- /dev/null +++ b/src/entities/service.rs @@ -0,0 +1,75 @@ +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.12 + +use sea_orm::entity::prelude::*; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)] +#[sea_orm(table_name = "service")] +pub struct Model { + #[sea_orm(primary_key, auto_increment = false)] + pub id: Uuid, + pub account_id: Uuid, + pub date: Date, + pub status: String, + #[sea_orm(column_type = "Text", nullable)] + pub notes: Option, + pub deadline_start: DateTime, + pub deadline_end: DateTime, + pub created_at: DateTime, + pub updated_at: DateTime, + pub completed_at: Option, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation { + #[sea_orm( + belongs_to = "super::account::Entity", + from = "Column::AccountId", + to = "super::account::Column::Id", + on_update = "Cascade", + on_delete = "Cascade" + )] + Account, + #[sea_orm(has_many = "super::report_service::Entity")] + ReportService, + #[sea_orm(has_many = "super::service_profile::Entity")] + ServiceProfile, +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::Account.def() + } +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::ReportService.def() + } +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::ServiceProfile.def() + } +} + +impl Related for Entity { + fn to() -> RelationDef { + super::service_profile::Relation::Profile.def() + } + fn via() -> Option { + Some(super::service_profile::Relation::Service.def().rev()) + } +} + +impl Related for Entity { + fn to() -> RelationDef { + super::report_service::Relation::Report.def() + } + fn via() -> Option { + Some(super::report_service::Relation::Service.def().rev()) + } +} + +impl ActiveModelBehavior for ActiveModel {} diff --git a/src/entities/service_profile.rs b/src/entities/service_profile.rs new file mode 100644 index 0000000..15516a9 --- /dev/null +++ b/src/entities/service_profile.rs @@ -0,0 +1,47 @@ +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.12 + +use sea_orm::entity::prelude::*; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)] +#[sea_orm(table_name = "service_profile")] +pub struct Model { + #[sea_orm(primary_key, auto_increment = false)] + pub service_id: Uuid, + #[sea_orm(primary_key, auto_increment = false)] + pub profile_id: Uuid, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation { + #[sea_orm( + belongs_to = "super::profile::Entity", + from = "Column::ProfileId", + to = "super::profile::Column::Id", + on_update = "Cascade", + on_delete = "Cascade" + )] + Profile, + #[sea_orm( + belongs_to = "super::service::Entity", + from = "Column::ServiceId", + to = "super::service::Column::Id", + on_update = "Cascade", + on_delete = "Cascade" + )] + Service, +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::Profile.def() + } +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::Service.def() + } +} + +impl ActiveModelBehavior for ActiveModel {} diff --git a/src/entities/user.rs b/src/entities/user.rs new file mode 100644 index 0000000..7b00c3d --- /dev/null +++ b/src/entities/user.rs @@ -0,0 +1,33 @@ +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.12 + +use sea_orm::entity::prelude::*; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)] +#[sea_orm(table_name = "user")] +pub struct Model { + #[sea_orm(primary_key, auto_increment = false)] + pub id: Uuid, + #[sea_orm(unique)] + pub username: String, + #[sea_orm(unique)] + pub email: String, + pub password_hash: String, + pub is_active: bool, + pub created_at: DateTime, + pub updated_at: DateTime, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation { + #[sea_orm(has_many = "super::profile::Entity")] + Profile, +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::Profile.def() + } +} + +impl ActiveModelBehavior for ActiveModel {} diff --git a/src/graphql/account.rs b/src/graphql/account.rs new file mode 100644 index 0000000..a87b841 --- /dev/null +++ b/src/graphql/account.rs @@ -0,0 +1,322 @@ +use async_graphql::{Context, ID, InputObject, Object, Result, SimpleObject}; +use chrono::{NaiveDate, NaiveDateTime}; +use sea_orm::{ColumnTrait, DatabaseConnection, EntityTrait, QueryFilter, QuerySelect}; +use std::sync::Arc; +use uuid::Uuid; + +use crate::entities::account; +use crate::entities::customer; + +// Main GraphQL Account type +#[derive(SimpleObject)] +pub struct Account { + pub id: ID, + pub customer_id: ID, + pub name: String, + pub street_address: String, + pub city: String, + pub state: String, + pub zip_code: String, + pub primary_contact_first_name: String, + pub primary_contact_last_name: String, + pub primary_contact_phone: String, + pub primary_contact_email: String, + pub secondary_contact_first_name: Option, + pub secondary_contact_last_name: Option, + pub secondary_contact_phone: Option, + pub secondary_contact_email: Option, + pub start_date: NaiveDate, + pub end_date: Option, + pub created_at: NaiveDateTime, + pub updated_at: NaiveDateTime, +} + +// Conversion from SeaORM model to GraphQL type +impl From for Account { + fn from(model: account::Model) -> Self { + Account { + id: ID(model.id.to_string()), + customer_id: ID(model.customer_id.to_string()), + name: model.name, + street_address: model.street_address, + city: model.city, + state: model.state, + zip_code: model.zip_code, + primary_contact_first_name: model.primary_contact_first_name, + primary_contact_last_name: model.primary_contact_last_name, + primary_contact_phone: model.primary_contact_phone, + primary_contact_email: model.primary_contact_email, + secondary_contact_first_name: model.secondary_contact_first_name, + secondary_contact_last_name: model.secondary_contact_last_name, + secondary_contact_phone: model.secondary_contact_phone, + secondary_contact_email: model.secondary_contact_email, + start_date: model.start_date, + end_date: model.end_date, + created_at: model.created_at, + updated_at: model.updated_at, + } + } +} + +// Input types for mutations +#[derive(InputObject)] +pub struct CreateAccountInput { + pub customer_id: ID, + pub name: String, + pub street_address: String, + pub city: String, + pub state: String, + pub zip_code: String, + pub primary_contact_first_name: String, + pub primary_contact_last_name: String, + pub primary_contact_phone: String, + pub primary_contact_email: String, + pub secondary_contact_first_name: Option, + pub secondary_contact_last_name: Option, + pub secondary_contact_phone: Option, + pub secondary_contact_email: Option, + pub start_date: NaiveDate, + pub end_date: Option, +} + +#[derive(InputObject)] +pub struct UpdateAccountInput { + pub customer_id: Option, + pub name: Option, + pub street_address: Option, + pub city: Option, + pub state: Option, + pub zip_code: Option, + pub primary_contact_first_name: Option, + pub primary_contact_last_name: Option, + pub primary_contact_phone: Option, + pub primary_contact_email: Option, + pub secondary_contact_first_name: Option, + pub secondary_contact_last_name: Option, + pub secondary_contact_phone: Option, + pub secondary_contact_email: Option, + pub start_date: Option, + pub end_date: Option, +} + +// Query resolvers +pub struct AccountQuery; + +#[Object] +impl AccountQuery { + // Get account by ID + pub async fn account(&self, ctx: &Context<'_>, id: ID) -> Result> { + let db = ctx.data::>()?; + let account_uuid = Uuid::parse_str(&id)?; + + let account = account::Entity::find_by_id(account_uuid) + .one(db.as_ref()) + .await?; + + Ok(account.map(Account::from)) + } + + // Get all accounts with pagination + pub async fn accounts( + &self, + ctx: &Context<'_>, + #[graphql(desc = "Number of items to return", default = 10)] limit: u64, + #[graphql(desc = "Number of items to skip", default = 0)] offset: u64, + ) -> Result> { + let db = ctx.data::>()?; + + let accounts = account::Entity::find() + .offset(offset) + .limit(limit) + .all(db.as_ref()) + .await?; + + Ok(accounts.into_iter().map(Account::from).collect()) + } + + // Get accounts by customer ID + pub async fn accounts_by_customer( + &self, + ctx: &Context<'_>, + customer_id: ID, + ) -> Result> { + let db = ctx.data::>()?; + let customer_uuid = Uuid::parse_str(&customer_id)?; + + let accounts = account::Entity::find() + .filter(account::Column::CustomerId.eq(customer_uuid)) + .all(db.as_ref()) + .await?; + + Ok(accounts.into_iter().map(Account::from).collect()) + } + + // Search accounts by name + pub async fn search_accounts(&self, ctx: &Context<'_>, name: String) -> Result> { + let db = ctx.data::>()?; + + let accounts = account::Entity::find() + .filter(account::Column::Name.contains(&name)) + .all(db.as_ref()) + .await?; + + Ok(accounts.into_iter().map(Account::from).collect()) + } +} + +// Mutation resolvers +pub struct AccountMutation; + +#[Object] +impl AccountMutation { + // Create a new account + pub async fn create_account( + &self, + ctx: &Context<'_>, + input: CreateAccountInput, + ) -> Result { + let db = ctx.data::>()?; + let customer_uuid = Uuid::parse_str(&input.customer_id)?; + + // Verify that the customer exists + let customer_exists = customer::Entity::find_by_id(customer_uuid) + .one(db.as_ref()) + .await? + .is_some(); + + if !customer_exists { + return Err(async_graphql::Error::new("Customer not found")); + } + + let new_account = account::ActiveModel { + id: sea_orm::Set(Uuid::new_v4()), + customer_id: sea_orm::Set(customer_uuid), + name: sea_orm::Set(input.name), + street_address: sea_orm::Set(input.street_address), + city: sea_orm::Set(input.city), + state: sea_orm::Set(input.state), + zip_code: sea_orm::Set(input.zip_code), + primary_contact_first_name: sea_orm::Set(input.primary_contact_first_name), + primary_contact_last_name: sea_orm::Set(input.primary_contact_last_name), + primary_contact_phone: sea_orm::Set(input.primary_contact_phone), + primary_contact_email: sea_orm::Set(input.primary_contact_email), + secondary_contact_first_name: sea_orm::Set(input.secondary_contact_first_name), + secondary_contact_last_name: sea_orm::Set(input.secondary_contact_last_name), + secondary_contact_phone: sea_orm::Set(input.secondary_contact_phone), + secondary_contact_email: sea_orm::Set(input.secondary_contact_email), + start_date: sea_orm::Set(input.start_date), + end_date: sea_orm::Set(input.end_date), + created_at: sea_orm::Set(chrono::Utc::now().naive_utc()), + updated_at: sea_orm::Set(chrono::Utc::now().naive_utc()), + }; + + let account = account::Entity::insert(new_account) + .exec_with_returning(db.as_ref()) + .await?; + + Ok(Account::from(account)) + } + + // Update an existing account + pub async fn update_account( + &self, + ctx: &Context<'_>, + id: ID, + input: UpdateAccountInput, + ) -> Result { + let db = ctx.data::>()?; + let account_uuid = Uuid::parse_str(&id)?; + + // First, find the existing account to make sure it exists + let existing_account = account::Entity::find_by_id(account_uuid) + .one(db.as_ref()) + .await? + .ok_or_else(|| async_graphql::Error::new("Account not found"))?; + + // Create an ActiveModel with only the fields that need updating + let mut account_to_update: account::ActiveModel = existing_account.into(); + + // Update only the fields that are provided (Some values) + if let Some(customer_id) = input.customer_id { + let customer_uuid = Uuid::parse_str(&customer_id)?; + + // Verify that the customer exists + let customer_exists = customer::Entity::find_by_id(customer_uuid) + .one(db.as_ref()) + .await? + .is_some(); + + if !customer_exists { + return Err(async_graphql::Error::new("Customer not found")); + } + + account_to_update.customer_id = sea_orm::Set(customer_uuid); + } + if let Some(name) = input.name { + account_to_update.name = sea_orm::Set(name); + } + if let Some(street_address) = input.street_address { + account_to_update.street_address = sea_orm::Set(street_address); + } + if let Some(city) = input.city { + account_to_update.city = sea_orm::Set(city); + } + if let Some(state) = input.state { + account_to_update.state = sea_orm::Set(state); + } + if let Some(zip_code) = input.zip_code { + account_to_update.zip_code = sea_orm::Set(zip_code); + } + if let Some(first_name) = input.primary_contact_first_name { + account_to_update.primary_contact_first_name = sea_orm::Set(first_name); + } + if let Some(last_name) = input.primary_contact_last_name { + account_to_update.primary_contact_last_name = sea_orm::Set(last_name); + } + if let Some(phone) = input.primary_contact_phone { + account_to_update.primary_contact_phone = sea_orm::Set(phone); + } + if let Some(email) = input.primary_contact_email { + account_to_update.primary_contact_email = sea_orm::Set(email); + } + if let Some(first_name) = input.secondary_contact_first_name { + account_to_update.secondary_contact_first_name = sea_orm::Set(Some(first_name)); + } + if let Some(last_name) = input.secondary_contact_last_name { + account_to_update.secondary_contact_last_name = sea_orm::Set(Some(last_name)); + } + if let Some(phone) = input.secondary_contact_phone { + account_to_update.secondary_contact_phone = sea_orm::Set(Some(phone)); + } + if let Some(email) = input.secondary_contact_email { + account_to_update.secondary_contact_email = sea_orm::Set(Some(email)); + } + if let Some(start_date) = input.start_date { + account_to_update.start_date = sea_orm::Set(start_date); + } + if let Some(end_date) = input.end_date { + account_to_update.end_date = sea_orm::Set(Some(end_date)); + } + + // Always update the updated_at timestamp + account_to_update.updated_at = sea_orm::Set(chrono::Utc::now().naive_utc()); + + let updated_account = account::Entity::update(account_to_update) + .exec(db.as_ref()) + .await?; + + Ok(Account::from(updated_account)) + } + + // Delete an account + pub async fn delete_account(&self, ctx: &Context<'_>, id: ID) -> Result { + let db = ctx.data::>()?; + let account_uuid = Uuid::parse_str(&id)?; + + let result = account::Entity::delete_by_id(account_uuid) + .exec(db.as_ref()) + .await?; + + Ok(result.rows_affected > 0) + } +} \ No newline at end of file diff --git a/src/graphql/customer.rs b/src/graphql/customer.rs new file mode 100644 index 0000000..3a92ff3 --- /dev/null +++ b/src/graphql/customer.rs @@ -0,0 +1,305 @@ +use async_graphql::{Context, ID, InputObject, Object, Result, SimpleObject}; +use chrono::{NaiveDate, NaiveDateTime}; +use sea_orm::{ColumnTrait, DatabaseConnection, EntityTrait, QueryFilter, QuerySelect}; +use std::sync::Arc; +use uuid::Uuid; + +use crate::entities::customer; + +// Main GraphQL Customer type +#[derive(SimpleObject)] +pub struct Customer { + pub id: ID, + pub name: String, + pub primary_contact_first_name: String, + pub primary_contact_last_name: String, + pub primary_contact_phone: String, + pub primary_contact_email: String, + pub secondary_contact_first_name: Option, + pub secondary_contact_last_name: Option, + pub secondary_contact_phone: Option, + pub secondary_contact_email: Option, + pub billing_contact_first_name: String, + pub billing_contact_last_name: String, + pub billing_street_address: String, + pub billing_city: String, + pub billing_state: String, + pub billing_zip_code: String, + pub billing_email: String, + pub billing_terms: String, + pub start_date: NaiveDate, + pub end_date: Option, + pub created_at: NaiveDateTime, // Changed to NaiveDateTime to match entity + pub updated_at: NaiveDateTime, // Changed to NaiveDateTime to match entity +} + +// Conversion from SeaORM model to GraphQL type +impl From for Customer { + fn from(model: customer::Model) -> Self { + Customer { + id: ID(model.id.to_string()), + name: model.name, + primary_contact_first_name: model.primary_contact_first_name, + primary_contact_last_name: model.primary_contact_last_name, + primary_contact_phone: model.primary_contact_phone, + primary_contact_email: model.primary_contact_email, + secondary_contact_first_name: model.secondary_contact_first_name, + secondary_contact_last_name: model.secondary_contact_last_name, + secondary_contact_phone: model.secondary_contact_phone, + secondary_contact_email: model.secondary_contact_email, + billing_contact_first_name: model.billing_contact_first_name, + billing_contact_last_name: model.billing_contact_last_name, + billing_street_address: model.billing_street_address, + billing_city: model.billing_city, + billing_state: model.billing_state, + billing_zip_code: model.billing_zip_code, + billing_email: model.billing_email, + billing_terms: model.billing_terms, + start_date: model.start_date, + end_date: model.end_date, + created_at: model.created_at, // Keep as NaiveDateTime + updated_at: model.updated_at, // Keep as NaiveDateTime + } + } +} + +// Input types for mutations +#[derive(InputObject)] +pub struct CreateCustomerInput { + pub name: String, + pub primary_contact_first_name: String, + pub primary_contact_last_name: String, + pub primary_contact_phone: String, + pub primary_contact_email: String, + pub secondary_contact_first_name: Option, + pub secondary_contact_last_name: Option, + pub secondary_contact_phone: Option, + pub secondary_contact_email: Option, + pub billing_contact_first_name: String, + pub billing_contact_last_name: String, + pub billing_street_address: String, + pub billing_city: String, + pub billing_state: String, + pub billing_zip_code: String, + pub billing_email: String, + pub billing_terms: String, + pub start_date: NaiveDate, + pub end_date: Option, +} + +#[derive(InputObject)] +pub struct UpdateCustomerInput { + pub name: Option, + pub primary_contact_first_name: Option, + pub primary_contact_last_name: Option, + pub primary_contact_phone: Option, + pub primary_contact_email: Option, + pub secondary_contact_first_name: Option, + pub secondary_contact_last_name: Option, + pub secondary_contact_phone: Option, + pub secondary_contact_email: Option, + pub billing_contact_first_name: Option, + pub billing_contact_last_name: Option, + pub billing_street_address: Option, + pub billing_city: Option, + pub billing_state: Option, + pub billing_zip_code: Option, + pub billing_email: Option, + pub billing_terms: Option, + pub start_date: Option, + pub end_date: Option, +} + +// Query resolvers +pub struct CustomerQuery; + +#[Object] +impl CustomerQuery { + // Get customer by ID + pub async fn customer(&self, ctx: &Context<'_>, id: ID) -> Result> { + let db = ctx.data::>()?; + let customer_uuid = Uuid::parse_str(&id)?; + + let customer = customer::Entity::find_by_id(customer_uuid) + .one(db.as_ref()) + .await?; + + Ok(customer.map(Customer::from)) + } + + // Get all customers with pagination + pub async fn customers( + &self, + ctx: &Context<'_>, + #[graphql(desc = "Number of items to return", default = 10)] limit: u64, + #[graphql(desc = "Number of items to skip", default = 0)] offset: u64, + ) -> Result> { + let db = ctx.data::>()?; + + let customers = customer::Entity::find() + .offset(offset) + .limit(limit) + .all(db.as_ref()) + .await?; + + Ok(customers.into_iter().map(Customer::from).collect()) + } + + // Search customers by name + pub async fn search_customers(&self, ctx: &Context<'_>, name: String) -> Result> { + let db = ctx.data::>()?; + + let customers = customer::Entity::find() + .filter(customer::Column::Name.contains(&name)) + .all(db.as_ref()) + .await?; + + Ok(customers.into_iter().map(Customer::from).collect()) + } +} + +// Mutation resolvers +pub struct CustomerMutation; + +#[Object] +impl CustomerMutation { + // Create a new customer + pub async fn create_customer( + &self, + ctx: &Context<'_>, + input: CreateCustomerInput, + ) -> Result { + let db = ctx.data::>()?; + + let new_customer = customer::ActiveModel { + id: sea_orm::Set(Uuid::new_v4()), + name: sea_orm::Set(input.name), + primary_contact_first_name: sea_orm::Set(input.primary_contact_first_name), + primary_contact_last_name: sea_orm::Set(input.primary_contact_last_name), + primary_contact_phone: sea_orm::Set(input.primary_contact_phone), + primary_contact_email: sea_orm::Set(input.primary_contact_email), + secondary_contact_first_name: sea_orm::Set(input.secondary_contact_first_name), + secondary_contact_last_name: sea_orm::Set(input.secondary_contact_last_name), + secondary_contact_phone: sea_orm::Set(input.secondary_contact_phone), + secondary_contact_email: sea_orm::Set(input.secondary_contact_email), + billing_contact_first_name: sea_orm::Set(input.billing_contact_first_name), + billing_contact_last_name: sea_orm::Set(input.billing_contact_last_name), + billing_street_address: sea_orm::Set(input.billing_street_address), + billing_city: sea_orm::Set(input.billing_city), + billing_state: sea_orm::Set(input.billing_state), + billing_zip_code: sea_orm::Set(input.billing_zip_code), + billing_email: sea_orm::Set(input.billing_email), + billing_terms: sea_orm::Set(input.billing_terms), + start_date: sea_orm::Set(input.start_date), + end_date: sea_orm::Set(input.end_date), + created_at: sea_orm::Set(chrono::Utc::now().naive_utc()), + updated_at: sea_orm::Set(chrono::Utc::now().naive_utc()), + }; + + let customer = customer::Entity::insert(new_customer) + .exec_with_returning(db.as_ref()) + .await?; + + Ok(Customer::from(customer)) + } + + // Update an existing customer + pub async fn update_customer( + &self, + ctx: &Context<'_>, + id: ID, + input: UpdateCustomerInput, + ) -> Result { + let db = ctx.data::>()?; + let customer_uuid = Uuid::parse_str(&id)?; + + // First, find the existing customer to make sure it exists + let existing_customer = customer::Entity::find_by_id(customer_uuid) + .one(db.as_ref()) + .await? + .ok_or_else(|| async_graphql::Error::new("Customer not found"))?; + + // Create an ActiveModel with only the fields that need updating + let mut customer_to_update: customer::ActiveModel = existing_customer.into(); + + // Update only the fields that are provided (Some values) + if let Some(name) = input.name { + customer_to_update.name = sea_orm::Set(name); + } + if let Some(first_name) = input.primary_contact_first_name { + customer_to_update.primary_contact_first_name = sea_orm::Set(first_name); + } + if let Some(last_name) = input.primary_contact_last_name { + customer_to_update.primary_contact_last_name = sea_orm::Set(last_name); + } + if let Some(phone) = input.primary_contact_phone { + customer_to_update.primary_contact_phone = sea_orm::Set(phone); + } + if let Some(email) = input.primary_contact_email { + customer_to_update.primary_contact_email = sea_orm::Set(email); + } + if let Some(first_name) = input.secondary_contact_first_name { + customer_to_update.secondary_contact_first_name = sea_orm::Set(Some(first_name)); + } + if let Some(last_name) = input.secondary_contact_last_name { + customer_to_update.secondary_contact_last_name = sea_orm::Set(Some(last_name)); + } + if let Some(phone) = input.secondary_contact_phone { + customer_to_update.secondary_contact_phone = sea_orm::Set(Some(phone)); + } + if let Some(email) = input.secondary_contact_email { + customer_to_update.secondary_contact_email = sea_orm::Set(Some(email)); + } + if let Some(first_name) = input.billing_contact_first_name { + customer_to_update.billing_contact_first_name = sea_orm::Set(first_name); + } + if let Some(last_name) = input.billing_contact_last_name { + customer_to_update.billing_contact_last_name = sea_orm::Set(last_name); + } + if let Some(address) = input.billing_street_address { + customer_to_update.billing_street_address = sea_orm::Set(address); + } + if let Some(city) = input.billing_city { + customer_to_update.billing_city = sea_orm::Set(city); + } + if let Some(state) = input.billing_state { + customer_to_update.billing_state = sea_orm::Set(state); + } + if let Some(zip) = input.billing_zip_code { + customer_to_update.billing_zip_code = sea_orm::Set(zip); + } + if let Some(email) = input.billing_email { + customer_to_update.billing_email = sea_orm::Set(email); + } + if let Some(terms) = input.billing_terms { + customer_to_update.billing_terms = sea_orm::Set(terms); + } + if let Some(start_date) = input.start_date { + customer_to_update.start_date = sea_orm::Set(start_date); + } + if let Some(end_date) = input.end_date { + customer_to_update.end_date = sea_orm::Set(Some(end_date)); + } + + // Always update the updated_at timestamp + customer_to_update.updated_at = sea_orm::Set(chrono::Utc::now().naive_utc()); + + let updated_customer = customer::Entity::update(customer_to_update) + .exec(db.as_ref()) + .await?; + + Ok(Customer::from(updated_customer)) + } + + // Delete a customer + pub async fn delete_customer(&self, ctx: &Context<'_>, id: ID) -> Result { + let db = ctx.data::>()?; + let customer_uuid = Uuid::parse_str(&id)?; + + let result = customer::Entity::delete_by_id(customer_uuid) + .exec(db.as_ref()) + .await?; + + Ok(result.rows_affected > 0) + } +} diff --git a/src/graphql/invoice.rs b/src/graphql/invoice.rs new file mode 100644 index 0000000..9c8c8a9 --- /dev/null +++ b/src/graphql/invoice.rs @@ -0,0 +1,261 @@ +use async_graphql::{Context, ID, InputObject, Object, Result, SimpleObject}; +use chrono::{NaiveDate, NaiveDateTime}; +use sea_orm::{ColumnTrait, DatabaseConnection, EntityTrait, QueryFilter, QuerySelect, prelude::Decimal}; +use std::sync::Arc; +use uuid::Uuid; + +use crate::entities::invoice; +use crate::entities::customer; + +// Main GraphQL Invoice type +#[derive(SimpleObject)] +pub struct Invoice { + pub id: ID, + pub customer_id: ID, + pub date: NaiveDate, + pub status: String, + pub date_paid: Option, + pub payment_type: Option, + pub total_amount: String, + pub created_at: NaiveDateTime, + pub updated_at: NaiveDateTime, + pub sent_at: Option, +} + +// Conversion from SeaORM model to GraphQL type +impl From for Invoice { + fn from(model: invoice::Model) -> Self { + Invoice { + id: ID(model.id.to_string()), + customer_id: ID(model.customer_id.to_string()), + date: model.date, + status: model.status, + date_paid: model.date_paid, + payment_type: model.payment_type, + total_amount: model.total_amount.to_string(), + created_at: model.created_at, + updated_at: model.updated_at, + sent_at: model.sent_at, + } + } +} + +// Input types for mutations +#[derive(InputObject)] +pub struct CreateInvoiceInput { + pub customer_id: ID, + pub date: NaiveDate, + pub status: String, + pub date_paid: Option, + pub payment_type: Option, + pub total_amount: String, + pub sent_at: Option, +} + +#[derive(InputObject)] +pub struct UpdateInvoiceInput { + pub customer_id: Option, + pub date: Option, + pub status: Option, + pub date_paid: Option, + pub payment_type: Option, + pub total_amount: Option, + pub sent_at: Option, +} + +// Query resolvers +pub struct InvoiceQuery; + +#[Object] +impl InvoiceQuery { + // Get invoice by ID + pub async fn invoice(&self, ctx: &Context<'_>, id: ID) -> Result> { + let db = ctx.data::>()?; + let invoice_uuid = Uuid::parse_str(&id)?; + + let invoice = invoice::Entity::find_by_id(invoice_uuid) + .one(db.as_ref()) + .await?; + + Ok(invoice.map(Invoice::from)) + } + + // Get all invoices with pagination + pub async fn invoices( + &self, + ctx: &Context<'_>, + #[graphql(desc = "Number of items to return", default = 10)] limit: u64, + #[graphql(desc = "Number of items to skip", default = 0)] offset: u64, + ) -> Result> { + let db = ctx.data::>()?; + + let invoices = invoice::Entity::find() + .offset(offset) + .limit(limit) + .all(db.as_ref()) + .await?; + + Ok(invoices.into_iter().map(Invoice::from).collect()) + } + + // Get invoices by customer ID + pub async fn invoices_by_customer( + &self, + ctx: &Context<'_>, + customer_id: ID, + ) -> Result> { + let db = ctx.data::>()?; + let customer_uuid = Uuid::parse_str(&customer_id)?; + + let invoices = invoice::Entity::find() + .filter(invoice::Column::CustomerId.eq(customer_uuid)) + .all(db.as_ref()) + .await?; + + Ok(invoices.into_iter().map(Invoice::from).collect()) + } + + // Get invoices by status + pub async fn invoices_by_status( + &self, + ctx: &Context<'_>, + status: String, + ) -> Result> { + let db = ctx.data::>()?; + + let invoices = invoice::Entity::find() + .filter(invoice::Column::Status.eq(status)) + .all(db.as_ref()) + .await?; + + Ok(invoices.into_iter().map(Invoice::from).collect()) + } +} + +// Mutation resolvers +pub struct InvoiceMutation; + +#[Object] +impl InvoiceMutation { + // Create a new invoice + pub async fn create_invoice( + &self, + ctx: &Context<'_>, + input: CreateInvoiceInput, + ) -> Result { + let db = ctx.data::>()?; + let customer_uuid = Uuid::parse_str(&input.customer_id)?; + + // Verify that the customer exists + let customer_exists = customer::Entity::find_by_id(customer_uuid) + .one(db.as_ref()) + .await? + .is_some(); + + if !customer_exists { + return Err(async_graphql::Error::new("Customer not found")); + } + + // Parse string value to Decimal + let total_amount = input.total_amount.parse::() + .map_err(|e| async_graphql::Error::new(format!("Invalid total_amount value: {}", e)))?; + + let new_invoice = invoice::ActiveModel { + id: sea_orm::Set(Uuid::new_v4()), + customer_id: sea_orm::Set(customer_uuid), + date: sea_orm::Set(input.date), + status: sea_orm::Set(input.status), + date_paid: sea_orm::Set(input.date_paid), + payment_type: sea_orm::Set(input.payment_type), + total_amount: sea_orm::Set(total_amount), + created_at: sea_orm::Set(chrono::Utc::now().naive_utc()), + updated_at: sea_orm::Set(chrono::Utc::now().naive_utc()), + sent_at: sea_orm::Set(input.sent_at), + }; + + let invoice = invoice::Entity::insert(new_invoice) + .exec_with_returning(db.as_ref()) + .await?; + + Ok(Invoice::from(invoice)) + } + + // Update an existing invoice + pub async fn update_invoice( + &self, + ctx: &Context<'_>, + id: ID, + input: UpdateInvoiceInput, + ) -> Result { + let db = ctx.data::>()?; + let invoice_uuid = Uuid::parse_str(&id)?; + + // First, find the existing invoice to make sure it exists + let existing_invoice = invoice::Entity::find_by_id(invoice_uuid) + .one(db.as_ref()) + .await? + .ok_or_else(|| async_graphql::Error::new("Invoice not found"))?; + + // Create an ActiveModel with only the fields that need updating + let mut invoice_to_update: invoice::ActiveModel = existing_invoice.into(); + + // Update only the fields that are provided (Some values) + if let Some(customer_id) = input.customer_id { + let customer_uuid = Uuid::parse_str(&customer_id)?; + + // Verify that the customer exists + let customer_exists = customer::Entity::find_by_id(customer_uuid) + .one(db.as_ref()) + .await? + .is_some(); + + if !customer_exists { + return Err(async_graphql::Error::new("Customer not found")); + } + + invoice_to_update.customer_id = sea_orm::Set(customer_uuid); + } + + if let Some(date) = input.date { + invoice_to_update.date = sea_orm::Set(date); + } + if let Some(status) = input.status { + invoice_to_update.status = sea_orm::Set(status); + } + if let Some(date_paid) = input.date_paid { + invoice_to_update.date_paid = sea_orm::Set(Some(date_paid)); + } + if let Some(payment_type) = input.payment_type { + invoice_to_update.payment_type = sea_orm::Set(Some(payment_type)); + } + if let Some(total_amount_str) = input.total_amount { + let total_amount = total_amount_str.parse::() + .map_err(|e| async_graphql::Error::new(format!("Invalid total_amount value: {}", e)))?; + invoice_to_update.total_amount = sea_orm::Set(total_amount); + } + if let Some(sent_at) = input.sent_at { + invoice_to_update.sent_at = sea_orm::Set(Some(sent_at)); + } + + // Always update the updated_at timestamp + invoice_to_update.updated_at = sea_orm::Set(chrono::Utc::now().naive_utc()); + + let updated_invoice = invoice::Entity::update(invoice_to_update) + .exec(db.as_ref()) + .await?; + + Ok(Invoice::from(updated_invoice)) + } + + // Delete an invoice + pub async fn delete_invoice(&self, ctx: &Context<'_>, id: ID) -> Result { + let db = ctx.data::>()?; + let invoice_uuid = Uuid::parse_str(&id)?; + + let result = invoice::Entity::delete_by_id(invoice_uuid) + .exec(db.as_ref()) + .await?; + + Ok(result.rows_affected > 0) + } +} \ No newline at end of file diff --git a/src/graphql/labor.rs b/src/graphql/labor.rs new file mode 100644 index 0000000..5e9a245 --- /dev/null +++ b/src/graphql/labor.rs @@ -0,0 +1,223 @@ +use async_graphql::{Context, ID, InputObject, Object, Result, SimpleObject}; +use chrono::{NaiveDate, NaiveDateTime}; +use sea_orm::{ColumnTrait, DatabaseConnection, EntityTrait, QueryFilter, QuerySelect, prelude::Decimal}; +use std::sync::Arc; +use uuid::Uuid; + +use crate::entities::labor; +use crate::entities::account; + +// Main GraphQL Labor type +#[derive(SimpleObject)] +pub struct Labor { + pub id: ID, + pub account_id: ID, + pub amount: String, + pub start_date: NaiveDate, + pub end_date: Option, + pub created_at: NaiveDateTime, + pub updated_at: NaiveDateTime, +} + +// Conversion from SeaORM model to GraphQL type +impl From for Labor { + fn from(model: labor::Model) -> Self { + Labor { + id: ID(model.id.to_string()), + account_id: ID(model.account_id.to_string()), + amount: model.amount.to_string(), + start_date: model.start_date, + end_date: model.end_date, + created_at: model.created_at, + updated_at: model.updated_at, + } + } +} + +// Input types for mutations +#[derive(InputObject)] +pub struct CreateLaborInput { + pub account_id: ID, + pub amount: String, + pub start_date: NaiveDate, + pub end_date: Option, +} + +#[derive(InputObject)] +pub struct UpdateLaborInput { + pub account_id: Option, + pub amount: Option, + pub start_date: Option, + pub end_date: Option, +} + +// Query resolvers +pub struct LaborQuery; + +#[Object] +impl LaborQuery { + // Get labor by ID + pub async fn labor(&self, ctx: &Context<'_>, id: ID) -> Result> { + let db = ctx.data::>()?; + let labor_uuid = Uuid::parse_str(&id)?; + + let labor = labor::Entity::find_by_id(labor_uuid) + .one(db.as_ref()) + .await?; + + Ok(labor.map(Labor::from)) + } + + // Get all labors with pagination + pub async fn labors( + &self, + ctx: &Context<'_>, + #[graphql(desc = "Number of items to return", default = 10)] limit: u64, + #[graphql(desc = "Number of items to skip", default = 0)] offset: u64, + ) -> Result> { + let db = ctx.data::>()?; + + let labors = labor::Entity::find() + .offset(offset) + .limit(limit) + .all(db.as_ref()) + .await?; + + Ok(labors.into_iter().map(Labor::from).collect()) + } + + // Get labors by account ID + pub async fn labors_by_account( + &self, + ctx: &Context<'_>, + account_id: ID, + ) -> Result> { + let db = ctx.data::>()?; + let account_uuid = Uuid::parse_str(&account_id)?; + + let labors = labor::Entity::find() + .filter(labor::Column::AccountId.eq(account_uuid)) + .all(db.as_ref()) + .await?; + + Ok(labors.into_iter().map(Labor::from).collect()) + } +} + +// Mutation resolvers +pub struct LaborMutation; + +#[Object] +impl LaborMutation { + // Create a new labor + pub async fn create_labor( + &self, + ctx: &Context<'_>, + input: CreateLaborInput, + ) -> Result { + let db = ctx.data::>()?; + let account_uuid = Uuid::parse_str(&input.account_id)?; + + // Verify that the account exists + let account_exists = account::Entity::find_by_id(account_uuid) + .one(db.as_ref()) + .await? + .is_some(); + + if !account_exists { + return Err(async_graphql::Error::new("Account not found")); + } + + // Parse string value to Decimal + let amount = input.amount.parse::() + .map_err(|e| async_graphql::Error::new(format!("Invalid amount value: {}", e)))?; + + let new_labor = labor::ActiveModel { + id: sea_orm::Set(Uuid::new_v4()), + account_id: sea_orm::Set(account_uuid), + amount: sea_orm::Set(amount), + start_date: sea_orm::Set(input.start_date), + end_date: sea_orm::Set(input.end_date), + created_at: sea_orm::Set(chrono::Utc::now().naive_utc()), + updated_at: sea_orm::Set(chrono::Utc::now().naive_utc()), + }; + + let labor = labor::Entity::insert(new_labor) + .exec_with_returning(db.as_ref()) + .await?; + + Ok(Labor::from(labor)) + } + + // Update an existing labor + pub async fn update_labor( + &self, + ctx: &Context<'_>, + id: ID, + input: UpdateLaborInput, + ) -> Result { + let db = ctx.data::>()?; + let labor_uuid = Uuid::parse_str(&id)?; + + // First, find the existing labor to make sure it exists + let existing_labor = labor::Entity::find_by_id(labor_uuid) + .one(db.as_ref()) + .await? + .ok_or_else(|| async_graphql::Error::new("Labor not found"))?; + + // Create an ActiveModel with only the fields that need updating + let mut labor_to_update: labor::ActiveModel = existing_labor.into(); + + // Update only the fields that are provided (Some values) + if let Some(account_id) = input.account_id { + let account_uuid = Uuid::parse_str(&account_id)?; + + // Verify that the account exists + let account_exists = account::Entity::find_by_id(account_uuid) + .one(db.as_ref()) + .await? + .is_some(); + + if !account_exists { + return Err(async_graphql::Error::new("Account not found")); + } + + labor_to_update.account_id = sea_orm::Set(account_uuid); + } + + if let Some(amount_str) = input.amount { + let amount = amount_str.parse::() + .map_err(|e| async_graphql::Error::new(format!("Invalid amount value: {}", e)))?; + labor_to_update.amount = sea_orm::Set(amount); + } + + if let Some(start_date) = input.start_date { + labor_to_update.start_date = sea_orm::Set(start_date); + } + + if let Some(end_date) = input.end_date { + labor_to_update.end_date = sea_orm::Set(Some(end_date)); + } + + // Always update the updated_at timestamp + labor_to_update.updated_at = sea_orm::Set(chrono::Utc::now().naive_utc()); + + let updated_labor = labor::Entity::update(labor_to_update) + .exec(db.as_ref()) + .await?; + + Ok(Labor::from(updated_labor)) + } + + // Delete a labor + pub async fn delete_labor(&self, ctx: &Context<'_>, id: ID) -> Result { + let db = ctx.data::>()?; + let labor_uuid = Uuid::parse_str(&id)?; + + let result = labor::Entity::delete_by_id(labor_uuid) + .exec(db.as_ref()) + .await?; + + Ok(result.rows_affected > 0) + } +} \ No newline at end of file diff --git a/src/graphql/mod.rs b/src/graphql/mod.rs new file mode 100644 index 0000000..158991d --- /dev/null +++ b/src/graphql/mod.rs @@ -0,0 +1,649 @@ +use async_graphql::{EmptySubscription, Object, Schema}; +use chrono::NaiveDate; + +// Import your entity modules +pub mod customer; +pub mod account; +pub mod service; +pub mod project; +pub mod revenue; +pub mod schedule; +pub mod labor; +pub mod report; +pub mod invoice; +pub mod user; +pub mod profile; + +// Main Query struct that combines all query resolvers +pub struct Query; + +#[Object] +impl Query { + // Customer queries - delegate to CustomerQuery methods + async fn customer( + &self, + ctx: &async_graphql::Context<'_>, + id: async_graphql::ID, + ) -> async_graphql::Result> { + customer::CustomerQuery.customer(ctx, id).await + } + + async fn customers( + &self, + ctx: &async_graphql::Context<'_>, + #[graphql(desc = "Number of items to return", default = 10)] limit: u64, + #[graphql(desc = "Number of items to skip", default = 0)] offset: u64, + ) -> async_graphql::Result> { + customer::CustomerQuery.customers(ctx, limit, offset).await + } + + async fn search_customers( + &self, + ctx: &async_graphql::Context<'_>, + name: String, + ) -> async_graphql::Result> { + customer::CustomerQuery.search_customers(ctx, name).await + } + + // Account queries - delegate to AccountQuery methods + async fn account(&self, ctx: &async_graphql::Context<'_>, id: async_graphql::ID) -> async_graphql::Result> { + account::AccountQuery.account(ctx, id).await + } + + async fn accounts( + &self, + ctx: &async_graphql::Context<'_>, + #[graphql(desc = "Number of items to return", default = 10)] limit: u64, + #[graphql(desc = "Number of items to skip", default = 0)] offset: u64, + ) -> async_graphql::Result> { + account::AccountQuery.accounts(ctx, limit, offset).await + } + + async fn accounts_by_customer( + &self, + ctx: &async_graphql::Context<'_>, + customer_id: async_graphql::ID, + ) -> async_graphql::Result> { + account::AccountQuery.accounts_by_customer(ctx, customer_id).await + } + + async fn search_accounts( + &self, + ctx: &async_graphql::Context<'_>, + name: String, + ) -> async_graphql::Result> { + account::AccountQuery.search_accounts(ctx, name).await + } + + // Service queries - delegate to ServiceQuery methods + async fn service(&self, ctx: &async_graphql::Context<'_>, id: async_graphql::ID) -> async_graphql::Result> { + service::ServiceQuery.service(ctx, id).await + } + + async fn services( + &self, + ctx: &async_graphql::Context<'_>, + #[graphql(desc = "Number of items to return", default = 10)] limit: u64, + #[graphql(desc = "Number of items to skip", default = 0)] offset: u64, + ) -> async_graphql::Result> { + service::ServiceQuery.services(ctx, limit, offset).await + } + + async fn services_by_account( + &self, + ctx: &async_graphql::Context<'_>, + account_id: async_graphql::ID, + ) -> async_graphql::Result> { + service::ServiceQuery.services_by_account(ctx, account_id).await + } + + async fn services_by_status( + &self, + ctx: &async_graphql::Context<'_>, + status: String, + ) -> async_graphql::Result> { + service::ServiceQuery.services_by_status(ctx, status).await + } + + // Project queries - delegate to ProjectQuery methods + async fn project(&self, ctx: &async_graphql::Context<'_>, id: async_graphql::ID) -> async_graphql::Result> { + project::ProjectQuery.project(ctx, id).await + } + + async fn projects( + &self, + ctx: &async_graphql::Context<'_>, + #[graphql(desc = "Number of items to return", default = 10)] limit: u64, + #[graphql(desc = "Number of items to skip", default = 0)] offset: u64, + ) -> async_graphql::Result> { + project::ProjectQuery.projects(ctx, limit, offset).await + } + + async fn projects_by_customer( + &self, + ctx: &async_graphql::Context<'_>, + customer_id: async_graphql::ID, + ) -> async_graphql::Result> { + project::ProjectQuery.projects_by_customer(ctx, customer_id).await + } + + async fn projects_by_account( + &self, + ctx: &async_graphql::Context<'_>, + account_id: async_graphql::ID, + ) -> async_graphql::Result> { + project::ProjectQuery.projects_by_account(ctx, account_id).await + } + + async fn projects_by_status( + &self, + ctx: &async_graphql::Context<'_>, + status: String, + ) -> async_graphql::Result> { + project::ProjectQuery.projects_by_status(ctx, status).await + } + + // Revenue queries - delegate to RevenueQuery methods + async fn revenue(&self, ctx: &async_graphql::Context<'_>, id: async_graphql::ID) -> async_graphql::Result> { + revenue::RevenueQuery.revenue(ctx, id).await + } + + async fn revenues( + &self, + ctx: &async_graphql::Context<'_>, + #[graphql(desc = "Number of items to return", default = 10)] limit: u64, + #[graphql(desc = "Number of items to skip", default = 0)] offset: u64, + ) -> async_graphql::Result> { + revenue::RevenueQuery.revenues(ctx, limit, offset).await + } + + async fn revenues_by_account( + &self, + ctx: &async_graphql::Context<'_>, + account_id: async_graphql::ID, + ) -> async_graphql::Result> { + revenue::RevenueQuery.revenues_by_account(ctx, account_id).await + } + + // Labor queries - delegate to LaborQuery methods + async fn labor(&self, ctx: &async_graphql::Context<'_>, id: async_graphql::ID) -> async_graphql::Result> { + labor::LaborQuery.labor(ctx, id).await + } + + async fn labors( + &self, + ctx: &async_graphql::Context<'_>, + #[graphql(desc = "Number of items to return", default = 10)] limit: u64, + #[graphql(desc = "Number of items to skip", default = 0)] offset: u64, + ) -> async_graphql::Result> { + labor::LaborQuery.labors(ctx, limit, offset).await + } + + async fn labors_by_account( + &self, + ctx: &async_graphql::Context<'_>, + account_id: async_graphql::ID, + ) -> async_graphql::Result> { + labor::LaborQuery.labors_by_account(ctx, account_id).await + } + + // Schedule queries - delegate to ScheduleQuery methods + async fn schedule(&self, ctx: &async_graphql::Context<'_>, id: async_graphql::ID) -> async_graphql::Result> { + schedule::ScheduleQuery.schedule(ctx, id).await + } + + async fn schedules( + &self, + ctx: &async_graphql::Context<'_>, + #[graphql(desc = "Number of items to return", default = 10)] limit: u64, + #[graphql(desc = "Number of items to skip", default = 0)] offset: u64, + ) -> async_graphql::Result> { + schedule::ScheduleQuery.schedules(ctx, limit, offset).await + } + + async fn schedules_by_account( + &self, + ctx: &async_graphql::Context<'_>, + account_id: async_graphql::ID, + ) -> async_graphql::Result> { + schedule::ScheduleQuery.schedules_by_account(ctx, account_id).await + } + + // Invoice queries - delegate to InvoiceQuery methods + async fn invoice(&self, ctx: &async_graphql::Context<'_>, id: async_graphql::ID) -> async_graphql::Result> { + invoice::InvoiceQuery.invoice(ctx, id).await + } + + async fn invoices( + &self, + ctx: &async_graphql::Context<'_>, + #[graphql(desc = "Number of items to return", default = 10)] limit: u64, + #[graphql(desc = "Number of items to skip", default = 0)] offset: u64, + ) -> async_graphql::Result> { + invoice::InvoiceQuery.invoices(ctx, limit, offset).await + } + + async fn invoices_by_customer( + &self, + ctx: &async_graphql::Context<'_>, + customer_id: async_graphql::ID, + ) -> async_graphql::Result> { + invoice::InvoiceQuery.invoices_by_customer(ctx, customer_id).await + } + + async fn invoices_by_status( + &self, + ctx: &async_graphql::Context<'_>, + status: String, + ) -> async_graphql::Result> { + invoice::InvoiceQuery.invoices_by_status(ctx, status).await + } + + // Report queries - delegate to ReportQuery methods + async fn report(&self, ctx: &async_graphql::Context<'_>, id: async_graphql::ID) -> async_graphql::Result> { + report::ReportQuery.report(ctx, id).await + } + + async fn reports( + &self, + ctx: &async_graphql::Context<'_>, + #[graphql(desc = "Number of items to return", default = 10)] limit: u64, + #[graphql(desc = "Number of items to skip", default = 0)] offset: u64, + ) -> async_graphql::Result> { + report::ReportQuery.reports(ctx, limit, offset).await + } + + async fn reports_by_team_member( + &self, + ctx: &async_graphql::Context<'_>, + team_member_id: async_graphql::ID, + ) -> async_graphql::Result> { + report::ReportQuery.reports_by_team_member(ctx, team_member_id).await + } + + async fn reports_by_date( + &self, + ctx: &async_graphql::Context<'_>, + date: NaiveDate, + ) -> async_graphql::Result> { + report::ReportQuery.reports_by_date(ctx, date).await + } + + // User queries - delegate to UserQuery methods + async fn user(&self, ctx: &async_graphql::Context<'_>, id: async_graphql::ID) -> async_graphql::Result> { + user::UserQuery.user(ctx, id).await + } + + async fn users( + &self, + ctx: &async_graphql::Context<'_>, + #[graphql(desc = "Number of items to return", default = 10)] limit: u64, + #[graphql(desc = "Number of items to skip", default = 0)] offset: u64, + ) -> async_graphql::Result> { + user::UserQuery.users(ctx, limit, offset).await + } + + async fn user_by_username( + &self, + ctx: &async_graphql::Context<'_>, + username: String, + ) -> async_graphql::Result> { + user::UserQuery.user_by_username(ctx, username).await + } + + async fn user_by_email( + &self, + ctx: &async_graphql::Context<'_>, + email: String, + ) -> async_graphql::Result> { + user::UserQuery.user_by_email(ctx, email).await + } + + // Profile queries - delegate to ProfileQuery methods + async fn profile(&self, ctx: &async_graphql::Context<'_>, id: async_graphql::ID) -> async_graphql::Result> { + profile::ProfileQuery.profile(ctx, id).await + } + + async fn profiles( + &self, + ctx: &async_graphql::Context<'_>, + #[graphql(desc = "Number of items to return", default = 10)] limit: u64, + #[graphql(desc = "Number of items to skip", default = 0)] offset: u64, + ) -> async_graphql::Result> { + profile::ProfileQuery.profiles(ctx, limit, offset).await + } + + async fn profile_by_user( + &self, + ctx: &async_graphql::Context<'_>, + user_id: async_graphql::ID, + ) -> async_graphql::Result> { + profile::ProfileQuery.profile_by_user(ctx, user_id).await + } + + async fn profiles_by_role( + &self, + ctx: &async_graphql::Context<'_>, + role: String, + ) -> async_graphql::Result> { + profile::ProfileQuery.profiles_by_role(ctx, role).await + } +} + +// Main Mutation struct that combines all mutation resolvers +pub struct Mutation; + +#[Object] +impl Mutation { + // Customer mutations - delegate to CustomerMutation methods + async fn create_customer( + &self, + ctx: &async_graphql::Context<'_>, + input: customer::CreateCustomerInput, + ) -> async_graphql::Result { + customer::CustomerMutation.create_customer(ctx, input).await + } + + async fn update_customer( + &self, + ctx: &async_graphql::Context<'_>, + id: async_graphql::ID, + input: customer::UpdateCustomerInput, + ) -> async_graphql::Result { + customer::CustomerMutation + .update_customer(ctx, id, input) + .await + } + + async fn delete_customer( + &self, + ctx: &async_graphql::Context<'_>, + id: async_graphql::ID, + ) -> async_graphql::Result { + customer::CustomerMutation.delete_customer(ctx, id).await + } + + // Account mutations - delegate to AccountMutation methods + async fn create_account(&self, ctx: &async_graphql::Context<'_>, input: account::CreateAccountInput) -> async_graphql::Result { + account::AccountMutation.create_account(ctx, input).await + } + + async fn update_account( + &self, + ctx: &async_graphql::Context<'_>, + id: async_graphql::ID, + input: account::UpdateAccountInput, + ) -> async_graphql::Result { + account::AccountMutation + .update_account(ctx, id, input) + .await + } + + async fn delete_account( + &self, + ctx: &async_graphql::Context<'_>, + id: async_graphql::ID, + ) -> async_graphql::Result { + account::AccountMutation.delete_account(ctx, id).await + } + + // Service mutations - delegate to ServiceMutation methods + async fn create_service( + &self, + ctx: &async_graphql::Context<'_>, + input: service::CreateServiceInput, + ) -> async_graphql::Result { + service::ServiceMutation.create_service(ctx, input).await + } + + async fn update_service( + &self, + ctx: &async_graphql::Context<'_>, + id: async_graphql::ID, + input: service::UpdateServiceInput, + ) -> async_graphql::Result { + service::ServiceMutation + .update_service(ctx, id, input) + .await + } + + async fn delete_service( + &self, + ctx: &async_graphql::Context<'_>, + id: async_graphql::ID, + ) -> async_graphql::Result { + service::ServiceMutation.delete_service(ctx, id).await + } + + // Project mutations - delegate to ProjectMutation methods + async fn create_project( + &self, + ctx: &async_graphql::Context<'_>, + input: project::CreateProjectInput, + ) -> async_graphql::Result { + project::ProjectMutation.create_project(ctx, input).await + } + + async fn update_project( + &self, + ctx: &async_graphql::Context<'_>, + id: async_graphql::ID, + input: project::UpdateProjectInput, + ) -> async_graphql::Result { + project::ProjectMutation + .update_project(ctx, id, input) + .await + } + + async fn delete_project( + &self, + ctx: &async_graphql::Context<'_>, + id: async_graphql::ID, + ) -> async_graphql::Result { + project::ProjectMutation.delete_project(ctx, id).await + } + + // Revenue mutations - delegate to RevenueMutation methods + async fn create_revenue( + &self, + ctx: &async_graphql::Context<'_>, + input: revenue::CreateRevenueInput, + ) -> async_graphql::Result { + revenue::RevenueMutation.create_revenue(ctx, input).await + } + + async fn update_revenue( + &self, + ctx: &async_graphql::Context<'_>, + id: async_graphql::ID, + input: revenue::UpdateRevenueInput, + ) -> async_graphql::Result { + revenue::RevenueMutation + .update_revenue(ctx, id, input) + .await + } + + async fn delete_revenue( + &self, + ctx: &async_graphql::Context<'_>, + id: async_graphql::ID, + ) -> async_graphql::Result { + revenue::RevenueMutation.delete_revenue(ctx, id).await + } + + // Labor mutations - delegate to LaborMutation methods + async fn create_labor( + &self, + ctx: &async_graphql::Context<'_>, + input: labor::CreateLaborInput, + ) -> async_graphql::Result { + labor::LaborMutation.create_labor(ctx, input).await + } + + async fn update_labor( + &self, + ctx: &async_graphql::Context<'_>, + id: async_graphql::ID, + input: labor::UpdateLaborInput, + ) -> async_graphql::Result { + labor::LaborMutation + .update_labor(ctx, id, input) + .await + } + + async fn delete_labor( + &self, + ctx: &async_graphql::Context<'_>, + id: async_graphql::ID, + ) -> async_graphql::Result { + labor::LaborMutation.delete_labor(ctx, id).await + } + + // Schedule mutations - delegate to ScheduleMutation methods + async fn create_schedule( + &self, + ctx: &async_graphql::Context<'_>, + input: schedule::CreateScheduleInput, + ) -> async_graphql::Result { + schedule::ScheduleMutation.create_schedule(ctx, input).await + } + + async fn update_schedule( + &self, + ctx: &async_graphql::Context<'_>, + id: async_graphql::ID, + input: schedule::UpdateScheduleInput, + ) -> async_graphql::Result { + schedule::ScheduleMutation + .update_schedule(ctx, id, input) + .await + } + + async fn delete_schedule( + &self, + ctx: &async_graphql::Context<'_>, + id: async_graphql::ID, + ) -> async_graphql::Result { + schedule::ScheduleMutation.delete_schedule(ctx, id).await + } + + // Invoice mutations - delegate to InvoiceMutation methods + async fn create_invoice( + &self, + ctx: &async_graphql::Context<'_>, + input: invoice::CreateInvoiceInput, + ) -> async_graphql::Result { + invoice::InvoiceMutation.create_invoice(ctx, input).await + } + + async fn update_invoice( + &self, + ctx: &async_graphql::Context<'_>, + id: async_graphql::ID, + input: invoice::UpdateInvoiceInput, + ) -> async_graphql::Result { + invoice::InvoiceMutation + .update_invoice(ctx, id, input) + .await + } + + async fn delete_invoice( + &self, + ctx: &async_graphql::Context<'_>, + id: async_graphql::ID, + ) -> async_graphql::Result { + invoice::InvoiceMutation.delete_invoice(ctx, id).await + } + + // Report mutations - delegate to ReportMutation methods + async fn create_report( + &self, + ctx: &async_graphql::Context<'_>, + input: report::CreateReportInput, + ) -> async_graphql::Result { + report::ReportMutation.create_report(ctx, input).await + } + + async fn update_report( + &self, + ctx: &async_graphql::Context<'_>, + id: async_graphql::ID, + input: report::UpdateReportInput, + ) -> async_graphql::Result { + report::ReportMutation + .update_report(ctx, id, input) + .await + } + + async fn delete_report( + &self, + ctx: &async_graphql::Context<'_>, + id: async_graphql::ID, + ) -> async_graphql::Result { + report::ReportMutation.delete_report(ctx, id).await + } + + // User mutations - delegate to UserMutation methods + async fn create_user( + &self, + ctx: &async_graphql::Context<'_>, + input: user::CreateUserInput, + ) -> async_graphql::Result { + user::UserMutation.create_user(ctx, input).await + } + + async fn update_user( + &self, + ctx: &async_graphql::Context<'_>, + id: async_graphql::ID, + input: user::UpdateUserInput, + ) -> async_graphql::Result { + user::UserMutation + .update_user(ctx, id, input) + .await + } + + async fn delete_user( + &self, + ctx: &async_graphql::Context<'_>, + id: async_graphql::ID, + ) -> async_graphql::Result { + user::UserMutation.delete_user(ctx, id).await + } + + // Profile mutations - delegate to ProfileMutation methods + async fn create_profile( + &self, + ctx: &async_graphql::Context<'_>, + input: profile::CreateProfileInput, + ) -> async_graphql::Result { + profile::ProfileMutation.create_profile(ctx, input).await + } + + async fn update_profile( + &self, + ctx: &async_graphql::Context<'_>, + id: async_graphql::ID, + input: profile::UpdateProfileInput, + ) -> async_graphql::Result { + profile::ProfileMutation + .update_profile(ctx, id, input) + .await + } + + async fn delete_profile( + &self, + ctx: &async_graphql::Context<'_>, + id: async_graphql::ID, + ) -> async_graphql::Result { + profile::ProfileMutation.delete_profile(ctx, id).await + } +} + +// Create the GraphQL schema type +pub type AppSchema = Schema; + +// Function to build the schema (call this in your main app setup) +pub fn create_schema() -> AppSchema { + Schema::build(Query, Mutation, EmptySubscription).finish() +} diff --git a/src/graphql/profile.rs b/src/graphql/profile.rs new file mode 100644 index 0000000..ba27d7e --- /dev/null +++ b/src/graphql/profile.rs @@ -0,0 +1,275 @@ +use async_graphql::{Context, ID, InputObject, Object, Result, SimpleObject}; +use chrono::NaiveDateTime; +use sea_orm::{ColumnTrait, DatabaseConnection, EntityTrait, QueryFilter, QuerySelect}; +use std::sync::Arc; +use uuid::Uuid; + +use crate::entities::profile; +use crate::entities::user; + +// Main GraphQL Profile type +#[derive(SimpleObject)] +pub struct Profile { + pub id: ID, + pub user_id: ID, + pub first_name: String, + pub last_name: String, + pub primary_phone: String, + pub secondary_phone: Option, + pub email: String, + pub role: String, + pub created_at: NaiveDateTime, + pub updated_at: NaiveDateTime, +} + +// Conversion from SeaORM model to GraphQL type +impl From for Profile { + fn from(model: profile::Model) -> Self { + Profile { + id: ID(model.id.to_string()), + user_id: ID(model.user_id.to_string()), + first_name: model.first_name, + last_name: model.last_name, + primary_phone: model.primary_phone, + secondary_phone: model.secondary_phone, + email: model.email, + role: model.role, + created_at: model.created_at, + updated_at: model.updated_at, + } + } +} + +// Input types for mutations +#[derive(InputObject)] +pub struct CreateProfileInput { + pub user_id: ID, + pub first_name: String, + pub last_name: String, + pub primary_phone: String, + pub secondary_phone: Option, + pub email: String, + pub role: String, +} + +#[derive(InputObject)] +pub struct UpdateProfileInput { + pub user_id: Option, + pub first_name: Option, + pub last_name: Option, + pub primary_phone: Option, + pub secondary_phone: Option, + pub email: Option, + pub role: Option, +} + +// Query resolvers +pub struct ProfileQuery; + +#[Object] +impl ProfileQuery { + // Get profile by ID + pub async fn profile(&self, ctx: &Context<'_>, id: ID) -> Result> { + let db = ctx.data::>()?; + let profile_uuid = Uuid::parse_str(&id)?; + + let profile = profile::Entity::find_by_id(profile_uuid) + .one(db.as_ref()) + .await?; + + Ok(profile.map(Profile::from)) + } + + // Get all profiles with pagination + pub async fn profiles( + &self, + ctx: &Context<'_>, + #[graphql(desc = "Number of items to return", default = 10)] limit: u64, + #[graphql(desc = "Number of items to skip", default = 0)] offset: u64, + ) -> Result> { + let db = ctx.data::>()?; + + let profiles = profile::Entity::find() + .offset(offset) + .limit(limit) + .all(db.as_ref()) + .await?; + + Ok(profiles.into_iter().map(Profile::from).collect()) + } + + // Get profile by user ID + pub async fn profile_by_user(&self, ctx: &Context<'_>, user_id: ID) -> Result> { + let db = ctx.data::>()?; + let user_uuid = Uuid::parse_str(&user_id)?; + + let profile = profile::Entity::find() + .filter(profile::Column::UserId.eq(user_uuid)) + .one(db.as_ref()) + .await?; + + Ok(profile.map(Profile::from)) + } + + // Get profiles by role + pub async fn profiles_by_role(&self, ctx: &Context<'_>, role: String) -> Result> { + let db = ctx.data::>()?; + + let profiles = profile::Entity::find() + .filter(profile::Column::Role.eq(role)) + .all(db.as_ref()) + .await?; + + Ok(profiles.into_iter().map(Profile::from).collect()) + } +} + +// Mutation resolvers +pub struct ProfileMutation; + +#[Object] +impl ProfileMutation { + // Create a new profile + pub async fn create_profile( + &self, + ctx: &Context<'_>, + input: CreateProfileInput, + ) -> Result { + let db = ctx.data::>()?; + let user_uuid = Uuid::parse_str(&input.user_id)?; + + // Verify that the user exists + let user_exists = user::Entity::find_by_id(user_uuid) + .one(db.as_ref()) + .await? + .is_some(); + + if !user_exists { + return Err(async_graphql::Error::new("User not found")); + } + + // Check if a profile already exists for this user + let profile_exists = profile::Entity::find() + .filter(profile::Column::UserId.eq(user_uuid)) + .one(db.as_ref()) + .await? + .is_some(); + + if profile_exists { + return Err(async_graphql::Error::new("Profile already exists for this user")); + } + + let new_profile = profile::ActiveModel { + id: sea_orm::Set(Uuid::new_v4()), + user_id: sea_orm::Set(user_uuid), + first_name: sea_orm::Set(input.first_name), + last_name: sea_orm::Set(input.last_name), + primary_phone: sea_orm::Set(input.primary_phone), + secondary_phone: sea_orm::Set(input.secondary_phone), + email: sea_orm::Set(input.email), + role: sea_orm::Set(input.role), + created_at: sea_orm::Set(chrono::Utc::now().naive_utc()), + updated_at: sea_orm::Set(chrono::Utc::now().naive_utc()), + }; + + let profile = profile::Entity::insert(new_profile) + .exec_with_returning(db.as_ref()) + .await?; + + Ok(Profile::from(profile)) + } + + // Update an existing profile + pub async fn update_profile( + &self, + ctx: &Context<'_>, + id: ID, + input: UpdateProfileInput, + ) -> Result { + let db = ctx.data::>()?; + let profile_uuid = Uuid::parse_str(&id)?; + + // First, find the existing profile to make sure it exists + let existing_profile = profile::Entity::find_by_id(profile_uuid) + .one(db.as_ref()) + .await? + .ok_or_else(|| async_graphql::Error::new("Profile not found"))?; + + // Create an ActiveModel with only the fields that need updating + let mut profile_to_update: profile::ActiveModel = existing_profile.into(); + + // Update only the fields that are provided (Some values) + if let Some(user_id) = input.user_id { + let user_uuid = Uuid::parse_str(&user_id)?; + + // Verify that the user exists + let user_exists = user::Entity::find_by_id(user_uuid) + .one(db.as_ref()) + .await? + .is_some(); + + if !user_exists { + return Err(async_graphql::Error::new("User not found")); + } + + // Check if a profile already exists for this user + let profile_exists = profile::Entity::find() + .filter(profile::Column::UserId.eq(user_uuid)) + .filter(profile::Column::Id.ne(profile_uuid)) + .one(db.as_ref()) + .await? + .is_some(); + + if profile_exists { + return Err(async_graphql::Error::new("Profile already exists for this user")); + } + + profile_to_update.user_id = sea_orm::Set(user_uuid); + } + + if let Some(first_name) = input.first_name { + profile_to_update.first_name = sea_orm::Set(first_name); + } + + if let Some(last_name) = input.last_name { + profile_to_update.last_name = sea_orm::Set(last_name); + } + + if let Some(primary_phone) = input.primary_phone { + profile_to_update.primary_phone = sea_orm::Set(primary_phone); + } + + if let Some(secondary_phone) = input.secondary_phone { + profile_to_update.secondary_phone = sea_orm::Set(Some(secondary_phone)); + } + + if let Some(email) = input.email { + profile_to_update.email = sea_orm::Set(email); + } + + if let Some(role) = input.role { + profile_to_update.role = sea_orm::Set(role); + } + + // Always update the updated_at timestamp + profile_to_update.updated_at = sea_orm::Set(chrono::Utc::now().naive_utc()); + + let updated_profile = profile::Entity::update(profile_to_update) + .exec(db.as_ref()) + .await?; + + Ok(Profile::from(updated_profile)) + } + + // Delete a profile + pub async fn delete_profile(&self, ctx: &Context<'_>, id: ID) -> Result { + let db = ctx.data::>()?; + let profile_uuid = Uuid::parse_str(&id)?; + + let result = profile::Entity::delete_by_id(profile_uuid) + .exec(db.as_ref()) + .await?; + + Ok(result.rows_affected > 0) + } +} \ No newline at end of file diff --git a/src/graphql/project.rs b/src/graphql/project.rs new file mode 100644 index 0000000..c9f87ad --- /dev/null +++ b/src/graphql/project.rs @@ -0,0 +1,322 @@ +use async_graphql::{Context, ID, InputObject, Object, Result, SimpleObject}; +use chrono::{NaiveDate, NaiveDateTime}; +use sea_orm::{ColumnTrait, DatabaseConnection, EntityTrait, QueryFilter, QuerySelect, prelude::Decimal}; +use std::sync::Arc; +use uuid::Uuid; + +use crate::entities::project; +use crate::entities::customer; +use crate::entities::account; + +// Main GraphQL Project type +#[derive(SimpleObject)] +pub struct Project { + pub id: ID, + pub customer_id: ID, + pub account_id: Option, + pub date: NaiveDate, + pub status: String, + pub notes: Option, + pub labor: String, + pub amount: String, + pub created_at: NaiveDateTime, + pub updated_at: NaiveDateTime, + pub completed_at: Option, +} + +// Conversion from SeaORM model to GraphQL type +impl From for Project { + fn from(model: project::Model) -> Self { + Project { + id: ID(model.id.to_string()), + customer_id: ID(model.customer_id.to_string()), + account_id: model.account_id.map(|id| ID(id.to_string())), + date: model.date, + status: model.status, + notes: model.notes, + labor: model.labor.to_string(), + amount: model.amount.to_string(), + created_at: model.created_at, + updated_at: model.updated_at, + completed_at: model.completed_at, + } + } +} + +// Input types for mutations +#[derive(InputObject)] +pub struct CreateProjectInput { + pub customer_id: ID, + pub account_id: Option, + pub date: NaiveDate, + pub status: String, + pub notes: Option, + pub labor: String, + pub amount: String, + pub completed_at: Option, +} + +#[derive(InputObject)] +pub struct UpdateProjectInput { + pub customer_id: Option, + pub account_id: Option, + pub date: Option, + pub status: Option, + pub notes: Option, + pub labor: Option, + pub amount: Option, + pub completed_at: Option, +} + +// Query resolvers +pub struct ProjectQuery; + +#[Object] +impl ProjectQuery { + // Get project by ID + pub async fn project(&self, ctx: &Context<'_>, id: ID) -> Result> { + let db = ctx.data::>()?; + let project_uuid = Uuid::parse_str(&id)?; + + let project = project::Entity::find_by_id(project_uuid) + .one(db.as_ref()) + .await?; + + Ok(project.map(Project::from)) + } + + // Get all projects with pagination + pub async fn projects( + &self, + ctx: &Context<'_>, + #[graphql(desc = "Number of items to return", default = 10)] limit: u64, + #[graphql(desc = "Number of items to skip", default = 0)] offset: u64, + ) -> Result> { + let db = ctx.data::>()?; + + let projects = project::Entity::find() + .offset(offset) + .limit(limit) + .all(db.as_ref()) + .await?; + + Ok(projects.into_iter().map(Project::from).collect()) + } + + // Get projects by customer ID + pub async fn projects_by_customer( + &self, + ctx: &Context<'_>, + customer_id: ID, + ) -> Result> { + let db = ctx.data::>()?; + let customer_uuid = Uuid::parse_str(&customer_id)?; + + let projects = project::Entity::find() + .filter(project::Column::CustomerId.eq(customer_uuid)) + .all(db.as_ref()) + .await?; + + Ok(projects.into_iter().map(Project::from).collect()) + } + + // Get projects by account ID + pub async fn projects_by_account( + &self, + ctx: &Context<'_>, + account_id: ID, + ) -> Result> { + let db = ctx.data::>()?; + let account_uuid = Uuid::parse_str(&account_id)?; + + let projects = project::Entity::find() + .filter(project::Column::AccountId.eq(account_uuid)) + .all(db.as_ref()) + .await?; + + Ok(projects.into_iter().map(Project::from).collect()) + } + + // Get projects by status + pub async fn projects_by_status( + &self, + ctx: &Context<'_>, + status: String, + ) -> Result> { + let db = ctx.data::>()?; + + let projects = project::Entity::find() + .filter(project::Column::Status.eq(status)) + .all(db.as_ref()) + .await?; + + Ok(projects.into_iter().map(Project::from).collect()) + } +} + +// Mutation resolvers +pub struct ProjectMutation; + +#[Object] +impl ProjectMutation { + // Create a new project + pub async fn create_project( + &self, + ctx: &Context<'_>, + input: CreateProjectInput, + ) -> Result { + let db = ctx.data::>()?; + let customer_uuid = Uuid::parse_str(&input.customer_id)?; + + // Verify that the customer exists + let customer_exists = customer::Entity::find_by_id(customer_uuid) + .one(db.as_ref()) + .await? + .is_some(); + + if !customer_exists { + return Err(async_graphql::Error::new("Customer not found")); + } + + // If account_id is provided, verify that the account exists + let account_uuid = if let Some(account_id) = &input.account_id { + let account_uuid = Uuid::parse_str(account_id)?; + + let account_exists = account::Entity::find_by_id(account_uuid) + .one(db.as_ref()) + .await? + .is_some(); + + if !account_exists { + return Err(async_graphql::Error::new("Account not found")); + } + + Some(account_uuid) + } else { + None + }; + + // Parse string values to Decimal + let labor = input.labor.parse::() + .map_err(|e| async_graphql::Error::new(format!("Invalid labor value: {}", e)))?; + let amount = input.amount.parse::() + .map_err(|e| async_graphql::Error::new(format!("Invalid amount value: {}", e)))?; + + let new_project = project::ActiveModel { + id: sea_orm::Set(Uuid::new_v4()), + customer_id: sea_orm::Set(customer_uuid), + account_id: sea_orm::Set(account_uuid), + date: sea_orm::Set(input.date), + status: sea_orm::Set(input.status), + notes: sea_orm::Set(input.notes), + labor: sea_orm::Set(labor), + amount: sea_orm::Set(amount), + created_at: sea_orm::Set(chrono::Utc::now().naive_utc()), + updated_at: sea_orm::Set(chrono::Utc::now().naive_utc()), + completed_at: sea_orm::Set(input.completed_at), + }; + + let project = project::Entity::insert(new_project) + .exec_with_returning(db.as_ref()) + .await?; + + Ok(Project::from(project)) + } + + // Update an existing project + pub async fn update_project( + &self, + ctx: &Context<'_>, + id: ID, + input: UpdateProjectInput, + ) -> Result { + let db = ctx.data::>()?; + let project_uuid = Uuid::parse_str(&id)?; + + // First, find the existing project to make sure it exists + let existing_project = project::Entity::find_by_id(project_uuid) + .one(db.as_ref()) + .await? + .ok_or_else(|| async_graphql::Error::new("Project not found"))?; + + // Create an ActiveModel with only the fields that need updating + let mut project_to_update: project::ActiveModel = existing_project.into(); + + // Update only the fields that are provided (Some values) + if let Some(customer_id) = input.customer_id { + let customer_uuid = Uuid::parse_str(&customer_id)?; + + // Verify that the customer exists + let customer_exists = customer::Entity::find_by_id(customer_uuid) + .one(db.as_ref()) + .await? + .is_some(); + + if !customer_exists { + return Err(async_graphql::Error::new("Customer not found")); + } + + project_to_update.customer_id = sea_orm::Set(customer_uuid); + } + + if let Some(account_id) = input.account_id { + let account_uuid = Uuid::parse_str(&account_id)?; + + // Verify that the account exists + let account_exists = account::Entity::find_by_id(account_uuid) + .one(db.as_ref()) + .await? + .is_some(); + + if !account_exists { + return Err(async_graphql::Error::new("Account not found")); + } + + project_to_update.account_id = sea_orm::Set(Some(account_uuid)); + } + + if let Some(date) = input.date { + project_to_update.date = sea_orm::Set(date); + } + if let Some(status) = input.status { + project_to_update.status = sea_orm::Set(status); + } + if let Some(notes) = input.notes { + project_to_update.notes = sea_orm::Set(Some(notes)); + } + if let Some(labor_str) = input.labor { + let labor = labor_str.parse::() + .map_err(|e| async_graphql::Error::new(format!("Invalid labor value: {}", e)))?; + project_to_update.labor = sea_orm::Set(labor); + } + if let Some(amount_str) = input.amount { + let amount = amount_str.parse::() + .map_err(|e| async_graphql::Error::new(format!("Invalid amount value: {}", e)))?; + project_to_update.amount = sea_orm::Set(amount); + } + if let Some(completed_at) = input.completed_at { + project_to_update.completed_at = sea_orm::Set(Some(completed_at)); + } + + // Always update the updated_at timestamp + project_to_update.updated_at = sea_orm::Set(chrono::Utc::now().naive_utc()); + + let updated_project = project::Entity::update(project_to_update) + .exec(db.as_ref()) + .await?; + + Ok(Project::from(updated_project)) + } + + // Delete a project + pub async fn delete_project(&self, ctx: &Context<'_>, id: ID) -> Result { + let db = ctx.data::>()?; + let project_uuid = Uuid::parse_str(&id)?; + + let result = project::Entity::delete_by_id(project_uuid) + .exec(db.as_ref()) + .await?; + + Ok(result.rows_affected > 0) + } +} diff --git a/src/graphql/report.rs b/src/graphql/report.rs new file mode 100644 index 0000000..9117717 --- /dev/null +++ b/src/graphql/report.rs @@ -0,0 +1,224 @@ +use async_graphql::{Context, ID, InputObject, Object, Result, SimpleObject}; +use chrono::{NaiveDate, NaiveDateTime}; +use sea_orm::{ColumnTrait, DatabaseConnection, EntityTrait, QueryFilter, QuerySelect}; +use std::sync::Arc; +use uuid::Uuid; + +use crate::entities::report; +use crate::entities::profile; + +// Main GraphQL Report type +#[derive(SimpleObject)] +pub struct Report { + pub id: ID, + pub date: NaiveDate, + pub team_member_id: ID, + pub notes: Option, + pub created_at: NaiveDateTime, + pub updated_at: NaiveDateTime, +} + +// Conversion from SeaORM model to GraphQL type +impl From for Report { + fn from(model: report::Model) -> Self { + Report { + id: ID(model.id.to_string()), + date: model.date, + team_member_id: ID(model.team_member_id.to_string()), + notes: model.notes, + created_at: model.created_at, + updated_at: model.updated_at, + } + } +} + +// Input types for mutations +#[derive(InputObject)] +pub struct CreateReportInput { + pub date: NaiveDate, + pub team_member_id: ID, + pub notes: Option, +} + +#[derive(InputObject)] +pub struct UpdateReportInput { + pub date: Option, + pub team_member_id: Option, + pub notes: Option, +} + +// Query resolvers +pub struct ReportQuery; + +#[Object] +impl ReportQuery { + // Get report by ID + pub async fn report(&self, ctx: &Context<'_>, id: ID) -> Result> { + let db = ctx.data::>()?; + let report_uuid = Uuid::parse_str(&id)?; + + let report = report::Entity::find_by_id(report_uuid) + .one(db.as_ref()) + .await?; + + Ok(report.map(Report::from)) + } + + // Get all reports with pagination + pub async fn reports( + &self, + ctx: &Context<'_>, + #[graphql(desc = "Number of items to return", default = 10)] limit: u64, + #[graphql(desc = "Number of items to skip", default = 0)] offset: u64, + ) -> Result> { + let db = ctx.data::>()?; + + let reports = report::Entity::find() + .offset(offset) + .limit(limit) + .all(db.as_ref()) + .await?; + + Ok(reports.into_iter().map(Report::from).collect()) + } + + // Get reports by team member ID + pub async fn reports_by_team_member( + &self, + ctx: &Context<'_>, + team_member_id: ID, + ) -> Result> { + let db = ctx.data::>()?; + let team_member_uuid = Uuid::parse_str(&team_member_id)?; + + let reports = report::Entity::find() + .filter(report::Column::TeamMemberId.eq(team_member_uuid)) + .all(db.as_ref()) + .await?; + + Ok(reports.into_iter().map(Report::from).collect()) + } + + // Get reports by date + pub async fn reports_by_date( + &self, + ctx: &Context<'_>, + date: NaiveDate, + ) -> Result> { + let db = ctx.data::>()?; + + let reports = report::Entity::find() + .filter(report::Column::Date.eq(date)) + .all(db.as_ref()) + .await?; + + Ok(reports.into_iter().map(Report::from).collect()) + } +} + +// Mutation resolvers +pub struct ReportMutation; + +#[Object] +impl ReportMutation { + // Create a new report + pub async fn create_report( + &self, + ctx: &Context<'_>, + input: CreateReportInput, + ) -> Result { + let db = ctx.data::>()?; + let team_member_uuid = Uuid::parse_str(&input.team_member_id)?; + + // Verify that the team member exists + let team_member_exists = profile::Entity::find_by_id(team_member_uuid) + .one(db.as_ref()) + .await? + .is_some(); + + if !team_member_exists { + return Err(async_graphql::Error::new("Team member not found")); + } + + let new_report = report::ActiveModel { + id: sea_orm::Set(Uuid::new_v4()), + date: sea_orm::Set(input.date), + team_member_id: sea_orm::Set(team_member_uuid), + notes: sea_orm::Set(input.notes), + created_at: sea_orm::Set(chrono::Utc::now().naive_utc()), + updated_at: sea_orm::Set(chrono::Utc::now().naive_utc()), + }; + + let report = report::Entity::insert(new_report) + .exec_with_returning(db.as_ref()) + .await?; + + Ok(Report::from(report)) + } + + // Update an existing report + pub async fn update_report( + &self, + ctx: &Context<'_>, + id: ID, + input: UpdateReportInput, + ) -> Result { + let db = ctx.data::>()?; + let report_uuid = Uuid::parse_str(&id)?; + + // First, find the existing report to make sure it exists + let existing_report = report::Entity::find_by_id(report_uuid) + .one(db.as_ref()) + .await? + .ok_or_else(|| async_graphql::Error::new("Report not found"))?; + + // Create an ActiveModel with only the fields that need updating + let mut report_to_update: report::ActiveModel = existing_report.into(); + + // Update only the fields that are provided (Some values) + if let Some(date) = input.date { + report_to_update.date = sea_orm::Set(date); + } + + if let Some(team_member_id) = input.team_member_id { + let team_member_uuid = Uuid::parse_str(&team_member_id)?; + + // Verify that the team member exists + let team_member_exists = profile::Entity::find_by_id(team_member_uuid) + .one(db.as_ref()) + .await? + .is_some(); + + if !team_member_exists { + return Err(async_graphql::Error::new("Team member not found")); + } + + report_to_update.team_member_id = sea_orm::Set(team_member_uuid); + } + + if let Some(notes) = input.notes { + report_to_update.notes = sea_orm::Set(Some(notes)); + } + + // Always update the updated_at timestamp + report_to_update.updated_at = sea_orm::Set(chrono::Utc::now().naive_utc()); + + let updated_report = report::Entity::update(report_to_update) + .exec(db.as_ref()) + .await?; + + Ok(Report::from(updated_report)) + } + + // Delete a report + pub async fn delete_report(&self, ctx: &Context<'_>, id: ID) -> Result { + let db = ctx.data::>()?; + let report_uuid = Uuid::parse_str(&id)?; + + let result = report::Entity::delete_by_id(report_uuid) + .exec(db.as_ref()) + .await?; + + Ok(result.rows_affected > 0) + } +} \ No newline at end of file diff --git a/src/graphql/revenue.rs b/src/graphql/revenue.rs new file mode 100644 index 0000000..713d3b9 --- /dev/null +++ b/src/graphql/revenue.rs @@ -0,0 +1,223 @@ +use async_graphql::{Context, ID, InputObject, Object, Result, SimpleObject}; +use chrono::{NaiveDate, NaiveDateTime}; +use sea_orm::{ColumnTrait, DatabaseConnection, EntityTrait, QueryFilter, QuerySelect, prelude::Decimal}; +use std::sync::Arc; +use uuid::Uuid; + +use crate::entities::revenue; +use crate::entities::account; + +// Main GraphQL Revenue type +#[derive(SimpleObject)] +pub struct Revenue { + pub id: ID, + pub account_id: ID, + pub amount: String, + pub start_date: NaiveDate, + pub end_date: Option, + pub created_at: NaiveDateTime, + pub updated_at: NaiveDateTime, +} + +// Conversion from SeaORM model to GraphQL type +impl From for Revenue { + fn from(model: revenue::Model) -> Self { + Revenue { + id: ID(model.id.to_string()), + account_id: ID(model.account_id.to_string()), + amount: model.amount.to_string(), + start_date: model.start_date, + end_date: model.end_date, + created_at: model.created_at, + updated_at: model.updated_at, + } + } +} + +// Input types for mutations +#[derive(InputObject)] +pub struct CreateRevenueInput { + pub account_id: ID, + pub amount: String, + pub start_date: NaiveDate, + pub end_date: Option, +} + +#[derive(InputObject)] +pub struct UpdateRevenueInput { + pub account_id: Option, + pub amount: Option, + pub start_date: Option, + pub end_date: Option, +} + +// Query resolvers +pub struct RevenueQuery; + +#[Object] +impl RevenueQuery { + // Get revenue by ID + pub async fn revenue(&self, ctx: &Context<'_>, id: ID) -> Result> { + let db = ctx.data::>()?; + let revenue_uuid = Uuid::parse_str(&id)?; + + let revenue = revenue::Entity::find_by_id(revenue_uuid) + .one(db.as_ref()) + .await?; + + Ok(revenue.map(Revenue::from)) + } + + // Get all revenues with pagination + pub async fn revenues( + &self, + ctx: &Context<'_>, + #[graphql(desc = "Number of items to return", default = 10)] limit: u64, + #[graphql(desc = "Number of items to skip", default = 0)] offset: u64, + ) -> Result> { + let db = ctx.data::>()?; + + let revenues = revenue::Entity::find() + .offset(offset) + .limit(limit) + .all(db.as_ref()) + .await?; + + Ok(revenues.into_iter().map(Revenue::from).collect()) + } + + // Get revenues by account ID + pub async fn revenues_by_account( + &self, + ctx: &Context<'_>, + account_id: ID, + ) -> Result> { + let db = ctx.data::>()?; + let account_uuid = Uuid::parse_str(&account_id)?; + + let revenues = revenue::Entity::find() + .filter(revenue::Column::AccountId.eq(account_uuid)) + .all(db.as_ref()) + .await?; + + Ok(revenues.into_iter().map(Revenue::from).collect()) + } +} + +// Mutation resolvers +pub struct RevenueMutation; + +#[Object] +impl RevenueMutation { + // Create a new revenue + pub async fn create_revenue( + &self, + ctx: &Context<'_>, + input: CreateRevenueInput, + ) -> Result { + let db = ctx.data::>()?; + let account_uuid = Uuid::parse_str(&input.account_id)?; + + // Verify that the account exists + let account_exists = account::Entity::find_by_id(account_uuid) + .one(db.as_ref()) + .await? + .is_some(); + + if !account_exists { + return Err(async_graphql::Error::new("Account not found")); + } + + // Parse string value to Decimal + let amount = input.amount.parse::() + .map_err(|e| async_graphql::Error::new(format!("Invalid amount value: {}", e)))?; + + let new_revenue = revenue::ActiveModel { + id: sea_orm::Set(Uuid::new_v4()), + account_id: sea_orm::Set(account_uuid), + amount: sea_orm::Set(amount), + start_date: sea_orm::Set(input.start_date), + end_date: sea_orm::Set(input.end_date), + created_at: sea_orm::Set(chrono::Utc::now().naive_utc()), + updated_at: sea_orm::Set(chrono::Utc::now().naive_utc()), + }; + + let revenue = revenue::Entity::insert(new_revenue) + .exec_with_returning(db.as_ref()) + .await?; + + Ok(Revenue::from(revenue)) + } + + // Update an existing revenue + pub async fn update_revenue( + &self, + ctx: &Context<'_>, + id: ID, + input: UpdateRevenueInput, + ) -> Result { + let db = ctx.data::>()?; + let revenue_uuid = Uuid::parse_str(&id)?; + + // First, find the existing revenue to make sure it exists + let existing_revenue = revenue::Entity::find_by_id(revenue_uuid) + .one(db.as_ref()) + .await? + .ok_or_else(|| async_graphql::Error::new("Revenue not found"))?; + + // Create an ActiveModel with only the fields that need updating + let mut revenue_to_update: revenue::ActiveModel = existing_revenue.into(); + + // Update only the fields that are provided (Some values) + if let Some(account_id) = input.account_id { + let account_uuid = Uuid::parse_str(&account_id)?; + + // Verify that the account exists + let account_exists = account::Entity::find_by_id(account_uuid) + .one(db.as_ref()) + .await? + .is_some(); + + if !account_exists { + return Err(async_graphql::Error::new("Account not found")); + } + + revenue_to_update.account_id = sea_orm::Set(account_uuid); + } + + if let Some(amount_str) = input.amount { + let amount = amount_str.parse::() + .map_err(|e| async_graphql::Error::new(format!("Invalid amount value: {}", e)))?; + revenue_to_update.amount = sea_orm::Set(amount); + } + + if let Some(start_date) = input.start_date { + revenue_to_update.start_date = sea_orm::Set(start_date); + } + + if let Some(end_date) = input.end_date { + revenue_to_update.end_date = sea_orm::Set(Some(end_date)); + } + + // Always update the updated_at timestamp + revenue_to_update.updated_at = sea_orm::Set(chrono::Utc::now().naive_utc()); + + let updated_revenue = revenue::Entity::update(revenue_to_update) + .exec(db.as_ref()) + .await?; + + Ok(Revenue::from(updated_revenue)) + } + + // Delete a revenue + pub async fn delete_revenue(&self, ctx: &Context<'_>, id: ID) -> Result { + let db = ctx.data::>()?; + let revenue_uuid = Uuid::parse_str(&id)?; + + let result = revenue::Entity::delete_by_id(revenue_uuid) + .exec(db.as_ref()) + .await?; + + Ok(result.rows_affected > 0) + } +} diff --git a/src/graphql/schedule.rs b/src/graphql/schedule.rs new file mode 100644 index 0000000..5048295 --- /dev/null +++ b/src/graphql/schedule.rs @@ -0,0 +1,279 @@ +use async_graphql::{Context, ID, InputObject, Object, Result, SimpleObject}; +use chrono::{NaiveDate, NaiveDateTime}; +use sea_orm::{ColumnTrait, DatabaseConnection, EntityTrait, QueryFilter, QuerySelect}; +use std::sync::Arc; +use uuid::Uuid; + +use crate::entities::schedule; +use crate::entities::account; + +// Main GraphQL Schedule type +#[derive(SimpleObject)] +pub struct Schedule { + pub id: ID, + pub account_id: ID, + pub monday_service: bool, + pub tuesday_service: bool, + pub wednesday_service: bool, + pub thursday_service: bool, + pub friday_service: bool, + pub saturday_service: bool, + pub sunday_service: bool, + pub weekend_service: bool, + pub schedule_exception: Option, + pub start_date: NaiveDate, + pub end_date: Option, + pub created_at: NaiveDateTime, + pub updated_at: NaiveDateTime, +} + +// Conversion from SeaORM model to GraphQL type +impl From for Schedule { + fn from(model: schedule::Model) -> Self { + Schedule { + id: ID(model.id.to_string()), + account_id: ID(model.account_id.to_string()), + monday_service: model.monday_service, + tuesday_service: model.tuesday_service, + wednesday_service: model.wednesday_service, + thursday_service: model.thursday_service, + friday_service: model.friday_service, + saturday_service: model.saturday_service, + sunday_service: model.sunday_service, + weekend_service: model.weekend_service, + schedule_exception: model.schedule_exception, + start_date: model.start_date, + end_date: model.end_date, + created_at: model.created_at, + updated_at: model.updated_at, + } + } +} + +// Input types for mutations +#[derive(InputObject)] +pub struct CreateScheduleInput { + pub account_id: ID, + pub monday_service: bool, + pub tuesday_service: bool, + pub wednesday_service: bool, + pub thursday_service: bool, + pub friday_service: bool, + pub saturday_service: bool, + pub sunday_service: bool, + pub weekend_service: bool, + pub schedule_exception: Option, + pub start_date: NaiveDate, + pub end_date: Option, +} + +#[derive(InputObject)] +pub struct UpdateScheduleInput { + pub account_id: Option, + pub monday_service: Option, + pub tuesday_service: Option, + pub wednesday_service: Option, + pub thursday_service: Option, + pub friday_service: Option, + pub saturday_service: Option, + pub sunday_service: Option, + pub weekend_service: Option, + pub schedule_exception: Option, + pub start_date: Option, + pub end_date: Option, +} + +// Query resolvers +pub struct ScheduleQuery; + +#[Object] +impl ScheduleQuery { + // Get schedule by ID + pub async fn schedule(&self, ctx: &Context<'_>, id: ID) -> Result> { + let db = ctx.data::>()?; + let schedule_uuid = Uuid::parse_str(&id)?; + + let schedule = schedule::Entity::find_by_id(schedule_uuid) + .one(db.as_ref()) + .await?; + + Ok(schedule.map(Schedule::from)) + } + + // Get all schedules with pagination + pub async fn schedules( + &self, + ctx: &Context<'_>, + #[graphql(desc = "Number of items to return", default = 10)] limit: u64, + #[graphql(desc = "Number of items to skip", default = 0)] offset: u64, + ) -> Result> { + let db = ctx.data::>()?; + + let schedules = schedule::Entity::find() + .offset(offset) + .limit(limit) + .all(db.as_ref()) + .await?; + + Ok(schedules.into_iter().map(Schedule::from).collect()) + } + + // Get schedules by account ID + pub async fn schedules_by_account( + &self, + ctx: &Context<'_>, + account_id: ID, + ) -> Result> { + let db = ctx.data::>()?; + let account_uuid = Uuid::parse_str(&account_id)?; + + let schedules = schedule::Entity::find() + .filter(schedule::Column::AccountId.eq(account_uuid)) + .all(db.as_ref()) + .await?; + + Ok(schedules.into_iter().map(Schedule::from).collect()) + } +} + +// Mutation resolvers +pub struct ScheduleMutation; + +#[Object] +impl ScheduleMutation { + // Create a new schedule + pub async fn create_schedule( + &self, + ctx: &Context<'_>, + input: CreateScheduleInput, + ) -> Result { + let db = ctx.data::>()?; + let account_uuid = Uuid::parse_str(&input.account_id)?; + + // Verify that the account exists + let account_exists = account::Entity::find_by_id(account_uuid) + .one(db.as_ref()) + .await? + .is_some(); + + if !account_exists { + return Err(async_graphql::Error::new("Account not found")); + } + + let new_schedule = schedule::ActiveModel { + id: sea_orm::Set(Uuid::new_v4()), + account_id: sea_orm::Set(account_uuid), + monday_service: sea_orm::Set(input.monday_service), + tuesday_service: sea_orm::Set(input.tuesday_service), + wednesday_service: sea_orm::Set(input.wednesday_service), + thursday_service: sea_orm::Set(input.thursday_service), + friday_service: sea_orm::Set(input.friday_service), + saturday_service: sea_orm::Set(input.saturday_service), + sunday_service: sea_orm::Set(input.sunday_service), + weekend_service: sea_orm::Set(input.weekend_service), + schedule_exception: sea_orm::Set(input.schedule_exception), + start_date: sea_orm::Set(input.start_date), + end_date: sea_orm::Set(input.end_date), + created_at: sea_orm::Set(chrono::Utc::now().naive_utc()), + updated_at: sea_orm::Set(chrono::Utc::now().naive_utc()), + }; + + let schedule = schedule::Entity::insert(new_schedule) + .exec_with_returning(db.as_ref()) + .await?; + + Ok(Schedule::from(schedule)) + } + + // Update an existing schedule + pub async fn update_schedule( + &self, + ctx: &Context<'_>, + id: ID, + input: UpdateScheduleInput, + ) -> Result { + let db = ctx.data::>()?; + let schedule_uuid = Uuid::parse_str(&id)?; + + // First, find the existing schedule to make sure it exists + let existing_schedule = schedule::Entity::find_by_id(schedule_uuid) + .one(db.as_ref()) + .await? + .ok_or_else(|| async_graphql::Error::new("Schedule not found"))?; + + // Create an ActiveModel with only the fields that need updating + let mut schedule_to_update: schedule::ActiveModel = existing_schedule.into(); + + // Update only the fields that are provided (Some values) + if let Some(account_id) = input.account_id { + let account_uuid = Uuid::parse_str(&account_id)?; + + // Verify that the account exists + let account_exists = account::Entity::find_by_id(account_uuid) + .one(db.as_ref()) + .await? + .is_some(); + + if !account_exists { + return Err(async_graphql::Error::new("Account not found")); + } + + schedule_to_update.account_id = sea_orm::Set(account_uuid); + } + + if let Some(monday_service) = input.monday_service { + schedule_to_update.monday_service = sea_orm::Set(monday_service); + } + if let Some(tuesday_service) = input.tuesday_service { + schedule_to_update.tuesday_service = sea_orm::Set(tuesday_service); + } + if let Some(wednesday_service) = input.wednesday_service { + schedule_to_update.wednesday_service = sea_orm::Set(wednesday_service); + } + if let Some(thursday_service) = input.thursday_service { + schedule_to_update.thursday_service = sea_orm::Set(thursday_service); + } + if let Some(friday_service) = input.friday_service { + schedule_to_update.friday_service = sea_orm::Set(friday_service); + } + if let Some(saturday_service) = input.saturday_service { + schedule_to_update.saturday_service = sea_orm::Set(saturday_service); + } + if let Some(sunday_service) = input.sunday_service { + schedule_to_update.sunday_service = sea_orm::Set(sunday_service); + } + if let Some(weekend_service) = input.weekend_service { + schedule_to_update.weekend_service = sea_orm::Set(weekend_service); + } + if let Some(schedule_exception) = input.schedule_exception { + schedule_to_update.schedule_exception = sea_orm::Set(Some(schedule_exception)); + } + if let Some(start_date) = input.start_date { + schedule_to_update.start_date = sea_orm::Set(start_date); + } + if let Some(end_date) = input.end_date { + schedule_to_update.end_date = sea_orm::Set(Some(end_date)); + } + + // Always update the updated_at timestamp + schedule_to_update.updated_at = sea_orm::Set(chrono::Utc::now().naive_utc()); + + let updated_schedule = schedule::Entity::update(schedule_to_update) + .exec(db.as_ref()) + .await?; + + Ok(Schedule::from(updated_schedule)) + } + + // Delete a schedule + pub async fn delete_schedule(&self, ctx: &Context<'_>, id: ID) -> Result { + let db = ctx.data::>()?; + let schedule_uuid = Uuid::parse_str(&id)?; + + let result = schedule::Entity::delete_by_id(schedule_uuid) + .exec(db.as_ref()) + .await?; + + Ok(result.rows_affected > 0) + } +} \ No newline at end of file diff --git a/src/graphql/service.rs b/src/graphql/service.rs new file mode 100644 index 0000000..d593afb --- /dev/null +++ b/src/graphql/service.rs @@ -0,0 +1,254 @@ +use async_graphql::{Context, ID, InputObject, Object, Result, SimpleObject}; +use chrono::{NaiveDate, NaiveDateTime}; +use sea_orm::{ColumnTrait, DatabaseConnection, EntityTrait, QueryFilter, QuerySelect}; +use std::sync::Arc; +use uuid::Uuid; + +use crate::entities::service; +use crate::entities::account; + +// Main GraphQL Service type +#[derive(SimpleObject)] +pub struct Service { + pub id: ID, + pub account_id: ID, + pub date: NaiveDate, + pub status: String, + pub notes: Option, + pub deadline_start: NaiveDateTime, + pub deadline_end: NaiveDateTime, + pub created_at: NaiveDateTime, + pub updated_at: NaiveDateTime, + pub completed_at: Option, +} + +// Conversion from SeaORM model to GraphQL type +impl From for Service { + fn from(model: service::Model) -> Self { + Service { + id: ID(model.id.to_string()), + account_id: ID(model.account_id.to_string()), + date: model.date, + status: model.status, + notes: model.notes, + deadline_start: model.deadline_start, + deadline_end: model.deadline_end, + created_at: model.created_at, + updated_at: model.updated_at, + completed_at: model.completed_at, + } + } +} + +// Input types for mutations +#[derive(InputObject)] +pub struct CreateServiceInput { + pub account_id: ID, + pub date: NaiveDate, + pub status: String, + pub notes: Option, + pub deadline_start: NaiveDateTime, + pub deadline_end: NaiveDateTime, + pub completed_at: Option, +} + +#[derive(InputObject)] +pub struct UpdateServiceInput { + pub account_id: Option, + pub date: Option, + pub status: Option, + pub notes: Option, + pub deadline_start: Option, + pub deadline_end: Option, + pub completed_at: Option, +} + +// Query resolvers +pub struct ServiceQuery; + +#[Object] +impl ServiceQuery { + // Get service by ID + pub async fn service(&self, ctx: &Context<'_>, id: ID) -> Result> { + let db = ctx.data::>()?; + let service_uuid = Uuid::parse_str(&id)?; + + let service = service::Entity::find_by_id(service_uuid) + .one(db.as_ref()) + .await?; + + Ok(service.map(Service::from)) + } + + // Get all services with pagination + pub async fn services( + &self, + ctx: &Context<'_>, + #[graphql(desc = "Number of items to return", default = 10)] limit: u64, + #[graphql(desc = "Number of items to skip", default = 0)] offset: u64, + ) -> Result> { + let db = ctx.data::>()?; + + let services = service::Entity::find() + .offset(offset) + .limit(limit) + .all(db.as_ref()) + .await?; + + Ok(services.into_iter().map(Service::from).collect()) + } + + // Get services by account ID + pub async fn services_by_account( + &self, + ctx: &Context<'_>, + account_id: ID, + ) -> Result> { + let db = ctx.data::>()?; + let account_uuid = Uuid::parse_str(&account_id)?; + + let services = service::Entity::find() + .filter(service::Column::AccountId.eq(account_uuid)) + .all(db.as_ref()) + .await?; + + Ok(services.into_iter().map(Service::from).collect()) + } + + // Get services by status + pub async fn services_by_status( + &self, + ctx: &Context<'_>, + status: String, + ) -> Result> { + let db = ctx.data::>()?; + + let services = service::Entity::find() + .filter(service::Column::Status.eq(status)) + .all(db.as_ref()) + .await?; + + Ok(services.into_iter().map(Service::from).collect()) + } +} + +// Mutation resolvers +pub struct ServiceMutation; + +#[Object] +impl ServiceMutation { + // Create a new service + pub async fn create_service( + &self, + ctx: &Context<'_>, + input: CreateServiceInput, + ) -> Result { + let db = ctx.data::>()?; + let account_uuid = Uuid::parse_str(&input.account_id)?; + + // Verify that the account exists + let account_exists = account::Entity::find_by_id(account_uuid) + .one(db.as_ref()) + .await? + .is_some(); + + if !account_exists { + return Err(async_graphql::Error::new("Account not found")); + } + + let new_service = service::ActiveModel { + id: sea_orm::Set(Uuid::new_v4()), + account_id: sea_orm::Set(account_uuid), + date: sea_orm::Set(input.date), + status: sea_orm::Set(input.status), + notes: sea_orm::Set(input.notes), + deadline_start: sea_orm::Set(input.deadline_start), + deadline_end: sea_orm::Set(input.deadline_end), + created_at: sea_orm::Set(chrono::Utc::now().naive_utc()), + updated_at: sea_orm::Set(chrono::Utc::now().naive_utc()), + completed_at: sea_orm::Set(input.completed_at), + }; + + let service = service::Entity::insert(new_service) + .exec_with_returning(db.as_ref()) + .await?; + + Ok(Service::from(service)) + } + + // Update an existing service + pub async fn update_service( + &self, + ctx: &Context<'_>, + id: ID, + input: UpdateServiceInput, + ) -> Result { + let db = ctx.data::>()?; + let service_uuid = Uuid::parse_str(&id)?; + + // First, find the existing service to make sure it exists + let existing_service = service::Entity::find_by_id(service_uuid) + .one(db.as_ref()) + .await? + .ok_or_else(|| async_graphql::Error::new("Service not found"))?; + + // Create an ActiveModel with only the fields that need updating + let mut service_to_update: service::ActiveModel = existing_service.into(); + + // Update only the fields that are provided (Some values) + if let Some(account_id) = input.account_id { + let account_uuid = Uuid::parse_str(&account_id)?; + + // Verify that the account exists + let account_exists = account::Entity::find_by_id(account_uuid) + .one(db.as_ref()) + .await? + .is_some(); + + if !account_exists { + return Err(async_graphql::Error::new("Account not found")); + } + + service_to_update.account_id = sea_orm::Set(account_uuid); + } + if let Some(date) = input.date { + service_to_update.date = sea_orm::Set(date); + } + if let Some(status) = input.status { + service_to_update.status = sea_orm::Set(status); + } + if let Some(notes) = input.notes { + service_to_update.notes = sea_orm::Set(Some(notes)); + } + if let Some(deadline_start) = input.deadline_start { + service_to_update.deadline_start = sea_orm::Set(deadline_start); + } + if let Some(deadline_end) = input.deadline_end { + service_to_update.deadline_end = sea_orm::Set(deadline_end); + } + if let Some(completed_at) = input.completed_at { + service_to_update.completed_at = sea_orm::Set(Some(completed_at)); + } + + // Always update the updated_at timestamp + service_to_update.updated_at = sea_orm::Set(chrono::Utc::now().naive_utc()); + + let updated_service = service::Entity::update(service_to_update) + .exec(db.as_ref()) + .await?; + + Ok(Service::from(updated_service)) + } + + // Delete a service + pub async fn delete_service(&self, ctx: &Context<'_>, id: ID) -> Result { + let db = ctx.data::>()?; + let service_uuid = Uuid::parse_str(&id)?; + + let result = service::Entity::delete_by_id(service_uuid) + .exec(db.as_ref()) + .await?; + + Ok(result.rows_affected > 0) + } +} \ No newline at end of file diff --git a/src/graphql/user.rs b/src/graphql/user.rs new file mode 100644 index 0000000..cb580b5 --- /dev/null +++ b/src/graphql/user.rs @@ -0,0 +1,251 @@ +use async_graphql::{Context, ID, InputObject, Object, Result, SimpleObject}; +use chrono::NaiveDateTime; +use sea_orm::{ColumnTrait, DatabaseConnection, EntityTrait, QueryFilter, QuerySelect}; +use std::sync::Arc; +use uuid::Uuid; + +use crate::entities::user; + +// Main GraphQL User type +#[derive(SimpleObject)] +pub struct User { + pub id: ID, + pub username: String, + pub email: String, + // Note: We don't expose password_hash in the GraphQL API for security reasons + pub is_active: bool, + pub created_at: NaiveDateTime, + pub updated_at: NaiveDateTime, +} + +// Conversion from SeaORM model to GraphQL type +impl From for User { + fn from(model: user::Model) -> Self { + User { + id: ID(model.id.to_string()), + username: model.username, + email: model.email, + is_active: model.is_active, + created_at: model.created_at, + updated_at: model.updated_at, + } + } +} + +// Input types for mutations +#[derive(InputObject)] +pub struct CreateUserInput { + pub username: String, + pub email: String, + pub password: String, + pub is_active: bool, +} + +#[derive(InputObject)] +pub struct UpdateUserInput { + pub username: Option, + pub email: Option, + pub password: Option, + pub is_active: Option, +} + +// Query resolvers +pub struct UserQuery; + +#[Object] +impl UserQuery { + // Get user by ID + pub async fn user(&self, ctx: &Context<'_>, id: ID) -> Result> { + let db = ctx.data::>()?; + let user_uuid = Uuid::parse_str(&id)?; + + let user = user::Entity::find_by_id(user_uuid) + .one(db.as_ref()) + .await?; + + Ok(user.map(User::from)) + } + + // Get all users with pagination + pub async fn users( + &self, + ctx: &Context<'_>, + #[graphql(desc = "Number of items to return", default = 10)] limit: u64, + #[graphql(desc = "Number of items to skip", default = 0)] offset: u64, + ) -> Result> { + let db = ctx.data::>()?; + + let users = user::Entity::find() + .offset(offset) + .limit(limit) + .all(db.as_ref()) + .await?; + + Ok(users.into_iter().map(User::from).collect()) + } + + // Get user by username + pub async fn user_by_username(&self, ctx: &Context<'_>, username: String) -> Result> { + let db = ctx.data::>()?; + + let user = user::Entity::find() + .filter(user::Column::Username.eq(username)) + .one(db.as_ref()) + .await?; + + Ok(user.map(User::from)) + } + + // Get user by email + pub async fn user_by_email(&self, ctx: &Context<'_>, email: String) -> Result> { + let db = ctx.data::>()?; + + let user = user::Entity::find() + .filter(user::Column::Email.eq(email)) + .one(db.as_ref()) + .await?; + + Ok(user.map(User::from)) + } +} + +// Mutation resolvers +pub struct UserMutation; + +#[Object] +impl UserMutation { + // Create a new user + pub async fn create_user( + &self, + ctx: &Context<'_>, + input: CreateUserInput, + ) -> Result { + let db = ctx.data::>()?; + + // Check if username already exists + let username_exists = user::Entity::find() + .filter(user::Column::Username.eq(&input.username)) + .one(db.as_ref()) + .await? + .is_some(); + + if username_exists { + return Err(async_graphql::Error::new("Username already exists")); + } + + // Check if email already exists + let email_exists = user::Entity::find() + .filter(user::Column::Email.eq(&input.email)) + .one(db.as_ref()) + .await? + .is_some(); + + if email_exists { + return Err(async_graphql::Error::new("Email already exists")); + } + + // In a real application, you would hash the password here + // For simplicity, we're just storing it as is + let password_hash = input.password; + + let new_user = user::ActiveModel { + id: sea_orm::Set(Uuid::new_v4()), + username: sea_orm::Set(input.username), + email: sea_orm::Set(input.email), + password_hash: sea_orm::Set(password_hash), + is_active: sea_orm::Set(input.is_active), + created_at: sea_orm::Set(chrono::Utc::now().naive_utc()), + updated_at: sea_orm::Set(chrono::Utc::now().naive_utc()), + }; + + let user = user::Entity::insert(new_user) + .exec_with_returning(db.as_ref()) + .await?; + + Ok(User::from(user)) + } + + // Update an existing user + pub async fn update_user( + &self, + ctx: &Context<'_>, + id: ID, + input: UpdateUserInput, + ) -> Result { + let db = ctx.data::>()?; + let user_uuid = Uuid::parse_str(&id)?; + + // First, find the existing user to make sure it exists + let existing_user = user::Entity::find_by_id(user_uuid) + .one(db.as_ref()) + .await? + .ok_or_else(|| async_graphql::Error::new("User not found"))?; + + // Create an ActiveModel with only the fields that need updating + let mut user_to_update: user::ActiveModel = existing_user.into(); + + // Update only the fields that are provided (Some values) + if let Some(username) = input.username { + // Check if username already exists + let username_exists = user::Entity::find() + .filter(user::Column::Username.eq(&username)) + .filter(user::Column::Id.ne(user_uuid)) + .one(db.as_ref()) + .await? + .is_some(); + + if username_exists { + return Err(async_graphql::Error::new("Username already exists")); + } + + user_to_update.username = sea_orm::Set(username); + } + + if let Some(email) = input.email { + // Check if email already exists + let email_exists = user::Entity::find() + .filter(user::Column::Email.eq(&email)) + .filter(user::Column::Id.ne(user_uuid)) + .one(db.as_ref()) + .await? + .is_some(); + + if email_exists { + return Err(async_graphql::Error::new("Email already exists")); + } + + user_to_update.email = sea_orm::Set(email); + } + + if let Some(password) = input.password { + // In a real application, you would hash the password here + // For simplicity, we're just storing it as is + user_to_update.password_hash = sea_orm::Set(password); + } + + if let Some(is_active) = input.is_active { + user_to_update.is_active = sea_orm::Set(is_active); + } + + // Always update the updated_at timestamp + user_to_update.updated_at = sea_orm::Set(chrono::Utc::now().naive_utc()); + + let updated_user = user::Entity::update(user_to_update) + .exec(db.as_ref()) + .await?; + + Ok(User::from(updated_user)) + } + + // Delete a user + pub async fn delete_user(&self, ctx: &Context<'_>, id: ID) -> Result { + let db = ctx.data::>()?; + let user_uuid = Uuid::parse_str(&id)?; + + let result = user::Entity::delete_by_id(user_uuid) + .exec(db.as_ref()) + .await?; + + Ok(result.rows_affected > 0) + } +} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..0bff341 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,94 @@ +use std::sync::Arc; + +mod auth; +mod db; +mod entities; +mod graphql; + +use crate::auth::JwtMiddleware; +use actix_cors::Cors; +use actix_web::{App, HttpResponse, HttpServer, Result, web, http}; +use async_graphql_actix_web::{GraphQLRequest, GraphQLResponse}; +use sea_orm::DatabaseConnection; + +use crate::auth::handlers::{create_user, login, renew_token}; + +// GraphQL handler +async fn graphql_handler( + schema: web::Data, + db: web::Data>, + req: GraphQLRequest, +) -> GraphQLResponse { + schema + .execute(req.into_inner().data(db.as_ref().clone())) + .await + .into() +} + +// GraphQL Playground (for development/testing) +async fn graphql_playground() -> Result { + let html = async_graphql::http::playground_source( + async_graphql::http::GraphQLPlaygroundConfig::new("/graphql"), + ); + Ok(HttpResponse::Ok() + .content_type("text/html; charset=utf-8") + .body(html)) +} + +#[actix_web::main] +async fn main() -> std::io::Result<()> { + // Create a database connection and wrap in Arc + let db = Arc::new( + db::establish_connection() + .await + .expect("Failed to connect to database"), + ); + + // Create GraphQL schema + let schema = graphql::create_schema(); + + println!("🚀 Server starting at http://127.0.0.1:8080"); + println!("📊 GraphQL endpoint: http://127.0.0.1:8080/graphql"); + println!("🎮 GraphQL playground: http://127.0.0.1:8080/playground"); + println!("🔐 Authentication endpoint: http://127.0.0.1:8080/token"); + println!("🔄 Token renewal endpoint: http://127.0.0.1:8080/token/renew"); + println!("👤 User creation endpoint: http://127.0.0.1:8080/users"); + + HttpServer::new(move || { + // Configure CORS middleware + let cors = Cors::default() + // Allow same origin + .allowed_origin("http://127.0.0.1:8080") + .allowed_origin("http://localhost:8080") + // Allow Svelte development server origins + .allowed_origin("http://127.0.0.1:5173") + .allowed_origin("http://localhost:5173") + .allowed_methods(vec!["GET", "POST", "PUT", "DELETE"]) + .allowed_headers(vec![http::header::AUTHORIZATION, http::header::ACCEPT, http::header::CONTENT_TYPE]) + .supports_credentials() + .max_age(3600); + + App::new() + // Apply CORS middleware + .wrap(cors) + // Add database and schema to app data + .app_data(web::Data::new(db.clone())) + .app_data(web::Data::new(schema.clone())) + // GraphQL routes + .route("/playground", web::get().to(graphql_playground)) + // JWT Authentication routes + .route("/token", web::post().to(login)) + .route("/token/renew", web::post().to(renew_token)) + // User management routes + .route("/users", web::post().to(create_user)) + // Protected routes - JWT token required + .service( + web::scope("") // You can use a prefix like "/api" if you want + .wrap(JwtMiddleware) + .route("/graphql", web::post().to(graphql_handler)), // + ) + }) + .bind(("127.0.0.1", 8080))? + .run() + .await +}