Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9bc3c7c8fc | |||
| 0476315322 | |||
| e75b727a5a | |||
| c2aa742a79 | |||
| 49b413bf07 | |||
| 18c8595c2e | |||
| 2e6b94644f | |||
| b0bdc05071 | |||
| 2520d7e7a2 | |||
| 32985ea6fe | |||
| f8830a8b0f | |||
| c4352185d6 | |||
| f9986513f8 | |||
| 6f050aec7d | |||
| 05b787bc27 | |||
| 971046d568 | |||
| 602350d2e6 | |||
| f20d48fea2 | |||
| c53dc010de | |||
| 03e778a06f |
@@ -0,0 +1,199 @@
|
|||||||
|
name: Tests
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [main]
|
||||||
|
pull_request:
|
||||||
|
branches: [main]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
backend-tests:
|
||||||
|
name: Backend Tests
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Setup Node.js
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: '20'
|
||||||
|
cache: 'npm'
|
||||||
|
cache-dependency-path: backend/package-lock.json
|
||||||
|
|
||||||
|
- name: Install backend dependencies
|
||||||
|
run: |
|
||||||
|
cd backend
|
||||||
|
npm ci
|
||||||
|
|
||||||
|
- name: Generate Prisma client
|
||||||
|
run: |
|
||||||
|
cd backend
|
||||||
|
npx prisma generate
|
||||||
|
|
||||||
|
- name: Run backend tests
|
||||||
|
run: |
|
||||||
|
cd backend
|
||||||
|
npm test
|
||||||
|
|
||||||
|
frontend-unit-tests:
|
||||||
|
name: Frontend Unit Tests
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Setup Node.js
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: '20'
|
||||||
|
cache: 'npm'
|
||||||
|
cache-dependency-path: frontend/package-lock.json
|
||||||
|
|
||||||
|
- name: Install frontend dependencies
|
||||||
|
run: |
|
||||||
|
cd frontend
|
||||||
|
npm ci
|
||||||
|
|
||||||
|
- name: Run frontend tests
|
||||||
|
run: |
|
||||||
|
cd frontend
|
||||||
|
npm test
|
||||||
|
|
||||||
|
e2e-tests:
|
||||||
|
name: E2E Browser Tests
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Setup Node.js
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: '20'
|
||||||
|
|
||||||
|
- name: Install backend dependencies
|
||||||
|
run: |
|
||||||
|
cd backend
|
||||||
|
npm ci
|
||||||
|
|
||||||
|
- name: Generate Prisma client
|
||||||
|
run: |
|
||||||
|
cd backend
|
||||||
|
npx prisma generate
|
||||||
|
|
||||||
|
- name: Setup backend database
|
||||||
|
run: |
|
||||||
|
cd backend
|
||||||
|
npx prisma db push
|
||||||
|
env:
|
||||||
|
DATABASE_URL: file:${{ github.workspace }}/backend/prisma/e2e-test.db
|
||||||
|
|
||||||
|
- name: Install frontend dependencies
|
||||||
|
run: |
|
||||||
|
cd frontend
|
||||||
|
npm ci
|
||||||
|
|
||||||
|
- name: Install E2E test dependencies
|
||||||
|
run: |
|
||||||
|
cd e2e
|
||||||
|
npm ci
|
||||||
|
|
||||||
|
- name: Install Playwright browsers
|
||||||
|
run: |
|
||||||
|
cd e2e
|
||||||
|
npx playwright install chromium --with-deps
|
||||||
|
|
||||||
|
- name: Start servers and run E2E tests
|
||||||
|
run: |
|
||||||
|
# Start backend server in background
|
||||||
|
cd backend
|
||||||
|
DATABASE_URL="file:${{ github.workspace }}/backend/prisma/e2e-test.db" FRONTEND_URL="http://localhost:5173" npm run dev &
|
||||||
|
BACKEND_PID=$!
|
||||||
|
cd ..
|
||||||
|
|
||||||
|
# Wait for backend to be ready
|
||||||
|
echo "Waiting for backend server..."
|
||||||
|
for i in {1..30}; do
|
||||||
|
if curl -s http://localhost:8000/health > /dev/null; then
|
||||||
|
echo "Backend is ready!"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
echo "Attempt $i: Backend not ready yet..."
|
||||||
|
sleep 2
|
||||||
|
done
|
||||||
|
|
||||||
|
# Start frontend server in background
|
||||||
|
cd frontend
|
||||||
|
npm run dev -- --host &
|
||||||
|
FRONTEND_PID=$!
|
||||||
|
cd ..
|
||||||
|
|
||||||
|
# Wait for frontend to be ready
|
||||||
|
echo "Waiting for frontend server..."
|
||||||
|
for i in {1..30}; do
|
||||||
|
if curl -s http://localhost:5173 > /dev/null; then
|
||||||
|
echo "Frontend is ready!"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
echo "Attempt $i: Frontend not ready yet..."
|
||||||
|
sleep 2
|
||||||
|
done
|
||||||
|
|
||||||
|
# Run E2E tests
|
||||||
|
cd e2e
|
||||||
|
NO_SERVER=true CI=true npx playwright test
|
||||||
|
TEST_EXIT_CODE=$?
|
||||||
|
|
||||||
|
# Cleanup
|
||||||
|
kill $BACKEND_PID $FRONTEND_PID 2>/dev/null || true
|
||||||
|
|
||||||
|
exit $TEST_EXIT_CODE
|
||||||
|
env:
|
||||||
|
DATABASE_URL: file:${{ github.workspace }}/backend/prisma/e2e-test.db
|
||||||
|
|
||||||
|
- name: Upload Playwright report
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
if: always()
|
||||||
|
with:
|
||||||
|
name: playwright-report
|
||||||
|
path: e2e/playwright-report/
|
||||||
|
retention-days: 7
|
||||||
|
|
||||||
|
- name: Upload test results
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
if: failure()
|
||||||
|
with:
|
||||||
|
name: test-results
|
||||||
|
path: e2e/test-results/
|
||||||
|
retention-days: 7
|
||||||
|
|
||||||
|
# Security tests for data sanitization
|
||||||
|
security-tests:
|
||||||
|
name: Security Sanitization Tests
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Setup Node.js
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: '20'
|
||||||
|
cache: 'npm'
|
||||||
|
cache-dependency-path: backend/package-lock.json
|
||||||
|
|
||||||
|
- name: Install backend dependencies
|
||||||
|
run: |
|
||||||
|
cd backend
|
||||||
|
npm ci
|
||||||
|
|
||||||
|
- name: Generate Prisma client
|
||||||
|
run: |
|
||||||
|
cd backend
|
||||||
|
npx prisma generate
|
||||||
|
|
||||||
|
- name: Run security tests
|
||||||
|
run: |
|
||||||
|
cd backend
|
||||||
|
npx ts-node src/securityTest.ts
|
||||||
+110
-1
@@ -1,3 +1,112 @@
|
|||||||
|
# Dependencies
|
||||||
frontend/node_modules
|
frontend/node_modules
|
||||||
.DS_Store
|
backend/node_modules
|
||||||
|
|
||||||
|
# Database
|
||||||
backend/prisma/*.db
|
backend/prisma/*.db
|
||||||
|
backend/prisma/**/*.db
|
||||||
|
backend/prisma/*.db-journal
|
||||||
|
backend/prisma/**/*.db-journal
|
||||||
|
backend/prisma/dev.db
|
||||||
|
backend/prisma/e2e-test.db
|
||||||
|
backend/prisma/*.backup
|
||||||
|
|
||||||
|
# Uploads
|
||||||
|
backend/uploads/
|
||||||
|
|
||||||
|
# Generated files
|
||||||
|
backend/src/generated/
|
||||||
|
|
||||||
|
# Environment variables
|
||||||
|
.env
|
||||||
|
.env.local
|
||||||
|
.env.production
|
||||||
|
.env.staging
|
||||||
|
|
||||||
|
# Build outputs
|
||||||
|
frontend/dist/
|
||||||
|
frontend/build/
|
||||||
|
backend/dist/
|
||||||
|
|
||||||
|
# E2E Testing
|
||||||
|
e2e/node_modules/
|
||||||
|
e2e/test-results/
|
||||||
|
e2e/test-results-user/
|
||||||
|
e2e/playwright-report/
|
||||||
|
e2e/playwright-report-user/
|
||||||
|
e2e/.playwright/
|
||||||
|
|
||||||
|
# Temporary files
|
||||||
|
*.tmp
|
||||||
|
*.temp
|
||||||
|
*.bak
|
||||||
|
|
||||||
|
# Test artifacts (in case they appear in other locations)
|
||||||
|
**/playwright-report/
|
||||||
|
**/test-results/
|
||||||
|
**/playwright/.cache/
|
||||||
|
|
||||||
|
# Docker volumes (if any temporary ones are created)
|
||||||
|
docker-volumes/
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
*.log
|
||||||
|
logs/
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
|
||||||
|
# Runtime data
|
||||||
|
pids
|
||||||
|
*.pid
|
||||||
|
*.seed
|
||||||
|
*.pid.lock
|
||||||
|
|
||||||
|
# Coverage directory used by tools like istanbul
|
||||||
|
coverage/
|
||||||
|
*.lcov
|
||||||
|
|
||||||
|
# Vitest cache
|
||||||
|
.vitest/
|
||||||
|
|
||||||
|
# Playwright screenshots/videos on failure
|
||||||
|
**/screenshots/
|
||||||
|
**/videos/
|
||||||
|
|
||||||
|
# Dependency directories
|
||||||
|
jspm_packages/
|
||||||
|
|
||||||
|
# Optional npm cache directory
|
||||||
|
.npm
|
||||||
|
|
||||||
|
# Optional REPL history
|
||||||
|
.node_repl_history
|
||||||
|
|
||||||
|
# Output of 'npm pack'
|
||||||
|
*.tgz
|
||||||
|
|
||||||
|
# Yarn Integrity file
|
||||||
|
.yarn-integrity
|
||||||
|
|
||||||
|
# dotenv environment variables file
|
||||||
|
.env.test
|
||||||
|
|
||||||
|
# IDE/Editor files
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
*~
|
||||||
|
|
||||||
|
# OS generated files
|
||||||
|
.DS_Store
|
||||||
|
.DS_Store?
|
||||||
|
._*
|
||||||
|
.Spotlight-V100
|
||||||
|
.Trashes
|
||||||
|
ehthumbs.db
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
|
# Temporary files
|
||||||
|
*.tmp
|
||||||
|
*.temp
|
||||||
@@ -148,7 +148,7 @@ ExcaliDash/
|
|||||||
**Backend (.env):**
|
**Backend (.env):**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
DATABASE_URL="file:./prisma/dev.db"
|
DATABASE_URL="file:./dev.db"
|
||||||
PORT=8000
|
PORT=8000
|
||||||
NODE_ENV=development
|
NODE_ENV=development
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<img src="logoExcaliDash.png" alt="ExcaliDash Logo" width="80" height="88">
|
<img src="logoExcaliDash.png" alt="ExcaliDash Logo" width="80" height="88">
|
||||||
|
|
||||||
# ExcaliDash v0.1.5
|
# ExcaliDash v0.1.8
|
||||||
|
|
||||||

