Praveen-K-0503 commited on
Commit
8c2f1b1
·
1 Parent(s): 3a66575

config: wire HuggingFace Space praveendatascience/Crowd-Detection

Browse files

- Set HF_WEIGHTS_REPO to praveendatascience/crowd-counting-weights
- Add HF Space URL to ALLOWED_ORIGINS in api.py
- Set VITE_API_URL to https://praveendatascience-crowd-detection.hf.space in .env.production

This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. api.py +1 -1
  2. civic-platform/.env.example +0 -6
  3. civic-platform/.gitignore +0 -7
  4. civic-platform/README.md +0 -119
  5. civic-platform/ai/README.md +0 -15
  6. civic-platform/ai/schemas/civic-image-record.example.json +0 -11
  7. civic-platform/ai/schemas/complaint-text-record.example.json +0 -12
  8. civic-platform/ai/training/README.md +0 -73
  9. civic-platform/ai/training/civic-image-labels.md +0 -19
  10. civic-platform/app/api/session/login/route.ts +0 -77
  11. civic-platform/app/api/session/logout/route.ts +0 -10
  12. civic-platform/app/api/session/register/route.ts +0 -83
  13. civic-platform/app/complaints/[id]/page.tsx +0 -264
  14. civic-platform/app/complaints/page.tsx +0 -217
  15. civic-platform/app/dashboard/analytics/page.tsx +0 -184
  16. civic-platform/app/dashboard/complaints/[id]/page.tsx +0 -383
  17. civic-platform/app/dashboard/complaints/page.tsx +0 -113
  18. civic-platform/app/dashboard/page.tsx +0 -257
  19. civic-platform/app/emergency/page.tsx +0 -176
  20. civic-platform/app/globals.css +0 -61
  21. civic-platform/app/layout.tsx +0 -19
  22. civic-platform/app/login/page.tsx +0 -40
  23. civic-platform/app/map/page.tsx +0 -59
  24. civic-platform/app/notifications/page.tsx +0 -35
  25. civic-platform/app/page.tsx +0 -15
  26. civic-platform/app/report/location/page.tsx +0 -170
  27. civic-platform/app/report/page.tsx +0 -27
  28. civic-platform/app/report/review/page.tsx +0 -165
  29. civic-platform/app/report/success/page.tsx +0 -173
  30. civic-platform/app/tasks/page.tsx +0 -35
  31. civic-platform/app/template.tsx +0 -16
  32. civic-platform/backend/.env.example +0 -12
  33. civic-platform/backend/README.md +0 -38
  34. civic-platform/backend/backend-dev.log +0 -5
  35. civic-platform/backend/backend-run.log +0 -2
  36. civic-platform/backend/backend.crash.log +0 -0
  37. civic-platform/backend/backend.detached.log +0 -1
  38. civic-platform/backend/backend.dev.log +0 -0
  39. civic-platform/backend/package-lock.json +0 -1817
  40. civic-platform/backend/package.json +0 -30
  41. civic-platform/backend/server.err.log +0 -0
  42. civic-platform/backend/server.out.log +0 -0
  43. civic-platform/backend/sql/001_initial_schema.sql +0 -246
  44. civic-platform/backend/sql/002_seed_core_data.sql +0 -133
  45. civic-platform/backend/sql/003_performance_indexes.sql +0 -32
  46. civic-platform/backend/sql/README.md +0 -22
  47. civic-platform/backend/src/app.ts +0 -48
  48. civic-platform/backend/src/config/env.ts +0 -28
  49. civic-platform/backend/src/db/pool.ts +0 -11
  50. civic-platform/backend/src/lib/audit.ts +0 -29
api.py CHANGED
@@ -27,7 +27,7 @@ app = FastAPI()
27
  # The ALLOWED_ORIGINS env var can be set on HF Spaces to your exact Vercel URL.
28
  _raw_origins = os.environ.get(
29
  "ALLOWED_ORIGINS",
30
- "http://localhost:5173,http://127.0.0.1:5173,http://localhost:4173,http://127.0.0.1:4173,http://localhost:3000,http://127.0.0.1:3000"
31
  )
32
  ALLOWED_ORIGINS = [o.strip() for o in _raw_origins.split(",") if o.strip()]
33
 
 
27
  # The ALLOWED_ORIGINS env var can be set on HF Spaces to your exact Vercel URL.
28
  _raw_origins = os.environ.get(
29
  "ALLOWED_ORIGINS",
30
+ "http://localhost:5173,http://127.0.0.1:5173,http://localhost:4173,http://127.0.0.1:4173,http://localhost:3000,http://127.0.0.1:3000,https://praveendatascience-crowd-detection.hf.space"
31
  )
32
  ALLOWED_ORIGINS = [o.strip() for o in _raw_origins.split(",") if o.strip()]
33
 
civic-platform/.env.example DELETED
@@ -1,6 +0,0 @@
1
- NEXT_PUBLIC_API_BASE_URL=http://localhost:4000/api
2
-
3
- # Optional. If set, the public map uses Mapbox streets tiles.
4
- # Leave blank to use free OpenStreetMap tiles.
5
- MAPBOX_TOKEN=
6
- NEXT_PUBLIC_MAPBOX_TOKEN=
 
 
 
 
 
 
 