|

|
||||||

|

|
||||||
@@ -22,6 +22,8 @@ A self-hosted dashboard and organizer for [Excalidraw](https://github.com/excali
|
|||||||
- [Installation](#installation)
|
- [Installation](#installation)
|
||||||
- [Docker Hub (Recommended)](#dockerhub-recommended)
|
- [Docker Hub (Recommended)](#dockerhub-recommended)
|
||||||
- [Docker Build](#docker-build)
|
- [Docker Build](#docker-build)
|
||||||
|
- [Reverse Proxy / Traefik Setups](#reverse-proxy--traefik-setups-docker)
|
||||||
|
- [Multi-Container / Kubernetes Deployments](#multi-container--kubernetes-deployments)
|
||||||
- [Development](#development)
|
- [Development](#development)
|
||||||
- [Clone the Repository](#clone-the-repository)
|
- [Clone the Repository](#clone-the-repository)
|
||||||
- [Frontend](#frontend)
|
- [Frontend](#frontend)
|
||||||
@@ -75,7 +77,7 @@ See [release notes](https://github.com/ZimengXiong/ExcaliDash/releases) for a sp
|
|||||||
# Installation
|
# Installation
|
||||||
|
|
||||||
> [!CAUTION]
|
> [!CAUTION]
|
||||||
> NOT for production use. While attempts have been made at hardening (XSS/dompurify, CORS, rate-limiting, sanitization), they are inadequate for public deployment. Do not expose any ports. Currently lacking CSRF.
|
> NOT for production use. While attempts have been made at hardening (XSS/dompurify, CORS, rate-limiting, sanitization), they are inadequate for public deployment. Do not expose any ports.
|
||||||
|
|
||||||
> [!CAUTION]
|
> [!CAUTION]
|
||||||
> ExcaliDash is in BETA. Please backup your data regularly (e.g. with cron).
|
> ExcaliDash is in BETA. Please backup your data regularly (e.g. with cron).
|
||||||
@@ -114,6 +116,44 @@ docker compose up -d
|
|||||||
# Access the frontend at localhost:6767
|
# Access the frontend at localhost:6767
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Reverse Proxy / Traefik Setups (Docker)
|
||||||
|
|
||||||
|
When running ExcaliDash behind Traefik, Nginx, or another reverse proxy, configure both containers so that API + WebSocket calls resolve correctly:
|
||||||
|
|
||||||
|
- `FRONTEND_URL` (backend) must match the public URL that users hit (e.g. `https://excalidash.example.com`). This controls CORS and Socket.IO origin checks.
|
||||||
|
- `BACKEND_URL` (frontend) tells the Nginx container how to reach the backend from inside Docker/Kubernetes. Override it if your reverse proxy exposes the backend under a different hostname.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# docker-compose.yml example
|
||||||
|
backend:
|
||||||
|
environment:
|
||||||
|
- FRONTEND_URL=https://excalidash.example.com
|
||||||
|
frontend:
|
||||||
|
environment:
|
||||||
|
# For standard Docker Compose (default)
|
||||||
|
# - BACKEND_URL=backend:8000
|
||||||
|
# For Kubernetes, use the service DNS name:
|
||||||
|
- BACKEND_URL=excalidash-backend.default.svc.cluster.local:8000
|
||||||
|
```
|
||||||
|
|
||||||
|
### Multi-Container / Kubernetes Deployments
|
||||||
|
|
||||||
|
When running multiple backend replicas (e.g., Kubernetes, Docker Swarm, or load-balanced containers), you **must** set the `CSRF_SECRET` environment variable to the same value across all instances.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Generate a secure secret
|
||||||
|
openssl rand -base64 32
|
||||||
|
```
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# docker-compose.yml or k8s deployment
|
||||||
|
backend:
|
||||||
|
environment:
|
||||||
|
- CSRF_SECRET=your-generated-secret-here
|
||||||
|
```
|
||||||
|
|
||||||
|
Without this, each container generates its own ephemeral CSRF secret, causing token validation failures when requests are routed to different replicas. Single-container deployments work without this setting.
|
||||||
|
|
||||||
# Development
|
# Development
|
||||||
|
|
||||||
## Clone the Repository
|
## Clone the Repository
|
||||||
|
|||||||
+22
-23
@@ -1,30 +1,29 @@
|
|||||||
# ExcaliDash v0.1.5
|
CSRF Protection (8a78b2b)
|
||||||
|
|
||||||
Date: 2025-11-23
|
- Implemented comprehensive CSRF (Cross-Site Request Forgery) protection for enhanced security
|
||||||
|
- Added new backend/src/security.ts module for security utilities
|
||||||
|
- Frontend API layer now handles CSRF tokens automatically
|
||||||
|
- Added integration tests for CSRF validation
|
||||||
|
|
||||||
Compatibility: v0.1.x (Backward Compatible)
|
Upload Progress Indicator (8f9b9b4)
|
||||||
|
|
||||||
# Security
|
- Added a visual upload progress bar when users upload files
|
||||||
|
- New UploadContext for managing upload state across components
|
||||||
|
- New UploadStatus component displaying real-time upload progress
|
||||||
|
- Save status indicator when navigating back from the editor
|
||||||
|
- Improved error handling and recovery for failed uploads
|
||||||
|
|
||||||
- RCE: implemented strict Zod schema validation and input sanitization on file uploads; added path traversal guards to file handling logic
|
Bug Fixes
|
||||||
|
|
||||||
- XSS: used DOMPurify for HTML sanitization; blocked execution-capable SVG attributes and enforces CSP headers.
|
- Fixed broken e2e tests (cae8f3c)
|
||||||
|
- Replaced deprecated substr() with substring()
|
||||||
|
- Fixed stale state issues in error handling
|
||||||
|
- Fixed missing useEffect dependencies
|
||||||
|
- Fixed CSS class conflicts in progress bar styling
|
||||||
|
- Added error recovery for save state in Editor
|
||||||
|
|
||||||
- DoS: moved CPU-intensive operations to worker threads to prevent event loop blocking; request rate limiting (1,000 req/15 min per IP) and streaming for large files
|
Infrastructure
|
||||||
|
|
||||||
# Infras & Deployment
|
- Updated docker-compose configurations with new environment variables
|
||||||
|
- E2E test suite improvements and reliability fixes
|
||||||
- non-root execution (uid 1001) in containers
|
- Added Kubernetes deployment note in README
|
||||||
- migrated to multi-stage Docker builds
|
|
||||||
|
|
||||||
# Database
|
|
||||||
|
|
||||||
- migrated to better-sqlite3, converted all DB interactions to non-blocking async operations and offloaded integrity checks to worker threads.
|
|
||||||
|
|
||||||
- implemented SQLite magic header validation; added automatic backup triggers preceding data import
|
|
||||||
|
|
||||||
- input validation logic
|
|
||||||
|
|
||||||
# Frontend
|
|
||||||
|
|
||||||
- updated Settings UI to show version
|
|
||||||
|
|||||||
@@ -5,6 +5,12 @@ set -e
|
|||||||
if [ ! -f "/app/prisma/schema.prisma" ]; then
|
if [ ! -f "/app/prisma/schema.prisma" ]; then
|
||||||
echo "Mount is empty. Hydrating /app/prisma..."
|
echo "Mount is empty. Hydrating /app/prisma..."
|
||||||
cp -R /app/prisma_template/. /app/prisma/
|
cp -R /app/prisma_template/. /app/prisma/
|
||||||
|
else
|
||||||
|
# Volume exists but may be missing new migrations from an upgrade
|
||||||
|
# Always sync schema and migrations from template to ensure upgrades work
|
||||||
|
echo "Syncing schema and migrations from template..."
|
||||||
|
cp /app/prisma_template/schema.prisma /app/prisma/schema.prisma
|
||||||
|
cp -R /app/prisma_template/migrations/. /app/prisma/migrations/
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# 2. Fix permissions unconditionally (Running as root)
|
# 2. Fix permissions unconditionally (Running as root)
|
||||||
|
|||||||
Generated
+1900
-343
File diff suppressed because it is too large
Load Diff
+10
-5
@@ -1,11 +1,13 @@
|
|||||||
{
|
{
|
||||||
"name": "backend",
|
"name": "backend",
|
||||||
"version": "0.1.5",
|
"version": "0.3.0",
|
||||||
"description": "",
|
"description": "",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "nodemon src/index.ts",
|
"dev": "nodemon src/index.ts",
|
||||||
"test": "echo \"Error: no test specified\" && exit 1"
|
"test": "vitest run",
|
||||||
|
"test:watch": "vitest",
|
||||||
|
"test:coverage": "vitest run --coverage"
|
||||||
},
|
},
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
"author": "",
|
"author": "",
|
||||||
@@ -14,7 +16,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@prisma/client": "^5.22.0",
|
"@prisma/client": "^5.22.0",
|
||||||
"@types/archiver": "^7.0.0",
|
"@types/archiver": "^7.0.0",
|
||||||
"@types/jsdom": "^27.0.0",
|
"@types/jsdom": "^21.1.7",
|
||||||
"@types/multer": "^2.0.0",
|
"@types/multer": "^2.0.0",
|
||||||
"@types/socket.io": "^3.0.1",
|
"@types/socket.io": "^3.0.1",
|
||||||
"archiver": "^7.0.1",
|
"archiver": "^7.0.1",
|
||||||
@@ -23,7 +25,7 @@
|
|||||||
"dompurify": "^3.3.0",
|
"dompurify": "^3.3.0",
|
||||||
"dotenv": "^17.2.3",
|
"dotenv": "^17.2.3",
|
||||||
"express": "^5.1.0",
|
"express": "^5.1.0",
|
||||||
"jsdom": "^27.2.0",
|
"jsdom": "^22.1.0",
|
||||||
"multer": "^2.0.2",
|
"multer": "^2.0.2",
|
||||||
"prisma": "^5.22.0",
|
"prisma": "^5.22.0",
|
||||||
"socket.io": "^4.8.1",
|
"socket.io": "^4.8.1",
|
||||||
@@ -33,8 +35,11 @@
|
|||||||
"@types/cors": "^2.8.19",
|
"@types/cors": "^2.8.19",
|
||||||
"@types/express": "^5.0.5",
|
"@types/express": "^5.0.5",
|
||||||
"@types/node": "^24.10.1",
|
"@types/node": "^24.10.1",
|
||||||
|
"@types/supertest": "^6.0.3",
|
||||||
"nodemon": "^3.1.11",
|
"nodemon": "^3.1.11",
|
||||||
|
"supertest": "^7.1.4",
|
||||||
"ts-node": "^10.9.2",
|
"ts-node": "^10.9.2",
|
||||||
"typescript": "^5.9.3"
|
"typescript": "^5.9.3",
|
||||||
|
"vitest": "^4.0.15"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,168 @@
|
|||||||
|
/**
|
||||||
|
* CSRF Tests - Horizontal Scaling (K8s) Validation
|
||||||
|
*
|
||||||
|
* PR #20 review concern:
|
||||||
|
* "Worried that in memory token store might not work on horizontal scaling"
|
||||||
|
*
|
||||||
|
* Fix:
|
||||||
|
* - CSRF tokens are now stateless and HMAC-signed using a shared `CSRF_SECRET`.
|
||||||
|
* - Any pod can validate any token as long as all pods share the same secret.
|
||||||
|
*
|
||||||
|
* These tests prove:
|
||||||
|
* - Tokens validate correctly for the issuing client id
|
||||||
|
* - Tokens do NOT validate for a different client id
|
||||||
|
* - Tokens expire after 24 hours
|
||||||
|
* - Tokens validate across separate module instances (simulated pods)
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { describe, it, expect, beforeAll, afterEach, vi } from "vitest";
|
||||||
|
|
||||||
|
const SHARED_SECRET = "test-shared-csrf-secret";
|
||||||
|
|
||||||
|
beforeAll(() => {
|
||||||
|
// Must be shared across instances/pods for horizontal scaling.
|
||||||
|
process.env.CSRF_SECRET = SHARED_SECRET;
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
vi.useRealTimers();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("CSRF - stateless HMAC tokens", () => {
|
||||||
|
it("creates a token in payload.signature format and validates for same client id", async () => {
|
||||||
|
const { createCsrfToken, validateCsrfToken } = await import("../security");
|
||||||
|
|
||||||
|
const clientId = "test-client-1";
|
||||||
|
const token = createCsrfToken(clientId);
|
||||||
|
|
||||||
|
expect(typeof token).toBe("string");
|
||||||
|
// base64url(payload).base64url(signature)
|
||||||
|
expect(token).toMatch(/^[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$/);
|
||||||
|
expect(validateCsrfToken(clientId, token)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("rejects validation for a different client id (token binding)", async () => {
|
||||||
|
const { createCsrfToken, validateCsrfToken } = await import("../security");
|
||||||
|
|
||||||
|
const token = createCsrfToken("client-a");
|
||||||
|
expect(validateCsrfToken("client-b", token)).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("rejects malformed tokens", async () => {
|
||||||
|
const { validateCsrfToken } = await import("../security");
|
||||||
|
|
||||||
|
expect(validateCsrfToken("client", "not-a-token")).toBe(false);
|
||||||
|
expect(validateCsrfToken("client", "a.b.c")).toBe(false);
|
||||||
|
expect(validateCsrfToken("client", "")).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("revokeCsrfToken is a no-op for stateless tokens (does not break callers)", async () => {
|
||||||
|
const { createCsrfToken, validateCsrfToken, revokeCsrfToken } = await import(
|
||||||
|
"../security"
|
||||||
|
);
|
||||||
|
|
||||||
|
const clientId = "client-revoke";
|
||||||
|
const token = createCsrfToken(clientId);
|
||||||
|
|
||||||
|
expect(validateCsrfToken(clientId, token)).toBe(true);
|
||||||
|
revokeCsrfToken(clientId);
|
||||||
|
// Stateless token remains valid until expiry
|
||||||
|
expect(validateCsrfToken(clientId, token)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("expires tokens after 24 hours", async () => {
|
||||||
|
vi.useFakeTimers();
|
||||||
|
vi.setSystemTime(new Date("2025-01-01T00:00:00.000Z"));
|
||||||
|
|
||||||
|
const { createCsrfToken, validateCsrfToken } = await import("../security");
|
||||||
|
|
||||||
|
const clientId = "client-expiry";
|
||||||
|
const token = createCsrfToken(clientId);
|
||||||
|
expect(validateCsrfToken(clientId, token)).toBe(true);
|
||||||
|
|
||||||
|
// 24h + 1ms later
|
||||||
|
vi.setSystemTime(new Date("2025-01-02T00:00:00.001Z"));
|
||||||
|
expect(validateCsrfToken(clientId, token)).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("CSRF - horizontal scaling (simulated pods)", () => {
|
||||||
|
it("validates across module instances (pod A issues, pod B validates)", async () => {
|
||||||
|
const clientId = "user-123";
|
||||||
|
|
||||||
|
vi.resetModules();
|
||||||
|
const podA = await import("../security");
|
||||||
|
const token = podA.createCsrfToken(clientId);
|
||||||
|
|
||||||
|
// Simulate a different pod (new Node.js process / fresh module state)
|
||||||
|
vi.resetModules();
|
||||||
|
const podB = await import("../security");
|
||||||
|
|
||||||
|
expect(podB.validateCsrfToken(clientId, token)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("has 0% failure rate under round-robin validation across 3 pods", async () => {
|
||||||
|
const clientId = "user-round-robin";
|
||||||
|
|
||||||
|
const pods: Array<{
|
||||||
|
createCsrfToken: (clientId: string) => string;
|
||||||
|
validateCsrfToken: (clientId: string, token: string) => boolean;
|
||||||
|
}> = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < 3; i++) {
|
||||||
|
vi.resetModules();
|
||||||
|
pods.push(await import("../security"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Token issued on one pod
|
||||||
|
const token = pods[0].createCsrfToken(clientId);
|
||||||
|
|
||||||
|
// Validate on alternating pods (simulates a non-sticky load balancer)
|
||||||
|
const attempts = 60;
|
||||||
|
let failures = 0;
|
||||||
|
|
||||||
|
for (let i = 0; i < attempts; i++) {
|
||||||
|
const pod = pods[i % pods.length];
|
||||||
|
if (!pod.validateCsrfToken(clientId, token)) failures++;
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(failures).toBe(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("CSRF - referer origin parsing", () => {
|
||||||
|
it("extracts exact origin from a referer URL", async () => {
|
||||||
|
const { getOriginFromReferer } = await import("../security");
|
||||||
|
|
||||||
|
expect(getOriginFromReferer("https://example.com/path?x=1")).toBe(
|
||||||
|
"https://example.com"
|
||||||
|
);
|
||||||
|
expect(getOriginFromReferer("http://localhost:5173/some/page")).toBe(
|
||||||
|
"http://localhost:5173"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not allow prefix tricks (origin must be parsed)", async () => {
|
||||||
|
const { getOriginFromReferer } = await import("../security");
|
||||||
|
|
||||||
|
expect(
|
||||||
|
getOriginFromReferer("https://example.com.evil.com/anything")
|
||||||
|
).toBe("https://example.com.evil.com");
|
||||||
|
|
||||||
|
// `startsWith("https://example.com")` would incorrectly allow this.
|
||||||
|
expect(getOriginFromReferer("https://example.com@evil.com/anything")).toBe(
|
||||||
|
"https://evil.com"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns null for invalid or non-http(s) referers", async () => {
|
||||||
|
const { getOriginFromReferer } = await import("../security");
|
||||||
|
|
||||||
|
expect(getOriginFromReferer("")).toBeNull();
|
||||||
|
expect(getOriginFromReferer("not a url")).toBeNull();
|
||||||
|
expect(getOriginFromReferer("file:///etc/passwd")).toBeNull();
|
||||||
|
expect(getOriginFromReferer(null)).toBeNull();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,218 @@
|
|||||||
|
/**
|
||||||
|
* Test utilities for backend integration tests
|
||||||
|
*/
|
||||||
|
import { PrismaClient } from "../generated/client";
|
||||||
|
import path from "path";
|
||||||
|
import { execSync } from "child_process";
|
||||||
|
|
||||||
|
// Use a separate test database
|
||||||
|
const TEST_DB_PATH = path.resolve(__dirname, "../../prisma/test.db");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a test Prisma client pointing to the test database
|
||||||
|
*/
|
||||||
|
export const getTestPrisma = () => {
|
||||||
|
const databaseUrl = `file:${TEST_DB_PATH}`;
|
||||||
|
process.env.DATABASE_URL = databaseUrl;
|
||||||
|
return new PrismaClient({
|
||||||
|
datasources: {
|
||||||
|
db: {
|
||||||
|
url: databaseUrl,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setup the test database by running migrations
|
||||||
|
*/
|
||||||
|
export const setupTestDb = () => {
|
||||||
|
const databaseUrl = `file:${TEST_DB_PATH}`;
|
||||||
|
process.env.DATABASE_URL = databaseUrl;
|
||||||
|
|
||||||
|
// Run Prisma migrations to create the test database
|
||||||
|
try {
|
||||||
|
execSync("npx prisma db push --skip-generate", {
|
||||||
|
cwd: path.resolve(__dirname, "../../"),
|
||||||
|
env: { ...process.env, DATABASE_URL: databaseUrl },
|
||||||
|
stdio: "pipe",
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to setup test database:", error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clean up the test database between tests
|
||||||
|
*/
|
||||||
|
export const cleanupTestDb = async (prisma: PrismaClient) => {
|
||||||
|
// Delete all drawings and collections (except Trash)
|
||||||
|
await prisma.drawing.deleteMany({});
|
||||||
|
await prisma.collection.deleteMany({
|
||||||
|
where: { id: { not: "trash" } },
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize test database with required data
|
||||||
|
*/
|
||||||
|
export const initTestDb = async (prisma: PrismaClient) => {
|
||||||
|
// Ensure Trash collection exists
|
||||||
|
const trash = await prisma.collection.findUnique({
|
||||||
|
where: { id: "trash" },
|
||||||
|
});
|
||||||
|
if (!trash) {
|
||||||
|
await prisma.collection.create({
|
||||||
|
data: { id: "trash", name: "Trash" },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate a sample base64 PNG image data URL
|
||||||
|
* This creates a small but valid PNG for testing
|
||||||
|
*/
|
||||||
|
export const generateSampleImageDataUrl = (size: "small" | "medium" | "large" = "small"): string => {
|
||||||
|
// Minimal 1x1 red PNG (smallest valid PNG possible)
|
||||||
|
const smallPng = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8DwHwAFBQIAX8jx0gAAAABJRU5ErkJggg==";
|
||||||
|
|
||||||
|
if (size === "small") {
|
||||||
|
return `data:image/png;base64,${smallPng}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// For medium/large, repeat the pattern to create larger payloads
|
||||||
|
const repetitions = size === "medium" ? 1000 : 10000;
|
||||||
|
const paddedBase64 = smallPng.repeat(repetitions);
|
||||||
|
|
||||||
|
return `data:image/png;base64,${paddedBase64}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate a large image data URL that exceeds the 10000 char limit
|
||||||
|
* This is specifically designed to catch the truncation bug from issue #17
|
||||||
|
*/
|
||||||
|
export const generateLargeImageDataUrl = (): string => {
|
||||||
|
// Create a base64 string that's definitely larger than 10000 characters
|
||||||
|
// This simulates a real image that would get truncated by the old code
|
||||||
|
const baseImage = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8DwHwAFBQIAX8jx0gAAAABJRU5ErkJggg==";
|
||||||
|
// Repeat to create a ~50KB payload
|
||||||
|
const largeBase64 = baseImage.repeat(500);
|
||||||
|
return `data:image/png;base64,${largeBase64}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a sample Excalidraw files object with embedded images
|
||||||
|
*/
|
||||||
|
export const createSampleFilesObject = (imageCount: number = 1, size: "small" | "large" = "small") => {
|
||||||
|
const files: Record<string, any> = {};
|
||||||
|
|
||||||
|
for (let i = 0; i < imageCount; i++) {
|
||||||
|
const fileId = `file-${i}-${Date.now()}`;
|
||||||
|
files[fileId] = {
|
||||||
|
id: fileId,
|
||||||
|
mimeType: "image/png",
|
||||||
|
dataURL: size === "large" ? generateLargeImageDataUrl() : generateSampleImageDataUrl("small"),
|
||||||
|
created: Date.now(),
|
||||||
|
lastRetrieved: Date.now(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return files;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a minimal valid Excalidraw drawing payload
|
||||||
|
*/
|
||||||
|
export const createTestDrawingPayload = (options: {
|
||||||
|
name?: string;
|
||||||
|
files?: Record<string, any> | null;
|
||||||
|
elements?: any[];
|
||||||
|
appState?: any;
|
||||||
|
} = {}) => {
|
||||||
|
return {
|
||||||
|
name: options.name ?? "Test Drawing",
|
||||||
|
elements: options.elements ?? [
|
||||||
|
{
|
||||||
|
id: "element-1",
|
||||||
|
type: "rectangle",
|
||||||
|
x: 100,
|
||||||
|
y: 100,
|
||||||
|
width: 200,
|
||||||
|
height: 100,
|
||||||
|
angle: 0,
|
||||||
|
strokeColor: "#000000",
|
||||||
|
backgroundColor: "transparent",
|
||||||
|
fillStyle: "hachure",
|
||||||
|
strokeWidth: 1,
|
||||||
|
strokeStyle: "solid",
|
||||||
|
roughness: 1,
|
||||||
|
opacity: 100,
|
||||||
|
groupIds: [],
|
||||||
|
frameId: null,
|
||||||
|
roundness: null,
|
||||||
|
seed: 12345,
|
||||||
|
version: 1,
|
||||||
|
versionNonce: 1,
|
||||||
|
isDeleted: false,
|
||||||
|
boundElements: null,
|
||||||
|
updated: Date.now(),
|
||||||
|
link: null,
|
||||||
|
locked: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
appState: options.appState ?? {
|
||||||
|
viewBackgroundColor: "#ffffff",
|
||||||
|
gridSize: null,
|
||||||
|
},
|
||||||
|
files: options.files ?? null,
|
||||||
|
preview: null,
|
||||||
|
collectionId: null,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compare two files objects to check if image data was preserved
|
||||||
|
*/
|
||||||
|
export const compareFilesObjects = (original: Record<string, any>, received: Record<string, any>): {
|
||||||
|
isEqual: boolean;
|
||||||
|
differences: string[];
|
||||||
|
} => {
|
||||||
|
const differences: string[] = [];
|
||||||
|
|
||||||
|
const originalKeys = Object.keys(original);
|
||||||
|
const receivedKeys = Object.keys(received);
|
||||||
|
|
||||||
|
if (originalKeys.length !== receivedKeys.length) {
|
||||||
|
differences.push(`Key count mismatch: original=${originalKeys.length}, received=${receivedKeys.length}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const key of originalKeys) {
|
||||||
|
if (!(key in received)) {
|
||||||
|
differences.push(`Missing key: ${key}`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const origFile = original[key];
|
||||||
|
const recvFile = received[key];
|
||||||
|
|
||||||
|
// Check dataURL specifically - this is where truncation would occur
|
||||||
|
if (origFile.dataURL !== recvFile.dataURL) {
|
||||||
|
differences.push(
|
||||||
|
`DataURL mismatch for ${key}: ` +
|
||||||
|
`original length=${origFile.dataURL?.length ?? 0}, ` +
|
||||||
|
`received length=${recvFile.dataURL?.length ?? 0}`
|
||||||
|
);
|
||||||
|
|
||||||
|
// Check if it was truncated
|
||||||
|
if (recvFile.dataURL && origFile.dataURL?.startsWith(recvFile.dataURL.substring(0, 100))) {
|
||||||
|
differences.push(`TRUNCATION DETECTED: dataURL was cut short`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
isEqual: differences.length === 0,
|
||||||
|
differences,
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
|
|
||||||
/* !!! This is code generated by Prisma. Do not edit directly. !!! */
|
|
||||||
/* eslint-disable */
|
|
||||||
// biome-ignore-all lint: generated file
|
|
||||||
// @ts-nocheck
|
|
||||||
/*
|
|
||||||
* This file should be your main import to use Prisma-related types and utilities in a browser.
|
|
||||||
* Use it to get access to models, enums, and input types.
|
|
||||||
*
|
|
||||||
* This file does not contain a `PrismaClient` class, nor several other helpers that are intended as server-side only.
|
|
||||||
* See `client.ts` for the standard, server-side entry point.
|
|
||||||
*
|
|
||||||
* 🟢 You can import this file directly.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import * as Prisma from './internal/prismaNamespaceBrowser'
|
|
||||||
export { Prisma }
|
|
||||||
export * as $Enums from './enums'
|
|
||||||
export * from './enums';
|
|
||||||
/**
|
|
||||||
* Model Collection
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
export type Collection = Prisma.CollectionModel
|
|
||||||
/**
|
|
||||||
* Model Drawing
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
export type Drawing = Prisma.DrawingModel
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
|
|
||||||
/* !!! This is code generated by Prisma. Do not edit directly. !!! */
|
|
||||||
/* eslint-disable */
|
|
||||||
// biome-ignore-all lint: generated file
|
|
||||||
// @ts-nocheck
|
|
||||||
/*
|
|
||||||
* This file should be your main import to use Prisma. Through it you get access to all the models, enums, and input types.
|
|
||||||
* If you're looking for something you can import in the client-side of your application, please refer to the `browser.ts` file instead.
|
|
||||||
*
|
|
||||||
* 🟢 You can import this file directly.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import * as process from 'node:process'
|
|
||||||
import * as path from 'node:path'
|
|
||||||
|
|
||||||
import * as runtime from "@prisma/client/runtime/client"
|
|
||||||
import * as $Enums from "./enums"
|
|
||||||
import * as $Class from "./internal/class"
|
|
||||||
import * as Prisma from "./internal/prismaNamespace"
|
|
||||||
|
|
||||||
export * as $Enums from './enums'
|
|
||||||
export * from "./enums"
|
|
||||||
/**
|
|
||||||
* ## Prisma Client
|
|
||||||
*
|
|
||||||
* Type-safe database client for TypeScript
|
|
||||||
* @example
|
|
||||||
* ```
|
|
||||||
* const prisma = new PrismaClient()
|
|
||||||
* // Fetch zero or more Collections
|
|
||||||
* const collections = await prisma.collection.findMany()
|
|
||||||
* ```
|
|
||||||
*
|
|
||||||
* Read more in our [docs](https://www.prisma.io/docs/reference/tools-and-interfaces/prisma-client).
|
|
||||||
*/
|
|
||||||
export const PrismaClient = $Class.getPrismaClientClass()
|
|
||||||
export type PrismaClient<LogOpts extends Prisma.LogLevel = never, OmitOpts extends Prisma.PrismaClientOptions["omit"] = Prisma.PrismaClientOptions["omit"], ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = $Class.PrismaClient<LogOpts, OmitOpts, ExtArgs>
|
|
||||||
export { Prisma }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Model Collection
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
export type Collection = Prisma.CollectionModel
|
|
||||||
/**
|
|
||||||
* Model Drawing
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
export type Drawing = Prisma.DrawingModel
|
|
||||||
@@ -1,272 +0,0 @@
|
|||||||
|
|
||||||
/* !!! This is code generated by Prisma. Do not edit directly. !!! */
|
|
||||||
/* eslint-disable */
|
|
||||||
// biome-ignore-all lint: generated file
|
|
||||||
// @ts-nocheck
|
|
||||||
/*
|
|
||||||
* This file exports various common sort, input & filter types that are not directly linked to a particular model.
|
|
||||||
*
|
|
||||||
* 🟢 You can import this file directly.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import type * as runtime from "@prisma/client/runtime/client"
|
|
||||||
import * as $Enums from "./enums"
|
|
||||||
import type * as Prisma from "./internal/prismaNamespace"
|
|
||||||
|
|
||||||
|
|
||||||
export type StringFilter<$PrismaModel = never> = {
|
|
||||||
equals?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
|
||||||
in?: string[]
|
|
||||||
notIn?: string[]
|
|
||||||
lt?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
|
||||||
lte?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
|
||||||
gt?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
|
||||||
gte?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
|
||||||
contains?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
|
||||||
startsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
|
||||||
endsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
|
||||||
not?: Prisma.NestedStringFilter<$PrismaModel> | string
|
|
||||||
}
|
|
||||||
|
|
||||||
export type DateTimeFilter<$PrismaModel = never> = {
|
|
||||||
equals?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
|
||||||
in?: Date[] | string[]
|
|
||||||
notIn?: Date[] | string[]
|
|
||||||
lt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
|
||||||
lte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
|
||||||
gt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
|
||||||
gte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
|
||||||
not?: Prisma.NestedDateTimeFilter<$PrismaModel> | Date | string
|
|
||||||
}
|
|
||||||
|
|
||||||
export type StringWithAggregatesFilter<$PrismaModel = never> = {
|
|
||||||
equals?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
|
||||||
in?: string[]
|
|
||||||
notIn?: string[]
|
|
||||||
lt?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
|
||||||
lte?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
|
||||||
gt?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
|
||||||
gte?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
|
||||||
contains?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
|
||||||
startsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
|
||||||
endsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
|
||||||
not?: Prisma.NestedStringWithAggregatesFilter<$PrismaModel> | string
|
|
||||||
_count?: Prisma.NestedIntFilter<$PrismaModel>
|
|
||||||
_min?: Prisma.NestedStringFilter<$PrismaModel>
|
|
||||||
_max?: Prisma.NestedStringFilter<$PrismaModel>
|
|
||||||
}
|
|
||||||
|
|
||||||
export type DateTimeWithAggregatesFilter<$PrismaModel = never> = {
|
|
||||||
equals?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
|
||||||
in?: Date[] | string[]
|
|
||||||
notIn?: Date[] | string[]
|
|
||||||
lt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
|
||||||
lte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
|
||||||
gt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
|
||||||
gte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
|
||||||
not?: Prisma.NestedDateTimeWithAggregatesFilter<$PrismaModel> | Date | string
|
|
||||||
_count?: Prisma.NestedIntFilter<$PrismaModel>
|
|
||||||
_min?: Prisma.NestedDateTimeFilter<$PrismaModel>
|
|
||||||
_max?: Prisma.NestedDateTimeFilter<$PrismaModel>
|
|
||||||
}
|
|
||||||
|
|
||||||
export type IntFilter<$PrismaModel = never> = {
|
|
||||||
equals?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
|
||||||
in?: number[]
|
|
||||||
notIn?: number[]
|
|
||||||
lt?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
|
||||||
lte?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
|
||||||
gt?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
|
||||||
gte?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
|
||||||
not?: Prisma.NestedIntFilter<$PrismaModel> | number
|
|
||||||
}
|
|
||||||
|
|
||||||
export type StringNullableFilter<$PrismaModel = never> = {
|
|
||||||
equals?: string | Prisma.StringFieldRefInput<$PrismaModel> | null
|
|
||||||
in?: string[] | null
|
|
||||||
notIn?: string[] | null
|
|
||||||
lt?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
|
||||||
lte?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
|
||||||
gt?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
|
||||||
gte?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
|
||||||
contains?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
|
||||||
startsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
|
||||||
endsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
|
||||||
not?: Prisma.NestedStringNullableFilter<$PrismaModel> | string | null
|
|
||||||
}
|
|
||||||
|
|
||||||
export type SortOrderInput = {
|
|
||||||
sort: Prisma.SortOrder
|
|
||||||
nulls?: Prisma.NullsOrder
|
|
||||||
}
|
|
||||||
|
|
||||||
export type IntWithAggregatesFilter<$PrismaModel = never> = {
|
|
||||||
equals?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
|
||||||
in?: number[]
|
|
||||||
notIn?: number[]
|
|
||||||
lt?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
|
||||||
lte?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
|
||||||
gt?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
|
||||||
gte?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
|
||||||
not?: Prisma.NestedIntWithAggregatesFilter<$PrismaModel> | number
|
|
||||||
_count?: Prisma.NestedIntFilter<$PrismaModel>
|
|
||||||
_avg?: Prisma.NestedFloatFilter<$PrismaModel>
|
|
||||||
_sum?: Prisma.NestedIntFilter<$PrismaModel>
|
|
||||||
_min?: Prisma.NestedIntFilter<$PrismaModel>
|
|
||||||
_max?: Prisma.NestedIntFilter<$PrismaModel>
|
|
||||||
}
|
|
||||||
|
|
||||||
export type StringNullableWithAggregatesFilter<$PrismaModel = never> = {
|
|
||||||
equals?: string | Prisma.StringFieldRefInput<$PrismaModel> | null
|
|
||||||
in?: string[] | null
|
|
||||||
notIn?: string[] | null
|
|
||||||
lt?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
|
||||||
lte?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
|
||||||
gt?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
|
||||||
gte?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
|
||||||
contains?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
|
||||||
startsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
|
||||||
endsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
|
||||||
not?: Prisma.NestedStringNullableWithAggregatesFilter<$PrismaModel> | string | null
|
|
||||||
_count?: Prisma.NestedIntNullableFilter<$PrismaModel>
|
|
||||||
_min?: Prisma.NestedStringNullableFilter<$PrismaModel>
|
|
||||||
_max?: Prisma.NestedStringNullableFilter<$PrismaModel>
|
|
||||||
}
|
|
||||||
|
|
||||||
export type NestedStringFilter<$PrismaModel = never> = {
|
|
||||||
equals?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
|
||||||
in?: string[]
|
|
||||||
notIn?: string[]
|
|
||||||
lt?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
|
||||||
lte?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
|
||||||
gt?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
|
||||||
gte?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
|
||||||
contains?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
|
||||||
startsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
|
||||||
endsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
|
||||||
not?: Prisma.NestedStringFilter<$PrismaModel> | string
|
|
||||||
}
|
|
||||||
|
|
||||||
export type NestedDateTimeFilter<$PrismaModel = never> = {
|
|
||||||
equals?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
|
||||||
in?: Date[] | string[]
|
|
||||||
notIn?: Date[] | string[]
|
|
||||||
lt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
|
||||||
lte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
|
||||||
gt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
|
||||||
gte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
|
||||||
not?: Prisma.NestedDateTimeFilter<$PrismaModel> | Date | string
|
|
||||||
}
|
|
||||||
|
|
||||||
export type NestedStringWithAggregatesFilter<$PrismaModel = never> = {
|
|
||||||
equals?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
|
||||||
in?: string[]
|
|
||||||
notIn?: string[]
|
|
||||||
lt?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
|
||||||
lte?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
|
||||||
gt?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
|
||||||
gte?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
|
||||||
contains?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
|
||||||
startsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
|
||||||
endsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
|
||||||
not?: Prisma.NestedStringWithAggregatesFilter<$PrismaModel> | string
|
|
||||||
_count?: Prisma.NestedIntFilter<$PrismaModel>
|
|
||||||
_min?: Prisma.NestedStringFilter<$PrismaModel>
|
|
||||||
_max?: Prisma.NestedStringFilter<$PrismaModel>
|
|
||||||
}
|
|
||||||
|
|
||||||
export type NestedIntFilter<$PrismaModel = never> = {
|
|
||||||
equals?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
|
||||||
in?: number[]
|
|
||||||
notIn?: number[]
|
|
||||||
lt?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
|
||||||
lte?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
|
||||||
gt?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
|
||||||
gte?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
|
||||||
not?: Prisma.NestedIntFilter<$PrismaModel> | number
|
|
||||||
}
|
|
||||||
|
|
||||||
export type NestedDateTimeWithAggregatesFilter<$PrismaModel = never> = {
|
|
||||||
equals?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
|
||||||
in?: Date[] | string[]
|
|
||||||
notIn?: Date[] | string[]
|
|
||||||
lt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
|
||||||
lte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
|
||||||
gt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
|
||||||
gte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
|
||||||
not?: Prisma.NestedDateTimeWithAggregatesFilter<$PrismaModel> | Date | string
|
|
||||||
_count?: Prisma.NestedIntFilter<$PrismaModel>
|
|
||||||
_min?: Prisma.NestedDateTimeFilter<$PrismaModel>
|
|
||||||
_max?: Prisma.NestedDateTimeFilter<$PrismaModel>
|
|
||||||
}
|
|
||||||
|
|
||||||
export type NestedStringNullableFilter<$PrismaModel = never> = {
|
|
||||||
equals?: string | Prisma.StringFieldRefInput<$PrismaModel> | null
|
|
||||||
in?: string[] | null
|
|
||||||
notIn?: string[] | null
|
|
||||||
lt?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
|
||||||
lte?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
|
||||||
gt?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
|
||||||
gte?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
|
||||||
contains?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
|
||||||
startsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
|
||||||
endsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
|
||||||
not?: Prisma.NestedStringNullableFilter<$PrismaModel> | string | null
|
|
||||||
}
|
|
||||||
|
|
||||||
export type NestedIntWithAggregatesFilter<$PrismaModel = never> = {
|
|
||||||
equals?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
|
||||||
in?: number[]
|
|
||||||
notIn?: number[]
|
|
||||||
lt?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
|
||||||
lte?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
|
||||||
gt?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
|
||||||
gte?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
|
||||||
not?: Prisma.NestedIntWithAggregatesFilter<$PrismaModel> | number
|
|
||||||
_count?: Prisma.NestedIntFilter<$PrismaModel>
|
|
||||||
_avg?: Prisma.NestedFloatFilter<$PrismaModel>
|
|
||||||
_sum?: Prisma.NestedIntFilter<$PrismaModel>
|
|
||||||
_min?: Prisma.NestedIntFilter<$PrismaModel>
|
|
||||||
_max?: Prisma.NestedIntFilter<$PrismaModel>
|
|
||||||
}
|
|
||||||
|
|
||||||
export type NestedFloatFilter<$PrismaModel = never> = {
|
|
||||||
equals?: number | Prisma.FloatFieldRefInput<$PrismaModel>
|
|
||||||
in?: number[]
|
|
||||||
notIn?: number[]
|
|
||||||
lt?: number | Prisma.FloatFieldRefInput<$PrismaModel>
|
|
||||||
lte?: number | Prisma.FloatFieldRefInput<$PrismaModel>
|
|
||||||
gt?: number | Prisma.FloatFieldRefInput<$PrismaModel>
|
|
||||||
gte?: number | Prisma.FloatFieldRefInput<$PrismaModel>
|
|
||||||
not?: Prisma.NestedFloatFilter<$PrismaModel> | number
|
|
||||||
}
|
|
||||||
|
|
||||||
export type NestedStringNullableWithAggregatesFilter<$PrismaModel = never> = {
|
|
||||||
equals?: string | Prisma.StringFieldRefInput<$PrismaModel> | null
|
|
||||||
in?: string[] | null
|
|
||||||
notIn?: string[] | null
|
|
||||||
lt?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
|
||||||
lte?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
|
||||||
gt?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
|
||||||
gte?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
|
||||||
contains?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
|
||||||
startsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
|
||||||
endsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
|
||||||
not?: Prisma.NestedStringNullableWithAggregatesFilter<$PrismaModel> | string | null
|
|
||||||
_count?: Prisma.NestedIntNullableFilter<$PrismaModel>
|
|
||||||
_min?: Prisma.NestedStringNullableFilter<$PrismaModel>
|
|
||||||
_max?: Prisma.NestedStringNullableFilter<$PrismaModel>
|
|
||||||
}
|
|
||||||
|
|
||||||
export type NestedIntNullableFilter<$PrismaModel = never> = {
|
|
||||||
equals?: number | Prisma.IntFieldRefInput<$PrismaModel> | null
|
|
||||||
in?: number[] | null
|
|
||||||
notIn?: number[] | null
|
|
||||||
lt?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
|
||||||
lte?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
|
||||||
gt?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
|
||||||
gte?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
|
||||||
not?: Prisma.NestedIntNullableFilter<$PrismaModel> | number | null
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
-1
@@ -1 +0,0 @@
|
|||||||
export * from "./index"
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
module.exports = { ...require('.') }
|
|
||||||
-1
@@ -1 +0,0 @@
|
|||||||
export * from "./default"
|
|
||||||
File diff suppressed because one or more lines are too long
@@ -1,15 +0,0 @@
|
|||||||
|
|
||||||
/* !!! This is code generated by Prisma. Do not edit directly. !!! */
|
|
||||||
/* eslint-disable */
|
|
||||||
// biome-ignore-all lint: generated file
|
|
||||||
// @ts-nocheck
|
|
||||||
/*
|
|
||||||
* This file exports all enum related types from the schema.
|
|
||||||
*
|
|
||||||
* 🟢 You can import this file directly.
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// This file is empty because there are no enums in the schema.
|
|
||||||
export {}
|
|
||||||
@@ -1,195 +0,0 @@
|
|||||||
|
|
||||||
Object.defineProperty(exports, "__esModule", { value: true });
|
|
||||||
|
|
||||||
const {
|
|
||||||
Decimal,
|
|
||||||
objectEnumValues,
|
|
||||||
makeStrictEnum,
|
|
||||||
Public,
|
|
||||||
getRuntime,
|
|
||||||
skip
|
|
||||||
} = require('./runtime/index-browser.js')
|
|
||||||
|
|
||||||
|
|
||||||
const Prisma = {}
|
|
||||||
|
|
||||||
exports.Prisma = Prisma
|
|
||||||
exports.$Enums = {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Prisma Client JS version: 5.22.0
|
|
||||||
* Query Engine version: 605197351a3c8bdd595af2d2a9bc3025bca48ea2
|
|
||||||
*/
|
|
||||||
Prisma.prismaVersion = {
|
|
||||||
client: "5.22.0",
|
|
||||||
engine: "605197351a3c8bdd595af2d2a9bc3025bca48ea2"
|
|
||||||
}
|
|
||||||
|
|
||||||
Prisma.PrismaClientKnownRequestError = () => {
|
|
||||||
const runtimeName = getRuntime().prettyName;
|
|
||||||
throw new Error(`PrismaClientKnownRequestError is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
|
||||||
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
|
||||||
)};
|
|
||||||
Prisma.PrismaClientUnknownRequestError = () => {
|
|
||||||
const runtimeName = getRuntime().prettyName;
|
|
||||||
throw new Error(`PrismaClientUnknownRequestError is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
|
||||||
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
|
||||||
)}
|
|
||||||
Prisma.PrismaClientRustPanicError = () => {
|
|
||||||
const runtimeName = getRuntime().prettyName;
|
|
||||||
throw new Error(`PrismaClientRustPanicError is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
|
||||||
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
|
||||||
)}
|
|
||||||
Prisma.PrismaClientInitializationError = () => {
|
|
||||||
const runtimeName = getRuntime().prettyName;
|
|
||||||
throw new Error(`PrismaClientInitializationError is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
|
||||||
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
|
||||||
)}
|
|
||||||
Prisma.PrismaClientValidationError = () => {
|
|
||||||
const runtimeName = getRuntime().prettyName;
|
|
||||||
throw new Error(`PrismaClientValidationError is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
|
||||||
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
|
||||||
)}
|
|
||||||
Prisma.NotFoundError = () => {
|
|
||||||
const runtimeName = getRuntime().prettyName;
|
|
||||||
throw new Error(`NotFoundError is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
|
||||||
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
|
||||||
)}
|
|
||||||
Prisma.Decimal = Decimal
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Re-export of sql-template-tag
|
|
||||||
*/
|
|
||||||
Prisma.sql = () => {
|
|
||||||
const runtimeName = getRuntime().prettyName;
|
|
||||||
throw new Error(`sqltag is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
|
||||||
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
|
||||||
)}
|
|
||||||
Prisma.empty = () => {
|
|
||||||
const runtimeName = getRuntime().prettyName;
|
|
||||||
throw new Error(`empty is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
|
||||||
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
|
||||||
)}
|
|
||||||
Prisma.join = () => {
|
|
||||||
const runtimeName = getRuntime().prettyName;
|
|
||||||
throw new Error(`join is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
|
||||||
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
|
||||||
)}
|
|
||||||
Prisma.raw = () => {
|
|
||||||
const runtimeName = getRuntime().prettyName;
|
|
||||||
throw new Error(`raw is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
|
||||||
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
|
||||||
)}
|
|
||||||
Prisma.validator = Public.validator
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Extensions
|
|
||||||
*/
|
|
||||||
Prisma.getExtensionContext = () => {
|
|
||||||
const runtimeName = getRuntime().prettyName;
|
|
||||||
throw new Error(`Extensions.getExtensionContext is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
|
||||||
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
|
||||||
)}
|
|
||||||
Prisma.defineExtension = () => {
|
|
||||||
const runtimeName = getRuntime().prettyName;
|
|
||||||
throw new Error(`Extensions.defineExtension is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
|
||||||
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
|
||||||
)}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Shorthand utilities for JSON filtering
|
|
||||||
*/
|
|
||||||
Prisma.DbNull = objectEnumValues.instances.DbNull
|
|
||||||
Prisma.JsonNull = objectEnumValues.instances.JsonNull
|
|
||||||
Prisma.AnyNull = objectEnumValues.instances.AnyNull
|
|
||||||
|
|
||||||
Prisma.NullTypes = {
|
|
||||||
DbNull: objectEnumValues.classes.DbNull,
|
|
||||||
JsonNull: objectEnumValues.classes.JsonNull,
|
|
||||||
AnyNull: objectEnumValues.classes.AnyNull
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Enums
|
|
||||||
*/
|
|
||||||
|
|
||||||
exports.Prisma.TransactionIsolationLevel = makeStrictEnum({
|
|
||||||
Serializable: 'Serializable'
|
|
||||||
});
|
|
||||||
|
|
||||||
exports.Prisma.CollectionScalarFieldEnum = {
|
|
||||||
id: 'id',
|
|
||||||
name: 'name',
|
|
||||||
createdAt: 'createdAt',
|
|
||||||
updatedAt: 'updatedAt'
|
|
||||||
};
|
|
||||||
|
|
||||||
exports.Prisma.DrawingScalarFieldEnum = {
|
|
||||||
id: 'id',
|
|
||||||
name: 'name',
|
|
||||||
elements: 'elements',
|
|
||||||
appState: 'appState',
|
|
||||||
files: 'files',
|
|
||||||
preview: 'preview',
|
|
||||||
version: 'version',
|
|
||||||
collectionId: 'collectionId',
|
|
||||||
createdAt: 'createdAt',
|
|
||||||
updatedAt: 'updatedAt'
|
|
||||||
};
|
|
||||||
|
|
||||||
exports.Prisma.LibraryScalarFieldEnum = {
|
|
||||||
id: 'id',
|
|
||||||
items: 'items',
|
|
||||||
createdAt: 'createdAt',
|
|
||||||
updatedAt: 'updatedAt'
|
|
||||||
};
|
|
||||||
|
|
||||||
exports.Prisma.SortOrder = {
|
|
||||||
asc: 'asc',
|
|
||||||
desc: 'desc'
|
|
||||||
};
|
|
||||||
|
|
||||||
exports.Prisma.NullsOrder = {
|
|
||||||
first: 'first',
|
|
||||||
last: 'last'
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
exports.Prisma.ModelName = {
|
|
||||||
Collection: 'Collection',
|
|
||||||
Drawing: 'Drawing',
|
|
||||||
Library: 'Library'
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This is a stub Prisma Client that will error at runtime if called.
|
|
||||||
*/
|
|
||||||
class PrismaClient {
|
|
||||||
constructor() {
|
|
||||||
return new Proxy(this, {
|
|
||||||
get(target, prop) {
|
|
||||||
let message
|
|
||||||
const runtime = getRuntime()
|
|
||||||
if (runtime.isEdge) {
|
|
||||||
message = `PrismaClient is not configured to run in ${runtime.prettyName}. In order to run Prisma Client on edge runtime, either:
|
|
||||||
- Use Prisma Accelerate: https://pris.ly/d/accelerate
|
|
||||||
- Use Driver Adapters: https://pris.ly/d/driver-adapters
|
|
||||||
`;
|
|
||||||
} else {
|
|
||||||
message = 'PrismaClient is unable to run in this browser environment, or has been bundled for the browser (running in `' + runtime.prettyName + '`).'
|
|
||||||
}
|
|
||||||
|
|
||||||
message += `
|
|
||||||
If this is unexpected, please open an issue: https://pris.ly/prisma-prisma-bug-report`
|
|
||||||
|
|
||||||
throw new Error(message)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
exports.PrismaClient = PrismaClient
|
|
||||||
|
|
||||||
Object.assign(exports, Prisma)
|
|
||||||
-4984
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user