civic-platform/.gitignore DELETED
@@ -1,7 +0,0 @@
1
- node_modules/
2
- .next/
3
- dist/
4
- .env
5
- .env.local
6
- backend/.env
7
- backend/dist/
 
 
 
 
 
 
 
 
civic-platform/README.md DELETED
@@ -1,119 +0,0 @@
1
- # Civic Platform
2
-
3
- A mobile-first civic issue reporting and resolution platform for day-to-day public problems and emergency incidents.
4
-
5
- ## Vision
6
-
7
- Citizens report issues with photo, video, voice, text, and geo-location. The system classifies the complaint, assigns priority, routes it to the correct government department, and tracks the full lifecycle from submission to closure.
8
-
9
- ## Core goals
10
-
11
- - Make complaint reporting simple and fast
12
- - Support day-to-day civic domains and emergency domains
13
- - Route complaints to the correct department with priority
14
- - Provide transparent status tracking to citizens
15
- - Give officials a fast dashboard for triage, assignment, and monitoring
16
- - Keep the system scalable, low-latency, and future-ready
17
-
18
- ## Main domains
19
-
20
- 1. Roads and Transportation
21
- 2. Sanitation and Waste Management
22
- 3. Water Supply, Sewerage, and Drainage
23
- 4. Street Lighting and Electrical Infrastructure
24
- 5. Public Infrastructure and Amenities
25
- 6. Environment and Public Health
26
-
27
- ## Emergency domains
28
-
29
- 1. Fire Emergencies
30
- 2. Flood and Water Disaster
31
- 3. Structural and Infrastructure Hazard
32
- 4. Electrical Hazard
33
- 5. Disaster and Rescue
34
-
35
- ## Complaint lifecycle
36
-
37
- 1. Draft
38
- 2. Submitted
39
- 3. Validated
40
- 4. Classified
41
- 5. Prioritized
42
- 6. Assigned
43
- 7. Accepted
44
- 8. In Progress
45
- 9. Resolved
46
- 10. Citizen Verified
47
- 11. Closed
48
-
49
- Additional states:
50
-
51
- - Escalated
52
- - Reopened
53
- - Duplicate
54
- - Rejected
55
- - On Hold
56
-
57
- ## Proposed stack
58
-
59
- ### Frontend
60
-
61
- - Next.js
62
- - TypeScript
63
- - Tailwind CSS
64
- - shadcn/ui
65
- - Framer Motion
66
- - Leaflet with OpenStreetMap
67
-
68
- ### Platform
69
-
70
- - Supabase Auth
71
- - Supabase Postgres
72
- - PostGIS
73
- - Supabase Storage
74
- - Supabase Realtime
75
- - Supabase Edge Functions
76
-
77
- ### AI
78
-
79
- - Whisper for speech-to-text
80
- - DistilBERT or similar lightweight text classifier
81
- - YOLOv8n or MobileNet for image classification
82
-
83
- ## App areas
84
-
85
- - Citizen portal
86
- - Department dashboard
87
- - Field officer workspace
88
- - Super admin configuration
89
- - Analytics and SLA dashboard
90
-
91
- ## Folder plan
92
-
93
- - `app/` Next.js App Router pages
94
- - `components/` shared UI sections
95
- - `lib/` config, types, and helpers
96
- - `data/` static seed content used by the frontend
97
- - `docs/` architecture, lifecycle, and page planning
98
-
99
- ## Planning docs
100
-
101
- - `docs/PROJECT_BLUEPRINT.md`
102
- - `docs/FRONTEND_PLAN.md`
103
- - `docs/PAGE_MAP.md`
104
- - `docs/ROLES_AND_PERMISSIONS.md`
105
- - `docs/COMPLAINT_LIFECYCLE.md`
106
- - `docs/DOMAIN_ROUTING_MAP.md`
107
- - `docs/PRIORITY_SCORING.md`
108
- - `docs/DATABASE_SCHEMA.md`
109
- - `docs/BACKEND_STRUCTURE.md`
110
- - `docs/LOCAL_DATABASE_SETUP.md`
111
-
112
- ## Next build steps
113
-
114
- 1. Finalize data model and routing rules
115
- 2. Build the design system and page structure
116
- 3. Implement citizen report flow
117
- 4. Add admin dashboard and complaint lifecycle updates
118
- 5. Connect Supabase auth, database, storage, and realtime
119
- 6. Add AI-assisted classification after the MVP workflow is stable
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
civic-platform/ai/README.md DELETED
@@ -1,15 +0,0 @@
1
- # CivicPulse AI Workspace
2
-
3
- This folder is reserved for model training assets, dataset schemas, experiments, and evaluation results.
4
-
5
- Recommended subfolders:
6
-
7
- - `datasets/text`
8
- - `datasets/vision`
9
- - `datasets/audio`
10
- - `evals`
11
- - `notebooks`
12
- - `schemas`
13
-
14
- The live product currently uses an assistive heuristic AI layer in the application code. This workspace is where model-backed training and evaluation can be added next.
15
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
civic-platform/ai/schemas/civic-image-record.example.json DELETED
@@ -1,11 +0,0 @@
1
- {
2
- "image": "path-or-huggingface-image-object",
3
- "label": "Roads and Transportation",
4
- "metadata": {
5
- "complaintCode": "CP-2026-000001",
6
- "source": "citizen_upload",
7
- "city": "Hyderabad",
8
- "latitude": 17.385,
9
- "longitude": 78.4867
10
- }
11
- }
 
 
 
 
 
 
 
 
 
 
 
 
civic-platform/ai/schemas/complaint-text-record.example.json DELETED
@@ -1,12 +0,0 @@
1
- {
2
- "title": "Streetlight not working near school gate",
3
- "description": "Two streetlights are off near the main gate and the road is dark at night.",
4
- "address_line": "School Road",
5
- "landmark": "Near Government High School",
6
- "city_name": "Sample City",
7
- "state_name": "Sample State",
8
- "domain": "Street Lighting and Electrical Infrastructure",
9
- "sub_problem": "Broken streetlight",
10
- "priority": "P2",
11
- "is_emergency": false
12
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
civic-platform/ai/training/README.md DELETED
@@ -1,73 +0,0 @@
1
- # CivicPulse Vision Training Plan
2
-
3
- The current app has a safe baseline for image evidence:
4
-
5
- - It extracts GPS metadata from JPEG photos when available.
6
- - It uses address search, GPS, or map pinning when photo GPS metadata is missing.
7
- - It passes lightweight visual signals into the AI assistant when filenames contain civic issue hints.
8
-
9
- For accurate image classification, train a real model on labeled civic images. A perfect model is not realistic, but a good production model is achievable with enough labeled examples and evaluation.
10
-
11
- ## Recommended Labels
12
-
13
- Use the same project domains as labels:
14
-
15
- - Roads and Transportation
16
- - Sanitation and Waste Management
17
- - Water Supply, Sewerage, and Drainage
18
- - Street Lighting and Electrical Infrastructure
19
- - Public Infrastructure and Amenities
20
- - Environment and Public Health
21
- - Fire Emergencies
22
- - Flood and Water Disaster
23
- - Structural and Infrastructure Hazard
24
- - Electrical Hazard
25
- - Disaster and Rescue
26
-
27
- ## Dataset Format
28
-
29
- For Hugging Face image classification, publish a dataset with:
30
-
31
- - `image`: image column
32
- - `label`: one of the labels above
33
-
34
- Example record:
35
-
36
- [civic-image-record.example.json](/C:/Users/Praveen/Documents/New%20project/civic-platform/ai/schemas/civic-image-record.example.json)
37
-
38
- ## Training Recommendation
39
-
40
- Start with image classification:
41
-
42
- - Model: `timm/mobilenetv3_small_100.lamb_in1k` for fast iteration
43
- - Upgrade: `timm/resnet50.a1_in1k` or `timm/vit_base_patch16_dinov3.lvd1689m` for better accuracy
44
- - Target metric: validation accuracy and per-class recall
45
- - Minimum data target: 300 to 500 images per label for a useful first model
46
- - Better data target: 1,000+ images per label with different lighting, angles, weather, and phones
47
-
48
- For localization such as detecting a pothole box inside the image, move to object detection later with D-FINE or RT-DETR.
49
-
50
- ## Deployment Path
51
-
52
- 1. Collect and label civic images from real reports.
53
- 2. Upload the dataset to Hugging Face Hub.
54
- 3. Validate the dataset format before training.
55
- 4. Fine-tune an image classifier using Hugging Face Jobs.
56
- 5. Push the trained model to the Hub.
57
- 6. Add a backend inference endpoint that calls the trained model and returns:
58
- - predicted domain
59
- - confidence
60
- - top 3 alternatives
61
- - model version
62
- 7. Keep the operator override flow because image models can be wrong.
63
-
64
- ## Important Note
65
-
66
- Do not rely only on image classification. The best production result should combine:
67
-
68
- - image prediction
69
- - text/voice description
70
- - issue domain chosen by the citizen
71
- - geo-location and ward
72
- - nearby duplicate complaints
73
- - operator override
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
civic-platform/ai/training/civic-image-labels.md DELETED
@@ -1,19 +0,0 @@
1
- # CivicPulse Image Labels
2
-
3
- Use these labels for the first image classification dataset.
4
-
5
- | Label | Example visual cases |
6
- |---|---|
7
- | Roads and Transportation | potholes, broken roads, road blockage, damaged footpath, damaged divider |
8
- | Sanitation and Waste Management | garbage piles, overflowing bins, illegal dumping, dirty streets |
9
- | Water Supply, Sewerage, and Drainage | water leakage, sewage overflow, clogged drains, waterlogging |
10
- | Street Lighting and Electrical Infrastructure | broken streetlights, damaged poles, exposed wires, transformer issues |
11
- | Public Infrastructure and Amenities | open manholes, broken bus stops, damaged public toilets, park damage |
12
- | Environment and Public Health | fallen trees, stagnant water, smoke, dead animals, mosquito breeding areas |
13
- | Fire Emergencies | visible fire, heavy smoke, burning debris |
14
- | Flood and Water Disaster | floodwater, severe rain overflow, storm damage |
15
- | Structural and Infrastructure Hazard | wall cracks, building collapse signs, bridge damage |
16
- | Electrical Hazard | live wires, transformer blast, major short circuit evidence |
17
- | Disaster and Rescue | landslide, storm collapse, road-blocking tree fall, rescue hazard |
18
-
19
- Labeling rule: if one image contains multiple issues, choose the most urgent visible issue and store secondary labels in metadata for future multi-label training.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
civic-platform/app/api/session/login/route.ts DELETED
@@ -1,77 +0,0 @@
1
- import { cookies } from "next/headers";
2
- import { NextRequest, NextResponse } from "next/server";
3
- import bcrypt from "bcryptjs";
4
- import pool from "@/lib/db";
5
- import { encodeSession, SESSION_COOKIE_NAME, type SessionUser } from "@/lib/session";
6
-
7
- type UserRow = SessionUser & { passwordHash: string | null };
8
-
9
- export async function POST(request: NextRequest) {
10
- try {
11
- const body = (await request.json()) as { email?: string; password?: string };
12
- const email = body.email?.trim().toLowerCase() ?? "";
13
- const password = body.password ?? "";
14
-
15
- if (!email) {
16
- return NextResponse.json({ error: "Email is required" }, { status: 400 });
17
- }
18
- if (!password) {
19
- return NextResponse.json({ error: "Password is required" }, { status: 400 });
20
- }
21
-
22
- // Query users table joined with roles
23
- const result = await pool.query<UserRow>(
24
- `SELECT
25
- u.id,
26
- u.full_name AS "fullName",
27
- u.email,
28
- r.name AS role,
29
- u.department_id AS "departmentId",
30
- u.ward_id AS "wardId",
31
- u.password_hash AS "passwordHash"
32
- FROM users u
33
- INNER JOIN roles r ON r.id = u.role_id
34
- WHERE lower(u.email) = $1
35
- AND u.is_active = TRUE
36
- LIMIT 1`,
37
- [email],
38
- );
39
-
40
- const user = result.rows[0] ?? null;
41
-
42
- if (!user) {
43
- return NextResponse.json({ error: "Invalid email or password" }, { status: 401 });
44
- }
45
-
46
- // Support both real bcrypt hashes AND the dev-placeholder-hash used in seed data
47
- let passwordValid = false;
48
- if (user.passwordHash === "dev-placeholder-hash" && password === "civicpulse123") {
49
- passwordValid = true;
50
- } else if (user.passwordHash) {
51
- passwordValid = await bcrypt.compare(password, user.passwordHash);
52
- }
53
-
54
- if (!passwordValid) {
55
- return NextResponse.json({ error: "Invalid email or password" }, { status: 401 });
56
- }
57
-
58
- const { passwordHash: _ph, ...sessionUser } = user;
59
- const cookieStore = await cookies();
60
-
61
- cookieStore.set(SESSION_COOKIE_NAME, encodeSession(sessionUser), {
62
- httpOnly: true,
63
- sameSite: "lax",
64
- path: "/",
65
- secure: process.env.NODE_ENV === "production",
66
- maxAge: 60 * 60 * 12,
67
- });
68
-
69
- return NextResponse.json({ data: sessionUser });
70
- } catch (error) {
71
- console.error("[login] Error:", error);
72
- return NextResponse.json(
73
- { error: error instanceof Error ? error.message : "Login failed" },
74
- { status: 500 },
75
- );
76
- }
77
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
civic-platform/app/api/session/logout/route.ts DELETED
@@ -1,10 +0,0 @@
1
- import { cookies } from "next/headers";
2
- import { NextResponse } from "next/server";
3
- import { SESSION_COOKIE_NAME } from "@/lib/session";
4
-
5
- export async function POST() {
6
- const cookieStore = await cookies();
7
- cookieStore.delete(SESSION_COOKIE_NAME);
8
-
9
- return NextResponse.json({ data: { ok: true } });
10
- }
 
 
 
 
 
 
 
 
 
 
 
civic-platform/app/api/session/register/route.ts DELETED
@@ -1,83 +0,0 @@
1
- import { cookies } from "next/headers";
2
- import { NextRequest, NextResponse } from "next/server";
3
- import bcrypt from "bcryptjs";
4
- import pool from "@/lib/db";
5
- import { encodeSession, SESSION_COOKIE_NAME, type SessionUser } from "@/lib/session";
6
-
7
- type UserRow = SessionUser & { passwordHash: string | null };
8
-
9
- export async function POST(request: NextRequest) {
10
- try {
11
- const body = (await request.json()) as {
12
- fullName?: string;
13
- email?: string;
14
- phone?: string;
15
- password?: string;
16
- };
17
-
18
- const fullName = body.fullName?.trim() ?? "";
19
- const email = body.email?.trim().toLowerCase() ?? "";
20
- const phone = body.phone?.trim() || null;
21
- const password = body.password ?? "";
22
-
23
- // Validation
24
- if (!fullName) {
25
- return NextResponse.json({ error: "Full name is required" }, { status: 400 });
26
- }
27
- if (!email) {
28
- return NextResponse.json({ error: "Email is required" }, { status: 400 });
29
- }
30
- if (!password || password.length < 8) {
31
- return NextResponse.json({ error: "Password must be at least 8 characters" }, { status: 400 });
32
- }
33
-
34
- // Check if email already exists
35
- const existing = await pool.query("SELECT id FROM users WHERE lower(email) = $1", [email]);
36
- if (existing.rows.length > 0) {
37
- return NextResponse.json({ error: "An account already exists for this email" }, { status: 409 });
38
- }
39
-
40
- // Hash password
41
- const passwordHash = await bcrypt.hash(password, 12);
42
-
43
- // Look up citizen role ID
44
- const roleResult = await pool.query("SELECT id FROM roles WHERE name = 'citizen' LIMIT 1");
45
- const roleId = roleResult.rows[0]?.id;
46
- if (!roleId) {
47
- return NextResponse.json({ error: "Citizen role is not configured in the database" }, { status: 500 });
48
- }
49
-
50
- // Insert new user
51
- const insertResult = await pool.query<UserRow>(
52
- `INSERT INTO users (full_name, email, phone, password_hash, role_id)
53
- VALUES ($1, $2, $3, $4, $5)
54
- RETURNING
55
- id,
56
- full_name AS "fullName",
57
- email,
58
- 'citizen' AS role,
59
- department_id AS "departmentId",
60
- ward_id AS "wardId",
61
- password_hash AS "passwordHash"`,
62
- [fullName, email, phone, passwordHash, roleId],
63
- );
64
-
65
- const user = insertResult.rows[0];
66
- const { passwordHash: _ph, ...sessionUser } = user;
67
-
68
- const cookieStore = await cookies();
69
- cookieStore.set(SESSION_COOKIE_NAME, encodeSession(sessionUser), {
70
- httpOnly: true,
71
- sameSite: "lax",
72
- path: "/",
73
- secure: process.env.NODE_ENV === "production",
74
- maxAge: 60 * 60 * 12,
75
- });
76
-
77
- return NextResponse.json({ data: sessionUser }, { status: 201 });
78
- } catch (error) {
79
- console.error("[register] Error:", error);
80
- const message = error instanceof Error ? error.message : "Registration failed";
81
- return NextResponse.json({ error: message }, { status: 500 });
82
- }
83
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
civic-platform/app/complaints/[id]/page.tsx DELETED
@@ -1,264 +0,0 @@
1
- import Link from "next/link";
2
- import { AlertTriangle, Clock3, MapPinned, ShieldCheck, Waves } from "lucide-react";
3
- import { AppShell } from "@/components/app-shell";
4
- import { CitizenFollowUpPanel } from "@/components/citizen-follow-up-panel";
5
- import { ComplaintMediaGallery } from "@/components/complaint-media-gallery";
6
- import { SectionHeader } from "@/components/section-header";
7
- import { requireRole } from "@/lib/auth";
8
- import {
9
- getComplaintDetail,
10
- getComplaintFeedback,
11
- getComplaintHistory,
12
- getComplaintMedia,
13
- type ComplaintFeedbackItem,
14
- type ComplaintMediaItem,
15
- type ComplaintStatusHistoryItem,
16
- } from "@/lib/api";
17
-
18
- type ComplaintDetailPageProps = {
19
- params: Promise<{
20
- id: string;
21
- }>;
22
- };
23
-
24
- function formatStatus(status: string) {
25
- return status
26
- .split("_")
27
- .map((part) => part.charAt(0).toUpperCase() + part.slice(1))
28
- .join(" ");
29
- }
30
-
31
- function formatPriority(priority: string) {
32
- switch (priority) {
33
- case "P1":
34
- return "P1 Critical";
35
- case "P2":
36
- return "P2 High";
37
- case "P3":
38
- return "P3 Medium";
39
- default:
40
- return "P4 Low";
41
- }
42
- }
43
-
44
- function formatDateTime(value: string) {
45
- return new Date(value).toLocaleString();
46
- }
47
-
48
- function timelineTone(status: string) {
49
- switch (status) {
50
- case "submitted":
51
- return "bg-civic-primary";
52
- case "validated":
53
- return "bg-civic-secondary";
54
- case "classified":
55
- case "prioritized":
56
- return "bg-amber-500";
57
- case "assigned":
58
- return "bg-slate-800";
59
- case "in_progress":
60
- return "bg-emerald-600";
61
- case "resolved":
62
- case "closed":
63
- return "bg-emerald-700";
64
- case "reopened":
65
- case "escalated":
66
- return "bg-civic-danger";
67
- default:
68
- return "bg-slate-300";
69
- }
70
- }
71
-
72
- export default async function ComplaintDetailPage({ params }: ComplaintDetailPageProps) {
73
- await requireRole(["citizen"]);
74
- const { id } = await params;
75
-
76
- let complaintStatus = "In Progress";
77
- let complaintPriority = "P2 High";
78
- let complaintLocation = "Market Road, Ward 12";
79
- let complaintDomain = "Water Supply, Sewerage, and Drainage";
80
- let complaintDescription = "Overflow affecting entry and traffic movement near the market.";
81
- let complaintCode = "CP-2026-00124";
82
- let complaintDepartment = "Water and Sewerage";
83
- let media: ComplaintMediaItem[] = [];
84
- let feedback: ComplaintFeedbackItem[] = [];
85
- let rawStatus = "in_progress";
86
- let citizenId = "00000000-0000-0000-0000-000000000001";
87
- let timeline: Array<{ title: string; time: string; note: string; tone: string }> = [
88
- {
89
- title: "Submitted",
90
- time: "Pending",
91
- note: "Complaint created by citizen with image, description, and geo-tagged location.",
92
- tone: "bg-civic-primary",
93
- },
94
- ];
95
-
96
- try {
97
- const [complaint, history, mediaItems, feedbackItems] = await Promise.all([
98
- getComplaintDetail(id),
99
- getComplaintHistory(id),
100
- getComplaintMedia(id),
101
- getComplaintFeedback(id),
102
- ]);
103
-
104
- rawStatus = complaint.status;
105
- complaintStatus = formatStatus(complaint.status);
106
- complaintPriority = formatPriority(complaint.priorityLevel);
107
- complaintLocation = complaint.addressLine ?? complaint.landmark ?? "Location pending";
108
- complaintDomain = complaint.domainName ?? "Unclassified";
109
- complaintDescription = complaint.description ?? complaintDescription;
110
- complaintCode = complaint.complaintCode;
111
- complaintDepartment = complaint.departmentName ?? complaintDepartment;
112
- citizenId = complaint.citizenId;
113
- media = mediaItems;
114
- feedback = feedbackItems;
115
-
116
- if (history.length > 0) {
117
- timeline = history.map((entry: ComplaintStatusHistoryItem) => ({
118
- title: formatStatus(entry.newStatus),
119
- time: formatDateTime(entry.createdAt),
120
- note: entry.changeReason ?? `Complaint moved to ${formatStatus(entry.newStatus)}.`,
121
- tone: timelineTone(entry.newStatus),
122
- }));
123
- }
124
- } catch {
125
- // Keep fallback values for design flow when backend is unavailable.
126
- }
127
-
128
- return (
129
- <AppShell>
130
- <section className="grid gap-6 rounded-[2rem] bg-civic-primary px-6 py-8 text-white shadow-civic lg:grid-cols-[1.08fr_0.92fr] lg:px-8">
131
- <div className="space-y-4">
132
- <p className="text-sm font-semibold uppercase tracking-[0.22em] text-white/75">Complaint detail</p>
133
- <h1 className="text-4xl font-semibold tracking-tight">Track your complaint in one place.</h1>
134
- <p className="max-w-2xl text-sm leading-7 text-white/80">
135
- Check the current status, department ownership, attached evidence, and any updates from the city team.
136
- </p>
137
- </div>
138
-
139
- <div className="grid gap-4 sm:grid-cols-2">
140
- {[
141
- { label: "Complaint ID", value: complaintCode },
142
- { label: "Current status", value: complaintStatus },
143
- { label: "Priority", value: complaintPriority },
144
- { label: "Assigned department", value: complaintDepartment },
145
- ].map((card) => (
146
- <div key={card.label} className="rounded-3xl border border-white/15 bg-white/10 p-5 backdrop-blur">
147
- <p className="text-sm font-semibold uppercase tracking-[0.16em] text-white/75">{card.label}</p>
148
- <p className="mt-3 text-xl font-semibold text-white">{card.value}</p>
149
- </div>
150
- ))}
151
- </div>
152
- </section>
153
-
154
- <section className="grid gap-6 lg:grid-cols-[1.1fr_0.9fr]">
155
- <section className="rounded-[2rem] border border-slate-200 bg-white p-6 shadow-sm sm:p-8">
156
- <div className="flex flex-wrap items-start justify-between gap-4">
157
- <div>
158
- <p className="text-sm font-semibold uppercase tracking-[0.22em] text-civic-secondary">Timeline</p>
159
- <h2 className="mt-2 text-2xl font-semibold text-civic-text">Status history</h2>
160
- </div>
161
- <div className="rounded-full bg-amber-50 px-4 py-2 text-sm font-semibold text-amber-800">
162
- Current state: {complaintStatus}
163
- </div>
164
- </div>
165
-
166
- <div className="mt-8 space-y-6">
167
- {timeline.map((entry, index) => (
168
- <div key={`${entry.title}-${entry.time}-${index}`} className="grid gap-4 md:grid-cols-[auto_1fr] md:gap-5">
169
- <div className="flex items-start gap-4 md:flex-col md:items-center">
170
- <div className={["mt-1 h-4 w-4 rounded-full", entry.tone].join(" ")} />
171
- {index !== timeline.length - 1 ? <div className="hidden h-16 w-px bg-slate-200 md:block" /> : null}
172
- </div>
173
- <div className="rounded-3xl bg-slate-50 p-5">
174
- <div className="flex flex-wrap items-center justify-between gap-3">
175
- <h3 className="text-lg font-semibold text-civic-text">{entry.title}</h3>
176
- <div className="inline-flex items-center gap-2 rounded-full bg-white px-3 py-2 text-xs font-semibold text-slate-700">
177
- <Clock3 className="h-4 w-4" />
178
- {entry.time}
179
- </div>
180
- </div>
181
- <p className="mt-3 text-sm leading-6 text-civic-muted">{entry.note}</p>
182
- </div>
183
- </div>
184
- ))}
185
- </div>
186
- </section>
187
-
188
- <div className="space-y-6">
189
- <section className="rounded-[2rem] border border-slate-200 bg-white p-6 shadow-sm">
190
- <SectionHeader eyebrow="Location" icon={MapPinned} title="Complaint context" />
191
-
192
- <div className="mt-5 space-y-3">
193
- {[complaintLocation, `Domain: ${complaintDomain}`, complaintDescription].map((item) => (
194
- <div key={item} className="rounded-2xl bg-slate-50 px-4 py-3 text-sm leading-6 text-civic-text">
195
- {item}
196
- </div>
197
- ))}
198
- </div>
199
- </section>
200
-
201
- <ComplaintMediaGallery
202
- items={media}
203
- title="Photos, video, and audio"
204
- emptyLabel="No evidence has been added to this complaint yet."
205
- />
206
-
207
- <CitizenFollowUpPanel complaintId={id} citizenId={citizenId} status={rawStatus} feedback={feedback} />
208
- </div>
209
- </section>
210
-
211
- <section className="grid gap-6 lg:grid-cols-[0.95fr_1.05fr]">
212
- <section className="rounded-[2rem] border border-emerald-200 bg-emerald-50 p-6 shadow-sm">
213
- <div className="flex items-start gap-3">
214
- <ShieldCheck className="mt-0.5 h-5 w-5 text-emerald-700" />
215
- <div>
216
- <h2 className="text-lg font-semibold text-emerald-950">What you can see here</h2>
217
- <p className="mt-2 text-sm leading-6 text-emerald-900/85">
218
- This page shows your complaint progress and public-safe updates without exposing internal staff notes.
219
- </p>
220
- </div>
221
- </div>
222
- </section>
223
-
224
- <section className="rounded-[2rem] border border-amber-200 bg-amber-50 p-6 shadow-sm">
225
- <div className="flex items-start gap-3">
226
- <AlertTriangle className="mt-0.5 h-5 w-5 text-amber-700" />
227
- <div>
228
- <h2 className="text-lg font-semibold text-amber-950">Need more help?</h2>
229
- <p className="mt-2 text-sm leading-6 text-amber-900/85">
230
- If the issue is marked resolved but still exists, use the reopen option below so the team can review it again.
231
- </p>
232
- </div>
233
- </div>
234
- </section>
235
- </section>
236
-
237
- <section className="flex flex-wrap items-center justify-between gap-4 rounded-[2rem] border border-slate-200 bg-white p-6 shadow-sm">
238
- <div className="space-y-2">
239
- <p className="text-sm font-semibold uppercase tracking-[0.22em] text-civic-secondary">Quick actions</p>
240
- <h2 className="text-2xl font-semibold text-civic-text">Keep track of this report or return to your list.</h2>
241
- <p className="max-w-2xl text-sm leading-6 text-civic-muted">
242
- You can go back to all complaints, stay on this page for updates, or report another issue if needed.
243
- </p>
244
- </div>
245
-
246
- <div className="flex flex-wrap gap-3">
247
- <Link
248
- className="inline-flex rounded-full border border-slate-200 px-5 py-3 text-sm font-semibold text-slate-700 transition hover:border-civic-primary hover:text-civic-primary"
249
- href="/complaints"
250
- >
251
- Back to complaints
252
- </Link>
253
- <Link
254
- className="inline-flex items-center gap-2 rounded-full bg-slate-900 px-5 py-3 text-sm font-semibold text-white transition hover:-translate-y-0.5"
255
- href="/report"
256
- >
257
- Report another issue
258
- <Waves className="h-4 w-4" />
259
- </Link>
260
- </div>
261
- </section>
262
- </AppShell>
263
- );
264
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
civic-platform/app/complaints/page.tsx DELETED
@@ -1,217 +0,0 @@
1
- import Link from "next/link";
2
- import { Clock3, MapPinned, Search } from "lucide-react";
3
- import { AppShell } from "@/components/app-shell";
4
- import { PriorityBadge } from "@/components/priority-badge";
5
- import { StatusBadge } from "@/components/status-badge";
6
- import { getCitizenComplaints } from "@/lib/api";
7
- import { requireRole } from "@/lib/auth";
8
-
9
- const complaintStats = [
10
- { label: "Active", value: "04" },
11
- { label: "Resolved", value: "09" },
12
- { label: "Reopened", value: "01" },
13
- ];
14
-
15
- type ComplaintCard = {
16
- id: string;
17
- complaintCode?: string;
18
- title: string;
19
- domain: string;
20
- status: string;
21
- priority: string;
22
- location: string;
23
- updatedAt: string;
24
- };
25
-
26
- const fallbackComplaints: ComplaintCard[] = [
27
- {
28
- id: "CP-2026-00124",
29
- complaintCode: "CP-2026-00124",
30
- title: "Sewage overflow near market entrance",
31
- domain: "Water Supply, Sewerage, and Drainage",
32
- status: "In Progress",
33
- priority: "P2 High",
34
- location: "Market Road, Ward 12",
35
- updatedAt: "Updated 1 hour ago",
36
- },
37
- {
38
- id: "CP-2026-00108",
39
- complaintCode: "CP-2026-00108",
40
- title: "Streetlight not working on school road",
41
- domain: "Street Lighting and Electrical Infrastructure",
42
- status: "Assigned",
43
- priority: "P3 Medium",
44
- location: "School Street, Ward 8",
45
- updatedAt: "Updated today",
46
- },
47
- {
48
- id: "CP-2026-00097",
49
- complaintCode: "CP-2026-00097",
50
- title: "Overflowing garbage bin near bus stop",
51
- domain: "Sanitation and Waste Management",
52
- status: "Resolved",
53
- priority: "P3 Medium",
54
- location: "Central Bus Stop, Ward 3",
55
- updatedAt: "Resolved yesterday",
56
- },
57
- {
58
- id: "CP-2026-00091",
59
- complaintCode: "CP-2026-00091",
60
- title: "Open manhole beside community hall",
61
- domain: "Public Infrastructure and Amenities",
62
- status: "Reopened",
63
- priority: "P1 Critical",
64
- location: "Community Hall Lane, Ward 5",
65
- updatedAt: "Reopened 3 hours ago",
66
- },
67
- ];
68
-
69
- const filters = ["All", "Active", "Resolved", "Reopened", "Emergency"];
70
-
71
- function formatStatus(status: string) {
72
- return status
73
- .split("_")
74
- .map((part) => part.charAt(0).toUpperCase() + part.slice(1))
75
- .join(" ");
76
- }
77
-
78
- function formatPriority(priority: string) {
79
- switch (priority) {
80
- case "P1":
81
- return "P1 Critical";
82
- case "P2":
83
- return "P2 High";
84
- case "P3":
85
- return "P3 Medium";
86
- default:
87
- return "P4 Low";
88
- }
89
- }
90
-
91
- export default async function ComplaintsPage() {
92
- const user = await requireRole(["citizen"]);
93
- let complaints: ComplaintCard[] = fallbackComplaints;
94
-
95
- try {
96
- const apiComplaints = await getCitizenComplaints(user.id);
97
-
98
- if (apiComplaints.length > 0) {
99
- complaints = apiComplaints.map((complaint) => ({
100
- id: complaint.id,
101
- title: complaint.title,
102
- domain: complaint.domainName ?? "Unclassified",
103
- status: formatStatus(complaint.status),
104
- priority: formatPriority(complaint.priorityLevel),
105
- location: complaint.departmentName ?? "Department pending",
106
- updatedAt: new Date(complaint.submittedAt).toLocaleString(),
107
- complaintCode: complaint.complaintCode,
108
- }));
109
- }
110
- } catch {
111
- complaints = fallbackComplaints;
112
- }
113
-
114
- return (
115
- <AppShell>
116
- <section className="relative overflow-hidden rounded-[2.5rem] bg-gradient-to-br from-[#0F172A] via-civic-primary to-[#1E3A8A] px-8 py-12 text-white shadow-civic lg:px-12 lg:py-16">
117
- <div className="absolute -left-32 -top-32 h-96 w-96 rounded-full bg-white/10 blur-[100px]" />
118
-
119
- <div className="relative flex flex-wrap items-center justify-between gap-6">
120
- <div className="space-y-4">
121
- <h1 className="text-4xl font-extrabold tracking-tight sm:text-5xl">My Reports</h1>
122
- <p className="text-lg text-slate-300">Track the status of the issues you've reported in your neighborhood.</p>
123
- </div>
124
- <Link
125
- className="group inline-flex items-center gap-2 rounded-full bg-white px-7 py-4 text-base font-bold text-civic-primary shadow-xl transition-all hover:-translate-y-1 hover:shadow-2xl"
126
- href="/report"
127
- >
128
- Report a new issue
129
- </Link>
130
- </div>
131
-
132
- <div className="relative mt-10 grid gap-5 sm:grid-cols-3">
133
- {complaintStats.map((stat) => (
134
- <div key={stat.label} className="rounded-[2rem] border border-white/15 bg-white/10 p-6 backdrop-blur-xl">
135
- <p className="text-xs font-bold uppercase tracking-[0.2em] text-white/60">{stat.label}</p>
136
- <p className="mt-4 text-4xl font-extrabold text-white">{stat.value}</p>
137
- </div>
138
- ))}
139
- </div>
140
- </section>
141
-
142
- <section className="space-y-6">
143
- <div className="rounded-[2rem] border border-slate-200 bg-white p-5 shadow-sm">
144
- <div className="flex flex-col gap-4 lg:flex-row lg:items-center lg:justify-between">
145
- <div className="flex flex-wrap gap-2">
146
- {filters.map((filter) => (
147
- <button
148
- key={filter}
149
- type="button"
150
- className={[
151
- "rounded-full px-4 py-2 text-sm font-semibold transition",
152
- filter === "All"
153
- ? "bg-civic-primary text-white"
154
- : "bg-slate-100 text-slate-700 hover:bg-slate-200",
155
- ].join(" ")}
156
- >
157
- {filter}
158
- </button>
159
- ))}
160
- </div>
161
-
162
- <div className="flex items-center gap-3 rounded-full border border-slate-200 bg-slate-50 px-4 py-3 text-sm text-civic-muted lg:min-w-80">
163
- <Search className="h-4 w-4" />
164
- Search by complaint ID or area
165
- </div>
166
- </div>
167
- </div>
168
-
169
- <section className="rounded-[2.5rem] border border-slate-200 bg-white/70 p-8 shadow-glass backdrop-blur-xl">
170
- <h2 className="text-2xl font-extrabold text-slate-900">Recent Activity</h2>
171
-
172
- <div className="mt-8 space-y-5">
173
- {complaints.map((complaint) => (
174
- <article
175
- key={complaint.id}
176
- className="group relative overflow-hidden rounded-[2rem] border border-slate-200 bg-white p-6 shadow-sm transition-all hover:-translate-y-1 hover:border-slate-300 hover:shadow-xl"
177
- >
178
- <div className="flex flex-wrap items-start justify-between gap-4">
179
- <div className="space-y-3">
180
- <div className="flex flex-wrap items-center gap-3">
181
- <span className="rounded-full bg-slate-100 px-3.5 py-1 text-xs font-bold text-slate-700">
182
- {complaint.complaintCode ?? complaint.id}
183
- </span>
184
- <StatusBadge status={complaint.status} />
185
- <PriorityBadge priority={complaint.priority} />
186
- </div>
187
- <h3 className="text-2xl font-bold text-slate-900">{complaint.title}</h3>
188
- <p className="text-sm font-medium text-slate-500">{complaint.domain}</p>
189
- </div>
190
-
191
- <div className="flex items-center gap-2 rounded-full border border-slate-100 bg-slate-50 px-4 py-2 text-xs font-bold text-slate-500">
192
- <Clock3 className="h-4 w-4" />
193
- {complaint.updatedAt}
194
- </div>
195
- </div>
196
-
197
- <div className="mt-6 flex items-center gap-2 text-sm font-medium text-slate-500">
198
- <MapPinned className="h-4 w-4" />
199
- {complaint.location}
200
- </div>
201
-
202
- <div className="mt-6 flex flex-wrap gap-3">
203
- <Link
204
- className="inline-flex items-center gap-2 rounded-full bg-slate-900 px-6 py-3 text-sm font-bold text-white shadow-md transition hover:bg-slate-800 hover:shadow-lg"
205
- href={`/complaints/${complaint.id}`}
206
- >
207
- View Details
208
- </Link>
209
- </div>
210
- </article>
211
- ))}
212
- </div>
213
- </section>
214
- </section>
215
- </AppShell>
216
- );
217
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
civic-platform/app/dashboard/analytics/page.tsx DELETED
@@ -1,184 +0,0 @@
1
- import Link from "next/link";
2
- import { AppShell } from "@/components/app-shell";
3
- import { EmptyState } from "@/components/empty-state";
4
- import { LiveSyncBadge } from "@/components/live-sync-badge";
5
- import { requireRole } from "@/lib/auth";
6
- import { getAnalyticsExportUrl, getAnalyticsSummary, type AnalyticsSummary } from "@/lib/api";
7
- import { ArrowDownToLine, BarChart3, Clock3, Siren, TrendingUp } from "lucide-react";
8
-
9
- const fallbackAnalytics: AnalyticsSummary = {
10
- trends: [],
11
- domainBreakdown: [],
12
- departmentPerformance: [],
13
- kpis: {
14
- emergencyCount: 0,
15
- reopenedCount: 0,
16
- repeatComplaintRiskCount: 0,
17
- slaBreaches: 0,
18
- },
19
- };
20
-
21
- export default async function DashboardAnalyticsPage() {
22
- await requireRole(["department_operator", "municipal_admin"]);
23
-
24
- let analytics = fallbackAnalytics;
25
-
26
- try {
27
- analytics = await getAnalyticsSummary();
28
- } catch {
29
- analytics = fallbackAnalytics;
30
- }
31
-
32
- const maxTrend = Math.max(
33
- 1,
34
- ...analytics.trends.flatMap((item) => [item.reportedCount, item.resolvedCount]),
35
- );
36
-
37
- return (
38
- <AppShell>
39
- <section className="rounded-[2rem] bg-gradient-to-br from-slate-950 via-civic-primary to-[#0b6a8f] px-6 py-8 text-white shadow-civic lg:px-8">
40
- <div className="flex flex-wrap items-center justify-between gap-4">
41
- <div className="space-y-3">
42
- <p className="text-sm font-semibold uppercase tracking-[0.22em] text-white/70">Analytics</p>
43
- <h1 className="text-4xl font-semibold tracking-tight">Track performance, trends, and launch readiness.</h1>
44
- <p className="max-w-2xl text-sm leading-7 text-white/80">
45
- Use this view to monitor complaint trends, department throughput, SLA risk, and repeat civic failures.
46
- </p>
47
- </div>
48
- <div className="flex flex-wrap items-center gap-3">
49
- <LiveSyncBadge label="Analytics live refresh" />
50
- <a
51
- className="inline-flex items-center gap-2 rounded-full bg-white px-5 py-3 text-sm font-semibold text-civic-primary transition hover:-translate-y-0.5"
52
- href={getAnalyticsExportUrl()}
53
- target="_blank"
54
- rel="noreferrer"
55
- >
56
- <ArrowDownToLine className="h-4 w-4" />
57
- Export CSV
58
- </a>
59
- </div>
60
- </div>
61
- </section>
62
-
63
- <section className="grid gap-6 lg:grid-cols-4">
64
- {[
65
- { label: "Emergency", value: analytics.kpis.emergencyCount, icon: Siren },
66
- { label: "Reopened", value: analytics.kpis.reopenedCount, icon: TrendingUp },
67
- { label: "Repeat risk", value: analytics.kpis.repeatComplaintRiskCount, icon: BarChart3 },
68
- { label: "SLA breaches", value: analytics.kpis.slaBreaches, icon: Clock3 },
69
- ].map((item) => {
70
- const Icon = item.icon;
71
- return (
72
- <div key={item.label} className="rounded-[2rem] border border-slate-200 bg-white p-6 shadow-sm">
73
- <div className="flex h-11 w-11 items-center justify-center rounded-2xl bg-civic-secondary/15 text-civic-secondary">
74
- <Icon className="h-5 w-5" />
75
- </div>
76
- <p className="mt-5 text-sm font-semibold uppercase tracking-[0.16em] text-civic-muted">{item.label}</p>
77
- <p className="mt-2 text-3xl font-semibold text-civic-text">{item.value}</p>
78
- </div>
79
- );
80
- })}
81
- </section>
82
-
83
- <section className="grid gap-6 xl:grid-cols-[1.15fr_0.85fr]">
84
- <section className="rounded-[2rem] border border-slate-200 bg-white p-6 shadow-sm">
85
- <div>
86
- <p className="text-sm font-semibold uppercase tracking-[0.22em] text-civic-secondary">Trends</p>
87
- <h2 className="mt-2 text-2xl font-semibold text-civic-text">Reported vs resolved over the last 7 days</h2>
88
- </div>
89
- <div className="mt-6 space-y-4">
90
- {analytics.trends.length === 0 ? (
91
- <EmptyState icon={TrendingUp} title="No trend data yet" description="Trend data will appear once complaints start moving through the system." />
92
- ) : (
93
- analytics.trends.map((item) => (
94
- <div key={item.day} className="space-y-2">
95
- <div className="flex items-center justify-between gap-4 text-sm">
96
- <span className="font-semibold text-civic-text">{item.day}</span>
97
- <span className="text-civic-muted">
98
- reported {item.reportedCount} · resolved {item.resolvedCount}
99
- </span>
100
- </div>
101
- <div className="grid gap-2">
102
- <div className="h-3 overflow-hidden rounded-full bg-slate-100">
103
- <div
104
- className="h-full rounded-full bg-civic-primary"
105
- style={{ width: `${(item.reportedCount / maxTrend) * 100}%` }}
106
- />
107
- </div>
108
- <div className="h-3 overflow-hidden rounded-full bg-slate-100">
109
- <div
110
- className="h-full rounded-full bg-civic-success"
111
- style={{ width: `${(item.resolvedCount / maxTrend) * 100}%` }}
112
- />
113
- </div>
114
- </div>
115
- </div>
116
- ))
117
- )}
118
- </div>
119
- </section>
120
-
121
- <section className="rounded-[2rem] border border-slate-200 bg-white p-6 shadow-sm">
122
- <div>
123
- <p className="text-sm font-semibold uppercase tracking-[0.22em] text-civic-secondary">Domains</p>
124
- <h2 className="mt-2 text-2xl font-semibold text-civic-text">Complaint share by category</h2>
125
- </div>
126
- <div className="mt-6 space-y-3">
127
- {analytics.domainBreakdown.length === 0 ? (
128
- <EmptyState icon={BarChart3} title="No domain data yet" description="Domain distribution appears once complaints are recorded." />
129
- ) : (
130
- analytics.domainBreakdown.map((item) => (
131
- <div key={item.domainName} className="rounded-3xl bg-slate-50 p-4">
132
- <div className="flex items-center justify-between gap-4">
133
- <span className="text-sm font-semibold text-civic-text">{item.domainName}</span>
134
- <span className="rounded-full bg-white px-3 py-1 text-xs font-semibold text-slate-700">
135
- {item.complaintCount}
136
- </span>
137
- </div>
138
- </div>
139
- ))
140
- )}
141
- </div>
142
- </section>
143
- </section>
144
-
145
- <section className="rounded-[2rem] border border-slate-200 bg-white p-6 shadow-sm">
146
- <div className="flex flex-wrap items-center justify-between gap-4">
147
- <div>
148
- <p className="text-sm font-semibold uppercase tracking-[0.22em] text-civic-secondary">Departments</p>
149
- <h2 className="mt-2 text-2xl font-semibold text-civic-text">Performance and response time</h2>
150
- </div>
151
- <Link
152
- href="/dashboard"
153
- className="rounded-full border border-slate-200 px-4 py-2 text-sm font-semibold text-slate-700 transition hover:border-civic-primary hover:text-civic-primary"
154
- >
155
- Back to overview
156
- </Link>
157
- </div>
158
-
159
- <div className="mt-6 overflow-x-auto">
160
- <table className="min-w-full text-left text-sm">
161
- <thead>
162
- <tr className="border-b border-slate-200 text-civic-muted">
163
- <th className="px-3 py-3 font-semibold">Department</th>
164
- <th className="px-3 py-3 font-semibold">Pending</th>
165
- <th className="px-3 py-3 font-semibold">Resolved</th>
166
- <th className="px-3 py-3 font-semibold">Avg resolution (hrs)</th>
167
- </tr>
168
- </thead>
169
- <tbody>
170
- {analytics.departmentPerformance.map((item) => (
171
- <tr key={item.departmentName} className="border-b border-slate-100">
172
- <td className="px-3 py-4 font-semibold text-civic-text">{item.departmentName}</td>
173
- <td className="px-3 py-4 text-civic-muted">{item.pendingCount}</td>
174
- <td className="px-3 py-4 text-civic-muted">{item.resolvedCount}</td>
175
- <td className="px-3 py-4 text-civic-muted">{item.averageResolutionHours.toFixed(2)}</td>
176
- </tr>
177
- ))}
178
- </tbody>
179
- </table>
180
- </div>
181
- </section>
182
- </AppShell>
183
- );
184
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
civic-platform/app/dashboard/complaints/[id]/page.tsx DELETED
@@ -1,383 +0,0 @@
1
- import Link from "next/link";
2
- import {
3
- ArrowRight,
4
- CheckCircle2,
5
- ClipboardList,
6
- Clock3,
7
- MessageSquareText,
8
- ShieldAlert,
9
- UserCog,
10
- } from "lucide-react";
11
- import { AdminAssignAction } from "@/components/admin-assign-action";
12
- import { AdminAssignmentHistory } from "@/components/admin-assignment-history";
13
- import { AdminNotesPanel } from "@/components/admin-notes-panel";
14
- import { AppShell } from "@/components/app-shell";
15
- import { AdminStatusActions } from "@/components/admin-status-actions";
16
- import { ComplaintAiPanel } from "@/components/complaint-ai-panel";
17
- import { ComplaintMediaGallery } from "@/components/complaint-media-gallery";
18
- import { ResolutionProofUploader } from "@/components/resolution-proof-uploader";
19
- import { SectionHeader } from "@/components/section-header";
20
- import { requireRole } from "@/lib/auth";
21
- import {
22
- getComplaintDetail,
23
- getComplaintAssignments,
24
- getComplaintAiInsights,
25
- getComplaintHistory,
26
- getComplaintMedia,
27
- getComplaintNotes,
28
- type ComplaintAiInsights,
29
- type ComplaintAssignmentItem,
30
- type ComplaintMediaItem,
31
- type ComplaintNoteItem,
32
- type ComplaintStatusHistoryItem,
33
- } from "@/lib/api";
34
-
35
- const internalActions = [
36
- "Validate complaint",
37
- "Adjust domain or sub-problem if needed",
38
- "Assign responsible department",
39
- "Assign field officer later",
40
- "Change lifecycle state",
41
- "Escalate if public danger is immediate",
42
- ];
43
-
44
- type DashboardComplaintDetailPageProps = {
45
- params: Promise<{
46
- id: string;
47
- }>;
48
- };
49
-
50
- function formatStatus(status: string) {
51
- return status
52
- .split("_")
53
- .map((part) => part.charAt(0).toUpperCase() + part.slice(1))
54
- .join(" ");
55
- }
56
-
57
- function formatPriority(priority: string) {
58
- switch (priority) {
59
- case "P1":
60
- return "P1 Critical";
61
- case "P2":
62
- return "P2 High";
63
- case "P3":
64
- return "P3 Medium";
65
- default:
66
- return "P4 Low";
67
- }
68
- }
69
-
70
- function formatDateTime(value: string) {
71
- return new Date(value).toLocaleString();
72
- }
73
-
74
- function getPriorityRationale(priority: string, isEmergency: boolean) {
75
- if (isEmergency || priority === "P1 Critical") {
76
- return "Emergency or public-danger signals require immediate routing and shortest possible response time.";
77
- }
78
-
79
- if (priority === "P2 High") {
80
- return "High-impact infrastructure or safety risk needs fast ownership before it grows into a wider area failure.";
81
- }
82
-
83
- if (priority === "P3 Medium") {
84
- return "Standard operational issue with visible citizen impact. Keep it moving through the active queue.";
85
- }
86
-
87
- return "Lower-impact maintenance issue that can stay in the planned service backlog unless conditions worsen.";
88
- }
89
-
90
- function getRoutingConfidence(domain: string, assignments: ComplaintAssignmentItem[], department: string) {
91
- if (assignments.length > 0) {
92
- return `Routing has already been acted on. The latest ownership sits with ${assignments[0]?.departmentName ?? department}.`;
93
- }
94
-
95
- if (domain === "Fire Emergencies" || domain === "Flood and Water Disaster" || domain === "Disaster and Rescue") {
96
- return `Emergency domain strongly matches ${department} and should stay on the fastest escalation path.`;
97
- }
98
-
99
- return `Current domain and complaint context point to ${department} as the primary queue owner.`;
100
- }
101
-
102
- function getOperationalState(status: string, assignments: ComplaintAssignmentItem[]) {
103
- if (status === "Resolved" || status === "Closed") {
104
- return "Work appears completed. Review proof, citizen follow-up, and any reopen risk.";
105
- }
106
-
107
- if (status === "Reopened" || status === "Escalated") {
108
- return "Complaint needs renewed attention. Recheck ownership, on-ground risk, and SLA exposure.";
109
- }
110
-
111
- if (assignments.length > 0) {
112
- return "Complaint is in active departmental handling. Track acceptance and field progress closely.";
113
- }
114
-
115
- return "Complaint still needs clear ownership. Assignment is the next critical operator action.";
116
- }
117
-
118
- function timelineTone(status: string) {
119
- switch (status) {
120
- case "submitted":
121
- return "bg-civic-primary";
122
- case "validated":
123
- return "bg-civic-secondary";
124
- case "classified":
125
- case "prioritized":
126
- return "bg-amber-500";
127
- case "assigned":
128
- return "bg-slate-800";
129
- case "in_progress":
130
- return "bg-emerald-600";
131
- case "resolved":
132
- case "closed":
133
- return "bg-emerald-700";
134
- case "reopened":
135
- case "escalated":
136
- return "bg-civic-danger";
137
- default:
138
- return "bg-slate-300";
139
- }
140
- }
141
-
142
- export default async function DashboardComplaintDetailPage({ params }: DashboardComplaintDetailPageProps) {
143
- const user = await requireRole(["department_operator", "municipal_admin"]);
144
- const { id } = await params;
145
-
146
- let complaintCode = "CP-2026-00141";
147
- let complaintStatusValue = "submitted";
148
- let complaintStatus = "Submitted";
149
- let complaintPriority = "P1 Critical";
150
- let complaintDomain = "Public Infrastructure and Amenities";
151
- let complaintTitle = "Open manhole on high-traffic road";
152
- let complaintLocation = "Temple Junction, Ward 4";
153
- let complaintDescription = "High-risk public hazard affecting traffic near the junction.";
154
- let complaintDepartment = "Municipal Maintenance";
155
- let isEmergency = true;
156
- let media: ComplaintMediaItem[] = [];
157
- let assignments: ComplaintAssignmentItem[] = [];
158
- let notes: ComplaintNoteItem[] = [];
159
- let aiInsights: ComplaintAiInsights | null = null;
160
- let internalTimeline: Array<{ title: string; time: string; note: string; tone: string }> = [
161
- {
162
- title: "Submitted",
163
- time: "Pending",
164
- note: "Complaint created by citizen with image and geo-tag.",
165
- tone: "bg-civic-primary",
166
- },
167
- ];
168
-
169
- try {
170
- const [complaint, assignmentItems, history, mediaItems, noteItems, aiData] = await Promise.all([
171
- getComplaintDetail(id),
172
- getComplaintAssignments(id),
173
- getComplaintHistory(id),
174
- getComplaintMedia(id),
175
- getComplaintNotes(id),
176
- getComplaintAiInsights(id),
177
- ]);
178
-
179
- complaintCode = complaint.complaintCode;
180
- complaintStatusValue = complaint.status;
181
- complaintStatus = formatStatus(complaint.status);
182
- complaintPriority = formatPriority(complaint.priorityLevel);
183
- complaintDomain = complaint.domainName ?? complaintDomain;
184
- complaintTitle = complaint.title;
185
- complaintLocation = complaint.addressLine ?? complaint.landmark ?? complaintLocation;
186
- complaintDescription = complaint.description ?? complaintDescription;
187
- complaintDepartment = complaint.departmentName ?? complaintDepartment;
188
- isEmergency = complaint.isEmergency;
189
- assignments = assignmentItems;
190
- media = mediaItems;
191
- notes = noteItems;
192
- aiInsights = aiData;
193
-
194
- if (history.length > 0) {
195
- internalTimeline = history.map((entry: ComplaintStatusHistoryItem) => ({
196
- title: formatStatus(entry.newStatus),
197
- time: formatDateTime(entry.createdAt),
198
- note: entry.changeReason ?? `Complaint moved to ${formatStatus(entry.newStatus)}.`,
199
- tone: timelineTone(entry.newStatus),
200
- }));
201
- }
202
- } catch {
203
- // Keep fallback values when backend is unavailable.
204
- }
205
-
206
- const complaintMeta = [
207
- { label: "Complaint ID", value: complaintCode },
208
- { label: "Current status", value: complaintStatus },
209
- { label: "Priority", value: complaintPriority },
210
- { label: "Domain", value: complaintDomain },
211
- ];
212
-
213
- const complaintContext = [
214
- `Issue: ${complaintTitle}`,
215
- `Location: ${complaintLocation}`,
216
- `Likely department: ${complaintDepartment}`,
217
- complaintDescription,
218
- ];
219
-
220
- const routingDecisionItems = [
221
- `Current queue owner: ${assignments[0]?.departmentName ?? complaintDepartment}`,
222
- `Operational mode: ${isEmergency ? "Emergency escalation path" : "Standard civic workflow"}`,
223
- `Priority rationale: ${getPriorityRationale(complaintPriority, isEmergency)}`,
224
- `Routing confidence: ${getRoutingConfidence(complaintDomain, assignments, complaintDepartment)}`,
225
- `Current action state: ${getOperationalState(complaintStatus, assignments)}`,
226
- ];
227
-
228
- return (
229
- <AppShell>
230
- <section className="grid gap-6 rounded-[2rem] bg-civic-primary px-6 py-8 text-white shadow-civic lg:grid-cols-[1.08fr_0.92fr] lg:px-8">
231
- <div className="space-y-4">
232
- <p className="text-sm font-semibold uppercase tracking-[0.22em] text-white/75">Internal complaint view</p>
233
- <h1 className="text-4xl font-semibold tracking-tight">Manage one complaint from triage to resolution.</h1>
234
- <p className="max-w-2xl text-sm leading-7 text-white/80">
235
- Review context, route the work, assign the right team, and move the complaint through the next action.
236
- </p>
237
- </div>
238
-
239
- <div className="grid gap-4 sm:grid-cols-2">
240
- {complaintMeta.map((item) => (
241
- <div key={item.label} className="rounded-3xl border border-white/15 bg-white/10 p-5 backdrop-blur">
242
- <p className="text-sm font-semibold uppercase tracking-[0.16em] text-white/75">{item.label}</p>
243
- <p className="mt-3 text-xl font-semibold text-white">{item.value}</p>
244
- </div>
245
- ))}
246
- </div>
247
- </section>
248
-
249
- <section className="grid gap-6 lg:grid-cols-[1.12fr_0.88fr]">
250
- <div className="space-y-6">
251
- <section className="rounded-[2rem] border border-slate-200 bg-white p-6 shadow-sm">
252
- <SectionHeader eyebrow="Complaint context" icon={ClipboardList} title="Citizen submission details" />
253
-
254
- <div className="mt-5 space-y-3">
255
- {complaintContext.map((item) => (
256
- <div key={item} className="rounded-2xl bg-slate-50 px-4 py-3 text-sm leading-6 text-civic-text">
257
- {item}
258
- </div>
259
- ))}
260
- </div>
261
- </section>
262
-
263
- <section className="rounded-[2rem] border border-slate-200 bg-white p-6 shadow-sm">
264
- <SectionHeader eyebrow="Lifecycle" icon={Clock3} iconClassName="bg-civic-primary text-white" title="Internal status timeline" />
265
-
266
- <div className="mt-6 space-y-5">
267
- {internalTimeline.map((entry, index) => (
268
- <div key={`${entry.title}-${entry.time}-${index}`} className="grid gap-4 md:grid-cols-[auto_1fr] md:gap-5">
269
- <div className="flex items-start gap-4 md:flex-col md:items-center">
270
- <div className={["mt-1 h-4 w-4 rounded-full", entry.tone].join(" ")} />
271
- {index !== internalTimeline.length - 1 ? <div className="hidden h-16 w-px bg-slate-200 md:block" /> : null}
272
- </div>
273
- <div className="rounded-3xl bg-slate-50 p-5">
274
- <div className="flex flex-wrap items-center justify-between gap-3">
275
- <h3 className="text-lg font-semibold text-civic-text">{entry.title}</h3>
276
- <div className="inline-flex items-center gap-2 rounded-full bg-white px-3 py-2 text-xs font-semibold text-slate-700">
277
- <Clock3 className="h-4 w-4" />
278
- {entry.time}
279
- </div>
280
- </div>
281
- <p className="mt-3 text-sm leading-6 text-civic-muted">{entry.note}</p>
282
- </div>
283
- </div>
284
- ))}
285
- </div>
286
- </section>
287
-
288
- <section className="rounded-[2rem] border border-slate-200 bg-white p-6 shadow-sm">
289
- <SectionHeader eyebrow="Internal notes" icon={MessageSquareText} iconClassName="bg-amber-100 text-amber-700" title="Operator-only information" />
290
-
291
- <div className="mt-5">
292
- <AdminNotesPanel complaintId={id} initialNotes={notes} operatorId={user.id} />
293
- </div>
294
- </section>
295
-
296
- <AdminAssignmentHistory assignments={assignments} />
297
- </div>
298
-
299
- <div className="space-y-6">
300
- <section className="rounded-[2rem] border border-slate-200 bg-white p-6 shadow-sm">
301
- <SectionHeader eyebrow="Priority and routing" icon={ShieldAlert} iconClassName="bg-red-100 text-civic-danger" title="Department decision block" />
302
-
303
- <div className="mt-5 space-y-3">
304
- {routingDecisionItems.map((item) => (
305
- <div key={item} className="rounded-2xl bg-slate-50 px-4 py-3 text-sm leading-6 text-civic-text">
306
- {item}
307
- </div>
308
- ))}
309
- </div>
310
-
311
- <div className="mt-5">
312
- <AdminAssignAction complaintId={id} suggestedDepartment={complaintDepartment} operatorId={user.id} />
313
- </div>
314
- </section>
315
-
316
- <ComplaintAiPanel insights={aiInsights} />
317
-
318
- <section className="rounded-[2rem] border border-slate-200 bg-white p-6 shadow-sm">
319
- <SectionHeader eyebrow="Actions" icon={UserCog} title="Operator workflow controls" />
320
-
321
- <div className="mt-5 space-y-3">
322
- {internalActions.map((action) => (
323
- <div key={action} className="rounded-2xl bg-slate-50 px-4 py-3 text-sm leading-6 text-civic-text">
324
- {action}
325
- </div>
326
- ))}
327
- </div>
328
-
329
- <div className="mt-5">
330
- <AdminStatusActions complaintId={id} currentStatus={complaintStatusValue} operatorId={user.id} />
331
- </div>
332
- </section>
333
-
334
- <ComplaintMediaGallery
335
- items={media}
336
- title="Complaint evidence"
337
- emptyLabel="No evidence has been uploaded for this complaint yet."
338
- />
339
-
340
- <ResolutionProofUploader complaintId={id} uploadedBy={user.id} label="Attach final proof from operations" />
341
-
342
- <section className="rounded-[2rem] border border-emerald-200 bg-emerald-50 p-6 shadow-sm">
343
- <div className="flex items-start gap-3">
344
- <CheckCircle2 className="mt-0.5 h-5 w-5 text-emerald-700" />
345
- <div>
346
- <h2 className="text-lg font-semibold text-emerald-950">Internal-only context</h2>
347
- <p className="mt-2 text-sm leading-6 text-emerald-900/85">
348
- Notes, routing context, and assignment controls stay visible only to internal teams.
349
- </p>
350
- </div>
351
- </div>
352
- </section>
353
- </div>
354
- </section>
355
-
356
- <section className="flex flex-wrap items-center justify-between gap-4 rounded-[2rem] border border-slate-200 bg-white p-6 shadow-sm">
357
- <div className="space-y-2">
358
- <p className="text-sm font-semibold uppercase tracking-[0.22em] text-civic-secondary">Queue navigation</p>
359
- <h2 className="text-2xl font-semibold text-civic-text">Jump back to the queue or move into emergency operations.</h2>
360
- <p className="max-w-2xl text-sm leading-6 text-civic-muted">
361
- Use the queue to continue triage work, or switch to emergency reporting when a fast-response situation needs immediate handling.
362
- </p>
363
- </div>
364
-
365
- <div className="flex flex-wrap gap-3">
366
- <Link
367
- className="inline-flex rounded-full border border-slate-200 px-5 py-3 text-sm font-semibold text-slate-700 transition hover:border-civic-primary hover:text-civic-primary"
368
- href="/dashboard/complaints"
369
- >
370
- Back to queue
371
- </Link>
372
- <Link
373
- className="inline-flex items-center gap-2 rounded-full bg-slate-900 px-5 py-3 text-sm font-semibold text-white transition hover:-translate-y-0.5"
374
- href="/emergency"
375
- >
376
- Open emergency reporting
377
- <ArrowRight className="h-4 w-4" />
378
- </Link>
379
- </div>
380
- </section>
381
- </AppShell>
382
- );
383
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
civic-platform/app/dashboard/complaints/page.tsx DELETED
@@ -1,113 +0,0 @@
1
- import { AppShell } from "@/components/app-shell";
2
- import {
3
- DashboardQueueClient,
4
- type QueueComplaintCard,
5
- } from "@/components/dashboard-queue-client";
6
- import { getAllComplaints } from "@/lib/api";
7
- import { requireRole } from "@/lib/auth";
8
-
9
- const filterGroups = [
10
- {
11
- title: "Status" as const,
12
- options: ["All", "Submitted", "Assigned", "In Progress", "Resolved", "Reopened"],
13
- },
14
- {
15
- title: "Priority" as const,
16
- options: ["All", "P1 Critical", "P2 High", "P3 Medium", "P4 Low"],
17
- },
18
- {
19
- title: "Domain" as const,
20
- options: ["All", "Roads", "Sanitation", "Water", "Electrical", "Infrastructure", "Environment"],
21
- },
22
- ];
23
-
24
- const fallbackComplaints: QueueComplaintCard[] = [
25
- {
26
- id: "CP-2026-00141",
27
- complaintCode: "CP-2026-00141",
28
- title: "Open manhole on high-traffic road",
29
- domain: "Public Infrastructure and Amenities",
30
- status: "Submitted",
31
- priority: "P1 Critical",
32
- department: "Municipal Maintenance",
33
- location: "Temple Junction, Ward 4",
34
- updatedAt: "5 minutes ago",
35
- submittedAt: new Date(Date.now() - 5 * 60 * 1000).toISOString(),
36
- },
37
- {
38
- id: "CP-2026-00139",
39
- complaintCode: "CP-2026-00139",
40
- title: "Live wire hanging near bus stop",
41
- domain: "Electrical Hazard",
42
- status: "Assigned",
43
- priority: "P1 Critical",
44
- department: "Electrical Safety Response",
45
- location: "Bus Stand Road, Ward 9",
46
- updatedAt: "12 minutes ago",
47
- submittedAt: new Date(Date.now() - 12 * 60 * 1000).toISOString(),
48
- },
49
- {
50
- id: "CP-2026-00124",
51
- complaintCode: "CP-2026-00124",
52
- title: "Sewage overflow near market entrance",
53
- domain: "Water Supply, Sewerage, and Drainage",
54
- status: "In Progress",
55
- priority: "P2 High",
56
- department: "Water and Sewerage",
57
- location: "Market Road, Ward 12",
58
- updatedAt: "1 hour ago",
59
- submittedAt: new Date(Date.now() - 60 * 60 * 1000).toISOString(),
60
- },
61
- ];
62
-
63
- function formatStatus(status: string) {
64
- return status
65
- .split("_")
66
- .map((part) => part.charAt(0).toUpperCase() + part.slice(1))
67
- .join(" ");
68
- }
69
-
70
- function formatPriority(priority: string) {
71
- switch (priority) {
72
- case "P1":
73
- return "P1 Critical";
74
- case "P2":
75
- return "P2 High";
76
- case "P3":
77
- return "P3 Medium";
78
- default:
79
- return "P4 Low";
80
- }
81
- }
82
-
83
- export default async function DashboardComplaintsPage() {
84
- const user = await requireRole(["department_operator", "municipal_admin"]);
85
- let complaints: QueueComplaintCard[] = fallbackComplaints;
86
-
87
- try {
88
- const apiComplaints = await getAllComplaints();
89
-
90
- if (apiComplaints.length > 0) {
91
- complaints = apiComplaints.map((complaint) => ({
92
- id: complaint.id,
93
- complaintCode: complaint.complaintCode,
94
- title: complaint.title,
95
- domain: complaint.domainName ?? "Unclassified",
96
- status: formatStatus(complaint.status),
97
- priority: formatPriority(complaint.priorityLevel),
98
- department: complaint.departmentName ?? "Department pending",
99
- location: complaint.addressLine ?? complaint.landmark ?? "Location pending",
100
- updatedAt: new Date(complaint.submittedAt).toLocaleString(),
101
- submittedAt: complaint.submittedAt,
102
- }));
103
- }
104
- } catch {
105
- complaints = fallbackComplaints;
106
- }
107
-
108
- return (
109
- <AppShell>
110
- <DashboardQueueClient complaints={complaints} filterGroups={filterGroups} operatorId={user.id} />
111
- </AppShell>
112
- );
113
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
civic-platform/app/dashboard/page.tsx DELETED
@@ -1,257 +0,0 @@
1
- import Link from "next/link";
2
- import {
3
- ArrowRight,
4
- Building2,
5
- Clock3,
6
- MapPinned,
7
- Siren,
8
- TimerReset,
9
- } from "lucide-react";
10
- import { AppShell } from "@/components/app-shell";
11
- import { EmptyState } from "@/components/empty-state";
12
- import { LiveSyncBadge } from "@/components/live-sync-badge";
13
- import { PriorityBadge } from "@/components/priority-badge";
14
- import { requireRole } from "@/lib/auth";
15
- import { getDashboardSummary, type DashboardSummary } from "@/lib/api";
16
-
17
- const focusItems = [
18
- "Urgent complaints",
19
- "Unaccepted assignments",
20
- "SLA breaches",
21
- "Pending field updates",
22
- ];
23
-
24
- export default async function DashboardPage() {
25
- await requireRole(["department_operator", "municipal_admin"]);
26
-
27
- const fallbackSummary: DashboardSummary = {
28
- overview: {
29
- openComplaints: 184,
30
- criticalComplaints: 7,
31
- slaRisk: 19,
32
- resolvedToday: 42,
33
- },
34
- urgentQueue: [
35
- {
36
- id: "sample-urgent-1",
37
- complaintCode: "CP-2026-00141",
38
- title: "Open manhole on high-traffic road",
39
- domainName: "Public Infrastructure and Amenities",
40
- priorityLevel: "P1",
41
- location: "Temple Junction, Ward 4",
42
- submittedAt: new Date().toISOString(),
43
- },
44
- {
45
- id: "sample-urgent-2",
46
- complaintCode: "CP-2026-00139",
47
- title: "Live wire hanging near bus stop",
48
- domainName: "Electrical Hazard",
49
- priorityLevel: "P1",
50
- location: "Bus Stand Road, Ward 9",
51
- submittedAt: new Date().toISOString(),
52
- },
53
- ],
54
- departmentWorkload: [
55
- { departmentName: "Roads and Public Works", pendingCount: 31 },
56
- { departmentName: "Sanitation and Solid Waste", pendingCount: 28 },
57
- { departmentName: "Water and Sewerage", pendingCount: 44 },
58
- { departmentName: "Electrical and Street Lighting", pendingCount: 22 },
59
- ],
60
- };
61
-
62
- let summary: DashboardSummary = fallbackSummary;
63
-
64
- try {
65
- summary = await getDashboardSummary();
66
- } catch {
67
- summary = fallbackSummary;
68
- }
69
-
70
- const overviewStats = [
71
- { label: "Open complaints", value: String(summary.overview.openComplaints), tone: "text-civic-text" },
72
- { label: "Critical", value: String(summary.overview.criticalComplaints), tone: "text-civic-danger" },
73
- { label: "SLA risk", value: String(summary.overview.slaRisk), tone: "text-amber-700" },
74
- { label: "Resolved today", value: String(summary.overview.resolvedToday), tone: "text-emerald-700" },
75
- ];
76
-
77
- return (
78
- <AppShell>
79
- <section className="relative overflow-hidden rounded-[2.5rem] bg-gradient-to-br from-[#09090b] via-civic-primary to-[#1e3a8a] px-8 py-10 text-white shadow-civic lg:px-10 lg:py-12">
80
- <div className="absolute -left-32 -top-32 h-96 w-96 rounded-full bg-white/5 blur-[100px]" />
81
- <div className="relative flex flex-wrap items-end justify-between gap-6">
82
- <div className="space-y-4">
83
- <p className="text-sm font-bold uppercase tracking-[0.25em] text-white/70">Operations</p>
84
- <h1 className="text-5xl font-extrabold tracking-tight">Command Center</h1>
85
- </div>
86
- <div className="flex flex-wrap items-center gap-4">
87
- <LiveSyncBadge label="Live Refresh" />
88
- <Link
89
- className="group inline-flex items-center gap-2 rounded-full border border-white/20 bg-white/5 px-6 py-3.5 text-sm font-semibold text-white backdrop-blur-md transition hover:border-white/40 hover:bg-white/10"
90
- href="/dashboard/analytics"
91
- >
92
- Analytics
93
- <ArrowRight className="h-4 w-4 transition-transform group-hover:translate-x-1" />
94
- </Link>
95
- <Link
96
- className="group inline-flex items-center gap-2 rounded-full bg-white px-6 py-3.5 text-sm font-bold text-civic-primary shadow-xl transition hover:-translate-y-0.5 hover:shadow-2xl"
97
- href="/dashboard/complaints"
98
- >
99
- Open Queue
100
- <ArrowRight className="h-4 w-4 transition-transform group-hover:translate-x-1" />
101
- </Link>
102
- </div>
103
- </div>
104
-
105
- <div className="relative mt-8 grid gap-5 sm:grid-cols-2 xl:grid-cols-4">
106
- {overviewStats.map((stat) => (
107
- <div key={stat.label} className="group rounded-[2rem] border border-white/15 bg-white/10 p-6 backdrop-blur-xl transition hover:bg-white/15">
108
- <p className="text-xs font-bold uppercase tracking-[0.2em] text-white/60">{stat.label}</p>
109
- <p className={["mt-4 text-4xl font-extrabold tracking-tight", stat.tone.replace("text-civic-text", "text-white").replace("text-civic-danger", "text-rose-400").replace("text-amber-700", "text-amber-400").replace("text-emerald-700", "text-emerald-400")].join(" ")}>{stat.value}</p>
110
- </div>
111
- ))}
112
- </div>
113
- </section>
114
-
115
- <section className="grid gap-6 xl:grid-cols-[1.1fr_0.9fr]">
116
- <section className="rounded-[2.5rem] border border-slate-200 bg-white/80 p-8 shadow-glass backdrop-blur-3xl transition hover:shadow-glass-hover">
117
- <div className="flex flex-wrap items-center justify-between gap-4">
118
- <div>
119
- <p className="text-sm font-bold uppercase tracking-[0.25em] text-civic-secondary">Urgent queue</p>
120
- <h2 className="mt-2 text-3xl font-extrabold text-slate-900">Priority Incidents</h2>
121
- </div>
122
- <div className="inline-flex animate-pulse items-center gap-2 rounded-full bg-rose-100 px-4 py-2 text-sm font-bold text-rose-700">
123
- <Siren className="h-4 w-4" />
124
- Live Feed
125
- </div>
126
- </div>
127
-
128
- <div className="mt-8 space-y-5">
129
- {summary.urgentQueue.length === 0 ? (
130
- <EmptyState
131
- description="Critical complaints will appear here as soon as they enter the queue."
132
- icon={Siren}
133
- title="No urgent complaints right now"
134
- />
135
- ) : (
136
- summary.urgentQueue.map((item) => (
137
- <article key={item.id} className="group relative overflow-hidden rounded-[2rem] border-2 border-rose-100 bg-gradient-to-br from-rose-50 to-white p-6 shadow-sm transition-all hover:-translate-y-1 hover:border-rose-200 hover:shadow-xl">
138
- <div className="absolute right-0 top-0 h-32 w-32 -translate-y-8 translate-x-8 rounded-full bg-rose-200/50 blur-3xl transition group-hover:bg-rose-300/50" />
139
- <div className="relative flex flex-wrap items-start justify-between gap-4">
140
- <div className="space-y-3">
141
- <div className="flex flex-wrap items-center gap-3">
142
- <span className="rounded-full bg-white px-3.5 py-1 text-xs font-bold text-slate-700 shadow-sm">
143
- {item.complaintCode}
144
- </span>
145
- <PriorityBadge priority={`${item.priorityLevel} ${item.priorityLevel === "P1" ? "Critical" : item.priorityLevel === "P2" ? "High" : item.priorityLevel === "P3" ? "Medium" : "Low"}`} />
146
- </div>
147
- <h3 className="text-2xl font-bold text-slate-900">{item.title}</h3>
148
- <p className="text-sm font-medium text-slate-500">{item.domainName ?? "Unclassified"}</p>
149
- </div>
150
-
151
- <div className="rounded-full bg-white/80 px-4 py-2 text-xs font-bold text-rose-700 shadow-sm backdrop-blur-md">
152
- {new Date(item.submittedAt).toLocaleString()}
153
- </div>
154
- </div>
155
-
156
- <div className="relative mt-5 flex flex-wrap items-center gap-4 text-sm font-medium text-slate-600">
157
- <div className="inline-flex items-center gap-2">
158
- <MapPinned className="h-4 w-4 text-slate-400" />
159
- {item.location ?? "Location pending"}
160
- </div>
161
- </div>
162
-
163
- <div className="relative mt-6">
164
- <Link
165
- className="group/btn inline-flex items-center gap-2 rounded-full bg-slate-900 px-6 py-3 text-sm font-bold text-white shadow-md transition hover:bg-slate-800 hover:shadow-lg"
166
- href={`/dashboard/complaints/${item.id}`}
167
- >
168
- Open Incident
169
- <ArrowRight className="h-4 w-4 transition-transform group-hover/btn:translate-x-1" />
170
- </Link>
171
- </div>
172
- </article>
173
- ))
174
- )}
175
- </div>
176
- </section>
177
-
178
- <div className="space-y-6">
179
- <section className="rounded-[2.5rem] border border-slate-200 bg-white/80 p-8 shadow-glass backdrop-blur-3xl transition hover:shadow-glass-hover">
180
- <div className="flex items-center gap-4">
181
- <div className="flex h-14 w-14 items-center justify-center rounded-2xl bg-civic-secondary/15 text-civic-secondary shadow-inner">
182
- <Building2 className="h-6 w-6" />
183
- </div>
184
- <div>
185
- <p className="text-sm font-bold uppercase tracking-[0.2em] text-civic-secondary">Departments</p>
186
- <h2 className="text-2xl font-extrabold text-slate-900">Current Workload</h2>
187
- </div>
188
- </div>
189
-
190
- <div className="mt-8 space-y-4">
191
- {summary.departmentWorkload.length === 0 ? (
192
- <EmptyState
193
- description="Department workload will appear here once complaints are assigned into active queues."
194
- icon={Building2}
195
- title="No department workload yet"
196
- />
197
- ) : (
198
- summary.departmentWorkload.map((item) => (
199
- <div key={item.departmentName} className="group rounded-3xl border border-slate-100 bg-white p-5 shadow-sm transition hover:-translate-y-1 hover:border-slate-200 hover:shadow-md">
200
- <div className="flex items-center justify-between gap-4">
201
- <h3 className="text-lg font-bold text-slate-800">{item.departmentName}</h3>
202
- <span className="rounded-full bg-slate-100 px-4 py-1.5 text-xs font-bold text-slate-700 transition group-hover:bg-civic-secondary group-hover:text-white">
203
- {item.pendingCount} pending
204
- </span>
205
- </div>
206
- </div>
207
- ))
208
- )}
209
- </div>
210
- </section>
211
-
212
- <section className="rounded-[2.5rem] border border-slate-200 bg-white/80 p-8 shadow-glass backdrop-blur-3xl transition hover:shadow-glass-hover">
213
- <div className="flex items-center gap-4">
214
- <div className="flex h-14 w-14 items-center justify-center rounded-2xl bg-amber-100 text-amber-600 shadow-inner">
215
- <TimerReset className="h-6 w-6" />
216
- </div>
217
- <div>
218
- <p className="text-sm font-bold uppercase tracking-[0.2em] text-civic-secondary">Focus</p>
219
- <h2 className="text-2xl font-extrabold text-slate-900">Immediate Attention</h2>
220
- </div>
221
- </div>
222
-
223
- <div className="mt-8 grid gap-4 sm:grid-cols-2">
224
- {focusItems.map((item) => (
225
- <div key={item} className="rounded-2xl border border-slate-100 bg-white px-5 py-4 text-sm font-bold text-slate-700 shadow-sm transition hover:border-slate-200 hover:shadow-md">
226
- {item}
227
- </div>
228
- ))}
229
- </div>
230
- </section>
231
-
232
- <section className="overflow-hidden rounded-[2.5rem] bg-gradient-to-br from-civic-primary to-blue-900 p-8 text-white shadow-xl transition hover:shadow-2xl">
233
- <div className="flex items-center gap-4">
234
- <div className="flex h-14 w-14 items-center justify-center rounded-2xl bg-white/20 shadow-inner backdrop-blur-md">
235
- <Clock3 className="h-6 w-6 text-white" />
236
- </div>
237
- <div>
238
- <p className="text-sm font-bold uppercase tracking-[0.2em] text-white/70">Queue</p>
239
- <h2 className="text-2xl font-extrabold">Continue Operations</h2>
240
- </div>
241
- </div>
242
-
243
- <div className="mt-8">
244
- <Link
245
- className="group inline-flex items-center gap-3 rounded-full bg-white px-7 py-4 text-sm font-bold text-civic-primary shadow-lg transition hover:-translate-y-1 hover:shadow-xl"
246
- href="/dashboard/complaints"
247
- >
248
- Go to Complaint Queue
249
- <ArrowRight className="h-5 w-5 transition-transform group-hover:translate-x-1" />
250
- </Link>
251
- </div>
252
- </section>
253
- </div>
254
- </section>
255
- </AppShell>
256
- );
257
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
civic-platform/app/emergency/page.tsx DELETED
@@ -1,176 +0,0 @@
1
- import Link from "next/link";
2
- import { AlertTriangle, Flame, RadioTower, ShieldAlert, Siren, Waves, Zap } from "lucide-react";
3
- import { AppShell } from "@/components/app-shell";
4
-
5
- const emergencyTypes = [
6
- {
7
- title: "Fire",
8
- summary: "Fire outbreak, smoke, electrical fire, or public flame hazard.",
9
- icon: Flame,
10
- },
11
- {
12
- title: "Flood",
13
- summary: "Flooding, dangerous water accumulation, or rapid overflow.",
14
- icon: Waves,
15
- },
16
- {
17
- title: "Electrical Hazard",
18
- summary: "Live wire, transformer blast, or severe public electrical risk.",
19
- icon: Zap,
20
- },
21
- {
22
- title: "Rescue / Disaster",
23
- summary: "Collapse, storm damage, trapped people, or multi-agency incident.",
24
- icon: RadioTower,
25
- },
26
- ];
27
-
28
- const emergencyRules = [
29
- "Emergency reports should take fewer steps than standard complaints.",
30
- "Location must be captured first or confirmed manually.",
31
- "Priority should default to P1 unless the complaint is invalid.",
32
- "The system should route directly to the emergency queue.",
33
- ];
34
-
35
- const minimalFields = [
36
- "Emergency type",
37
- "Photo or short video",
38
- "Current location or exact pin",
39
- "Short description of danger",
40
- "Optional contact number",
41
- ];
42
-
43
- export default function EmergencyPage() {
44
- return (
45
- <AppShell>
46
- <section className="grid gap-6 rounded-[2rem] bg-civic-danger px-6 py-8 text-white shadow-civic lg:grid-cols-[1.08fr_0.92fr] lg:px-8">
47
- <div className="space-y-4">
48
- <div className="inline-flex items-center gap-2 rounded-full bg-white/10 px-4 py-2 text-sm font-medium text-white/90">
49
- <Siren className="h-4 w-4" />
50
- Emergency reporting flow
51
- </div>
52
- <h1 className="text-4xl font-semibold tracking-tight">Use the fastest possible flow for urgent public danger.</h1>
53
- <p className="max-w-2xl text-sm leading-7 text-white/85">
54
- Report fire, flood, electrical danger, or rescue situations with the fewest possible steps so the response team can act quickly.
55
- </p>
56
- </div>
57
-
58
- <div className="rounded-[1.75rem] border border-white/15 bg-white/10 p-5 backdrop-blur">
59
- <p className="text-sm font-semibold uppercase tracking-[0.22em] text-white/75">Default response behavior</p>
60
- <div className="mt-4 space-y-3">
61
- {[
62
- "Priority set to P1 Critical",
63
- "Emergency queue routing",
64
- "Immediate operator visibility",
65
- "Stronger notification path",
66
- ].map((item) => (
67
- <div key={item} className="rounded-2xl bg-white/10 px-4 py-3 text-sm font-medium text-white">
68
- {item}
69
- </div>
70
- ))}
71
- </div>
72
- </div>
73
- </section>
74
-
75
- <section className="grid gap-6 lg:grid-cols-[1.02fr_0.98fr]">
76
- <section className="rounded-[2rem] border border-slate-200 bg-white p-6 shadow-sm">
77
- <div className="flex flex-wrap items-start justify-between gap-4">
78
- <div>
79
- <p className="text-sm font-semibold uppercase tracking-[0.22em] text-civic-secondary">Emergency type</p>
80
- <h2 className="mt-2 text-2xl font-semibold text-civic-text">Choose the urgent incident category</h2>
81
- </div>
82
- <div className="rounded-full bg-red-50 px-4 py-2 text-sm font-semibold text-red-700">
83
- High priority flow
84
- </div>
85
- </div>
86
-
87
- <div className="mt-6 grid gap-4 md:grid-cols-2">
88
- {emergencyTypes.map(({ title, summary, icon: Icon }) => (
89
- <button
90
- key={title}
91
- type="button"
92
- className="group rounded-3xl border border-red-100 bg-red-50/50 p-5 text-left transition duration-200 hover:-translate-y-1 hover:border-civic-danger hover:bg-white hover:shadow-civic"
93
- >
94
- <div className="flex items-start justify-between gap-4">
95
- <div className="flex h-12 w-12 items-center justify-center rounded-2xl bg-white text-civic-danger">
96
- <Icon className="h-6 w-6" />
97
- </div>
98
- <span className="rounded-full bg-white px-3 py-1 text-xs font-semibold text-civic-danger">Select</span>
99
- </div>
100
- <h3 className="mt-4 text-lg font-semibold text-civic-text">{title}</h3>
101
- <p className="mt-2 text-sm leading-6 text-civic-muted">{summary}</p>
102
- </button>
103
- ))}
104
- </div>
105
- </section>
106
-
107
- <div className="space-y-6">
108
- <section className="rounded-[2rem] border border-slate-200 bg-white p-6 shadow-sm">
109
- <div className="flex items-center gap-3">
110
- <div className="flex h-11 w-11 items-center justify-center rounded-2xl bg-civic-danger/10 text-civic-danger">
111
- <ShieldAlert className="h-5 w-5" />
112
- </div>
113
- <div>
114
- <p className="text-sm font-semibold uppercase tracking-[0.16em] text-civic-secondary">Form scope</p>
115
- <h2 className="text-xl font-semibold text-civic-text">Minimal fields for emergency submission</h2>
116
- </div>
117
- </div>
118
-
119
- <div className="mt-5 space-y-3">
120
- {minimalFields.map((field) => (
121
- <div key={field} className="rounded-2xl bg-slate-50 px-4 py-3 text-sm font-medium text-civic-text">
122
- {field}
123
- </div>
124
- ))}
125
- </div>
126
- </section>
127
-
128
- <section className="rounded-[2rem] border border-amber-200 bg-amber-50 p-6 shadow-sm">
129
- <div className="flex items-start gap-3">
130
- <AlertTriangle className="mt-0.5 h-5 w-5 text-amber-700" />
131
- <div>
132
- <h2 className="text-lg font-semibold text-amber-950">Emergency reporting principles</h2>
133
- <p className="mt-2 text-sm leading-6 text-amber-900/85">
134
- This flow stays shorter than normal reporting and focuses only on what responders need first.
135
- </p>
136
- </div>
137
- </div>
138
-
139
- <div className="mt-5 space-y-3">
140
- {emergencyRules.map((rule) => (
141
- <div key={rule} className="rounded-2xl bg-white/75 px-4 py-3 text-sm leading-6 text-amber-950">
142
- {rule}
143
- </div>
144
- ))}
145
- </div>
146
- </section>
147
- </div>
148
- </section>
149
-
150
- <section className="flex flex-wrap items-center justify-between gap-4 rounded-[2rem] border border-slate-200 bg-white p-6 shadow-sm">
151
- <div>
152
- <p className="text-sm font-semibold uppercase tracking-[0.22em] text-civic-secondary">Switch reporting modes</p>
153
- <h2 className="mt-2 text-2xl font-semibold text-civic-text">Return to the normal report flow or operations dashboard.</h2>
154
- <p className="mt-2 max-w-2xl text-sm leading-6 text-civic-muted">
155
- Use the standard reporting flow for non-emergency complaints, or return to the dashboard to monitor active work.
156
- </p>
157
- </div>
158
-
159
- <div className="flex flex-wrap gap-3">
160
- <Link
161
- className="inline-flex rounded-full border border-slate-200 px-5 py-3 text-sm font-semibold text-slate-700 transition hover:border-civic-primary hover:text-civic-primary"
162
- href="/report"
163
- >
164
- Back to normal report flow
165
- </Link>
166
- <Link
167
- className="inline-flex rounded-full bg-slate-900 px-5 py-3 text-sm font-semibold text-white transition hover:-translate-y-0.5"
168
- href="/dashboard"
169
- >
170
- Return to dashboard
171
- </Link>
172
- </div>
173
- </section>
174
- </AppShell>
175
- );
176
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
civic-platform/app/globals.css DELETED
@@ -1,61 +0,0 @@
1
- @tailwind base;
2
- @tailwind components;
3
- @tailwind utilities;
4
- @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap');
5
- @import "mapbox-gl/dist/mapbox-gl.css";
6
-
7
- :root {
8
- color-scheme: light;
9
- }
10
-
11
- html {
12
- scroll-behavior: smooth;
13
- }
14
-
15
- body {
16
- margin: 0;
17
- min-height: 100vh;
18
- background:
19
- radial-gradient(circle at top left, rgba(20, 184, 166, 0.08), transparent 45%),
20
- radial-gradient(circle at bottom right, rgba(15, 76, 129, 0.08), transparent 40%),
21
- linear-gradient(to bottom, #f8fafc, #f1f5f9);
22
- background-attachment: fixed;
23
- color: #0f172a;
24
- font-family: 'Inter', Arial, Helvetica, sans-serif;
25
- letter-spacing: -0.015em;
26
- overflow-x: hidden;
27
- -webkit-font-smoothing: antialiased;
28
- }
29
-
30
- /* Premium Scrollbar */
31
- ::-webkit-scrollbar {
32
- width: 8px;
33
- height: 8px;
34
- }
35
-
36
- ::-webkit-scrollbar-track {
37
- background: transparent;
38
- }
39
-
40
- ::-webkit-scrollbar-thumb {
41
- background: rgba(100, 116, 139, 0.3);
42
- border-radius: 10px;
43
- }
44
-
45
- ::-webkit-scrollbar-thumb:hover {
46
- background: rgba(100, 116, 139, 0.5);
47
- }
48
-
49
- * {
50
- box-sizing: border-box;
51
- }
52
-
53
- a {
54
- color: inherit;
55
- text-decoration: none;
56
- }
57
-
58
- a {
59
- color: inherit;
60
- text-decoration: none;
61
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
civic-platform/app/layout.tsx DELETED
@@ -1,19 +0,0 @@
1
- import type { Metadata } from "next";
2
- import "./globals.css";
3
-
4
- export const metadata: Metadata = {
5
- title: "Civic Platform",
6
- description: "Crowdsourced civic issue reporting and resolution system",
7
- };
8
-
9
- export default function RootLayout({
10
- children,
11
- }: Readonly<{
12
- children: React.ReactNode;
13
- }>) {
14
- return (
15
- <html lang="en">
16
- <body suppressHydrationWarning>{children}</body>
17
- </html>
18
- );
19
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
civic-platform/app/login/page.tsx DELETED
@@ -1,40 +0,0 @@
1
- import { redirect } from "next/navigation";
2
- import { ShieldCheck } from "lucide-react";
3
- import { AppShell } from "@/components/app-shell";
4
- import { LoginForm } from "@/components/login-form";
5
- import { getCurrentUser } from "@/lib/auth";
6
-
7
- export default async function LoginPage() {
8
- const user = await getCurrentUser();
9
-
10
- if (user) {
11
- redirect(user.role === "citizen" ? "/complaints" : user.role === "field_officer" ? "/tasks" : "/dashboard");
12
- }
13
-
14
- return (
15
- <AppShell>
16
- <div className="mx-auto flex min-h-[75vh] w-full max-w-[420px] flex-col justify-center py-10 px-4 sm:px-0">
17
- <div className="mb-8 flex flex-col items-center text-center">
18
- <div className="mb-6 flex h-16 w-16 items-center justify-center rounded-[1.5rem] bg-gradient-to-br from-civic-primary to-[#0b5d87] text-white shadow-xl shadow-civic-primary/20">
19
- <ShieldCheck className="h-8 w-8" />
20
- </div>
21
- <h1 className="text-3xl font-extrabold tracking-tight text-slate-900">
22
- Welcome back
23
- </h1>
24
- <p className="mt-2 text-base text-slate-500">
25
- Sign in or create an account to participate
26
- </p>
27
- </div>
28
-
29
- <div className="relative w-full">
30
- {/* Subtle background glow */}
31
- <div className="absolute -inset-1 rounded-[2.5rem] bg-gradient-to-br from-civic-primary/15 via-transparent to-civic-secondary/10 blur-xl"></div>
32
-
33
- <div className="relative rounded-[2.5rem] border border-white/60 bg-white/70 p-6 shadow-2xl shadow-sky-900/5 backdrop-blur-2xl sm:p-8">
34
- <LoginForm />
35
- </div>
36
- </div>
37
- </div>
38
- </AppShell>
39
- );
40
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
civic-platform/app/map/page.tsx DELETED
@@ -1,59 +0,0 @@
1
- import { AppShell } from "@/components/app-shell";
2
- import { IssueMapBoard } from "@/components/issue-map-board";
3
- import { getCurrentUser } from "@/lib/auth";
4
- import {
5
- getMapComplaints,
6
- getPublicHotspots,
7
- type ComplaintHotspotItem,
8
- type ComplaintMapItem,
9
- } from "@/lib/api";
10
-
11
- const fallbackComplaints: ComplaintMapItem[] = [];
12
- const fallbackHotspots: ComplaintHotspotItem[] = [];
13
-
14
- export default async function MapPage() {
15
- const user = await getCurrentUser();
16
- const mapboxToken = process.env.MAPBOX_TOKEN ?? process.env.NEXT_PUBLIC_MAPBOX_TOKEN;
17
- const isOperationsView =
18
- user?.role === "department_operator" || user?.role === "municipal_admin" || user?.role === "field_officer";
19
-
20
- let complaints = fallbackComplaints;
21
- let hotspots = fallbackHotspots;
22
-
23
- try {
24
- [complaints, hotspots] = await Promise.all([
25
- getMapComplaints({ publicOnly: !isOperationsView }),
26
- getPublicHotspots(),
27
- ]);
28
- } catch {
29
- complaints = fallbackComplaints;
30
- hotspots = fallbackHotspots;
31
- }
32
-
33
- return (
34
- <AppShell>
35
- <section className="space-y-3">
36
- <p className="text-sm font-semibold uppercase tracking-[0.22em] text-civic-secondary">
37
- {isOperationsView ? "Operations map" : "Public map"}
38
- </p>
39
- <h1 className="text-4xl font-semibold tracking-tight text-civic-text">
40
- {isOperationsView
41
- ? "Monitor live civic issue activity with city-wide visibility."
42
- : "Explore active civic issues and hotspots across the city."}
43
- </h1>
44
- <p className="max-w-3xl text-sm leading-7 text-civic-muted">
45
- {isOperationsView
46
- ? "Use the live operations map to spot workload clusters, emergency incidents, and active complaint zones."
47
- : "Residents can browse public-safe complaint activity, see active clusters, and understand where issues are already being addressed."}
48
- </p>
49
- </section>
50
-
51
- <IssueMapBoard
52
- complaints={complaints}
53
- hotspots={hotspots}
54
- mapboxToken={mapboxToken}
55
- mode={isOperationsView ? "operations" : "public"}
56
- />
57
- </AppShell>
58
- );
59
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
civic-platform/app/notifications/page.tsx DELETED
@@ -1,35 +0,0 @@
1
- import { AppShell } from "@/components/app-shell";
2
- import { LiveSyncBadge } from "@/components/live-sync-badge";
3
- import { NotificationCenter } from "@/components/notification-center";
4
- import { requireUser } from "@/lib/auth";
5
- import { getNotifications, type ComplaintNotificationItem } from "@/lib/api";
6
-
7
- const fallbackNotifications: ComplaintNotificationItem[] = [];
8
-
9
- export default async function NotificationsPage() {
10
- const user = await requireUser();
11
- let notifications = fallbackNotifications;
12
-
13
- try {
14
- notifications = await getNotifications(user.id);
15
- } catch {
16
- notifications = fallbackNotifications;
17
- }
18
-
19
- return (
20
- <AppShell>
21
- <section className="space-y-3">
22
- <div className="flex flex-wrap items-center justify-between gap-3">
23
- <p className="text-sm font-semibold uppercase tracking-[0.22em] text-civic-secondary">Notifications</p>
24
- <LiveSyncBadge label="Inbox live refresh" />
25
- </div>
26
- <h1 className="text-4xl font-semibold tracking-tight text-civic-text">See the latest updates on your work and complaints.</h1>
27
- <p className="max-w-3xl text-sm leading-7 text-civic-muted">
28
- Assignment, field progress, reopening, and citizen verification updates appear here.
29
- </p>
30
- </section>
31
-
32
- <NotificationCenter notifications={notifications} userId={user.id} />
33
- </AppShell>
34
- );
35
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
civic-platform/app/page.tsx DELETED
@@ -1,15 +0,0 @@
1
- import { DomainGrid } from "@/components/domain-grid";
2
- import { Hero } from "@/components/hero";
3
- import { AppShell } from "@/components/app-shell";
4
-
5
- export default function Home() {
6
- return (
7
- <AppShell>
8
- <Hero />
9
-
10
- <section id="domains" className="py-6">
11
- <DomainGrid />
12
- </section>
13
- </AppShell>
14
- );
15
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
civic-platform/app/report/location/page.tsx DELETED
@@ -1,170 +0,0 @@
1
- import Link from "next/link";
2
- import { Crosshair, MapPinned, Navigation, ShieldAlert, Sparkles } from "lucide-react";
3
- import { AppShell } from "@/components/app-shell";
4
- import { FlowSteps } from "@/components/flow-steps";
5
-
6
- const steps = ["Choose domain", "Add evidence", "Confirm location", "Review", "Submit"];
7
-
8
- const locationChecks = [
9
- "Use current GPS for the quickest routing path.",
10
- "If GPS is weak, move the map pin to the exact road, lane, or landmark.",
11
- "Mention a nearby landmark so field staff can find the problem faster.",
12
- "Emergency incidents should be pinned as precisely as possible.",
13
- ];
14
-
15
- const locationFields = [
16
- "Latitude and longitude",
17
- "Resolved address",
18
- "Ward or zone",
19
- "Nearby landmark",
20
- "Road or locality name",
21
- "Manual pin adjustment status",
22
- ];
23
-
24
- export default function ReportLocationPage() {
25
- return (
26
- <AppShell>
27
- <section className="space-y-6">
28
- <div className="space-y-3">
29
- <p className="text-sm font-semibold uppercase tracking-[0.22em] text-civic-secondary">Report flow</p>
30
- <h1 className="text-4xl font-semibold tracking-tight text-civic-text">Confirm the issue location</h1>
31
- <p className="max-w-3xl text-sm leading-7 text-civic-muted">
32
- This step helps the system assign the correct ward, identify the responsible department, and improve
33
- routing accuracy for field teams.
34
- </p>
35
- </div>
36
-
37
- <FlowSteps currentStep={3} steps={steps} />
38
- </section>
39
-
40
- <section className="grid gap-6 lg:grid-cols-[1.15fr_0.85fr]">
41
- <div className="rounded-[2rem] border border-slate-200 bg-white p-6 shadow-sm sm:p-8">
42
- <div className="flex flex-wrap items-start justify-between gap-4">
43
- <div className="space-y-2">
44
- <p className="text-sm font-semibold uppercase tracking-[0.22em] text-civic-secondary">Step 3</p>
45
- <h2 className="text-2xl font-semibold text-civic-text">Map and address confirmation</h2>
46
- <p className="max-w-2xl text-sm leading-6 text-civic-muted">
47
- In the MVP, this screen should open the user location, show a map preview, and let the citizen correct
48
- the pin before moving to review.
49
- </p>
50
- </div>
51
- <div className="rounded-full bg-slate-100 px-4 py-2 text-sm font-semibold text-slate-700">
52
- Routing-ready
53
- </div>
54
- </div>
55
-
56
- <div className="mt-6 rounded-[1.75rem] border border-slate-200 bg-slate-50 p-5">
57
- <div className="flex items-center gap-3">
58
- <div className="flex h-12 w-12 items-center justify-center rounded-2xl bg-civic-primary text-white">
59
- <MapPinned className="h-6 w-6" />
60
- </div>
61
- <div>
62
- <p className="text-sm font-semibold uppercase tracking-[0.16em] text-civic-secondary">Interactive map</p>
63
- <h3 className="text-lg font-semibold text-civic-text">Pinned complaint location</h3>
64
- </div>
65
- </div>
66
-
67
- <div className="mt-5 flex min-h-[22rem] items-center justify-center rounded-[1.5rem] border border-dashed border-slate-300 bg-[linear-gradient(135deg,rgba(15,76,129,0.05),rgba(20,184,166,0.08))] p-6 text-center">
68
- <div className="max-w-md space-y-3">
69
- <div className="mx-auto flex h-14 w-14 items-center justify-center rounded-full bg-white shadow-sm">
70
- <Navigation className="h-6 w-6 text-civic-primary" />
71
- </div>
72
- <h4 className="text-xl font-semibold text-civic-text">Map preview placeholder</h4>
73
- <p className="text-sm leading-6 text-civic-muted">
74
- This area will use Leaflet with OpenStreetMap tiles, current location detection, drag-to-adjust pin,
75
- and reverse geocoding for address confirmation.
76
- </p>
77
- </div>
78
- </div>
79
-
80
- <div className="mt-5 flex flex-wrap gap-3">
81
- <button
82
- type="button"
83
- className="inline-flex items-center gap-2 rounded-full bg-civic-primary px-5 py-3 text-sm font-semibold text-white transition hover:-translate-y-0.5"
84
- >
85
- <Crosshair className="h-4 w-4" />
86
- Use current location
87
- </button>
88
- <button
89
- type="button"
90
- className="inline-flex items-center gap-2 rounded-full border border-slate-200 bg-white px-5 py-3 text-sm font-semibold text-slate-700 transition hover:border-civic-primary hover:text-civic-primary"
91
- >
92
- <MapPinned className="h-4 w-4" />
93
- Adjust map pin
94
- </button>
95
- </div>
96
- </div>
97
- </div>
98
-
99
- <div className="space-y-6">
100
- <section className="rounded-[2rem] border border-slate-200 bg-white p-6 shadow-sm">
101
- <div className="flex items-center gap-3">
102
- <div className="flex h-11 w-11 items-center justify-center rounded-2xl bg-civic-secondary/15 text-civic-secondary">
103
- <Sparkles className="h-5 w-5" />
104
- </div>
105
- <div>
106
- <p className="text-sm font-semibold uppercase tracking-[0.16em] text-civic-secondary">Stored data</p>
107
- <h2 className="text-xl font-semibold text-civic-text">Location fields for the complaint</h2>
108
- </div>
109
- </div>
110
-
111
- <div className="mt-5 grid gap-3">
112
- {locationFields.map((field) => (
113
- <div key={field} className="rounded-2xl bg-slate-50 px-4 py-3 text-sm font-medium text-civic-text">
114
- {field}
115
- </div>
116
- ))}
117
- </div>
118
- </section>
119
-
120
- <section className="rounded-[2rem] border border-amber-200 bg-amber-50 p-6 shadow-sm">
121
- <div className="flex items-start gap-3">
122
- <ShieldAlert className="mt-0.5 h-5 w-5 text-amber-700" />
123
- <div>
124
- <h2 className="text-lg font-semibold text-amber-950">Accuracy matters for emergency routing</h2>
125
- <p className="mt-2 text-sm leading-6 text-amber-900/85">
126
- A precise location helps responders find the incident faster, especially in flood, fire, collapse,
127
- or live wire complaints.
128
- </p>
129
- </div>
130
- </div>
131
-
132
- <div className="mt-5 space-y-3">
133
- {locationChecks.map((item) => (
134
- <div key={item} className="rounded-2xl bg-white/70 px-4 py-3 text-sm leading-6 text-amber-950">
135
- {item}
136
- </div>
137
- ))}
138
- </div>
139
- </section>
140
- </div>
141
- </section>
142
-
143
- <section className="flex flex-wrap items-center justify-between gap-4 rounded-[2rem] border border-slate-200 bg-white p-6 shadow-sm">
144
- <div>
145
- <p className="text-sm font-semibold uppercase tracking-[0.22em] text-civic-secondary">Next step</p>
146
- <h2 className="mt-2 text-2xl font-semibold text-civic-text">Review complaint details before submission</h2>
147
- <p className="mt-2 max-w-2xl text-sm leading-6 text-civic-muted">
148
- After location is confirmed, the user should see a review screen with domain, evidence, location, and the
149
- initial routing preview.
150
- </p>
151
- </div>
152
-
153
- <div className="flex flex-wrap gap-3">
154
- <Link
155
- className="inline-flex rounded-full border border-slate-200 px-5 py-3 text-sm font-semibold text-slate-700 transition hover:border-civic-primary hover:text-civic-primary"
156
- href="/report"
157
- >
158
- Back to report
159
- </Link>
160
- <Link
161
- className="inline-flex rounded-full bg-civic-primary px-5 py-3 text-sm font-semibold text-white transition hover:-translate-y-0.5"
162
- href="/report/review"
163
- >
164
- Continue to review
165
- </Link>
166
- </div>
167
- </section>
168
- </AppShell>
169
- );
170
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
civic-platform/app/report/page.tsx DELETED
@@ -1,27 +0,0 @@
1
- import { AppShell } from "@/components/app-shell";
2
- import { ReportForm } from "@/components/report-form";
3
- import { requireRole } from "@/lib/auth";
4
-
5
- export default async function ReportIssuePage() {
6
- const user = await requireRole(["citizen"]);
7
-
8
- return (
9
- <AppShell>
10
- <div className="mx-auto max-w-3xl space-y-8 pt-8">
11
- <div className="text-center space-y-4">
12
- <p className="text-sm font-bold uppercase tracking-[0.2em] text-civic-secondary">
13
- Citizen Reporting
14
- </p>
15
- <h1 className="text-4xl font-extrabold tracking-tight text-slate-900 sm:text-5xl">
16
- File a Report
17
- </h1>
18
- <p className="mx-auto max-w-xl text-lg text-slate-500">
19
- Tell us what happened and where. We will get it to the right department.
20
- </p>
21
- </div>
22
-
23
- <ReportForm citizenId={user.id} />
24
- </div>
25
- </AppShell>
26
- );
27
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
civic-platform/app/report/review/page.tsx DELETED
@@ -1,165 +0,0 @@
1
- import Link from "next/link";
2
- import { AlertTriangle, CheckCircle2, ChevronRight, ClipboardList, LocateFixed, ShieldAlert } from "lucide-react";
3
- import { AppShell } from "@/components/app-shell";
4
- import { FlowSteps } from "@/components/flow-steps";
5
-
6
- const steps = ["Choose domain", "Add evidence", "Confirm location", "Review", "Submit"];
7
-
8
- const reviewSections = [
9
- {
10
- icon: ClipboardList,
11
- title: "Complaint summary",
12
- items: [
13
- "Selected domain: Water Supply, Sewerage, and Drainage",
14
- "Likely sub-problem: Sewage overflow",
15
- "Urgency hint: High public impact",
16
- "Citizen note: Overflow near market entrance and bus stop",
17
- ],
18
- },
19
- {
20
- icon: LocateFixed,
21
- title: "Location confirmation",
22
- items: [
23
- "Coordinates captured from device",
24
- "Address resolved from map pin",
25
- "Ward and routing zone ready",
26
- "Nearby landmark noted for field staff",
27
- ],
28
- },
29
- {
30
- icon: CheckCircle2,
31
- title: "Evidence attached",
32
- items: [
33
- "1 photo uploaded",
34
- "0 video clips",
35
- "Optional voice note available for later support",
36
- "Media summary ready for department review",
37
- ],
38
- },
39
- ];
40
-
41
- const routingPreview = [
42
- "Primary department: Water and Sewerage",
43
- "Secondary support: Drainage unit if monsoon overflow is detected",
44
- "Initial priority: P2 High",
45
- "Notification path: citizen confirmation after submission",
46
- ];
47
-
48
- const submitChecks = [
49
- "Location is correct",
50
- "Complaint is within city jurisdiction",
51
- "Media is safe and relevant",
52
- "Description is clear enough for field response",
53
- ];
54
-
55
- export default function ReportReviewPage() {
56
- return (
57
- <AppShell>
58
- <section className="space-y-6">
59
- <div className="space-y-3">
60
- <p className="text-sm font-semibold uppercase tracking-[0.22em] text-civic-secondary">Report flow</p>
61
- <h1 className="text-4xl font-semibold tracking-tight text-civic-text">Review before final submission</h1>
62
- <p className="max-w-3xl text-sm leading-7 text-civic-muted">
63
- Check the issue details, location, and evidence once more before sending the complaint to the city team.
64
- </p>
65
- </div>
66
-
67
- <FlowSteps currentStep={4} steps={steps} />
68
- </section>
69
-
70
- <section className="grid gap-6 lg:grid-cols-[1.12fr_0.88fr]">
71
- <div className="space-y-6">
72
- {reviewSections.map(({ icon: Icon, title, items }) => (
73
- <section key={title} className="rounded-[2rem] border border-slate-200 bg-white p-6 shadow-sm">
74
- <div className="flex items-center gap-3">
75
- <div className="flex h-11 w-11 items-center justify-center rounded-2xl bg-civic-secondary/15 text-civic-secondary">
76
- <Icon className="h-5 w-5" />
77
- </div>
78
- <div>
79
- <p className="text-sm font-semibold uppercase tracking-[0.16em] text-civic-secondary">Summary</p>
80
- <h2 className="text-xl font-semibold text-civic-text">{title}</h2>
81
- </div>
82
- </div>
83
-
84
- <div className="mt-5 grid gap-3">
85
- {items.map((item) => (
86
- <div key={item} className="rounded-2xl bg-slate-50 px-4 py-3 text-sm leading-6 text-civic-text">
87
- {item}
88
- </div>
89
- ))}
90
- </div>
91
- </section>
92
- ))}
93
- </div>
94
-
95
- <div className="space-y-6">
96
- <section className="rounded-[2rem] border border-slate-200 bg-white p-6 shadow-sm">
97
- <div className="flex items-center gap-3">
98
- <div className="flex h-11 w-11 items-center justify-center rounded-2xl bg-civic-primary text-white">
99
- <ShieldAlert className="h-5 w-5" />
100
- </div>
101
- <div>
102
- <p className="text-sm font-semibold uppercase tracking-[0.16em] text-civic-secondary">Routing preview</p>
103
- <h2 className="text-xl font-semibold text-civic-text">What the system will do next</h2>
104
- </div>
105
- </div>
106
-
107
- <div className="mt-5 space-y-3">
108
- {routingPreview.map((item) => (
109
- <div key={item} className="rounded-2xl bg-slate-50 px-4 py-3 text-sm leading-6 text-civic-text">
110
- {item}
111
- </div>
112
- ))}
113
- </div>
114
- </section>
115
-
116
- <section className="rounded-[2rem] border border-amber-200 bg-amber-50 p-6 shadow-sm">
117
- <div className="flex items-start gap-3">
118
- <AlertTriangle className="mt-0.5 h-5 w-5 text-amber-700" />
119
- <div>
120
- <h2 className="text-lg font-semibold text-amber-950">Final checks before submission</h2>
121
- <p className="mt-2 text-sm leading-6 text-amber-900/85">
122
- A quick final check helps the city team act faster and reduces follow-up questions.
123
- </p>
124
- </div>
125
- </div>
126
-
127
- <div className="mt-5 space-y-3">
128
- {submitChecks.map((item) => (
129
- <div key={item} className="rounded-2xl bg-white/75 px-4 py-3 text-sm font-medium text-amber-950">
130
- {item}
131
- </div>
132
- ))}
133
- </div>
134
- </section>
135
- </div>
136
- </section>
137
-
138
- <section className="flex flex-wrap items-center justify-between gap-4 rounded-[2rem] border border-slate-200 bg-white p-6 shadow-sm">
139
- <div>
140
- <p className="text-sm font-semibold uppercase tracking-[0.22em] text-civic-secondary">Submit complaint</p>
141
- <h2 className="mt-2 text-2xl font-semibold text-civic-text">Ready to create the complaint record</h2>
142
- <p className="mt-2 max-w-2xl text-sm leading-6 text-civic-muted">
143
- Once submitted, your complaint will receive an ID and move into review, routing, and department assignment.
144
- </p>
145
- </div>
146
-
147
- <div className="flex flex-wrap gap-3">
148
- <Link
149
- className="inline-flex rounded-full border border-slate-200 px-5 py-3 text-sm font-semibold text-slate-700 transition hover:border-civic-primary hover:text-civic-primary"
150
- href="/report/location"
151
- >
152
- Back to location
153
- </Link>
154
- <Link
155
- className="inline-flex items-center gap-2 rounded-full bg-civic-primary px-5 py-3 text-sm font-semibold text-white transition hover:-translate-y-0.5"
156
- href="/report/success"
157
- >
158
- Submit complaint
159
- <ChevronRight className="h-4 w-4" />
160
- </Link>
161
- </div>
162
- </section>
163
- </AppShell>
164
- );
165
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
civic-platform/app/report/success/page.tsx DELETED
@@ -1,173 +0,0 @@
1
- import Link from "next/link";
2
- import { ArrowRight, CheckCircle2, Clock3, Hash, MapPinned, ShieldCheck } from "lucide-react";
3
- import { AppShell } from "@/components/app-shell";
4
- import { FlowSteps } from "@/components/flow-steps";
5
-
6
- const steps = ["Choose domain", "Add evidence", "Confirm location", "Review", "Submit"];
7
-
8
- const nextActions = [
9
- "Your report is stored with a complaint ID for tracking.",
10
- "The first visible status is Submitted.",
11
- "Routing and priority decide which department picks it up next.",
12
- "You will see updates when the complaint is assigned, worked on, or resolved.",
13
- ];
14
-
15
- const summaryCards = [
16
- {
17
- icon: Hash,
18
- label: "Complaint ID",
19
- value: "CP-2026-00124",
20
- },
21
- {
22
- icon: Clock3,
23
- label: "Current status",
24
- value: "Submitted",
25
- },
26
- {
27
- icon: MapPinned,
28
- label: "Location state",
29
- value: "Geo-tag confirmed",
30
- },
31
- ];
32
-
33
- type ReportSuccessPageProps = {
34
- searchParams?: Promise<{
35
- id?: string;
36
- code?: string;
37
- media?: string;
38
- }>;
39
- };
40
-
41
- export default async function ReportSuccessPage({ searchParams }: ReportSuccessPageProps) {
42
- const resolvedSearchParams = searchParams ? await searchParams : undefined;
43
- const complaintId = resolvedSearchParams?.id ?? "sample-complaint";
44
- const complaintCode = resolvedSearchParams?.code ?? "CP-2026-00124";
45
- const mediaStatus = resolvedSearchParams?.media ?? "complete";
46
-
47
- return (
48
- <AppShell>
49
- <section className="space-y-6">
50
- <div className="space-y-3">
51
- <p className="text-sm font-semibold uppercase tracking-[0.22em] text-civic-secondary">Report flow</p>
52
- <h1 className="text-4xl font-semibold tracking-tight text-civic-text">Complaint submitted successfully</h1>
53
- <p className="max-w-3xl text-sm leading-7 text-civic-muted">
54
- Your complaint has been recorded. Keep the complaint ID handy and use the tracking page for updates.
55
- </p>
56
- </div>
57
-
58
- <FlowSteps currentStep={5} steps={steps} />
59
- </section>
60
-
61
- <section className="rounded-[2rem] bg-civic-primary px-6 py-8 text-white shadow-civic lg:px-8">
62
- <div className="grid gap-8 lg:grid-cols-[1.15fr_0.85fr]">
63
- <div className="space-y-5">
64
- <div className="flex h-16 w-16 items-center justify-center rounded-3xl bg-white text-civic-primary">
65
- <CheckCircle2 className="h-8 w-8" />
66
- </div>
67
- <div className="space-y-3">
68
- <h2 className="text-3xl font-semibold tracking-tight">Your complaint is now in the system.</h2>
69
- <p className="max-w-2xl text-sm leading-7 text-white/80">
70
- It will now move through review, routing, assignment, and resolution. You can track progress from your
71
- complaints page at any time.
72
- </p>
73
- {mediaStatus === "partial" ? (
74
- <p className="rounded-2xl bg-white/10 px-4 py-3 text-sm text-white/90">
75
- Your complaint was submitted, but one or more media files could not be uploaded.
76
- </p>
77
- ) : null}
78
- </div>
79
- <div className="flex flex-wrap gap-3">
80
- <Link
81
- className="inline-flex items-center gap-2 rounded-full bg-white px-5 py-3 text-sm font-semibold text-civic-primary transition hover:-translate-y-0.5"
82
- href="/complaints"
83
- >
84
- View my complaints
85
- <ArrowRight className="h-4 w-4" />
86
- </Link>
87
- <Link
88
- className="inline-flex items-center gap-2 rounded-full border border-white/30 px-5 py-3 text-sm font-semibold text-white transition hover:bg-white/10"
89
- href={`/complaints/${complaintId}`}
90
- >
91
- Open complaint detail
92
- <ArrowRight className="h-4 w-4" />
93
- </Link>
94
- </div>
95
- </div>
96
-
97
- <div className="grid gap-4">
98
- {summaryCards.map(({ icon: Icon, label, value }) => (
99
- <div key={label} className="rounded-3xl border border-white/15 bg-white/10 p-5 backdrop-blur">
100
- <Icon className="mb-4 h-6 w-6 text-civic-accent" />
101
- <p className="text-sm font-semibold uppercase tracking-[0.16em] text-white/75">{label}</p>
102
- <p className="mt-2 text-xl font-semibold text-white">
103
- {label === "Complaint ID" ? complaintCode : value}
104
- </p>
105
- </div>
106
- ))}
107
- </div>
108
- </div>
109
- </section>
110
-
111
- <section className="grid gap-6 lg:grid-cols-[1fr_1fr]">
112
- <section className="rounded-[2rem] border border-slate-200 bg-white p-6 shadow-sm">
113
- <div className="flex items-center gap-3">
114
- <div className="flex h-11 w-11 items-center justify-center rounded-2xl bg-civic-secondary/15 text-civic-secondary">
115
- <ShieldCheck className="h-5 w-5" />
116
- </div>
117
- <div>
118
- <p className="text-sm font-semibold uppercase tracking-[0.16em] text-civic-secondary">What happens next</p>
119
- <h2 className="text-xl font-semibold text-civic-text">Post-submission workflow</h2>
120
- </div>
121
- </div>
122
-
123
- <div className="mt-5 space-y-3">
124
- {nextActions.map((item) => (
125
- <div key={item} className="rounded-2xl bg-slate-50 px-4 py-3 text-sm leading-6 text-civic-text">
126
- {item}
127
- </div>
128
- ))}
129
- </div>
130
- </section>
131
-
132
- <section className="rounded-[2rem] border border-slate-200 bg-white p-6 shadow-sm">
133
- <p className="text-sm font-semibold uppercase tracking-[0.22em] text-civic-secondary">Helpful shortcuts</p>
134
- <h2 className="mt-2 text-2xl font-semibold text-civic-text">What you can do from here</h2>
135
- <p className="mt-3 text-sm leading-7 text-civic-muted">
136
- You can report another issue, open the complaint detail page, or switch to emergency reporting if the
137
- situation has become urgent.
138
- </p>
139
-
140
- <div className="mt-6 grid gap-3 sm:grid-cols-2">
141
- {[
142
- "Complaint ID",
143
- "Current status",
144
- "Expected next step",
145
- "Tracking shortcut",
146
- "Submit another issue",
147
- "Emergency reporting shortcut",
148
- ].map((item) => (
149
- <div key={item} className="rounded-2xl border border-slate-200 p-4 text-sm font-medium text-civic-text">
150
- {item}
151
- </div>
152
- ))}
153
- </div>
154
-
155
- <div className="mt-6 flex flex-wrap gap-3">
156
- <Link
157
- className="inline-flex rounded-full border border-slate-200 px-5 py-3 text-sm font-semibold text-slate-700 transition hover:border-civic-primary hover:text-civic-primary"
158
- href="/report"
159
- >
160
- Submit another issue
161
- </Link>
162
- <Link
163
- className="inline-flex rounded-full bg-civic-danger px-5 py-3 text-sm font-semibold text-white transition hover:-translate-y-0.5"
164
- href="/emergency"
165
- >
166
- Emergency reporting
167
- </Link>
168
- </div>
169
- </section>
170
- </section>
171
- </AppShell>
172
- );
173
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
civic-platform/app/tasks/page.tsx DELETED
@@ -1,35 +0,0 @@
1
- import { AppShell } from "@/components/app-shell";
2
- import { FieldTaskBoard } from "@/components/field-task-board";
3
- import { LiveSyncBadge } from "@/components/live-sync-badge";
4
- import { requireRole } from "@/lib/auth";
5
- import { getOfficerAssignments, type ComplaintAssignmentItem } from "@/lib/api";
6
-
7
- const fallbackAssignments: ComplaintAssignmentItem[] = [];
8
-
9
- export default async function TasksPage() {
10
- const user = await requireRole(["field_officer"]);
11
- let assignments = fallbackAssignments;
12
-
13
- try {
14
- assignments = await getOfficerAssignments(user.id);
15
- } catch {
16
- assignments = fallbackAssignments;
17
- }
18
-
19
- return (
20
- <AppShell>
21
- <section className="space-y-3">
22
- <div className="flex flex-wrap items-center justify-between gap-3">
23
- <p className="text-sm font-semibold uppercase tracking-[0.22em] text-civic-secondary">Field operations</p>
24
- <LiveSyncBadge label="Task board live refresh" />
25
- </div>
26
- <h1 className="text-4xl font-semibold tracking-tight text-civic-text">Work through assigned on-ground tasks.</h1>
27
- <p className="max-w-3xl text-sm leading-7 text-civic-muted">
28
- Accept work, start field action, and mark completion so the complaint lifecycle stays accurate for citizens and operators.
29
- </p>
30
- </section>
31
-
32
- <FieldTaskBoard assignments={assignments} officerId={user.id} />
33
- </AppShell>
34
- );
35
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
civic-platform/app/template.tsx DELETED
@@ -1,16 +0,0 @@
1
- "use client";
2
-
3
- import { motion } from "framer-motion";
4
-
5
- export default function Template({ children }: { children: React.ReactNode }) {
6
- return (
7
- <motion.div
8
- initial={{ opacity: 0, y: 15 }}
9
- animate={{ opacity: 1, y: 0 }}
10
- exit={{ opacity: 0, y: -15 }}
11
- transition={{ ease: "circOut", duration: 0.4 }}
12
- >
13
- {children}
14
- </motion.div>
15
- );
16
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
civic-platform/backend/.env.example DELETED
@@ -1,12 +0,0 @@
1
- PORT=4000
2
- NODE_ENV=development
3
- DB_HOST=localhost
4
- DB_PORT=5432
5
- DB_NAME=civicpulse
6
- DB_USER=postgres
7
- DB_PASSWORD=your_password_here
8
- DB_SSL=false
9
- DEMO_AUTH_PASSWORD=civicpulse123
10
- CORS_ORIGINS=http://localhost:3000
11
- RATE_LIMIT_WINDOW_MS=60000
12
- RATE_LIMIT_MAX_REQUESTS=120
 
 
 
 
 
 
 
 
 
 
 
 
 
civic-platform/backend/README.md DELETED
@@ -1,38 +0,0 @@
1
- # Backend
2
-
3
- Node.js and TypeScript backend for the CivicPulse project.
4
-
5
- ## Quick start
6
-
7
- 1. Copy `.env.example` to `.env`
8
- 2. Set your PostgreSQL connection values
9
- 3. Create the `civicpulse` database
10
- 4. Apply `sql/001_initial_schema.sql`
11
- 5. Apply `sql/002_seed_core_data.sql`
12
- 6. Run `npm install`
13
- 7. Run `npm run dev`
14
-
15
- Alternative local setup:
16
-
17
- - Run `npm run db:init` to apply both SQL files using the configured PostgreSQL connection
18
-
19
- ## Main folders
20
-
21
- - `src/config` environment config
22
- - `src/db` PostgreSQL connection
23
- - `src/routes` shared routes
24
- - `src/modules/complaints` complaint APIs
25
- - `sql` database schema files
26
-
27
- ## First routes
28
-
29
- - `GET /api/health`
30
- - `GET /api/departments`
31
- - `GET /api/domains`
32
- - `GET /api/complaints`
33
- - `GET /api/complaints/citizen/:citizenId`
34
- - `GET /api/complaints/:id`
35
- - `GET /api/complaints/:id/history`
36
- - `POST /api/complaints`
37
- - `PATCH /api/complaints/:id/status`
38
- - `POST /api/complaints/:id/assign`
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
civic-platform/backend/backend-dev.log DELETED
@@ -1,5 +0,0 @@
1
-
2
- > civicpulse-backend@0.1.0 dev
3
- > tsx watch src/server.ts
4
-
5
- CivicPulse backend listening on port 4000
 
 
 
 
 
 
civic-platform/backend/backend-run.log DELETED
@@ -1,2 +0,0 @@
1
- CivicPulse backend listening on port 4000
2
- ^C
 
 
 
civic-platform/backend/backend.crash.log DELETED
Binary file (88 Bytes)
 
civic-platform/backend/backend.detached.log DELETED
@@ -1 +0,0 @@
1
- CivicPulse backend listening on port 4000
 
 
civic-platform/backend/backend.dev.log DELETED
Binary file (88 Bytes)
 
civic-platform/backend/package-lock.json DELETED
@@ -1,1817 +0,0 @@
1
- {
2
- "name": "civicpulse-backend",
3
- "version": "0.1.0",
4
- "lockfileVersion": 3,
5
- "requires": true,
6
- "packages": {
7
- "": {
8
- "name": "civicpulse-backend",
9
- "version": "0.1.0",
10
- "dependencies": {
11
- "cors": "^2.8.5",
12
- "dotenv": "^16.4.7",
13
- "express": "^4.21.2",
14
- "multer": "^2.1.1",
15
- "pg": "^8.13.3"
16
- },
17
- "devDependencies": {
18
- "@types/cors": "^2.8.17",
19
- "@types/express": "^5.0.1",
20
- "@types/multer": "^2.1.0",
21
- "@types/node": "^22.13.10",
22
- "@types/pg": "^8.11.11",
23
- "tsx": "^4.19.3",
24
- "typescript": "^5.8.2"
25
- }
26
- },
27
- "node_modules/@esbuild/aix-ppc64": {
28
- "version": "0.27.4",
29
- "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.4.tgz",
30
- "integrity": "sha512-cQPwL2mp2nSmHHJlCyoXgHGhbEPMrEEU5xhkcy3Hs/O7nGZqEpZ2sUtLaL9MORLtDfRvVl2/3PAuEkYZH0Ty8Q==",
31
- "cpu": [
32
- "ppc64"
33
- ],
34
- "dev": true,
35
- "license": "MIT",
36
- "optional": true,
37
- "os": [
38
- "aix"
39
- ],
40
- "engines": {
41
- "node": ">=18"
42
- }
43
- },
44
- "node_modules/@esbuild/android-arm": {
45
- "version": "0.27.4",
46
- "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.4.tgz",
47
- "integrity": "sha512-X9bUgvxiC8CHAGKYufLIHGXPJWnr0OCdR0anD2e21vdvgCI8lIfqFbnoeOz7lBjdrAGUhqLZLcQo6MLhTO2DKQ==",
48
- "cpu": [
49
- "arm"
50
- ],
51
- "dev": true,
52
- "license": "MIT",
53
- "optional": true,
54
- "os": [
55
- "android"
56
- ],
57
- "engines": {
58
- "node": ">=18"
59
- }
60
- },
61
- "node_modules/@esbuild/android-arm64": {
62
- "version": "0.27.4",
63
- "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.4.tgz",
64
- "integrity": "sha512-gdLscB7v75wRfu7QSm/zg6Rx29VLdy9eTr2t44sfTW7CxwAtQghZ4ZnqHk3/ogz7xao0QAgrkradbBzcqFPasw==",
65
- "cpu": [
66
- "arm64"
67
- ],
68
- "dev": true,
69
- "license": "MIT",
70
- "optional": true,
71
- "os": [
72
- "android"
73
- ],
74
- "engines": {
75
- "node": ">=18"
76
- }
77
- },
78
- "node_modules/@esbuild/android-x64": {
79
- "version": "0.27.4",
80
- "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.4.tgz",
81
- "integrity": "sha512-PzPFnBNVF292sfpfhiyiXCGSn9HZg5BcAz+ivBuSsl6Rk4ga1oEXAamhOXRFyMcjwr2DVtm40G65N3GLeH1Lvw==",
82
- "cpu": [
83
- "x64"
84
- ],
85
- "dev": true,
86
- "license": "MIT",
87
- "optional": true,
88
- "os": [
89
- "android"
90
- ],
91
- "engines": {
92
- "node": ">=18"
93
- }
94
- },
95
- "node_modules/@esbuild/darwin-arm64": {
96
- "version": "0.27.4",
97
- "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.4.tgz",
98
- "integrity": "sha512-b7xaGIwdJlht8ZFCvMkpDN6uiSmnxxK56N2GDTMYPr2/gzvfdQN8rTfBsvVKmIVY/X7EM+/hJKEIbbHs9oA4tQ==",
99
- "cpu": [
100
- "arm64"
101
- ],
102
- "dev": true,
103
- "license": "MIT",
104
- "optional": true,
105
- "os": [
106
- "darwin"
107
- ],
108
- "engines": {
109
- "node": ">=18"
110
- }
111
- },
112
- "node_modules/@esbuild/darwin-x64": {
113
- "version": "0.27.4",
114
- "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.4.tgz",
115
- "integrity": "sha512-sR+OiKLwd15nmCdqpXMnuJ9W2kpy0KigzqScqHI3Hqwr7IXxBp3Yva+yJwoqh7rE8V77tdoheRYataNKL4QrPw==",
116
- "cpu": [
117
- "x64"
118
- ],
119
- "dev": true,
120
- "license": "MIT",
121
- "optional": true,
122
- "os": [
123
- "darwin"
124
- ],
125
- "engines": {
126
- "node": ">=18"
127
- }
128
- },
129
- "node_modules/@esbuild/freebsd-arm64": {
130
- "version": "0.27.4",
131
- "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.4.tgz",
132
- "integrity": "sha512-jnfpKe+p79tCnm4GVav68A7tUFeKQwQyLgESwEAUzyxk/TJr4QdGog9sqWNcUbr/bZt/O/HXouspuQDd9JxFSw==",
133
- "cpu": [
134
- "arm64"
135
- ],
136
- "dev": true,
137
- "license": "MIT",
138
- "optional": true,
139
- "os": [
140
- "freebsd"
141
- ],
142
- "engines": {
143
- "node": ">=18"
144
- }
145
- },
146
- "node_modules/@esbuild/freebsd-x64": {
147
- "version": "0.27.4",
148
- "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.4.tgz",
149
- "integrity": "sha512-2kb4ceA/CpfUrIcTUl1wrP/9ad9Atrp5J94Lq69w7UwOMolPIGrfLSvAKJp0RTvkPPyn6CIWrNy13kyLikZRZQ==",
150
- "cpu": [
151
- "x64"
152
- ],
153
- "dev": true,
154
- "license": "MIT",
155
- "optional": true,
156
- "os": [
157
- "freebsd"
158
- ],
159
- "engines": {
160
- "node": ">=18"
161
- }
162
- },
163
- "node_modules/@esbuild/linux-arm": {
164
- "version": "0.27.4",
165
- "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.4.tgz",
166
- "integrity": "sha512-aBYgcIxX/wd5n2ys0yESGeYMGF+pv6g0DhZr3G1ZG4jMfruU9Tl1i2Z+Wnj9/KjGz1lTLCcorqE2viePZqj4Eg==",
167
- "cpu": [
168
- "arm"
169
- ],
170
- "dev": true,
171
- "license": "MIT",
172
- "optional": true,
173
- "os": [
174
- "linux"
175
- ],
176
- "engines": {
177
- "node": ">=18"
178
- }
179
- },
180
- "node_modules/@esbuild/linux-arm64": {
181
- "version": "0.27.4",
182
- "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.4.tgz",
183
- "integrity": "sha512-7nQOttdzVGth1iz57kxg9uCz57dxQLHWxopL6mYuYthohPKEK0vU0C3O21CcBK6KDlkYVcnDXY099HcCDXd9dA==",
184
- "cpu": [
185
- "arm64"
186
- ],
187
- "dev": true,
188
- "license": "MIT",
189
- "optional": true,
190
- "os": [
191
- "linux"
192
- ],
193
- "engines": {
194
- "node": ">=18"
195
- }
196
- },
197
- "node_modules/@esbuild/linux-ia32": {
198
- "version": "0.27.4",
199
- "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.4.tgz",
200
- "integrity": "sha512-oPtixtAIzgvzYcKBQM/qZ3R+9TEUd1aNJQu0HhGyqtx6oS7qTpvjheIWBbes4+qu1bNlo2V4cbkISr8q6gRBFA==",
201
- "cpu": [
202
- "ia32"
203
- ],
204
- "dev": true,
205
- "license": "MIT",
206
- "optional": true,
207
- "os": [
208
- "linux"
209
- ],
210
- "engines": {
211
- "node": ">=18"
212
- }
213
- },
214
- "node_modules/@esbuild/linux-loong64": {
215
- "version": "0.27.4",
216
- "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.4.tgz",
217
- "integrity": "sha512-8mL/vh8qeCoRcFH2nM8wm5uJP+ZcVYGGayMavi8GmRJjuI3g1v6Z7Ni0JJKAJW+m0EtUuARb6Lmp4hMjzCBWzA==",
218
- "cpu": [
219
- "loong64"
220
- ],
221
- "dev": true,
222
- "license": "MIT",
223
- "optional": true,
224
- "os": [
225
- "linux"
226
- ],
227
- "engines": {
228
- "node": ">=18"
229
- }
230
- },
231
- "node_modules/@esbuild/linux-mips64el": {
232
- "version": "0.27.4",
233
- "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.4.tgz",
234
- "integrity": "sha512-1RdrWFFiiLIW7LQq9Q2NES+HiD4NyT8Itj9AUeCl0IVCA459WnPhREKgwrpaIfTOe+/2rdntisegiPWn/r/aAw==",
235
- "cpu": [
236
- "mips64el"
237
- ],
238
- "dev": true,
239
- "license": "MIT",
240
- "optional": true,
241
- "os": [
242
- "linux"
243
- ],
244
- "engines": {
245
- "node": ">=18"
246
- }
247
- },
248
- "node_modules/@esbuild/linux-ppc64": {
249
- "version": "0.27.4",
250
- "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.4.tgz",
251
- "integrity": "sha512-tLCwNG47l3sd9lpfyx9LAGEGItCUeRCWeAx6x2Jmbav65nAwoPXfewtAdtbtit/pJFLUWOhpv0FpS6GQAmPrHA==",
252
- "cpu": [
253
- "ppc64"
254
- ],
255
- "dev": true,
256
- "license": "MIT",
257
- "optional": true,
258
- "os": [
259
- "linux"
260
- ],
261
- "engines": {
262
- "node": ">=18"
263
- }
264
- },
265
- "node_modules/@esbuild/linux-riscv64": {
266
- "version": "0.27.4",
267
- "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.4.tgz",
268
- "integrity": "sha512-BnASypppbUWyqjd1KIpU4AUBiIhVr6YlHx/cnPgqEkNoVOhHg+YiSVxM1RLfiy4t9cAulbRGTNCKOcqHrEQLIw==",
269
- "cpu": [
270
- "riscv64"
271
- ],
272
- "dev": true,
273
- "license": "MIT",
274
- "optional": true,
275
- "os": [
276
- "linux"
277
- ],
278
- "engines": {
279
- "node": ">=18"
280
- }
281
- },
282
- "node_modules/@esbuild/linux-s390x": {
283
- "version": "0.27.4",
284
- "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.4.tgz",
285
- "integrity": "sha512-+eUqgb/Z7vxVLezG8bVB9SfBie89gMueS+I0xYh2tJdw3vqA/0ImZJ2ROeWwVJN59ihBeZ7Tu92dF/5dy5FttA==",
286
- "cpu": [
287
- "s390x"
288
- ],
289
- "dev": true,
290
- "license": "MIT",
291
- "optional": true,
292
- "os": [
293
- "linux"
294
- ],
295
- "engines": {
296
- "node": ">=18"
297
- }
298
- },
299
- "node_modules/@esbuild/linux-x64": {
300
- "version": "0.27.4",
301
- "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.4.tgz",
302
- "integrity": "sha512-S5qOXrKV8BQEzJPVxAwnryi2+Iq5pB40gTEIT69BQONqR7JH1EPIcQ/Uiv9mCnn05jff9umq/5nqzxlqTOg9NA==",
303
- "cpu": [
304
- "x64"
305
- ],
306
- "dev": true,
307
- "license": "MIT",
308
- "optional": true,
309
- "os": [
310
- "linux"
311
- ],
312
- "engines": {
313
- "node": ">=18"
314
- }
315
- },
316
- "node_modules/@esbuild/netbsd-arm64": {
317
- "version": "0.27.4",
318
- "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.4.tgz",
319
- "integrity": "sha512-xHT8X4sb0GS8qTqiwzHqpY00C95DPAq7nAwX35Ie/s+LO9830hrMd3oX0ZMKLvy7vsonee73x0lmcdOVXFzd6Q==",
320
- "cpu": [
321
- "arm64"
322
- ],
323
- "dev": true,
324
- "license": "MIT",
325
- "optional": true,
326
- "os": [
327
- "netbsd"
328
- ],
329
- "engines": {
330
- "node": ">=18"
331
- }
332
- },
333
- "node_modules/@esbuild/netbsd-x64": {
334
- "version": "0.27.4",
335
- "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.4.tgz",
336
- "integrity": "sha512-RugOvOdXfdyi5Tyv40kgQnI0byv66BFgAqjdgtAKqHoZTbTF2QqfQrFwa7cHEORJf6X2ht+l9ABLMP0dnKYsgg==",
337
- "cpu": [
338
- "x64"
339
- ],
340
- "dev": true,
341
- "license": "MIT",
342
- "optional": true,
343
- "os": [
344
- "netbsd"
345
- ],
346
- "engines": {
347
- "node": ">=18"
348
- }
349
- },
350
- "node_modules/@esbuild/openbsd-arm64": {
351
- "version": "0.27.4",
352
- "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.4.tgz",
353
- "integrity": "sha512-2MyL3IAaTX+1/qP0O1SwskwcwCoOI4kV2IBX1xYnDDqthmq5ArrW94qSIKCAuRraMgPOmG0RDTA74mzYNQA9ow==",
354
- "cpu": [
355
- "arm64"
356
- ],
357
- "dev": true,
358
- "license": "MIT",
359
- "optional": true,
360
- "os": [
361
- "openbsd"
362
- ],
363
- "engines": {
364
- "node": ">=18"
365
- }
366
- },
367
- "node_modules/@esbuild/openbsd-x64": {
368
- "version": "0.27.4",
369
- "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.4.tgz",
370
- "integrity": "sha512-u8fg/jQ5aQDfsnIV6+KwLOf1CmJnfu1ShpwqdwC0uA7ZPwFws55Ngc12vBdeUdnuWoQYx/SOQLGDcdlfXhYmXQ==",
371
- "cpu": [
372
- "x64"
373
- ],
374
- "dev": true,
375
- "license": "MIT",
376
- "optional": true,
377
- "os": [
378
- "openbsd"
379
- ],
380
- "engines": {
381
- "node": ">=18"
382
- }
383
- },
384
- "node_modules/@esbuild/openharmony-arm64": {
385
- "version": "0.27.4",
386
- "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.4.tgz",
387
- "integrity": "sha512-JkTZrl6VbyO8lDQO3yv26nNr2RM2yZzNrNHEsj9bm6dOwwu9OYN28CjzZkH57bh4w0I2F7IodpQvUAEd1mbWXg==",
388
- "cpu": [
389
- "arm64"
390
- ],
391
- "dev": true,
392
- "license": "MIT",
393
- "optional": true,
394
- "os": [
395
- "openharmony"
396
- ],
397
- "engines": {
398
- "node": ">=18"
399
- }
400
- },
401
- "node_modules/@esbuild/sunos-x64": {
402
- "version": "0.27.4",
403
- "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.4.tgz",
404
- "integrity": "sha512-/gOzgaewZJfeJTlsWhvUEmUG4tWEY2Spp5M20INYRg2ZKl9QPO3QEEgPeRtLjEWSW8FilRNacPOg8R1uaYkA6g==",
405
- "cpu": [
406
- "x64"
407
- ],
408
- "dev": true,
409
- "license": "MIT",
410
- "optional": true,
411
- "os": [
412
- "sunos"
413
- ],
414
- "engines": {
415
- "node": ">=18"
416
- }
417
- },
418
- "node_modules/@esbuild/win32-arm64": {
419
- "version": "0.27.4",
420
- "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.4.tgz",
421
- "integrity": "sha512-Z9SExBg2y32smoDQdf1HRwHRt6vAHLXcxD2uGgO/v2jK7Y718Ix4ndsbNMU/+1Qiem9OiOdaqitioZwxivhXYg==",
422
- "cpu": [
423
- "arm64"
424
- ],
425
- "dev": true,
426
- "license": "MIT",
427
- "optional": true,
428
- "os": [
429
- "win32"
430
- ],
431
- "engines": {
432
- "node": ">=18"
433
- }
434
- },
435
- "node_modules/@esbuild/win32-ia32": {
436
- "version": "0.27.4",
437
- "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.4.tgz",
438
- "integrity": "sha512-DAyGLS0Jz5G5iixEbMHi5KdiApqHBWMGzTtMiJ72ZOLhbu/bzxgAe8Ue8CTS3n3HbIUHQz/L51yMdGMeoxXNJw==",
439
- "cpu": [
440
- "ia32"
441
- ],
442
- "dev": true,
443
- "license": "MIT",
444
- "optional": true,
445
- "os": [
446
- "win32"
447
- ],
448
- "engines": {
449
- "node": ">=18"
450
- }
451
- },
452
- "node_modules/@esbuild/win32-x64": {
453
- "version": "0.27.4",
454
- "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.4.tgz",
455
- "integrity": "sha512-+knoa0BDoeXgkNvvV1vvbZX4+hizelrkwmGJBdT17t8FNPwG2lKemmuMZlmaNQ3ws3DKKCxpb4zRZEIp3UxFCg==",
456
- "cpu": [
457
- "x64"
458
- ],
459
- "dev": true,
460
- "license": "MIT",
461
- "optional": true,
462
- "os": [
463
- "win32"
464
- ],
465
- "engines": {
466
- "node": ">=18"
467
- }
468
- },
469
- "node_modules/@types/body-parser": {
470
- "version": "1.19.6",
471
- "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz",
472
- "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==",
473
- "dev": true,
474
- "license": "MIT",
475
- "dependencies": {
476
- "@types/connect": "*",
477
- "@types/node": "*"
478
- }
479
- },
480
- "node_modules/@types/connect": {
481
- "version": "3.4.38",
482
- "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz",
483
- "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==",
484
- "dev": true,
485
- "license": "MIT",
486
- "dependencies": {
487
- "@types/node": "*"
488
- }
489
- },
490
- "node_modules/@types/cors": {
491
- "version": "2.8.19",
492
- "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.19.tgz",
493
- "integrity": "sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==",
494
- "dev": true,
495
- "license": "MIT",
496
- "dependencies": {
497
- "@types/node": "*"
498
- }
499
- },
500
- "node_modules/@types/express": {
501
- "version": "5.0.6",
502
- "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.6.tgz",
503
- "integrity": "sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==",
504
- "dev": true,
505
- "license": "MIT",
506
- "dependencies": {
507
- "@types/body-parser": "*",
508
- "@types/express-serve-static-core": "^5.0.0",
509
- "@types/serve-static": "^2"
510
- }
511
- },
512
- "node_modules/@types/express-serve-static-core": {
513
- "version": "5.1.1",
514
- "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.1.1.tgz",
515
- "integrity": "sha512-v4zIMr/cX7/d2BpAEX3KNKL/JrT1s43s96lLvvdTmza1oEvDudCqK9aF/djc/SWgy8Yh0h30TZx5VpzqFCxk5A==",
516
- "dev": true,
517
- "license": "MIT",
518
- "dependencies": {
519
- "@types/node": "*",
520
- "@types/qs": "*",
521
- "@types/range-parser": "*",
522
- "@types/send": "*"
523
- }
524
- },
525
- "node_modules/@types/http-errors": {
526
- "version": "2.0.5",
527
- "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz",
528
- "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==",
529
- "dev": true,
530
- "license": "MIT"
531
- },
532
- "node_modules/@types/multer": {
533
- "version": "2.1.0",
534
- "resolved": "https://registry.npmjs.org/@types/multer/-/multer-2.1.0.tgz",
535
- "integrity": "sha512-zYZb0+nJhOHtPpGDb3vqPjwpdeGlGC157VpkqNQL+UU2qwoacoQ7MpsAmUptI/0Oa127X32JzWDqQVEXp2RcIA==",
536
- "dev": true,
537
- "license": "MIT",
538
- "dependencies": {
539
- "@types/express": "*"
540
- }
541
- },
542
- "node_modules/@types/node": {
543
- "version": "22.19.15",
544
- "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.15.tgz",
545
- "integrity": "sha512-F0R/h2+dsy5wJAUe3tAU6oqa2qbWY5TpNfL/RGmo1y38hiyO1w3x2jPtt76wmuaJI4DQnOBu21cNXQ2STIUUWg==",
546
- "dev": true,
547
- "license": "MIT",
548
- "dependencies": {
549
- "undici-types": "~6.21.0"
550
- }
551
- },
552
- "node_modules/@types/pg": {
553
- "version": "8.20.0",
554
- "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.20.0.tgz",
555
- "integrity": "sha512-bEPFOaMAHTEP1EzpvHTbmwR8UsFyHSKsRisLIHVMXnpNefSbGA1bD6CVy+qKjGSqmZqNqBDV2azOBo8TgkcVow==",
556
- "dev": true,
557
- "license": "MIT",
558
- "dependencies": {
559
- "@types/node": "*",
560
- "pg-protocol": "*",
561
- "pg-types": "^2.2.0"
562
- }
563
- },
564
- "node_modules/@types/qs": {
565
- "version": "6.15.0",
566
- "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.15.0.tgz",
567
- "integrity": "sha512-JawvT8iBVWpzTrz3EGw9BTQFg3BQNmwERdKE22vlTxawwtbyUSlMppvZYKLZzB5zgACXdXxbD3m1bXaMqP/9ow==",
568
- "dev": true,
569
- "license": "MIT"
570
- },
571
- "node_modules/@types/range-parser": {
572
- "version": "1.2.7",
573
- "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz",
574
- "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==",
575
- "dev": true,
576
- "license": "MIT"
577
- },
578
- "node_modules/@types/send": {
579
- "version": "1.2.1",
580
- "resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz",
581
- "integrity": "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==",
582
- "dev": true,
583
- "license": "MIT",
584
- "dependencies": {
585
- "@types/node": "*"
586
- }
587
- },
588
- "node_modules/@types/serve-static": {
589
- "version": "2.2.0",
590
- "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-2.2.0.tgz",
591
- "integrity": "sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ==",
592
- "dev": true,
593
- "license": "MIT",
594
- "dependencies": {
595
- "@types/http-errors": "*",
596
- "@types/node": "*"
597
- }
598
- },
599
- "node_modules/accepts": {
600
- "version": "1.3.8",
601
- "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
602
- "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
603
- "license": "MIT",
604
- "dependencies": {
605
- "mime-types": "~2.1.34",
606
- "negotiator": "0.6.3"
607
- },
608
- "engines": {
609
- "node": ">= 0.6"
610
- }
611
- },
612
- "node_modules/append-field": {
613
- "version": "1.0.0",
614
- "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz",
615
- "integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==",
616
- "license": "MIT"
617
- },
618
- "node_modules/array-flatten": {
619
- "version": "1.1.1",
620
- "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
621
- "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==",
622
- "license": "MIT"
623
- },
624
- "node_modules/body-parser": {
625
- "version": "1.20.4",
626
- "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.4.tgz",
627
- "integrity": "sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==",
628
- "license": "MIT",
629
- "dependencies": {
630
- "bytes": "~3.1.2",
631
- "content-type": "~1.0.5",
632
- "debug": "2.6.9",
633
- "depd": "2.0.0",
634
- "destroy": "~1.2.0",
635
- "http-errors": "~2.0.1",
636
- "iconv-lite": "~0.4.24",
637
- "on-finished": "~2.4.1",
638
- "qs": "~6.14.0",
639
- "raw-body": "~2.5.3",
640
- "type-is": "~1.6.18",
641
- "unpipe": "~1.0.0"
642
- },
643
- "engines": {
644
- "node": ">= 0.8",
645
- "npm": "1.2.8000 || >= 1.4.16"
646
- }
647
- },
648
- "node_modules/buffer-from": {
649
- "version": "1.1.2",
650
- "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
651
- "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
652
- "license": "MIT"
653
- },
654
- "node_modules/busboy": {
655
- "version": "1.6.0",
656
- "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz",
657
- "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==",
658
- "dependencies": {
659
- "streamsearch": "^1.1.0"
660
- },
661
- "engines": {
662
- "node": ">=10.16.0"
663
- }
664
- },
665
- "node_modules/bytes": {
666
- "version": "3.1.2",
667
- "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
668
- "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
669
- "license": "MIT",
670
- "engines": {
671
- "node": ">= 0.8"
672
- }
673
- },
674
- "node_modules/call-bind-apply-helpers": {
675
- "version": "1.0.2",
676
- "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
677
- "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
678
- "license": "MIT",
679
- "dependencies": {
680
- "es-errors": "^1.3.0",
681
- "function-bind": "^1.1.2"
682
- },
683
- "engines": {
684
- "node": ">= 0.4"
685
- }
686
- },
687
- "node_modules/call-bound": {
688
- "version": "1.0.4",
689
- "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
690
- "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
691
- "license": "MIT",
692
- "dependencies": {
693
- "call-bind-apply-helpers": "^1.0.2",
694
- "get-intrinsic": "^1.3.0"
695
- },
696
- "engines": {
697
- "node": ">= 0.4"
698
- },
699
- "funding": {
700
- "url": "https://github.com/sponsors/ljharb"
701
- }
702
- },
703
- "node_modules/concat-stream": {
704
- "version": "2.0.0",
705
- "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz",
706
- "integrity": "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==",
707
- "engines": [
708
- "node >= 6.0"
709
- ],
710
- "license": "MIT",
711
- "dependencies": {
712
- "buffer-from": "^1.0.0",
713
- "inherits": "^2.0.3",
714
- "readable-stream": "^3.0.2",
715
- "typedarray": "^0.0.6"
716
- }
717
- },
718
- "node_modules/content-disposition": {
719
- "version": "0.5.4",
720
- "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
721
- "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==",
722
- "license": "MIT",
723
- "dependencies": {
724
- "safe-buffer": "5.2.1"
725
- },
726
- "engines": {
727
- "node": ">= 0.6"
728
- }
729
- },
730
- "node_modules/content-type": {
731
- "version": "1.0.5",
732
- "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
733
- "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
734
- "license": "MIT",
735
- "engines": {
736
- "node": ">= 0.6"
737
- }
738
- },
739
- "node_modules/cookie": {
740
- "version": "0.7.2",
741
- "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
742
- "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
743
- "license": "MIT",
744
- "engines": {
745
- "node": ">= 0.6"
746
- }
747
- },
748
- "node_modules/cookie-signature": {
749
- "version": "1.0.7",
750
- "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz",
751
- "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==",
752
- "license": "MIT"
753
- },
754
- "node_modules/cors": {
755
- "version": "2.8.6",
756
- "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz",
757
- "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==",
758
- "license": "MIT",
759
- "dependencies": {
760
- "object-assign": "^4",
761
- "vary": "^1"
762
- },
763
- "engines": {
764
- "node": ">= 0.10"
765
- },
766
- "funding": {
767
- "type": "opencollective",
768
- "url": "https://opencollective.com/express"
769
- }
770
- },
771
- "node_modules/debug": {
772
- "version": "2.6.9",
773
- "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
774
- "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
775
- "license": "MIT",
776
- "dependencies": {
777
- "ms": "2.0.0"
778
- }
779
- },
780
- "node_modules/depd": {
781
- "version": "2.0.0",
782
- "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
783
- "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
784
- "license": "MIT",
785
- "engines": {
786
- "node": ">= 0.8"
787
- }
788
- },
789
- "node_modules/destroy": {
790
- "version": "1.2.0",
791
- "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
792
- "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==",
793
- "license": "MIT",
794
- "engines": {
795
- "node": ">= 0.8",
796
- "npm": "1.2.8000 || >= 1.4.16"
797
- }
798
- },
799
- "node_modules/dotenv": {
800
- "version": "16.6.1",
801
- "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz",
802
- "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==",
803
- "license": "BSD-2-Clause",
804
- "engines": {
805
- "node": ">=12"
806
- },
807
- "funding": {
808
- "url": "https://dotenvx.com"
809
- }
810
- },
811
- "node_modules/dunder-proto": {
812
- "version": "1.0.1",
813
- "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
814
- "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
815
- "license": "MIT",
816
- "dependencies": {
817
- "call-bind-apply-helpers": "^1.0.1",
818
- "es-errors": "^1.3.0",
819
- "gopd": "^1.2.0"
820
- },
821
- "engines": {
822
- "node": ">= 0.4"
823
- }
824
- },
825
- "node_modules/ee-first": {
826
- "version": "1.1.1",
827
- "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
828
- "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
829
- "license": "MIT"
830
- },
831
- "node_modules/encodeurl": {
832
- "version": "2.0.0",
833
- "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
834
- "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
835
- "license": "MIT",
836
- "engines": {
837
- "node": ">= 0.8"
838
- }
839
- },
840
- "node_modules/es-define-property": {
841
- "version": "1.0.1",
842
- "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
843
- "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
844
- "license": "MIT",
845
- "engines": {
846
- "node": ">= 0.4"
847
- }
848
- },
849
- "node_modules/es-errors": {
850
- "version": "1.3.0",
851
- "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
852
- "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
853
- "license": "MIT",
854
- "engines": {
855
- "node": ">= 0.4"
856
- }
857
- },
858
- "node_modules/es-object-atoms": {
859
- "version": "1.1.1",
860
- "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
861
- "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
862
- "license": "MIT",
863
- "dependencies": {
864
- "es-errors": "^1.3.0"
865
- },
866
- "engines": {
867
- "node": ">= 0.4"
868
- }
869
- },
870
- "node_modules/esbuild": {
871
- "version": "0.27.4",
872
- "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.4.tgz",
873
- "integrity": "sha512-Rq4vbHnYkK5fws5NF7MYTU68FPRE1ajX7heQ/8QXXWqNgqqJ/GkmmyxIzUnf2Sr/bakf8l54716CcMGHYhMrrQ==",
874
- "dev": true,
875
- "hasInstallScript": true,
876
- "license": "MIT",
877
- "bin": {
878
- "esbuild": "bin/esbuild"
879
- },
880
- "engines": {
881
- "node": ">=18"
882
- },
883
- "optionalDependencies": {
884
- "@esbuild/aix-ppc64": "0.27.4",
885
- "@esbuild/android-arm": "0.27.4",
886
- "@esbuild/android-arm64": "0.27.4",
887
- "@esbuild/android-x64": "0.27.4",
888
- "@esbuild/darwin-arm64": "0.27.4",
889
- "@esbuild/darwin-x64": "0.27.4",
890
- "@esbuild/freebsd-arm64": "0.27.4",
891
- "@esbuild/freebsd-x64": "0.27.4",
892
- "@esbuild/linux-arm": "0.27.4",
893
- "@esbuild/linux-arm64": "0.27.4",
894
- "@esbuild/linux-ia32": "0.27.4",
895
- "@esbuild/linux-loong64": "0.27.4",
896
- "@esbuild/linux-mips64el": "0.27.4",
897
- "@esbuild/linux-ppc64": "0.27.4",
898
- "@esbuild/linux-riscv64": "0.27.4",
899
- "@esbuild/linux-s390x": "0.27.4",
900
- "@esbuild/linux-x64": "0.27.4",
901
- "@esbuild/netbsd-arm64": "0.27.4",
902
- "@esbuild/netbsd-x64": "0.27.4",
903
- "@esbuild/openbsd-arm64": "0.27.4",
904
- "@esbuild/openbsd-x64": "0.27.4",
905
- "@esbuild/openharmony-arm64": "0.27.4",
906
- "@esbuild/sunos-x64": "0.27.4",
907
- "@esbuild/win32-arm64": "0.27.4",
908
- "@esbuild/win32-ia32": "0.27.4",
909
- "@esbuild/win32-x64": "0.27.4"
910
- }
911
- },
912
- "node_modules/escape-html": {
913
- "version": "1.0.3",
914
- "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
915
- "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
916
- "license": "MIT"
917
- },
918
- "node_modules/etag": {
919
- "version": "1.8.1",
920
- "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
921
- "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
922
- "license": "MIT",
923
- "engines": {
924
- "node": ">= 0.6"
925
- }
926
- },
927
- "node_modules/express": {
928
- "version": "4.22.1",
929
- "resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz",
930
- "integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==",
931
- "license": "MIT",
932
- "dependencies": {
933
- "accepts": "~1.3.8",
934
- "array-flatten": "1.1.1",
935
- "body-parser": "~1.20.3",
936
- "content-disposition": "~0.5.4",
937
- "content-type": "~1.0.4",
938
- "cookie": "~0.7.1",
939
- "cookie-signature": "~1.0.6",
940
- "debug": "2.6.9",
941
- "depd": "2.0.0",
942
- "encodeurl": "~2.0.0",
943
- "escape-html": "~1.0.3",
944
- "etag": "~1.8.1",
945
- "finalhandler": "~1.3.1",
946
- "fresh": "~0.5.2",
947
- "http-errors": "~2.0.0",
948
- "merge-descriptors": "1.0.3",
949
- "methods": "~1.1.2",
950
- "on-finished": "~2.4.1",
951
- "parseurl": "~1.3.3",
952
- "path-to-regexp": "~0.1.12",
953
- "proxy-addr": "~2.0.7",
954
- "qs": "~6.14.0",
955
- "range-parser": "~1.2.1",
956
- "safe-buffer": "5.2.1",
957
- "send": "~0.19.0",
958
- "serve-static": "~1.16.2",
959
- "setprototypeof": "1.2.0",
960
- "statuses": "~2.0.1",
961
- "type-is": "~1.6.18",
962
- "utils-merge": "1.0.1",
963
- "vary": "~1.1.2"
964
- },
965
- "engines": {
966
- "node": ">= 0.10.0"
967
- },
968
- "funding": {
969
- "type": "opencollective",
970
- "url": "https://opencollective.com/express"
971
- }
972
- },
973
- "node_modules/finalhandler": {
974
- "version": "1.3.2",
975
- "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz",
976
- "integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==",
977
- "license": "MIT",
978
- "dependencies": {
979
- "debug": "2.6.9",
980
- "encodeurl": "~2.0.0",
981
- "escape-html": "~1.0.3",
982
- "on-finished": "~2.4.1",
983
- "parseurl": "~1.3.3",
984
- "statuses": "~2.0.2",
985
- "unpipe": "~1.0.0"
986
- },
987
- "engines": {
988
- "node": ">= 0.8"
989
- }
990
- },
991
- "node_modules/forwarded": {
992
- "version": "0.2.0",
993
- "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
994
- "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
995
- "license": "MIT",
996
- "engines": {
997
- "node": ">= 0.6"
998
- }
999
- },
1000
- "node_modules/fresh": {
1001
- "version": "0.5.2",
1002
- "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
1003
- "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==",
1004
- "license": "MIT",
1005
- "engines": {
1006
- "node": ">= 0.6"
1007
- }
1008
- },
1009
- "node_modules/fsevents": {
1010
- "version": "2.3.3",
1011
- "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
1012
- "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
1013
- "dev": true,
1014
- "hasInstallScript": true,
1015
- "license": "MIT",
1016
- "optional": true,
1017
- "os": [
1018
- "darwin"
1019
- ],
1020
- "engines": {
1021
- "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
1022
- }
1023
- },
1024
- "node_modules/function-bind": {
1025
- "version": "1.1.2",
1026
- "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
1027
- "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
1028
- "license": "MIT",
1029
- "funding": {
1030
- "url": "https://github.com/sponsors/ljharb"
1031
- }
1032
- },
1033
- "node_modules/get-intrinsic": {
1034
- "version": "1.3.0",
1035
- "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
1036
- "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
1037
- "license": "MIT",
1038
- "dependencies": {
1039
- "call-bind-apply-helpers": "^1.0.2",
1040
- "es-define-property": "^1.0.1",
1041
- "es-errors": "^1.3.0",
1042
- "es-object-atoms": "^1.1.1",
1043
- "function-bind": "^1.1.2",
1044
- "get-proto": "^1.0.1",
1045
- "gopd": "^1.2.0",
1046
- "has-symbols": "^1.1.0",
1047
- "hasown": "^2.0.2",
1048
- "math-intrinsics": "^1.1.0"
1049
- },
1050
- "engines": {
1051
- "node": ">= 0.4"
1052
- },
1053
- "funding": {
1054
- "url": "https://github.com/sponsors/ljharb"
1055
- }
1056
- },
1057
- "node_modules/get-proto": {
1058
- "version": "1.0.1",
1059
- "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
1060
- "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
1061
- "license": "MIT",
1062
- "dependencies": {
1063
- "dunder-proto": "^1.0.1",
1064
- "es-object-atoms": "^1.0.0"
1065
- },
1066
- "engines": {
1067
- "node": ">= 0.4"
1068
- }
1069
- },
1070
- "node_modules/get-tsconfig": {
1071
- "version": "4.13.7",
1072
- "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.7.tgz",
1073
- "integrity": "sha512-7tN6rFgBlMgpBML5j8typ92BKFi2sFQvIdpAqLA2beia5avZDrMs0FLZiM5etShWq5irVyGcGMEA1jcDaK7A/Q==",
1074
- "dev": true,
1075
- "license": "MIT",
1076
- "dependencies": {
1077
- "resolve-pkg-maps": "^1.0.0"
1078
- },
1079
- "funding": {
1080
- "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1"
1081
- }
1082
- },
1083
- "node_modules/gopd": {
1084
- "version": "1.2.0",
1085
- "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
1086
- "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
1087
- "license": "MIT",
1088
- "engines": {
1089
- "node": ">= 0.4"
1090
- },
1091
- "funding": {
1092
- "url": "https://github.com/sponsors/ljharb"
1093
- }
1094
- },
1095
- "node_modules/has-symbols": {
1096
- "version": "1.1.0",
1097
- "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
1098
- "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
1099
- "license": "MIT",
1100
- "engines": {
1101
- "node": ">= 0.4"
1102
- },
1103
- "funding": {
1104
- "url": "https://github.com/sponsors/ljharb"
1105
- }
1106
- },
1107
- "node_modules/hasown": {
1108
- "version": "2.0.2",
1109
- "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
1110
- "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
1111
- "license": "MIT",
1112
- "dependencies": {
1113
- "function-bind": "^1.1.2"
1114
- },
1115
- "engines": {
1116
- "node": ">= 0.4"
1117
- }
1118
- },
1119
- "node_modules/http-errors": {
1120
- "version": "2.0.1",
1121
- "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz",
1122
- "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==",
1123
- "license": "MIT",
1124
- "dependencies": {
1125
- "depd": "~2.0.0",
1126
- "inherits": "~2.0.4",
1127
- "setprototypeof": "~1.2.0",
1128
- "statuses": "~2.0.2",
1129
- "toidentifier": "~1.0.1"
1130
- },
1131
- "engines": {
1132
- "node": ">= 0.8"
1133
- },
1134
- "funding": {
1135
- "type": "opencollective",
1136
- "url": "https://opencollective.com/express"
1137
- }
1138
- },
1139
- "node_modules/iconv-lite": {
1140
- "version": "0.4.24",
1141
- "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
1142
- "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
1143
- "license": "MIT",
1144
- "dependencies": {
1145
- "safer-buffer": ">= 2.1.2 < 3"
1146
- },
1147
- "engines": {
1148
- "node": ">=0.10.0"
1149
- }
1150
- },
1151
- "node_modules/inherits": {
1152
- "version": "2.0.4",
1153
- "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
1154
- "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
1155
- "license": "ISC"
1156
- },
1157
- "node_modules/ipaddr.js": {
1158
- "version": "1.9.1",
1159
- "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
1160
- "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
1161
- "license": "MIT",
1162
- "engines": {
1163
- "node": ">= 0.10"
1164
- }
1165
- },
1166
- "node_modules/math-intrinsics": {
1167
- "version": "1.1.0",
1168
- "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
1169
- "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
1170
- "license": "MIT",
1171
- "engines": {
1172
- "node": ">= 0.4"
1173
- }
1174
- },
1175
- "node_modules/media-typer": {
1176
- "version": "0.3.0",
1177
- "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
1178
- "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==",
1179
- "license": "MIT",
1180
- "engines": {
1181
- "node": ">= 0.6"
1182
- }
1183
- },
1184
- "node_modules/merge-descriptors": {
1185
- "version": "1.0.3",
1186
- "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz",
1187
- "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==",
1188
- "license": "MIT",
1189
- "funding": {
1190
- "url": "https://github.com/sponsors/sindresorhus"
1191
- }
1192
- },
1193
- "node_modules/methods": {
1194
- "version": "1.1.2",
1195
- "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
1196
- "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==",
1197
- "license": "MIT",
1198
- "engines": {
1199
- "node": ">= 0.6"
1200
- }
1201
- },
1202
- "node_modules/mime": {
1203
- "version": "1.6.0",
1204
- "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
1205
- "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
1206
- "license": "MIT",
1207
- "bin": {
1208
- "mime": "cli.js"
1209
- },
1210
- "engines": {
1211
- "node": ">=4"
1212
- }
1213
- },
1214
- "node_modules/mime-db": {
1215
- "version": "1.52.0",
1216
- "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
1217
- "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
1218
- "license": "MIT",
1219
- "engines": {
1220
- "node": ">= 0.6"
1221
- }
1222
- },
1223
- "node_modules/mime-types": {
1224
- "version": "2.1.35",
1225
- "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
1226
- "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
1227
- "license": "MIT",
1228
- "dependencies": {
1229
- "mime-db": "1.52.0"
1230
- },
1231
- "engines": {
1232
- "node": ">= 0.6"
1233
- }
1234
- },
1235
- "node_modules/ms": {
1236
- "version": "2.0.0",
1237
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
1238
- "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
1239
- "license": "MIT"
1240
- },
1241
- "node_modules/multer": {
1242
- "version": "2.1.1",
1243
- "resolved": "https://registry.npmjs.org/multer/-/multer-2.1.1.tgz",
1244
- "integrity": "sha512-mo+QTzKlx8R7E5ylSXxWzGoXoZbOsRMpyitcht8By2KHvMbf3tjwosZ/Mu/XYU6UuJ3VZnODIrak5ZrPiPyB6A==",
1245
- "license": "MIT",
1246
- "dependencies": {
1247
- "append-field": "^1.0.0",
1248
- "busboy": "^1.6.0",
1249
- "concat-stream": "^2.0.0",
1250
- "type-is": "^1.6.18"
1251
- },
1252
- "engines": {
1253
- "node": ">= 10.16.0"
1254
- },
1255
- "funding": {
1256
- "type": "opencollective",
1257
- "url": "https://opencollective.com/express"
1258
- }
1259
- },
1260
- "node_modules/negotiator": {
1261
- "version": "0.6.3",
1262
- "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
1263
- "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
1264
- "license": "MIT",
1265
- "engines": {
1266
- "node": ">= 0.6"
1267
- }
1268
- },
1269
- "node_modules/object-assign": {
1270
- "version": "4.1.1",
1271
- "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
1272
- "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
1273
- "license": "MIT",
1274
- "engines": {
1275
- "node": ">=0.10.0"
1276
- }
1277
- },
1278
- "node_modules/object-inspect": {
1279
- "version": "1.13.4",
1280
- "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
1281
- "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
1282
- "license": "MIT",
1283
- "engines": {
1284
- "node": ">= 0.4"
1285
- },
1286
- "funding": {
1287
- "url": "https://github.com/sponsors/ljharb"
1288
- }
1289
- },
1290
- "node_modules/on-finished": {
1291
- "version": "2.4.1",
1292
- "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
1293
- "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
1294
- "license": "MIT",
1295
- "dependencies": {
1296
- "ee-first": "1.1.1"
1297
- },
1298
- "engines": {
1299
- "node": ">= 0.8"
1300
- }
1301
- },
1302
- "node_modules/parseurl": {
1303
- "version": "1.3.3",
1304
- "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
1305
- "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
1306
- "license": "MIT",
1307
- "engines": {
1308
- "node": ">= 0.8"
1309
- }
1310
- },
1311
- "node_modules/path-to-regexp": {
1312
- "version": "0.1.13",
1313
- "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.13.tgz",
1314
- "integrity": "sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA==",
1315
- "license": "MIT"
1316
- },
1317
- "node_modules/pg": {
1318
- "version": "8.20.0",
1319
- "resolved": "https://registry.npmjs.org/pg/-/pg-8.20.0.tgz",
1320
- "integrity": "sha512-ldhMxz2r8fl/6QkXnBD3CR9/xg694oT6DZQ2s6c/RI28OjtSOpxnPrUCGOBJ46RCUxcWdx3p6kw/xnDHjKvaRA==",
1321
- "license": "MIT",
1322
- "dependencies": {
1323
- "pg-connection-string": "^2.12.0",
1324
- "pg-pool": "^3.13.0",
1325
- "pg-protocol": "^1.13.0",
1326
- "pg-types": "2.2.0",
1327
- "pgpass": "1.0.5"
1328
- },
1329
- "engines": {
1330
- "node": ">= 16.0.0"
1331
- },
1332
- "optionalDependencies": {
1333
- "pg-cloudflare": "^1.3.0"
1334
- },
1335
- "peerDependencies": {
1336
- "pg-native": ">=3.0.1"
1337
- },
1338
- "peerDependenciesMeta": {
1339
- "pg-native": {
1340
- "optional": true
1341
- }
1342
- }
1343
- },
1344
- "node_modules/pg-cloudflare": {
1345
- "version": "1.3.0",
1346
- "resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.3.0.tgz",
1347
- "integrity": "sha512-6lswVVSztmHiRtD6I8hw4qP/nDm1EJbKMRhf3HCYaqud7frGysPv7FYJ5noZQdhQtN2xJnimfMtvQq21pdbzyQ==",
1348
- "license": "MIT",
1349
- "optional": true
1350
- },
1351
- "node_modules/pg-connection-string": {
1352
- "version": "2.12.0",
1353
- "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.12.0.tgz",
1354
- "integrity": "sha512-U7qg+bpswf3Cs5xLzRqbXbQl85ng0mfSV/J0nnA31MCLgvEaAo7CIhmeyrmJpOr7o+zm0rXK+hNnT5l9RHkCkQ==",
1355
- "license": "MIT"
1356
- },
1357
- "node_modules/pg-int8": {
1358
- "version": "1.0.1",
1359
- "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz",
1360
- "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==",
1361
- "license": "ISC",
1362
- "engines": {
1363
- "node": ">=4.0.0"
1364
- }
1365
- },
1366
- "node_modules/pg-pool": {
1367
- "version": "3.13.0",
1368
- "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.13.0.tgz",
1369
- "integrity": "sha512-gB+R+Xud1gLFuRD/QgOIgGOBE2KCQPaPwkzBBGC9oG69pHTkhQeIuejVIk3/cnDyX39av2AxomQiyPT13WKHQA==",
1370
- "license": "MIT",
1371
- "peerDependencies": {
1372
- "pg": ">=8.0"
1373
- }
1374
- },
1375
- "node_modules/pg-protocol": {
1376
- "version": "1.13.0",
1377
- "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.13.0.tgz",
1378
- "integrity": "sha512-zzdvXfS6v89r6v7OcFCHfHlyG/wvry1ALxZo4LqgUoy7W9xhBDMaqOuMiF3qEV45VqsN6rdlcehHrfDtlCPc8w==",
1379
- "license": "MIT"
1380
- },
1381
- "node_modules/pg-types": {
1382
- "version": "2.2.0",
1383
- "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz",
1384
- "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==",
1385
- "license": "MIT",
1386
- "dependencies": {
1387
- "pg-int8": "1.0.1",
1388
- "postgres-array": "~2.0.0",
1389
- "postgres-bytea": "~1.0.0",
1390
- "postgres-date": "~1.0.4",
1391
- "postgres-interval": "^1.1.0"
1392
- },
1393
- "engines": {
1394
- "node": ">=4"
1395
- }
1396
- },
1397
- "node_modules/pgpass": {
1398
- "version": "1.0.5",
1399
- "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz",
1400
- "integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==",
1401
- "license": "MIT",
1402
- "dependencies": {
1403
- "split2": "^4.1.0"
1404
- }
1405
- },
1406
- "node_modules/postgres-array": {
1407
- "version": "2.0.0",
1408
- "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz",
1409
- "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==",
1410
- "license": "MIT",
1411
- "engines": {
1412
- "node": ">=4"
1413
- }
1414
- },
1415
- "node_modules/postgres-bytea": {
1416
- "version": "1.0.1",
1417
- "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.1.tgz",
1418
- "integrity": "sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ==",
1419
- "license": "MIT",
1420
- "engines": {
1421
- "node": ">=0.10.0"
1422
- }
1423
- },
1424
- "node_modules/postgres-date": {
1425
- "version": "1.0.7",
1426
- "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz",
1427
- "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==",
1428
- "license": "MIT",
1429
- "engines": {
1430
- "node": ">=0.10.0"
1431
- }
1432
- },
1433
- "node_modules/postgres-interval": {
1434
- "version": "1.2.0",
1435
- "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz",
1436
- "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==",
1437
- "license": "MIT",
1438
- "dependencies": {
1439
- "xtend": "^4.0.0"
1440
- },
1441
- "engines": {
1442
- "node": ">=0.10.0"
1443
- }
1444
- },
1445
- "node_modules/proxy-addr": {
1446
- "version": "2.0.7",
1447
- "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
1448
- "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
1449
- "license": "MIT",
1450
- "dependencies": {
1451
- "forwarded": "0.2.0",
1452
- "ipaddr.js": "1.9.1"
1453
- },
1454
- "engines": {
1455
- "node": ">= 0.10"
1456
- }
1457
- },
1458
- "node_modules/qs": {
1459
- "version": "6.14.2",
1460
- "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.2.tgz",
1461
- "integrity": "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==",
1462
- "license": "BSD-3-Clause",
1463
- "dependencies": {
1464
- "side-channel": "^1.1.0"
1465
- },
1466
- "engines": {
1467
- "node": ">=0.6"
1468
- },
1469
- "funding": {
1470
- "url": "https://github.com/sponsors/ljharb"
1471
- }
1472
- },
1473
- "node_modules/range-parser": {
1474
- "version": "1.2.1",
1475
- "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
1476
- "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
1477
- "license": "MIT",
1478
- "engines": {
1479
- "node": ">= 0.6"
1480
- }
1481
- },
1482
- "node_modules/raw-body": {
1483
- "version": "2.5.3",
1484
- "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz",
1485
- "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==",
1486
- "license": "MIT",
1487
- "dependencies": {
1488
- "bytes": "~3.1.2",
1489
- "http-errors": "~2.0.1",
1490
- "iconv-lite": "~0.4.24",
1491
- "unpipe": "~1.0.0"
1492
- },
1493
- "engines": {
1494
- "node": ">= 0.8"
1495
- }
1496
- },
1497
- "node_modules/readable-stream": {
1498
- "version": "3.6.2",
1499
- "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
1500
- "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
1501
- "license": "MIT",
1502
- "dependencies": {
1503
- "inherits": "^2.0.3",
1504
- "string_decoder": "^1.1.1",
1505
- "util-deprecate": "^1.0.1"
1506
- },
1507
- "engines": {
1508
- "node": ">= 6"
1509
- }
1510
- },
1511
- "node_modules/resolve-pkg-maps": {
1512
- "version": "1.0.0",
1513
- "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz",
1514
- "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==",
1515
- "dev": true,
1516
- "license": "MIT",
1517
- "funding": {
1518
- "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1"
1519
- }
1520
- },
1521
- "node_modules/safe-buffer": {
1522
- "version": "5.2.1",
1523
- "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
1524
- "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
1525
- "funding": [
1526
- {
1527
- "type": "github",
1528
- "url": "https://github.com/sponsors/feross"
1529
- },
1530
- {
1531
- "type": "patreon",
1532
- "url": "https://www.patreon.com/feross"
1533
- },
1534
- {
1535
- "type": "consulting",
1536
- "url": "https://feross.org/support"
1537
- }
1538
- ],
1539
- "license": "MIT"
1540
- },
1541
- "node_modules/safer-buffer": {
1542
- "version": "2.1.2",
1543
- "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
1544
- "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
1545
- "license": "MIT"
1546
- },
1547
- "node_modules/send": {
1548
- "version": "0.19.2",
1549
- "resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz",
1550
- "integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==",
1551
- "license": "MIT",
1552
- "dependencies": {
1553
- "debug": "2.6.9",
1554
- "depd": "2.0.0",
1555
- "destroy": "1.2.0",
1556
- "encodeurl": "~2.0.0",
1557
- "escape-html": "~1.0.3",
1558
- "etag": "~1.8.1",
1559
- "fresh": "~0.5.2",
1560
- "http-errors": "~2.0.1",
1561
- "mime": "1.6.0",
1562
- "ms": "2.1.3",
1563
- "on-finished": "~2.4.1",
1564
- "range-parser": "~1.2.1",
1565
- "statuses": "~2.0.2"
1566
- },
1567
- "engines": {
1568
- "node": ">= 0.8.0"
1569
- }
1570
- },
1571
- "node_modules/send/node_modules/ms": {
1572
- "version": "2.1.3",
1573
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
1574
- "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
1575
- "license": "MIT"
1576
- },
1577
- "node_modules/serve-static": {
1578
- "version": "1.16.3",
1579
- "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz",
1580
- "integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==",
1581
- "license": "MIT",
1582
- "dependencies": {
1583
- "encodeurl": "~2.0.0",
1584
- "escape-html": "~1.0.3",
1585
- "parseurl": "~1.3.3",
1586
- "send": "~0.19.1"
1587
- },
1588
- "engines": {
1589
- "node": ">= 0.8.0"
1590
- }
1591
- },
1592
- "node_modules/setprototypeof": {
1593
- "version": "1.2.0",
1594
- "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
1595
- "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
1596
- "license": "ISC"
1597
- },
1598
- "node_modules/side-channel": {
1599
- "version": "1.1.0",
1600
- "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
1601
- "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==",
1602
- "license": "MIT",
1603
- "dependencies": {
1604
- "es-errors": "^1.3.0",
1605
- "object-inspect": "^1.13.3",
1606
- "side-channel-list": "^1.0.0",
1607
- "side-channel-map": "^1.0.1",
1608
- "side-channel-weakmap": "^1.0.2"
1609
- },
1610
- "engines": {
1611
- "node": ">= 0.4"
1612
- },
1613
- "funding": {
1614
- "url": "https://github.com/sponsors/ljharb"
1615
- }
1616
- },
1617
- "node_modules/side-channel-list": {
1618
- "version": "1.0.0",
1619
- "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz",
1620
- "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==",
1621
- "license": "MIT",
1622
- "dependencies": {
1623
- "es-errors": "^1.3.0",
1624
- "object-inspect": "^1.13.3"
1625
- },
1626
- "engines": {
1627
- "node": ">= 0.4"
1628
- },
1629
- "funding": {
1630
- "url": "https://github.com/sponsors/ljharb"
1631
- }
1632
- },
1633
- "node_modules/side-channel-map": {
1634
- "version": "1.0.1",
1635
- "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz",
1636
- "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
1637
- "license": "MIT",
1638
- "dependencies": {
1639
- "call-bound": "^1.0.2",
1640
- "es-errors": "^1.3.0",
1641
- "get-intrinsic": "^1.2.5",
1642
- "object-inspect": "^1.13.3"
1643
- },
1644
- "engines": {
1645
- "node": ">= 0.4"
1646
- },
1647
- "funding": {
1648
- "url": "https://github.com/sponsors/ljharb"
1649
- }
1650
- },
1651
- "node_modules/side-channel-weakmap": {
1652
- "version": "1.0.2",
1653
- "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
1654
- "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
1655
- "license": "MIT",
1656
- "dependencies": {
1657
- "call-bound": "^1.0.2",
1658
- "es-errors": "^1.3.0",
1659
- "get-intrinsic": "^1.2.5",
1660
- "object-inspect": "^1.13.3",
1661
- "side-channel-map": "^1.0.1"
1662
- },
1663
- "engines": {
1664
- "node": ">= 0.4"
1665
- },
1666
- "funding": {
1667
- "url": "https://github.com/sponsors/ljharb"
1668
- }
1669
- },
1670
- "node_modules/split2": {
1671
- "version": "4.2.0",
1672
- "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz",
1673
- "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==",
1674
- "license": "ISC",
1675
- "engines": {
1676
- "node": ">= 10.x"
1677
- }
1678
- },
1679
- "node_modules/statuses": {
1680
- "version": "2.0.2",
1681
- "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
1682
- "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==",
1683
- "license": "MIT",
1684
- "engines": {
1685
- "node": ">= 0.8"
1686
- }
1687
- },
1688
- "node_modules/streamsearch": {
1689
- "version": "1.1.0",
1690
- "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz",
1691
- "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==",
1692
- "engines": {
1693
- "node": ">=10.0.0"
1694
- }
1695
- },
1696
- "node_modules/string_decoder": {
1697
- "version": "1.3.0",
1698
- "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
1699
- "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
1700
- "license": "MIT",
1701
- "dependencies": {
1702
- "safe-buffer": "~5.2.0"
1703
- }
1704
- },
1705
- "node_modules/toidentifier": {
1706
- "version": "1.0.1",
1707
- "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
1708
- "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
1709
- "license": "MIT",
1710
- "engines": {
1711
- "node": ">=0.6"
1712
- }
1713
- },
1714
- "node_modules/tsx": {
1715
- "version": "4.21.0",
1716
- "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz",
1717
- "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==",
1718
- "dev": true,
1719
- "license": "MIT",
1720
- "dependencies": {
1721
- "esbuild": "~0.27.0",
1722
- "get-tsconfig": "^4.7.5"
1723
- },
1724
- "bin": {
1725
- "tsx": "dist/cli.mjs"
1726
- },
1727
- "engines": {
1728
- "node": ">=18.0.0"
1729
- },
1730
- "optionalDependencies": {
1731
- "fsevents": "~2.3.3"
1732
- }
1733
- },
1734
- "node_modules/type-is": {
1735
- "version": "1.6.18",
1736
- "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
1737
- "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
1738
- "license": "MIT",
1739
- "dependencies": {
1740
- "media-typer": "0.3.0",
1741
- "mime-types": "~2.1.24"
1742
- },
1743
- "engines": {
1744
- "node": ">= 0.6"
1745
- }
1746
- },
1747
- "node_modules/typedarray": {
1748
- "version": "0.0.6",
1749
- "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
1750
- "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==",
1751
- "license": "MIT"
1752
- },
1753
- "node_modules/typescript": {
1754
- "version": "5.9.3",
1755
- "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
1756
- "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
1757
- "dev": true,
1758
- "license": "Apache-2.0",
1759
- "bin": {
1760
- "tsc": "bin/tsc",
1761
- "tsserver": "bin/tsserver"
1762
- },
1763
- "engines": {
1764
- "node": ">=14.17"
1765
- }
1766
- },
1767
- "node_modules/undici-types": {
1768
- "version": "6.21.0",
1769
- "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
1770
- "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
1771
- "dev": true,
1772
- "license": "MIT"
1773
- },
1774
- "node_modules/unpipe": {
1775
- "version": "1.0.0",
1776
- "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
1777
- "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
1778
- "license": "MIT",
1779
- "engines": {
1780
- "node": ">= 0.8"
1781
- }
1782
- },
1783
- "node_modules/util-deprecate": {
1784
- "version": "1.0.2",
1785
- "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
1786
- "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
1787
- "license": "MIT"
1788
- },
1789
- "node_modules/utils-merge": {
1790
- "version": "1.0.1",
1791
- "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
1792
- "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==",
1793
- "license": "MIT",
1794
- "engines": {
1795
- "node": ">= 0.4.0"
1796
- }
1797
- },
1798
- "node_modules/vary": {
1799
- "version": "1.1.2",
1800
- "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
1801
- "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
1802
- "license": "MIT",
1803
- "engines": {
1804
- "node": ">= 0.8"
1805
- }
1806
- },
1807
- "node_modules/xtend": {
1808
- "version": "4.0.2",
1809
- "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
1810
- "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
1811
- "license": "MIT",
1812
- "engines": {
1813
- "node": ">=0.4"
1814
- }
1815
- }
1816
- }
1817
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
civic-platform/backend/package.json DELETED
@@ -1,30 +0,0 @@
1
- {
2
- "name": "civicpulse-backend",
3
- "version": "0.1.0",
4
- "private": true,
5
- "type": "module",
6
- "scripts": {
7
- "dev": "tsx watch src/server.ts",
8
- "build": "tsc -p tsconfig.json",
9
- "start": "node dist/server.js",
10
- "db:init": "tsx src/scripts/init-db.ts",
11
- "db:seed": "tsx src/scripts/seed-db.ts",
12
- "smoke": "tsx src/scripts/smoke-test.ts"
13
- },
14
- "dependencies": {
15
- "cors": "^2.8.5",
16
- "dotenv": "^16.4.7",
17
- "express": "^4.21.2",
18
- "multer": "^2.1.1",
19
- "pg": "^8.13.3"
20
- },
21
- "devDependencies": {
22
- "@types/cors": "^2.8.17",
23
- "@types/express": "^5.0.1",
24
- "@types/multer": "^2.1.0",
25
- "@types/node": "^22.13.10",
26
- "@types/pg": "^8.11.11",
27
- "tsx": "^4.19.3",
28
- "typescript": "^5.8.2"
29
- }
30
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
civic-platform/backend/server.err.log DELETED
File without changes
civic-platform/backend/server.out.log DELETED
File without changes
civic-platform/backend/sql/001_initial_schema.sql DELETED
@@ -1,246 +0,0 @@
1
- CREATE EXTENSION IF NOT EXISTS "pgcrypto";
2
-
3
- CREATE TABLE roles (
4
- id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
5
- name VARCHAR(50) NOT NULL UNIQUE,
6
- description TEXT,
7
- created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
8
- );
9
-
10
- CREATE TABLE departments (
11
- id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
12
- name VARCHAR(120) NOT NULL UNIQUE,
13
- code VARCHAR(40) NOT NULL UNIQUE,
14
- description TEXT,
15
- is_emergency BOOLEAN NOT NULL DEFAULT FALSE,
16
- is_active BOOLEAN NOT NULL DEFAULT TRUE,
17
- created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
18
- );
19
-
20
- CREATE TABLE wards (
21
- id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
22
- name VARCHAR(120) NOT NULL,
23
- code VARCHAR(40) NOT NULL UNIQUE,
24
- city_name VARCHAR(120) NOT NULL,
25
- state_name VARCHAR(120),
26
- created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
27
- );
28
-
29
- CREATE TABLE users (
30
- id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
31
- full_name VARCHAR(160) NOT NULL,
32
- email VARCHAR(160) UNIQUE,
33
- phone VARCHAR(20) UNIQUE,
34
- password_hash TEXT,
35
- role_id UUID NOT NULL REFERENCES roles(id),
36
- department_id UUID REFERENCES departments(id),
37
- ward_id UUID REFERENCES wards(id),
38
- is_active BOOLEAN NOT NULL DEFAULT TRUE,
39
- created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
40
- updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
41
- );
42
-
43
- CREATE TABLE domains (
44
- id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
45
- name VARCHAR(120) NOT NULL UNIQUE,
46
- description TEXT,
47
- is_emergency BOOLEAN NOT NULL DEFAULT FALSE,
48
- is_active BOOLEAN NOT NULL DEFAULT TRUE,
49
- created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
50
- );
51
-
52
- CREATE TABLE sub_problems (
53
- id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
54
- domain_id UUID NOT NULL REFERENCES domains(id) ON DELETE CASCADE,
55
- name VARCHAR(120) NOT NULL,
56
- description TEXT,
57
- severity_hint VARCHAR(20),
58
- is_active BOOLEAN NOT NULL DEFAULT TRUE,
59
- created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
60
- CONSTRAINT unique_sub_problem_per_domain UNIQUE (domain_id, name)
61
- );
62
-
63
- CREATE TABLE complaints (
64
- id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
65
- complaint_code VARCHAR(40) NOT NULL UNIQUE,
66
- citizen_id UUID NOT NULL REFERENCES users(id),
67
- domain_id UUID REFERENCES domains(id),
68
- sub_problem_id UUID REFERENCES sub_problems(id),
69
- title VARCHAR(220) NOT NULL,
70
- description TEXT,
71
- status VARCHAR(32) NOT NULL,
72
- priority_level VARCHAR(2) NOT NULL,
73
- is_emergency BOOLEAN NOT NULL DEFAULT FALSE,
74
- department_id UUID REFERENCES departments(id),
75
- ward_id UUID REFERENCES wards(id),
76
- submitted_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
77
- resolved_at TIMESTAMPTZ,
78
- closed_at TIMESTAMPTZ,
79
- reopened_count INTEGER NOT NULL DEFAULT 0,
80
- created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
81
- updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
82
- CONSTRAINT complaints_status_check CHECK (
83
- status IN (
84
- 'submitted',
85
- 'validated',
86
- 'classified',
87
- 'prioritized',
88
- 'assigned',
89
- 'accepted',
90
- 'in_progress',
91
- 'resolved',
92
- 'citizen_verified',
93
- 'closed',
94
- 'escalated',
95
- 'reopened',
96
- 'duplicate',
97
- 'rejected',
98
- 'on_hold'
99
- )
100
- ),
101
- CONSTRAINT complaints_priority_check CHECK (priority_level IN ('P1', 'P2', 'P3', 'P4'))
102
- );
103
-
104
- CREATE TABLE complaint_locations (
105
- id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
106
- complaint_id UUID NOT NULL UNIQUE REFERENCES complaints(id) ON DELETE CASCADE,
107
- latitude NUMERIC(10, 7) NOT NULL,
108
- longitude NUMERIC(10, 7) NOT NULL,
109
- address_line TEXT,
110
- landmark VARCHAR(220),
111
- city_name VARCHAR(120),
112
- state_name VARCHAR(120),
113
- postal_code VARCHAR(20),
114
- ward_id UUID REFERENCES wards(id),
115
- created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
116
- );
117
-
118
- CREATE TABLE complaint_media (
119
- id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
120
- complaint_id UUID NOT NULL REFERENCES complaints(id) ON DELETE CASCADE,
121
- uploaded_by UUID REFERENCES users(id),
122
- media_type VARCHAR(20) NOT NULL,
123
- file_path TEXT NOT NULL,
124
- file_url TEXT,
125
- mime_type VARCHAR(120),
126
- is_resolution_proof BOOLEAN NOT NULL DEFAULT FALSE,
127
- created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
128
- CONSTRAINT complaint_media_type_check CHECK (media_type IN ('image', 'video', 'audio'))
129
- );
130
-
131
- CREATE TABLE complaint_status_history (
132
- id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
133
- complaint_id UUID NOT NULL REFERENCES complaints(id) ON DELETE CASCADE,
134
- old_status VARCHAR(32),
135
- new_status VARCHAR(32) NOT NULL,
136
- changed_by UUID REFERENCES users(id),
137
- change_reason TEXT,
138
- created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
139
- );
140
-
141
- CREATE TABLE assignments (
142
- id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
143
- complaint_id UUID NOT NULL REFERENCES complaints(id) ON DELETE CASCADE,
144
- department_id UUID NOT NULL REFERENCES departments(id),
145
- assigned_to_user_id UUID REFERENCES users(id),
146
- assigned_by_user_id UUID REFERENCES users(id),
147
- assignment_status VARCHAR(20) NOT NULL,
148
- assigned_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
149
- accepted_at TIMESTAMPTZ,
150
- completed_at TIMESTAMPTZ,
151
- notes TEXT,
152
- CONSTRAINT assignments_status_check CHECK (
153
- assignment_status IN ('assigned', 'accepted', 'in_progress', 'completed', 'reassigned')
154
- )
155
- );
156
-
157
- CREATE TABLE routing_rules (
158
- id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
159
- domain_id UUID REFERENCES domains(id),
160
- sub_problem_id UUID REFERENCES sub_problems(id),
161
- ward_id UUID REFERENCES wards(id),
162
- department_id UUID NOT NULL REFERENCES departments(id),
163
- priority_override VARCHAR(2),
164
- is_emergency_route BOOLEAN NOT NULL DEFAULT FALSE,
165
- is_active BOOLEAN NOT NULL DEFAULT TRUE,
166
- created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
167
- CONSTRAINT routing_rules_priority_check CHECK (priority_override IS NULL OR priority_override IN ('P1', 'P2', 'P3', 'P4'))
168
- );
169
-
170
- CREATE TABLE priority_rules (
171
- id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
172
- domain_id UUID REFERENCES domains(id),
173
- sub_problem_id UUID REFERENCES sub_problems(id),
174
- base_priority VARCHAR(2) NOT NULL,
175
- near_sensitive_zone_boost INTEGER NOT NULL DEFAULT 0,
176
- repeat_complaint_boost INTEGER NOT NULL DEFAULT 0,
177
- reopened_boost INTEGER NOT NULL DEFAULT 0,
178
- sla_breach_boost INTEGER NOT NULL DEFAULT 0,
179
- is_active BOOLEAN NOT NULL DEFAULT TRUE,
180
- created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
181
- CONSTRAINT priority_rules_base_check CHECK (base_priority IN ('P1', 'P2', 'P3', 'P4'))
182
- );
183
-
184
- CREATE TABLE complaint_notes (
185
- id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
186
- complaint_id UUID NOT NULL REFERENCES complaints(id) ON DELETE CASCADE,
187
- author_id UUID REFERENCES users(id),
188
- note_type VARCHAR(20) NOT NULL,
189
- note_text TEXT NOT NULL,
190
- is_internal BOOLEAN NOT NULL DEFAULT TRUE,
191
- created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
192
- CONSTRAINT complaint_notes_type_check CHECK (
193
- note_type IN ('operator_note', 'field_note', 'citizen_note', 'system_note')
194
- )
195
- );
196
-
197
- CREATE TABLE notifications (
198
- id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
199
- user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
200
- complaint_id UUID REFERENCES complaints(id) ON DELETE CASCADE,
201
- title VARCHAR(220) NOT NULL,
202
- message TEXT NOT NULL,
203
- notification_type VARCHAR(50) NOT NULL,
204
- is_read BOOLEAN NOT NULL DEFAULT FALSE,
205
- created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
206
- );
207
-
208
- CREATE TABLE feedback (
209
- id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
210
- complaint_id UUID NOT NULL REFERENCES complaints(id) ON DELETE CASCADE,
211
- citizen_id UUID NOT NULL REFERENCES users(id),
212
- rating INTEGER CHECK (rating BETWEEN 1 AND 5),
213
- comment TEXT,
214
- reopen_requested BOOLEAN NOT NULL DEFAULT FALSE,
215
- created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
216
- );
217
-
218
- CREATE TABLE audit_logs (
219
- id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
220
- actor_user_id UUID REFERENCES users(id),
221
- entity_type VARCHAR(50) NOT NULL,
222
- entity_id UUID,
223
- action VARCHAR(120) NOT NULL,
224
- details JSONB,
225
- created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
226
- );
227
-
228
- CREATE INDEX idx_users_role_id ON users(role_id);
229
- CREATE INDEX idx_users_department_id ON users(department_id);
230
- CREATE INDEX idx_users_ward_id ON users(ward_id);
231
- CREATE INDEX idx_sub_problems_domain_id ON sub_problems(domain_id);
232
- CREATE INDEX idx_complaints_citizen_id ON complaints(citizen_id);
233
- CREATE INDEX idx_complaints_domain_id ON complaints(domain_id);
234
- CREATE INDEX idx_complaints_department_id ON complaints(department_id);
235
- CREATE INDEX idx_complaints_ward_id ON complaints(ward_id);
236
- CREATE INDEX idx_complaints_status ON complaints(status);
237
- CREATE INDEX idx_complaints_priority_level ON complaints(priority_level);
238
- CREATE INDEX idx_complaints_submitted_at ON complaints(submitted_at);
239
- CREATE INDEX idx_complaint_media_complaint_id ON complaint_media(complaint_id);
240
- CREATE INDEX idx_complaint_status_history_complaint_id ON complaint_status_history(complaint_id);
241
- CREATE INDEX idx_assignments_complaint_id ON assignments(complaint_id);
242
- CREATE INDEX idx_assignments_department_id ON assignments(department_id);
243
- CREATE INDEX idx_routing_rules_department_id ON routing_rules(department_id);
244
- CREATE INDEX idx_notifications_user_id ON notifications(user_id);
245
- CREATE INDEX idx_feedback_complaint_id ON feedback(complaint_id);
246
- CREATE INDEX idx_audit_logs_actor_user_id ON audit_logs(actor_user_id);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
civic-platform/backend/sql/002_seed_core_data.sql DELETED
@@ -1,133 +0,0 @@
1
- INSERT INTO roles (id, name, description)
2
- VALUES
3
- ('10000000-0000-0000-0000-000000000001', 'citizen', 'Citizen user who reports and tracks complaints'),
4
- ('10000000-0000-0000-0000-000000000002', 'department_operator', 'Department operator who validates and assigns complaints'),
5
- ('10000000-0000-0000-0000-000000000003', 'municipal_admin', 'Municipal administrator with cross-department access'),
6
- ('10000000-0000-0000-0000-000000000004', 'field_officer', 'Field officer who executes assigned work on the ground')
7
- ON CONFLICT (name) DO NOTHING;
8
-
9
- INSERT INTO departments (id, name, code, description, is_emergency)
10
- VALUES
11
- ('20000000-0000-0000-0000-000000000001', 'Roads and Public Works', 'roads-public-works', 'Handles roads, potholes, and transport infrastructure issues', FALSE),
12
- ('20000000-0000-0000-0000-000000000002', 'Sanitation and Solid Waste', 'sanitation-solid-waste', 'Handles waste collection, garbage overflow, and sanitation complaints', FALSE),
13
- ('20000000-0000-0000-0000-000000000003', 'Water and Sewerage', 'water-sewerage', 'Handles water leakage, sewage, and drainage issues', FALSE),
14
- ('20000000-0000-0000-0000-000000000004', 'Electrical and Street Lighting', 'electrical-street-lighting', 'Handles streetlight and public electrical complaints', FALSE),
15
- ('20000000-0000-0000-0000-000000000005', 'Municipal Maintenance', 'municipal-maintenance', 'Handles open manholes and civic infrastructure maintenance', FALSE),
16
- ('20000000-0000-0000-0000-000000000006', 'Disaster Management Authority', 'disaster-management', 'Handles flood, collapse, rescue, and emergency coordination', TRUE)
17
- ON CONFLICT (code) DO NOTHING;
18
-
19
- INSERT INTO wards (id, name, code, city_name, state_name)
20
- VALUES
21
- ('30000000-0000-0000-0000-000000000001', 'Ward 4', 'ward-4', 'Sample City', 'Sample State'),
22
- ('30000000-0000-0000-0000-000000000002', 'Ward 8', 'ward-8', 'Sample City', 'Sample State'),
23
- ('30000000-0000-0000-0000-000000000003', 'Ward 12', 'ward-12', 'Sample City', 'Sample State')
24
- ON CONFLICT (code) DO NOTHING;
25
-
26
- INSERT INTO domains (id, name, description, is_emergency)
27
- VALUES
28
- ('40000000-0000-0000-0000-000000000001', 'Roads and Transportation', 'Road safety, traffic support, and mobility complaints', FALSE),
29
- ('40000000-0000-0000-0000-000000000002', 'Sanitation and Waste Management', 'Waste collection, cleanliness, and sanitation complaints', FALSE),
30
- ('40000000-0000-0000-0000-000000000003', 'Water Supply, Sewerage, and Drainage', 'Water access, sewage, and drainage complaints', FALSE),
31
- ('40000000-0000-0000-0000-000000000004', 'Street Lighting and Electrical Infrastructure', 'Public lighting and electrical safety complaints', FALSE),
32
- ('40000000-0000-0000-0000-000000000005', 'Public Infrastructure and Amenities', 'Maintenance needs for public assets and shared facilities', FALSE),
33
- ('40000000-0000-0000-0000-000000000006', 'Environment and Public Health', 'Public hygiene and environmental safety concerns', FALSE),
34
- ('40000000-0000-0000-0000-000000000007', 'Fire Emergencies', 'Emergency fire and smoke incidents', TRUE),
35
- ('40000000-0000-0000-0000-000000000008', 'Flood and Water Disaster', 'Critical flood and water disaster events', TRUE)
36
- ON CONFLICT (name) DO NOTHING;
37
-
38
- INSERT INTO users (id, full_name, email, phone, password_hash, role_id, department_id, ward_id)
39
- VALUES
40
- (
41
- '00000000-0000-0000-0000-000000000001',
42
- 'Sample Citizen',
43
- 'citizen@example.com',
44
- '9000000001',
45
- 'dev-placeholder-hash',
46
- '10000000-0000-0000-0000-000000000001',
47
- NULL,
48
- '30000000-0000-0000-0000-000000000003'
49
- ),
50
- (
51
- '11111111-1111-1111-1111-111111111111',
52
- 'Sample Operator',
53
- 'operator@example.com',
54
- '9000000002',
55
- 'dev-placeholder-hash',
56
- '10000000-0000-0000-0000-000000000002',
57
- '20000000-0000-0000-0000-000000000005',
58
- '30000000-0000-0000-0000-000000000001'
59
- ),
60
- (
61
- '22222222-2222-2222-2222-222222222222',
62
- 'Sample Field Officer',
63
- 'officer@example.com',
64
- '9000000003',
65
- 'dev-placeholder-hash',
66
- '10000000-0000-0000-0000-000000000004',
67
- '20000000-0000-0000-0000-000000000005',
68
- '30000000-0000-0000-0000-000000000001'
69
- ),
70
- (
71
- '22222222-2222-2222-2222-222222222223',
72
- 'Road Field Officer',
73
- 'roads.officer@example.com',
74
- '9000000004',
75
- 'dev-placeholder-hash',
76
- '10000000-0000-0000-0000-000000000004',
77
- '20000000-0000-0000-0000-000000000001',
78
- '30000000-0000-0000-0000-000000000001'
79
- ),
80
- (
81
- '22222222-2222-2222-2222-222222222224',
82
- 'Sanitation Field Officer',
83
- 'sanitation.officer@example.com',
84
- '9000000005',
85
- 'dev-placeholder-hash',
86
- '10000000-0000-0000-0000-000000000004',
87
- '20000000-0000-0000-0000-000000000002',
88
- '30000000-0000-0000-0000-000000000002'
89
- ),
90
- (
91
- '22222222-2222-2222-2222-222222222225',
92
- 'Water Field Officer',
93
- 'water.officer@example.com',
94
- '9000000006',
95
- 'dev-placeholder-hash',
96
- '10000000-0000-0000-0000-000000000004',
97
- '20000000-0000-0000-0000-000000000003',
98
- '30000000-0000-0000-0000-000000000002'
99
- ),
100
- (
101
- '22222222-2222-2222-2222-222222222226',
102
- 'Electrical Field Officer',
103
- 'electrical.officer@example.com',
104
- '9000000007',
105
- 'dev-placeholder-hash',
106
- '10000000-0000-0000-0000-000000000004',
107
- '20000000-0000-0000-0000-000000000004',
108
- '30000000-0000-0000-0000-000000000003'
109
- )
110
- ON CONFLICT (email) DO NOTHING;
111
-
112
- INSERT INTO routing_rules (domain_id, sub_problem_id, ward_id, department_id, priority_override, is_emergency_route)
113
- VALUES
114
- ('40000000-0000-0000-0000-000000000001', NULL, NULL, '20000000-0000-0000-0000-000000000001', NULL, FALSE),
115
- ('40000000-0000-0000-0000-000000000002', NULL, NULL, '20000000-0000-0000-0000-000000000002', NULL, FALSE),
116
- ('40000000-0000-0000-0000-000000000003', NULL, NULL, '20000000-0000-0000-0000-000000000003', NULL, FALSE),
117
- ('40000000-0000-0000-0000-000000000004', NULL, NULL, '20000000-0000-0000-0000-000000000004', NULL, FALSE),
118
- ('40000000-0000-0000-0000-000000000005', NULL, NULL, '20000000-0000-0000-0000-000000000005', 'P1', FALSE),
119
- ('40000000-0000-0000-0000-000000000006', NULL, NULL, '20000000-0000-0000-0000-000000000002', NULL, FALSE),
120
- ('40000000-0000-0000-0000-000000000007', NULL, NULL, '20000000-0000-0000-0000-000000000006', 'P1', TRUE),
121
- ('40000000-0000-0000-0000-000000000008', NULL, NULL, '20000000-0000-0000-0000-000000000006', 'P1', TRUE);
122
-
123
- INSERT INTO priority_rules (domain_id, sub_problem_id, base_priority)
124
- VALUES
125
- ('40000000-0000-0000-0000-000000000001', NULL, 'P3'),
126
- ('40000000-0000-0000-0000-000000000002', NULL, 'P3'),
127
- ('40000000-0000-0000-0000-000000000003', NULL, 'P2'),
128
- ('40000000-0000-0000-0000-000000000004', NULL, 'P3'),
129
- ('40000000-0000-0000-0000-000000000005', NULL, 'P2'),
130
- ('40000000-0000-0000-0000-000000000006', NULL, 'P3'),
131
- ('40000000-0000-0000-0000-000000000007', NULL, 'P1'),
132
- ('40000000-0000-0000-0000-000000000008', NULL, 'P1')
133
- ON CONFLICT DO NOTHING;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
civic-platform/backend/sql/003_performance_indexes.sql DELETED
@@ -1,32 +0,0 @@
1
- CREATE INDEX IF NOT EXISTS idx_complaints_status_submitted_at
2
- ON complaints (status, submitted_at DESC);
3
-
4
- CREATE INDEX IF NOT EXISTS idx_complaints_department_status
5
- ON complaints (department_id, status, submitted_at DESC);
6
-
7
- CREATE INDEX IF NOT EXISTS idx_complaints_domain_submitted_at
8
- ON complaints (domain_id, submitted_at DESC);
9
-
10
- CREATE INDEX IF NOT EXISTS idx_complaints_priority_status
11
- ON complaints (priority_level, status, submitted_at DESC);
12
-
13
- CREATE INDEX IF NOT EXISTS idx_assignments_assigned_to_status
14
- ON assignments (assigned_to_user_id, assignment_status, assigned_at DESC);
15
-
16
- CREATE INDEX IF NOT EXISTS idx_assignments_complaint_assigned_at
17
- ON assignments (complaint_id, assigned_at DESC);
18
-
19
- CREATE INDEX IF NOT EXISTS idx_notifications_user_read_created
20
- ON notifications (user_id, is_read, created_at DESC);
21
-
22
- CREATE INDEX IF NOT EXISTS idx_status_history_complaint_created
23
- ON complaint_status_history (complaint_id, created_at DESC);
24
-
25
- CREATE INDEX IF NOT EXISTS idx_feedback_complaint_created
26
- ON feedback (complaint_id, created_at DESC);
27
-
28
- CREATE INDEX IF NOT EXISTS idx_notes_complaint_created
29
- ON complaint_notes (complaint_id, created_at DESC);
30
-
31
- CREATE INDEX IF NOT EXISTS idx_locations_lat_lng
32
- ON complaint_locations (latitude, longitude);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
civic-platform/backend/sql/README.md DELETED
@@ -1,22 +0,0 @@
1
- # SQL Files
2
-
3
- ## Current files
4
-
5
- - `001_initial_schema.sql`
6
- - `002_seed_core_data.sql`
7
-
8
- ## Usage
9
-
10
- Apply the schema file to the `civicpulse` PostgreSQL database before running the complaint APIs.
11
-
12
- Recommended order:
13
-
14
- 1. create database
15
- 2. apply `001_initial_schema.sql`
16
- 3. apply `002_seed_core_data.sql`
17
- 4. start backend
18
-
19
- Future migrations should continue with:
20
-
21
- - `003_...`
22
- - `004_...`
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
civic-platform/backend/src/app.ts DELETED
@@ -1,48 +0,0 @@
1
- import fs from "node:fs";
2
- import path from "node:path";
3
- import cors from "cors";
4
- import express from "express";
5
- import { aiRouter } from "./modules/ai/ai.routes.js";
6
- import { authRouter } from "./modules/auth/auth.routes.js";
7
- import { env } from "./config/env.js";
8
- import { rateLimitMiddleware, securityHeaders } from "./lib/security.js";
9
- import { complaintRouter } from "./modules/complaints/complaint.routes.js";
10
- import { departmentRouter } from "./modules/departments/department.routes.js";
11
- import { domainRouter } from "./modules/domains/domain.routes.js";
12
- import { userRouter } from "./modules/users/user.routes.js";
13
- import { healthRouter } from "./routes/health.js";
14
-
15
- export function createApp() {
16
- const app = express();
17
- const uploadsDir = path.resolve(process.cwd(), "uploads");
18
-
19
- fs.mkdirSync(path.join(uploadsDir, "complaints"), { recursive: true });
20
-
21
- app.set("trust proxy", 1);
22
- app.use(
23
- cors({
24
- origin: env.corsOrigins.split(",").map((value) => value.trim()),
25
- }),
26
- );
27
- app.use(securityHeaders);
28
- app.use(rateLimitMiddleware);
29
- app.use(express.json({ limit: "10mb" }));
30
- app.use("/uploads", express.static(uploadsDir));
31
-
32
- app.get("/", (_req, res) => {
33
- res.json({
34
- name: "civicpulse-backend",
35
- message: "CivicPulse backend is running",
36
- });
37
- });
38
-
39
- app.use("/api/health", healthRouter);
40
- app.use("/api/ai", aiRouter);
41
- app.use("/api/auth", authRouter);
42
- app.use("/api/departments", departmentRouter);
43
- app.use("/api/domains", domainRouter);
44
- app.use("/api/users", userRouter);
45
- app.use("/api/complaints", complaintRouter);
46
-
47
- return app;
48
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
civic-platform/backend/src/config/env.ts DELETED
@@ -1,28 +0,0 @@
1
- import dotenv from "dotenv";
2
-
3
- dotenv.config();
4
-
5
- function requireEnv(name: string, fallback?: string): string {
6
- const value = process.env[name] ?? fallback;
7
-
8
- if (!value) {
9
- throw new Error(`Missing required environment variable: ${name}`);
10
- }
11
-
12
- return value;
13
- }
14
-
15
- export const env = {
16
- nodeEnv: requireEnv("NODE_ENV", "development"),
17
- port: Number(requireEnv("PORT", "4000")),
18
- corsOrigins: requireEnv("CORS_ORIGINS", "http://localhost:3000"),
19
- dbHost: requireEnv("DB_HOST", "localhost"),
20
- dbPort: Number(requireEnv("DB_PORT", "5432")),
21
- dbName: requireEnv("DB_NAME", "civicpulse"),
22
- dbUser: requireEnv("DB_USER", "postgres"),
23
- dbPassword: requireEnv("DB_PASSWORD"),
24
- dbSsl: requireEnv("DB_SSL", "false") === "true",
25
- demoAuthPassword: requireEnv("DEMO_AUTH_PASSWORD", "civicpulse123"),
26
- rateLimitWindowMs: Number(requireEnv("RATE_LIMIT_WINDOW_MS", "60000")),
27
- rateLimitMaxRequests: Number(requireEnv("RATE_LIMIT_MAX_REQUESTS", "120")),
28
- };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
civic-platform/backend/src/db/pool.ts DELETED
@@ -1,11 +0,0 @@
1
- import { Pool } from "pg";
2
- import { env } from "../config/env.js";
3
-
4
- export const db = new Pool({
5
- host: env.dbHost,
6
- port: env.dbPort,
7
- database: env.dbName,
8
- user: env.dbUser,
9
- password: env.dbPassword,
10
- ssl: env.dbSsl ? { rejectUnauthorized: false } : false,
11
- });
 
 
 
 
 
 
 
 
 
 
 
 
civic-platform/backend/src/lib/audit.ts DELETED
@@ -1,29 +0,0 @@
1
- import { db } from "../db/pool.js";
2
-
3
- export async function writeAuditLog(input: {
4
- actorUserId?: string | null;
5
- entityType: string;
6
- entityId?: string | null;
7
- action: string;
8
- details?: Record<string, unknown>;
9
- }) {
10
- await db.query(
11
- `
12
- INSERT INTO audit_logs (
13
- actor_user_id,
14
- entity_type,
15
- entity_id,
16
- action,
17
- details
18
- )
19
- VALUES ($1, $2, $3, $4, $5::jsonb)
20
- `,
21
- [
22
- input.actorUserId ?? null,
23
- input.entityType,
24
- input.entityId ?? null,
25
- input.action,
26
- JSON.stringify(input.details ?? {}),
27
- ],
28
- );
29
- }