From e590754f61621dcb4554ac54ae297bf438b500a1 Mon Sep 17 00:00:00 2001 From: Javed Hussain Date: Sat, 23 Aug 2025 07:22:26 +0530 Subject: [PATCH 01/13] Fixed latest docker tag on tags instead of branch --- .github/workflows/docker-publish.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml index 34ae5165..ec865ae5 100644 --- a/.github/workflows/docker-publish.yml +++ b/.github/workflows/docker-publish.yml @@ -58,7 +58,7 @@ jobs: type=ref,event=pr type=semver,pattern={{version}} type=semver,pattern={{major}}.{{minor}} - type=raw,value=latest,enable={{is_default_branch}} + type=semver,pattern=latest - name: Extract docs metadata id: docs-meta @@ -70,7 +70,7 @@ jobs: type=ref,event=pr type=semver,pattern={{version}} type=semver,pattern={{major}}.{{minor}} - type=raw,value=latest,enable={{is_default_branch}} + type=semver,pattern=latest - name: Build and push app uses: docker/build-push-action@v5 From 3b45ee12fb574dae864a128d6900f1e55b0b08c5 Mon Sep 17 00:00:00 2001 From: Javed Hussain Date: Sat, 23 Aug 2025 07:34:22 +0530 Subject: [PATCH 02/13] Fixed path routes issue with relative path --- app/backend/index.ts | 4 +- app/backend/package.json | 3 +- app/backend/src/db/index.ts | 2 +- package-lock.json | 146 +++++++++++++++++++++++++++++++++++- 4 files changed, 150 insertions(+), 5 deletions(-) diff --git a/app/backend/index.ts b/app/backend/index.ts index 95663794..dd7ad8db 100644 --- a/app/backend/index.ts +++ b/app/backend/index.ts @@ -67,13 +67,13 @@ initializeDatabase() app.listen(env.SERVER_PORT, env.SERVER_HOST, () => { console.log("─".repeat(75)); console.log( - `🚀 Server running at http://${env.SERVER_HOST}:${env.SERVER_PORT}`, + `🚀 Server running at http://${env.SERVER_HOST}:${env.SERVER_PORT}` ); console.log(`📊 Environment: ${env.NODE_ENV}`); console.log(`🗄️ Database: ${env.DATABASE_PATH}`); console.log(`🎭 Demo Mode: ${env.DEMO_MODE ? "Enabled" : "Disabled"}`); console.log( - `🌐 CORS: ${env.isDevelopment() ? "Permissive (Development)" : "Strict (Production)"}`, + `🌐 CORS: ${env.isDevelopment() ? "Permissive (Development)" : "Strict (Production)"}` ); if (!env.isDevelopment()) { console.log(`📋 Allowed origins: ${env.CORS_ORIGINS.join(", ")}`); diff --git a/app/backend/package.json b/app/backend/package.json index d8363b11..c1d2618f 100644 --- a/app/backend/package.json +++ b/app/backend/package.json @@ -5,7 +5,7 @@ "main": "dist/index.js", "type": "module", "scripts": { - "build": "tsc", + "build": "tsc && tsc-alias", "dev": "tsx --watch index.ts", "start": "node dist/index.js", "preview": "npm run build && npm run start", @@ -41,6 +41,7 @@ "nodemon": "^3.1.10", "prettier": "^3.6.2", "ts-node": "^10.9.2", + "tsc-alias": "^1.8.16", "tsx": "^4.20.3", "typescript": "^5.8.3" } diff --git a/app/backend/src/db/index.ts b/app/backend/src/db/index.ts index ab2e0cc2..3e3fcf03 100644 --- a/app/backend/src/db/index.ts +++ b/app/backend/src/db/index.ts @@ -8,7 +8,7 @@ const __dirname = path.dirname(__filename); const umzug = new Umzug({ migrations: { - glob: path.join(__dirname, "migrations/*.ts"), + glob: path.join(__dirname, "migrations/*.js"), resolve: ({ name, path: migrationPath }) => { return { name, diff --git a/package-lock.json b/package-lock.json index 0e56fb32..1381fd01 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,7 +15,7 @@ }, "app/backend": { "name": "server", - "version": "1.0.0", + "version": "0.0.1", "license": "ISC", "dependencies": { "@types/swagger-jsdoc": "^6.0.4", @@ -40,6 +40,7 @@ "nodemon": "^3.1.10", "prettier": "^3.6.2", "ts-node": "^10.9.2", + "tsc-alias": "^1.8.16", "tsx": "^4.20.3", "typescript": "^5.8.3" } @@ -3321,6 +3322,16 @@ "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", "license": "MIT" }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/axobject-query": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", @@ -4097,6 +4108,19 @@ "node": ">=0.3.1" } }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/docs": { "resolved": "app/docs", "link": true @@ -4730,6 +4754,27 @@ "node": ">= 6" } }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/gopd": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", @@ -4948,6 +4993,16 @@ ], "license": "BSD-3-Clause" }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, "node_modules/ignore-by-default": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", @@ -6025,6 +6080,20 @@ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "license": "MIT" }, + "node_modules/mylas": { + "version": "2.1.13", + "resolved": "https://registry.npmjs.org/mylas/-/mylas-2.1.13.tgz", + "integrity": "sha512-+MrqnJRtxdF+xngFfUUkIMQrUUL0KsxbADUkn23Z/4ibGg192Q+z+CQyiYwvWTsYjJygmMR8+w3ZDa98Zh6ESg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/raouldeheer" + } + }, "node_modules/nanoid": { "version": "3.3.11", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", @@ -6328,6 +6397,16 @@ "node": ">=16" } }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/perfect-debounce": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz", @@ -6361,6 +6440,19 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/plimit-lit": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/plimit-lit/-/plimit-lit-1.6.1.tgz", + "integrity": "sha512-B7+VDyb8Tl6oMJT9oSO2CW8XC/T4UcJGrwOVoNGwOQsQYhlpfajmrMj5xeejqaASq3V/EqThyOeATEOMuSEXiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "queue-lit": "^1.5.1" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/pony-cause": { "version": "2.1.11", "resolved": "https://registry.npmjs.org/pony-cause/-/pony-cause-2.1.11.tgz", @@ -6651,6 +6743,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/queue-lit": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/queue-lit/-/queue-lit-1.5.2.tgz", + "integrity": "sha512-tLc36IOPeMAubu8BkW8YDBV+WyIgKlYU7zUNs0J5Vk9skSZ4JfGlPOqplP0aHdfv7HL0B2Pg6nwiq60Qc6M2Hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -7342,6 +7444,16 @@ "node": ">=18" } }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/smart-buffer": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", @@ -8061,6 +8173,38 @@ } } }, + "node_modules/tsc-alias": { + "version": "1.8.16", + "resolved": "https://registry.npmjs.org/tsc-alias/-/tsc-alias-1.8.16.tgz", + "integrity": "sha512-QjCyu55NFyRSBAl6+MTFwplpFcnm2Pq01rR/uxfqJoLMm6X3O14KEGtaSDZpJYaE1bJBGDjD0eSuiIWPe2T58g==", + "dev": true, + "license": "MIT", + "dependencies": { + "chokidar": "^3.5.3", + "commander": "^9.0.0", + "get-tsconfig": "^4.10.0", + "globby": "^11.0.4", + "mylas": "^2.1.9", + "normalize-path": "^3.0.0", + "plimit-lit": "^1.2.6" + }, + "bin": { + "tsc-alias": "dist/bin/index.js" + }, + "engines": { + "node": ">=16.20.2" + } + }, + "node_modules/tsc-alias/node_modules/commander": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", + "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || >=14" + } + }, "node_modules/tslib": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", From b847cffc8f6a2d7f9a98eb04308131ec48292077 Mon Sep 17 00:00:00 2001 From: Javed Hussain Date: Sat, 23 Aug 2025 07:50:05 +0530 Subject: [PATCH 03/13] Fixed relative paths in multiple places --- app/backend/index.ts | 2 +- app/backend/tsconfig.json | 4 +++- docker/entrypoint.sh | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/app/backend/index.ts b/app/backend/index.ts index dd7ad8db..0fea771f 100644 --- a/app/backend/index.ts +++ b/app/backend/index.ts @@ -50,7 +50,7 @@ app.use("/api/config", configRoutes); if (env.isProduction()) { // @ts-ignore - const { handler } = await import("@frontend/build/handler.js"); + const { handler } = await import("../frontend/build/handler.js"); app.use(handler); } else { // In dev, redirect to SvelteKit dev server diff --git a/app/backend/tsconfig.json b/app/backend/tsconfig.json index 49c25787..614a6724 100644 --- a/app/backend/tsconfig.json +++ b/app/backend/tsconfig.json @@ -32,7 +32,9 @@ "@services": ["./src/services"], "@services/*": ["./src/services/*"], "@middleware": ["./src/middleware"], - "@middleware/*": ["./src/middleware/*"] + "@middleware/*": ["./src/middleware/*"], + "@frontend": ["./frontend"], + "@frontend/*": ["./frontend/*"] } }, "include": ["**/*.ts"], diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh index 9bdd585c..c3967a25 100644 --- a/docker/entrypoint.sh +++ b/docker/entrypoint.sh @@ -8,7 +8,7 @@ cd /app/backend # Run database migrations echo "Running database migrations..." -if node dist/db/migrate.js migrate; then +if node dist/src/db/migrate.js migrate; then echo "✓ Database migrations completed successfully" else echo "✗ Database migrations failed" From 5909cddc0ad66245b1cd6f4b6eb89a823a58fa95 Mon Sep 17 00:00:00 2001 From: Javed Hussain Date: Sat, 23 Aug 2025 11:19:13 +0530 Subject: [PATCH 04/13] Fixed environment variables and sensible defaults --- .env.example | 10 +--- app/backend/index.ts | 44 +++++++-------- app/backend/nodemon.json | 8 ++- app/backend/package.json | 4 +- app/backend/src/config/env.ts | 56 ++++++++++++------- app/backend/src/db/index.ts | 8 ++- app/backend/src/middleware/async-handler.ts | 8 +++ app/backend/src/middleware/error-handler.ts | 39 ++++++++----- app/backend/src/routes/configRoutes.ts | 7 ++- app/backend/src/routes/fuelLogRoutes.ts | 11 ++-- app/backend/src/routes/insuranceRoutes.ts | 9 +-- .../src/routes/maintenanceLogRoutes.ts | 11 ++-- app/backend/src/routes/pinRoutes.ts | 7 ++- app/backend/src/routes/puccRoutes.ts | 13 +++-- app/backend/src/routes/vehicleRoutes.ts | 11 ++-- app/frontend/package.json | 4 +- app/frontend/src/lib/stores/config.ts | 31 +++++----- app/frontend/src/lib/stores/vehicle.ts | 12 ++-- app/frontend/src/lib/utils/api.ts | 19 +++++++ app/frontend/src/routes/+layout.svelte | 1 + app/frontend/src/routes/login/+page.svelte | 22 +++----- app/frontend/svelte.config.js | 1 + docker/Dockerfile | 8 +-- docker/docker-compose.yml | 10 +--- docs/ENVIRONMENT.md | 18 +++--- package.json | 31 +++++----- 26 files changed, 225 insertions(+), 178 deletions(-) create mode 100644 app/backend/src/middleware/async-handler.ts create mode 100644 app/frontend/src/lib/utils/api.ts diff --git a/.env.example b/.env.example index b86c3bbf..afebe5c3 100644 --- a/.env.example +++ b/.env.example @@ -6,7 +6,7 @@ # ----------------------------------------------------------------------------- # Application Environment # ----------------------------------------------------------------------------- -NODE_ENV=development +ENVIRONMENT=development # ----------------------------------------------------------------------------- # Server Configuration @@ -22,11 +22,6 @@ CLIENT_PORT=5173 # ----------------------------------------------------------------------------- # API Configuration # ----------------------------------------------------------------------------- -# Base URL for API calls (used by frontend) -# In development: http://localhost:3000 -# In production: your domain or leave empty for relative URLs -API_BASE_URL=http://localhost:3000 - # SvelteKit public environment variables (PUBLIC_ prefix required) PUBLIC_API_BASE_URL=http://localhost:3000 @@ -39,9 +34,6 @@ DATABASE_PATH=./vehicles.db # ----------------------------------------------------------------------------- # Application Features # ----------------------------------------------------------------------------- -# Enable demo mode (disables certain features for public demos) -DEMO_MODE=false - # SvelteKit public demo mode (PUBLIC_ prefix required) PUBLIC_DEMO_MODE=false diff --git a/app/backend/index.ts b/app/backend/index.ts index 0fea771f..909811c3 100644 --- a/app/backend/index.ts +++ b/app/backend/index.ts @@ -1,9 +1,3 @@ -import { config } from "dotenv"; -import { resolve } from "path"; - -// Load environment variables from root directory -config({ path: resolve(process.cwd(), "../../.env") }); - import express from "express"; import cors from "cors"; import pinRoutes from "@routes/pinRoutes.js"; @@ -21,21 +15,21 @@ const app = express(); // Configure CORS - simplified for development const corsOptions = env.isDevelopment() ? { - // In development, allow all origins for easier debugging - origin: true, - credentials: true, - methods: ["GET", "POST", "PUT", "DELETE", "OPTIONS"], - allowedHeaders: ["Content-Type", "Authorization", "X-User-PIN"], - optionsSuccessStatus: 200, - } + // In development, allow all origins for easier debugging + origin: true, + credentials: true, + methods: ["GET", "POST", "PUT", "DELETE", "OPTIONS"], + allowedHeaders: ["Content-Type", "Authorization", "X-User-PIN"], + optionsSuccessStatus: 200, + } : { - // In production, use strict origin checking - origin: env.CORS_ORIGINS, - credentials: true, - methods: ["GET", "POST", "PUT", "DELETE", "OPTIONS"], - allowedHeaders: ["Content-Type", "Authorization", "X-User-PIN"], - optionsSuccessStatus: 200, - }; + // In production, use strict origin checking + origin: env.CORS_ORIGINS, + credentials: true, + methods: ["GET", "POST", "PUT", "DELETE", "OPTIONS"], + allowedHeaders: ["Content-Type", "Authorization", "X-User-PIN"], + optionsSuccessStatus: 200, + }; app.use(cors(corsOptions)); @@ -69,14 +63,14 @@ initializeDatabase() console.log( `🚀 Server running at http://${env.SERVER_HOST}:${env.SERVER_PORT}` ); - console.log(`📊 Environment: ${env.NODE_ENV}`); - console.log(`🗄️ Database: ${env.DATABASE_PATH}`); - console.log(`🎭 Demo Mode: ${env.DEMO_MODE ? "Enabled" : "Disabled"}`); + console.log(`Environment: ${env.ENVIRONMENT}`); + console.log(`Database: ${env.DATABASE_PATH}`); + console.log(`Demo Mode: ${env.DEMO_MODE ? "Enabled" : "Disabled"}`); console.log( - `🌐 CORS: ${env.isDevelopment() ? "Permissive (Development)" : "Strict (Production)"}` + `CORS: ${env.isDevelopment() ? "Permissive (Development)" : "Strict (Production)"}` ); if (!env.isDevelopment()) { - console.log(`📋 Allowed origins: ${env.CORS_ORIGINS.join(", ")}`); + console.log(`Allowed origins: ${env.CORS_ORIGINS.join(", ")}`); } console.log("─".repeat(75)); }); diff --git a/app/backend/nodemon.json b/app/backend/nodemon.json index ce986a7f..6acb793f 100644 --- a/app/backend/nodemon.json +++ b/app/backend/nodemon.json @@ -1,8 +1,10 @@ { - "watch": ["./**/*.ts"], + "watch": [ + "./**/*.ts" + ], "ext": "ts", "exec": "tsx index.ts", "env": { - "NODE_ENV": "development" + "ENVIRONMENT": "development" } -} +} \ No newline at end of file diff --git a/app/backend/package.json b/app/backend/package.json index c1d2618f..24ac4768 100644 --- a/app/backend/package.json +++ b/app/backend/package.json @@ -1,5 +1,5 @@ { - "name": "server", + "name": "backend", "version": "0.0.1", "description": "Tractor backend", "main": "dist/index.js", @@ -45,4 +45,4 @@ "tsx": "^4.20.3", "typescript": "^5.8.3" } -} +} \ No newline at end of file diff --git a/app/backend/src/config/env.ts b/app/backend/src/config/env.ts index e1406db5..87119580 100644 --- a/app/backend/src/config/env.ts +++ b/app/backend/src/config/env.ts @@ -1,11 +1,36 @@ -/** - * Environment Configuration - * Centralized environment variable management for the backend - */ +import { config } from "dotenv"; +import { resolve } from "path"; + +// Load environment variables from root directory +config({ + path: resolve(process.cwd(), "../../.env"), + override: true, + quiet: true, +}); + + +const getOrigins = (): string[] => { + const origins = process.env.CORS_ORIGINS; + if (!origins) { + // Allow localhost by default in development + if (process.env.ENVIRONMENT === "development") { + return ["*"]; + } else { + return [ + "http://localhost:5173", + "http://localhost:3000", + "http://127.0.0.1:5173", + "http://127.0.0.1:3000", + ] + } + } + + return origins.split(",").map(origin => origin.trim()).filter(Boolean); +} export const env = { // Application Environment - NODE_ENV: process.env.NODE_ENV || "development", + ENVIRONMENT: process.env.NODE_ENV || "development", // Server Configuration SERVER_HOST: process.env.SERVER_HOST || "0.0.0.0", @@ -15,26 +40,19 @@ export const env = { DATABASE_PATH: process.env.DATABASE_PATH || "./vehicles.db", // Application Features - DEMO_MODE: process.env.DEMO_MODE === "true", + DEMO_MODE: process.env.PUBLIC_DEMO_MODE === "true", // CORS Configuration - CORS_ORIGINS: process.env.CORS_ORIGINS?.split(",").map((origin) => - origin.trim(), - ) || [ - "http://localhost:5173", - "http://localhost:3000", - "http://127.0.0.1:5173", - "http://127.0.0.1:3000", - ], + CORS_ORIGINS: getOrigins(), // Logging Configuration LOG_LEVEL: process.env.LOG_LEVEL || "info", LOG_REQUESTS: process.env.LOG_REQUESTS === "true", // Helper methods - isDevelopment: () => process.env.NODE_ENV === "development", - isProduction: () => process.env.NODE_ENV === "production", - isTest: () => process.env.NODE_ENV === "test", + isDevelopment: () => !process.env.ENVIRONMENT || process.env.ENVIRONMENT === "development", + isProduction: () => process.env.ENVIRONMENT === "production", + isTest: () => process.env.ENVIRONMENT === "test", } as const; export default env; @@ -43,7 +61,7 @@ export default env; * Validate required environment variables */ export function validateEnvironment(): void { - const required = ["NODE_ENV"]; + const required: string[] = []; const missing = required.filter((key) => !process.env[key]); if (missing.length > 0) { @@ -65,4 +83,4 @@ export function validateEnvironment(): void { } console.log("✅ Environment validation passed"); -} +} \ No newline at end of file diff --git a/app/backend/src/db/index.ts b/app/backend/src/db/index.ts index 3e3fcf03..e13f7cea 100644 --- a/app/backend/src/db/index.ts +++ b/app/backend/src/db/index.ts @@ -6,9 +6,13 @@ import { db } from "./db.js"; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); +// Determine if we're running in development (TypeScript) or production (compiled JavaScript) +const isProduction = __filename.includes("/dist/"); +const migrationExtension = isProduction ? "js" : "ts"; + const umzug = new Umzug({ migrations: { - glob: path.join(__dirname, "migrations/*.js"), + glob: path.join(__dirname, `migrations/*.${migrationExtension}`), resolve: ({ name, path: migrationPath }) => { return { name, @@ -36,7 +40,7 @@ type Migration = typeof umzug._types.migration; const performDbMigrations = async () => { try { console.log("Running database migrations..."); - await umzug.up(); + await umzug.up({}); console.log("Migrations completed successfully"); } catch (error) { console.error("Migration failed:", error); diff --git a/app/backend/src/middleware/async-handler.ts b/app/backend/src/middleware/async-handler.ts new file mode 100644 index 00000000..0e28254b --- /dev/null +++ b/app/backend/src/middleware/async-handler.ts @@ -0,0 +1,8 @@ +import { Request, Response, NextFunction } from "express"; + +// Wrapper to catch async errors and pass them to Express error handler +export const asyncHandler = (fn: Function) => { + return (req: Request, res: Response, next: NextFunction) => { + Promise.resolve(fn(req, res, next)).catch(next); + }; +}; diff --git a/app/backend/src/middleware/error-handler.ts b/app/backend/src/middleware/error-handler.ts index e8345c64..d6fb3351 100644 --- a/app/backend/src/middleware/error-handler.ts +++ b/app/backend/src/middleware/error-handler.ts @@ -1,34 +1,47 @@ -import { Request, Response } from "express"; +import { Request, Response, NextFunction } from "express"; import { ValidationError } from "sequelize"; export const errorHandler = ( err: any, req: Request, res: Response, - next: any, + next: NextFunction ) => { + // Log the error for debugging + console.error(`Error in ${req.method} ${req.path}:`, err); + res.setHeader("Content-Type", "application/json"); + + let statusCode = 500; let body = {}; + switch (err.name) { case "SequelizeValidationError": + statusCode = 400; body = { type: "ValidationError", - errors: err.errors.map((e: any) => { - return { - message: e.message, - path: e.path, - }; - }), + errors: err.errors.map((e: any) => ({ + message: e.message, + path: e.path, + })), }; break; - default: + case "AuthError": + // Use the status from the error if available, otherwise default to 500 + statusCode = err.status || 500; body = { - type: err.name, + type: "AuthError", errors: [{ message: err.message }], }; + break; + default: + // Check if error has a status property + statusCode = err.status || err.statusCode || 500; + body = { + type: err.name || "Error", + errors: [{ message: err.message || "Internal server error" }], + }; } - console.error(err.name); - res.status(500); - res.send(JSON.stringify(body)); + res.status(statusCode).json(body); }; diff --git a/app/backend/src/routes/configRoutes.ts b/app/backend/src/routes/configRoutes.ts index e97e2119..d887a85a 100644 --- a/app/backend/src/routes/configRoutes.ts +++ b/app/backend/src/routes/configRoutes.ts @@ -4,11 +4,12 @@ import { getConfigByKey, updateConfig, } from "@controllers/ConfigController.js"; +import { asyncHandler } from "@middleware/async-handler.js"; const router = Router(); -router.get("/", getConfig); -router.get("/:key", getConfigByKey); // Alias for getConfig -router.put("/", updateConfig); +router.get("/", asyncHandler(getConfig)); +router.get("/:key", asyncHandler(getConfigByKey)); // Alias for getConfig +router.put("/", asyncHandler(updateConfig)); export default router; diff --git a/app/backend/src/routes/fuelLogRoutes.ts b/app/backend/src/routes/fuelLogRoutes.ts index 0bc22d0d..1190875c 100644 --- a/app/backend/src/routes/fuelLogRoutes.ts +++ b/app/backend/src/routes/fuelLogRoutes.ts @@ -7,13 +7,14 @@ import { deleteFuelLog, } from "@controllers/FuelLogController.js"; import { authenticatePin } from "@middleware/auth.js"; +import { asyncHandler } from "@middleware/async-handler.js"; const router = Router({ mergeParams: true }); -router.post("/", authenticatePin, addFuelLog); -router.get("/", authenticatePin, getFuelLogs); -router.get("/:id", authenticatePin, getFuelLogById); -router.put("/:id", authenticatePin, updateFuelLog); -router.delete("/:id", authenticatePin, deleteFuelLog); +router.post("/", authenticatePin, asyncHandler(addFuelLog)); +router.get("/", authenticatePin, asyncHandler(getFuelLogs)); +router.get("/:id", authenticatePin, asyncHandler(getFuelLogById)); +router.put("/:id", authenticatePin, asyncHandler(updateFuelLog)); +router.delete("/:id", authenticatePin, asyncHandler(deleteFuelLog)); export default router; diff --git a/app/backend/src/routes/insuranceRoutes.ts b/app/backend/src/routes/insuranceRoutes.ts index 1920429b..a3bc3c40 100644 --- a/app/backend/src/routes/insuranceRoutes.ts +++ b/app/backend/src/routes/insuranceRoutes.ts @@ -6,12 +6,13 @@ import { deleteInsurance, } from "@controllers/InsuranceController.js"; import { authenticatePin } from "@middleware/auth.js"; +import { asyncHandler } from "@middleware/async-handler.js"; const router = Router({ mergeParams: true }); -router.post("/", authenticatePin, addInsurance); -router.get("/", authenticatePin, getInsurances); -router.put("/:id", authenticatePin, updateInsurance); -router.delete("/:id", authenticatePin, deleteInsurance); +router.post("/", authenticatePin, asyncHandler(addInsurance)); +router.get("/", authenticatePin, asyncHandler(getInsurances)); +router.put("/:id", authenticatePin, asyncHandler(updateInsurance)); +router.delete("/:id", authenticatePin, asyncHandler(deleteInsurance)); export default router; diff --git a/app/backend/src/routes/maintenanceLogRoutes.ts b/app/backend/src/routes/maintenanceLogRoutes.ts index c9961524..e328fabd 100644 --- a/app/backend/src/routes/maintenanceLogRoutes.ts +++ b/app/backend/src/routes/maintenanceLogRoutes.ts @@ -7,13 +7,14 @@ import { deleteMaintenanceLog, } from "@controllers/MaintenanceLogController.js"; import { authenticatePin } from "@middleware/auth.js"; +import { asyncHandler } from "@middleware/async-handler.js"; const router = Router({ mergeParams: true }); -router.post("/", authenticatePin, addMaintenanceLog); -router.get("/", authenticatePin, getMaintenanceLogs); -router.get("/:id", authenticatePin, getMaintenanceLogById); -router.put("/:id", authenticatePin, updateMaintenanceLog); -router.delete("/:id", authenticatePin, deleteMaintenanceLog); +router.post("/", authenticatePin, asyncHandler(addMaintenanceLog)); +router.get("/", authenticatePin, asyncHandler(getMaintenanceLogs)); +router.get("/:id", authenticatePin, asyncHandler(getMaintenanceLogById)); +router.put("/:id", authenticatePin, asyncHandler(updateMaintenanceLog)); +router.delete("/:id", authenticatePin, asyncHandler(deleteMaintenanceLog)); export default router; diff --git a/app/backend/src/routes/pinRoutes.ts b/app/backend/src/routes/pinRoutes.ts index c980e836..6705b7e5 100644 --- a/app/backend/src/routes/pinRoutes.ts +++ b/app/backend/src/routes/pinRoutes.ts @@ -1,10 +1,11 @@ import { Router } from "express"; import { setPin, verifyPin, getPinStatus } from "@controllers/PinController.js"; +import { asyncHandler } from "@middleware/async-handler.js"; const router = Router(); -router.post("/pin", setPin); -router.post("/pin/verify", verifyPin); -router.get("/pin/status", getPinStatus); +router.post("/pin", asyncHandler(setPin)); +router.post("/pin/verify", asyncHandler(verifyPin)); +router.get("/pin/status", asyncHandler(getPinStatus)); export default router; diff --git a/app/backend/src/routes/puccRoutes.ts b/app/backend/src/routes/puccRoutes.ts index 8b8d9eb3..8f6b13b8 100644 --- a/app/backend/src/routes/puccRoutes.ts +++ b/app/backend/src/routes/puccRoutes.ts @@ -6,12 +6,17 @@ import { deletePollutionCertificate, } from "@controllers/PUCCController.js"; import { authenticatePin } from "@middleware/auth.js"; +import { asyncHandler } from "@middleware/async-handler.js"; const router = Router({ mergeParams: true }); -router.post("/", authenticatePin, addPollutionCertificate); -router.get("/", authenticatePin, getPollutionCertificates); -router.put("/:id", authenticatePin, updatePollutionCertificate); -router.delete("/:id", authenticatePin, deletePollutionCertificate); +router.post("/", authenticatePin, asyncHandler(addPollutionCertificate)); +router.get("/", authenticatePin, asyncHandler(getPollutionCertificates)); +router.put("/:id", authenticatePin, asyncHandler(updatePollutionCertificate)); +router.delete( + "/:id", + authenticatePin, + asyncHandler(deletePollutionCertificate) +); export default router; diff --git a/app/backend/src/routes/vehicleRoutes.ts b/app/backend/src/routes/vehicleRoutes.ts index 0408efc7..57daf9c1 100644 --- a/app/backend/src/routes/vehicleRoutes.ts +++ b/app/backend/src/routes/vehicleRoutes.ts @@ -7,6 +7,7 @@ import { deleteVehicle, } from "@controllers/VehicleController.js"; import { authenticatePin } from "@middleware/auth.js"; +import { asyncHandler } from "@middleware/async-handler.js"; import fuelLogRoutes from "./fuelLogRoutes.js"; import insuranceRoutes from "./insuranceRoutes.js"; import maintenanceLogRoutes from "./maintenanceLogRoutes.js"; @@ -14,11 +15,11 @@ import puccRoutes from "./puccRoutes.js"; const router = Router(); -router.post("/", authenticatePin, addVehicle); -router.get("/", authenticatePin, getAllVehicles); -router.get("/:id", authenticatePin, getVehicleById); -router.put("/:id", authenticatePin, updateVehicle); -router.delete("/:id", authenticatePin, deleteVehicle); +router.post("/", authenticatePin, asyncHandler(addVehicle)); +router.get("/", authenticatePin, asyncHandler(getAllVehicles)); +router.get("/:id", authenticatePin, asyncHandler(getVehicleById)); +router.put("/:id", authenticatePin, asyncHandler(updateVehicle)); +router.delete("/:id", authenticatePin, asyncHandler(deleteVehicle)); router.use("/:vehicleId/fuel-logs", fuelLogRoutes); router.use("/:vehicleId/insurance", insuranceRoutes); diff --git a/app/frontend/package.json b/app/frontend/package.json index 70219272..38a3fead 100644 --- a/app/frontend/package.json +++ b/app/frontend/package.json @@ -1,5 +1,5 @@ { - "name": "client", + "name": "frontend", "private": true, "version": "0.0.1", "type": "module", @@ -37,4 +37,4 @@ "svelte-loading-spinners": "^0.3.6", "svelte5-chartjs": "^1.0.0" } -} +} \ No newline at end of file diff --git a/app/frontend/src/lib/stores/config.ts b/app/frontend/src/lib/stores/config.ts index 2e8d0ebb..54e3964a 100644 --- a/app/frontend/src/lib/stores/config.ts +++ b/app/frontend/src/lib/stores/config.ts @@ -1,6 +1,7 @@ import { writable } from 'svelte/store'; import { browser } from '$app/environment'; import { env } from '$env/dynamic/public'; +import { getApiUrl } from '$lib/utils/api'; export interface Config { key: string; @@ -13,14 +14,11 @@ const createConfigStore = () => { async function fetchConfig() { if (browser) { try { - const response = await fetch( - `${env.PUBLIC_API_BASE_URL || 'http://localhost:3000'}/api/config`, - { - headers: { - 'X-User-PIN': localStorage.getItem('userPin') || '' - } + const response = await fetch(getApiUrl('/api/config'), { + headers: { + 'X-User-PIN': localStorage.getItem('userPin') || '' } - ); + }); if (response.ok) { const data: Config[] = await response.json(); set(data); @@ -36,17 +34,14 @@ const createConfigStore = () => { async function updateConfig(configs: Config[]) { if (browser) { try { - const response = await fetch( - `${env.PUBLIC_API_BASE_URL || 'http://localhost:3000'}/api/config`, - { - method: 'PUT', - headers: { - 'Content-Type': 'application/json', - 'X-User-PIN': localStorage.getItem('userPin') || '' - }, - body: JSON.stringify(configs) - } - ); + const response = await fetch(getApiUrl('/api/config'), { + method: 'PUT', + headers: { + 'Content-Type': 'application/json', + 'X-User-PIN': localStorage.getItem('userPin') || '' + }, + body: JSON.stringify(configs) + }); if (response.ok) { fetchConfig(); // Refresh the config after updating } else { diff --git a/app/frontend/src/lib/stores/vehicle.ts b/app/frontend/src/lib/stores/vehicle.ts index 1f963c62..fa4a71df 100644 --- a/app/frontend/src/lib/stores/vehicle.ts +++ b/app/frontend/src/lib/stores/vehicle.ts @@ -2,6 +2,7 @@ import { goto, replaceState } from '$app/navigation'; import { env } from '$env/dynamic/public'; import type { Vehicle } from '$lib/models/vehicle'; import { simulateNetworkDelay } from '$lib/utils/dev'; +import { getApiUrl } from '$lib/utils/api'; import { redirect } from '@sveltejs/kit'; import { writable } from 'svelte/store'; @@ -66,14 +67,11 @@ const createVehiclesStore = () => { }); // await simulateNetworkDelay(2000); // Simulate network delay for development try { - const response = await fetch( - `${env.PUBLIC_API_BASE_URL || 'http://localhost:3000'}/api/vehicles`, - { - headers: { - 'X-User-PIN': pin || '' - } + const response = await fetch(getApiUrl('/api/vehicles'), { + headers: { + 'X-User-PIN': pin || '' } - ); + }); if (response.ok) { const vehicles = await response.json(); if (Array.isArray(vehicles)) { diff --git a/app/frontend/src/lib/utils/api.ts b/app/frontend/src/lib/utils/api.ts new file mode 100644 index 00000000..9c13227a --- /dev/null +++ b/app/frontend/src/lib/utils/api.ts @@ -0,0 +1,19 @@ +import { env } from '$env/dynamic/public'; + +/** + * Constructs API URLs properly, handling base URL concatenation + */ +export function getApiUrl(path: string): string { + const baseUrl = env.PUBLIC_API_BASE_URL || 'http://localhost:3000'; + + // Remove trailing slash from base URL and leading slash from path to avoid double slashes + const cleanBaseUrl = baseUrl.replace(/\/$/, ''); + const cleanPath = path.replace(/^\//, ''); + + // If base URL is empty or just a slash, return the path as-is (relative URL) + if (!cleanBaseUrl || cleanBaseUrl === '') { + return `/${cleanPath}`; + } + + return `${cleanBaseUrl}/${cleanPath}`; +} diff --git a/app/frontend/src/routes/+layout.svelte b/app/frontend/src/routes/+layout.svelte index 1b27dc91..0147abff 100644 --- a/app/frontend/src/routes/+layout.svelte +++ b/app/frontend/src/routes/+layout.svelte @@ -22,6 +22,7 @@ $effect(() => { demoMode = env.PUBLIC_DEMO_MODE === 'true'; + console.log('env', env); if (browser) { const pin = localStorage.getItem('userPin'); isAuthenticated = !!pin; diff --git a/app/frontend/src/routes/login/+page.svelte b/app/frontend/src/routes/login/+page.svelte index 0557d82a..065f274d 100644 --- a/app/frontend/src/routes/login/+page.svelte +++ b/app/frontend/src/routes/login/+page.svelte @@ -4,6 +4,7 @@ import PinInput from '$components/auth/PinInput.svelte'; import ThemeToggle from '$components/common/ThemeToggle.svelte'; import { env } from '$env/dynamic/public'; + import { getApiUrl } from '$lib/utils/api'; import { ShieldEllipsis, ShieldPlus, Tractor } from '@lucide/svelte'; import { Jumper } from 'svelte-loading-spinners'; import { simulateNetworkDelay } from '$lib/utils/dev'; @@ -22,9 +23,7 @@ if (browser) { async function checkPinStatus() { try { - const response = await fetch( - `${env.PUBLIC_API_BASE_URL || 'http://localhost:3000'}/api/pin/status` - ); + const response = await fetch(getApiUrl('/api/pin/status')); if (response.ok) { const data = await response.json(); pinExists = data.exists; @@ -70,16 +69,13 @@ const endpointCall = async (pin: string, verify = true) => { const endpoint = verify ? '/api/pin/verify' : '/api/pin'; - const response = await fetch( - `${env.PUBLIC_API_BASE_URL || 'http://localhost:3000'}${endpoint}`, - { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ pin }) - } - ); + const response = await fetch(getApiUrl(endpoint), { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ pin }) + }); if (response.ok) { // Store PIN and redirect diff --git a/app/frontend/svelte.config.js b/app/frontend/svelte.config.js index 0431db98..9cad3f48 100644 --- a/app/frontend/svelte.config.js +++ b/app/frontend/svelte.config.js @@ -11,6 +11,7 @@ const config = { $components: './src/components', $lib: './src/lib' } + } }; diff --git a/docker/Dockerfile b/docker/Dockerfile index a2cc7bac..836da652 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -4,9 +4,9 @@ WORKDIR /app/frontend COPY app/frontend/package*.json ./ RUN npm install COPY app/frontend/ ./ -ENV PUBLIC_API_BASE_URL=/ +ENV API_BASE_URL=/ ENV PUBLIC_DEMO_MODE=false -ENV NODE_ENV=production +ENV ENVIRONMENT=production RUN npm run build # Stage 2: Build the Node.js backend @@ -15,7 +15,7 @@ WORKDIR /app/backend COPY app/backend/package*.json ./ RUN npm install COPY app/backend/ ./ -ENV NODE_ENV=production +ENV ENVIRONMENT=production ENV DB_PATH=./vehicles.db RUN npm run build @@ -39,7 +39,7 @@ WORKDIR /app/backend EXPOSE 3000 -ENV NODE_ENV=production +ENV ENVIRONMENT=production ENV PORT=3000 ENV DB_PATH=/data/vehicles.db ENTRYPOINT ["/app/entrypoint.sh"] diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 84a2f22d..d3b4dccf 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -1,6 +1,6 @@ services: app: - image: ghcr.io/javedh-dev/tracktor:latest + image: ghcr.io/javedh-dev/tracktor:dev container_name: tracktor-app restart: always ports: @@ -8,14 +8,10 @@ services: volumes: - tracktor-app-data:/data environment: - - NODE_ENV=production - - SERVER_PORT=3000 - - SERVER_HOST=0.0.0.0 + - ENVIRONMENT=production - DATABASE_PATH=/data/vehicles.db - - PUBLIC_API_BASE_URL= - - PUBLIC_DEMO_MODE=false + - API_BASE_URL="http://localhost:3333" - DEMO_MODE=false - - LOG_LEVEL=info - CORS_ORIGINS=* volumes: diff --git a/docs/ENVIRONMENT.md b/docs/ENVIRONMENT.md index 25d3195b..9fb32282 100644 --- a/docs/ENVIRONMENT.md +++ b/docs/ENVIRONMENT.md @@ -23,7 +23,7 @@ Both backend and frontend applications load their configuration from the root `. | Variable | Default | Description | | ---------- | ------------- | ------------------------------------------------------- | -| `NODE_ENV` | `development` | Application environment (development, production, test) | +| `ENVIRONMENT` | `development` | Application environment (development, production, test) | ### Server Configuration @@ -39,7 +39,7 @@ Both backend and frontend applications load their configuration from the root `. | Variable | Default | Description | | --------------------- | ----------------------- | -------------------------- | | `API_BASE_URL` | `http://localhost:3000` | Base URL for API calls | -| `PUBLIC_API_BASE_URL` | `http://localhost:3000` | Public API URL (SvelteKit) | +| `API_BASE_URL` | `http://localhost:3000` | Public API URL (SvelteKit) | ### Database Configuration @@ -72,22 +72,22 @@ Both backend and frontend applications load their configuration from the root `. ### Development ```bash -NODE_ENV=development +ENVIRONMENT=development SERVER_PORT=3000 CLIENT_PORT=5173 API_BASE_URL=http://localhost:3000 -PUBLIC_API_BASE_URL=http://localhost:3000 +API_BASE_URL=http://localhost:3000 DEMO_MODE=false ``` ### Production ```bash -NODE_ENV=production +ENVIRONMENT=production SERVER_PORT=3000 SERVER_HOST=0.0.0.0 DATABASE_PATH=/data/vehicles.db -PUBLIC_API_BASE_URL=https://your-domain.com +API_BASE_URL=https://your-domain.com DEMO_MODE=false LOG_LEVEL=warn ``` @@ -98,7 +98,7 @@ The Docker configuration automatically sets production-appropriate values: ```yaml environment: - - NODE_ENV=production + - ENVIRONMENT=production - SERVER_PORT=3000 - SERVER_HOST=0.0.0.0 - DATABASE_PATH=/data/vehicles.db @@ -144,7 +144,7 @@ environment: SvelteKit requires the `PUBLIC_` prefix for environment variables that need to be available in the browser. This is why we have both: - `API_BASE_URL` (backend only) -- `PUBLIC_API_BASE_URL` (frontend/browser accessible) +- `API_BASE_URL` (frontend/browser accessible) ## Troubleshooting @@ -152,7 +152,7 @@ SvelteKit requires the `PUBLIC_` prefix for environment variables that need to b 1. **Port conflicts**: Change `SERVER_PORT` or `CLIENT_PORT` if ports are in use 2. **CORS errors**: Add your domain to `CORS_ORIGINS` -3. **API connection issues**: Verify `PUBLIC_API_BASE_URL` matches your backend URL +3. **API connection issues**: Verify `API_BASE_URL` matches your backend URL 4. **Database issues**: Check `DATABASE_PATH` permissions and directory existence ### Debugging diff --git a/package.json b/package.json index b7d02b6b..fde50d0a 100644 --- a/package.json +++ b/package.json @@ -2,26 +2,25 @@ "name": "tracktor", "private": true, "workspaces": [ - "app/*", - "docs" + "app/*" ], "scripts": { "setup": "node scripts/setup-env.js", "test:cors": "node scripts/test-cors.js", "dev": "concurrently \"npm run dev:backend\" \"npm run dev:frontend\"", - "dev:backend": "npm run dev --workspace=server", - "dev:frontend": "npm run dev --workspace=client", - "build": "npm run build --workspace=server && npm run build --workspace=client", - "build:backend": "npm run build --workspace=server", - "build:frontend": "npm run build --workspace=client", - "start": "npm run start --workspace=server", - "preview": "npm run preview --workspace=server", - "db:migrate": "npm run db:migrate --workspace=server", - "db:seed": "npm run db:seed --workspace=server", - "db:status": "npm run db:status --workspace=server", - "format": "npm run format --workspace=client && npm run format --workspace=server", - "lint": "npm run lint --workspace=client", - "check": "npm run check --workspace=client", + "dev:backend": "npm run dev --workspace=backend", + "dev:frontend": "npm run dev --workspace=frontend", + "build": "npm run build --workspace=backend && npm run build --workspace=frontend", + "build:backend": "npm run build --workspace=backend", + "build:frontend": "npm run build --workspace=frontend", + "start": "npm run start --workspace=backend", + "preview": "npm run preview --workspace=backend", + "db:migrate": "npm run db:migrate --workspace=backend", + "db:seed": "npm run db:seed --workspace=backend", + "db:status": "npm run db:status --workspace=backend", + "format": "npm run format --workspace=frontend && npm run format --workspace=backend", + "lint": "npm run lint --workspace=frontend", + "check": "npm run check --workspace=frontend", "docs:dev": "npm run docs:dev --workspace=docs", "docs:build": "npm run docs:build --workspace=docs", "docs:preview": "npm run docs:preview --workspace=docs", @@ -31,4 +30,4 @@ "devDependencies": { "concurrently": "^9.2.0" } -} +} \ No newline at end of file From 4fec3a981a93db81be0216df17dfa9d8a2566ec5 Mon Sep 17 00:00:00 2001 From: Javed Hussain Date: Sat, 23 Aug 2025 12:34:43 +0530 Subject: [PATCH 05/13] Fixed environment variables and sensible defaults --- .env.example | 2 +- app/backend/.gitignore | 2 +- app/backend/package.json | 3 +- app/backend/src/config/env.ts | 2 +- app/backend/src/db/README.md | 2 +- app/backend/src/db/db.ts | 2 +- app/backend/src/models/Vehicle.ts | 7 +- app/frontend/package.json | 3 +- .../src/components/common/StatusBlock.svelte | 2 +- app/frontend/src/routes/+layout.svelte | 3 +- app/frontend/src/routes/login/+page.svelte | 2 +- docker/Dockerfile | 4 +- docker/docker-compose.yml | 2 +- docs/ENVIRONMENT.md | 6 +- package-lock.json | 21 +++--- package.json | 1 + scripts/setup-env.js | 66 +++++++++++++++++++ 17 files changed, 97 insertions(+), 33 deletions(-) create mode 100755 scripts/setup-env.js diff --git a/.env.example b/.env.example index afebe5c3..74fca1eb 100644 --- a/.env.example +++ b/.env.example @@ -29,7 +29,7 @@ PUBLIC_API_BASE_URL=http://localhost:3000 # Database Configuration # ----------------------------------------------------------------------------- # Database file path (SQLite) -DATABASE_PATH=./vehicles.db +DATABASE_PATH=./tracktor.db # ----------------------------------------------------------------------------- # Application Features diff --git a/app/backend/.gitignore b/app/backend/.gitignore index bef14ba0..ef286b02 100644 --- a/app/backend/.gitignore +++ b/app/backend/.gitignore @@ -1,4 +1,4 @@ node_modules .env -vehicles.db +tracktor.db dist/ diff --git a/app/backend/package.json b/app/backend/package.json index 24ac4768..29f82d2a 100644 --- a/app/backend/package.json +++ b/app/backend/package.json @@ -13,7 +13,8 @@ "db:seed": "tsx src/db/migrate.ts seed", "db:status": "tsx src/db/status.ts", "format": "prettier --write .", - "clean": "rm -rf dist" + "clean": "rm -rf dist", + "reset": "npm run clean && rm -rf tracktor.db node_modules && npm install" }, "keywords": [], "author": "", diff --git a/app/backend/src/config/env.ts b/app/backend/src/config/env.ts index 87119580..2d0112a2 100644 --- a/app/backend/src/config/env.ts +++ b/app/backend/src/config/env.ts @@ -37,7 +37,7 @@ export const env = { SERVER_PORT: Number(process.env.SERVER_PORT) || 3000, // Database Configuration - DATABASE_PATH: process.env.DATABASE_PATH || "./vehicles.db", + DATABASE_PATH: process.env.DATABASE_PATH || "./tracktor.db", // Application Features DEMO_MODE: process.env.PUBLIC_DEMO_MODE === "true", diff --git a/app/backend/src/db/README.md b/app/backend/src/db/README.md index 412ae3cf..21a0b8b1 100644 --- a/app/backend/src/db/README.md +++ b/app/backend/src/db/README.md @@ -46,7 +46,7 @@ The application automatically runs migrations and seeds data on startup based on ## Environment Variables -- `DB_PATH` - SQLite database file path (default: ./vehicles.db) +- `DB_PATH` - SQLite database file path (default: ./tracktor.db) - `DEMO_MODE` - Enable demo data seeding (default: false) - `AUTH_PIN` - Set authentication PIN - `SHOW_SQL` - Show SQL queries in console (default: false) diff --git a/app/backend/src/db/db.ts b/app/backend/src/db/db.ts index 2da8a6c3..c6470678 100644 --- a/app/backend/src/db/db.ts +++ b/app/backend/src/db/db.ts @@ -7,7 +7,7 @@ const showSql = process.env.SHOW_SQL === "true" || false; const db = new Sequelize({ dialect: "sqlite", - storage: `${process.env.DB_PATH || "./vehicles.db"}`, // Use environment variable for database path + storage: `${process.env.DB_PATH || "./tracktor.db"}`, // Use environment variable for database path logging: showSql, }); diff --git a/app/backend/src/models/Vehicle.ts b/app/backend/src/models/Vehicle.ts index 22f3e8f4..8415da32 100644 --- a/app/backend/src/models/Vehicle.ts +++ b/app/backend/src/models/Vehicle.ts @@ -15,12 +15,11 @@ interface VehicleAttributes { odometer?: number; } -interface VehicleCreationAttributes extends Optional {} +interface VehicleCreationAttributes extends Optional { } class Vehicle extends Model - implements VehicleAttributes -{ + implements VehicleAttributes { declare public id: string; declare public make: string; declare public model: string; @@ -93,7 +92,7 @@ Vehicle.init( msg: "VIN number can't be an empty string.", }, is: { - args: "^[A-HJ-NPR-Z0-9]{17}$", + args: "^[A-HJ-NPR-Z0-9]{3,}$", msg: "VIN number format is incorrect.", }, async isUnique(value: string) { diff --git a/app/frontend/package.json b/app/frontend/package.json index 38a3fead..cf615fe4 100644 --- a/app/frontend/package.json +++ b/app/frontend/package.json @@ -11,7 +11,8 @@ "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", "format": "prettier --write .", "lint": "prettier --check .", - "clean": "rm -rf build .svelte-kit" + "clean": "rm -rf build .svelte-kit", + "reset": "npm run clean && rm -rf node_modules && npm install" }, "devDependencies": { "@sveltejs/adapter-node": "^5.2.13", diff --git a/app/frontend/src/components/common/StatusBlock.svelte b/app/frontend/src/components/common/StatusBlock.svelte index ce3858d2..86b5080c 100644 --- a/app/frontend/src/components/common/StatusBlock.svelte +++ b/app/frontend/src/components/common/StatusBlock.svelte @@ -18,7 +18,7 @@ {#if message} -

+

{#each message.split('\n') as error} {error}
{/each} diff --git a/app/frontend/src/routes/+layout.svelte b/app/frontend/src/routes/+layout.svelte index 0147abff..38472e5a 100644 --- a/app/frontend/src/routes/+layout.svelte +++ b/app/frontend/src/routes/+layout.svelte @@ -58,8 +58,7 @@ > ⚠️ NOTICE: This is a demo instance. Data will be reset periodically and is not saved - permanently.
- Please avoid adding any persoanl info.

Default PIN : 123456 diff --git a/app/frontend/src/routes/login/+page.svelte b/app/frontend/src/routes/login/+page.svelte index 065f274d..9011c95d 100644 --- a/app/frontend/src/routes/login/+page.svelte +++ b/app/frontend/src/routes/login/+page.svelte @@ -54,7 +54,7 @@ async function handlePinComplete(pin: string) { loading = true; status.message = ''; - await simulateNetworkDelay(1000); // Simulate network delay for development + // await simulateNetworkDelay(1000); // Simulate network delay for development try { await endpointCall(pin, pinExists); } catch (e) { diff --git a/docker/Dockerfile b/docker/Dockerfile index 836da652..1df45100 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -16,7 +16,7 @@ COPY app/backend/package*.json ./ RUN npm install COPY app/backend/ ./ ENV ENVIRONMENT=production -ENV DB_PATH=./vehicles.db +ENV DB_PATH=./tracktor.db RUN npm run build # Stage 3: Final production image @@ -41,5 +41,5 @@ EXPOSE 3000 ENV ENVIRONMENT=production ENV PORT=3000 -ENV DB_PATH=/data/vehicles.db +ENV DB_PATH=/data/tracktor.db ENTRYPOINT ["/app/entrypoint.sh"] diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index d3b4dccf..238d1a3a 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -9,7 +9,7 @@ services: - tracktor-app-data:/data environment: - ENVIRONMENT=production - - DATABASE_PATH=/data/vehicles.db + - DATABASE_PATH=/data/tracktor.db - API_BASE_URL="http://localhost:3333" - DEMO_MODE=false - CORS_ORIGINS=* diff --git a/docs/ENVIRONMENT.md b/docs/ENVIRONMENT.md index 9fb32282..dd996e87 100644 --- a/docs/ENVIRONMENT.md +++ b/docs/ENVIRONMENT.md @@ -45,7 +45,7 @@ Both backend and frontend applications load their configuration from the root `. | Variable | Default | Description | | --------------- | --------------- | ------------------------- | -| `DATABASE_PATH` | `./vehicles.db` | SQLite database file path | +| `DATABASE_PATH` | `./tracktor.db` | SQLite database file path | ### Application Features @@ -86,7 +86,7 @@ DEMO_MODE=false ENVIRONMENT=production SERVER_PORT=3000 SERVER_HOST=0.0.0.0 -DATABASE_PATH=/data/vehicles.db +DATABASE_PATH=/data/tracktor.db API_BASE_URL=https://your-domain.com DEMO_MODE=false LOG_LEVEL=warn @@ -101,7 +101,7 @@ environment: - ENVIRONMENT=production - SERVER_PORT=3000 - SERVER_HOST=0.0.0.0 - - DATABASE_PATH=/data/vehicles.db + - DATABASE_PATH=/data/tracktor.db - PUBLIC_DEMO_MODE=false - LOG_LEVEL=info ``` diff --git a/package-lock.json b/package-lock.json index 1381fd01..730d06a5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,15 +6,13 @@ "": { "name": "tracktor", "workspaces": [ - "app/*", - "docs" + "app/*" ], "devDependencies": { "concurrently": "^9.2.0" } }, "app/backend": { - "name": "server", "version": "0.0.1", "license": "ISC", "dependencies": { @@ -407,7 +405,6 @@ } }, "app/frontend": { - "name": "client", "version": "0.0.1", "dependencies": { "@lucide/svelte": "^0.525.0", @@ -3341,6 +3338,10 @@ "node": ">= 0.4" } }, + "node_modules/backend": { + "resolved": "app/backend", + "link": true + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -3722,10 +3723,6 @@ "node": ">=6" } }, - "node_modules/client": { - "resolved": "app/frontend", - "link": true - }, "node_modules/cliui": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", @@ -4571,6 +4568,10 @@ "node": ">= 0.8" } }, + "node_modules/frontend": { + "resolved": "app/frontend", + "link": true + }, "node_modules/fs-constants": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", @@ -7238,10 +7239,6 @@ "node": ">= 18" } }, - "node_modules/server": { - "resolved": "app/backend", - "link": true - }, "node_modules/set-blocking": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", diff --git a/package.json b/package.json index fde50d0a..59c9b397 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "docs:build": "npm run docs:build --workspace=docs", "docs:preview": "npm run docs:preview --workspace=docs", "clean": "npm run clean --workspaces --if-present", + "reset": "npm run reset --workspaces --if-present", "install:all": "npm install --workspaces" }, "devDependencies": { diff --git a/scripts/setup-env.js b/scripts/setup-env.js new file mode 100755 index 00000000..db3497db --- /dev/null +++ b/scripts/setup-env.js @@ -0,0 +1,66 @@ +#!/usr/bin/env node + +const fs = require("fs"); +const path = require("path"); + +console.log("Setting up Tracktor development environment..."); + +// Create scripts directory if it doesn't exist +const scriptsDir = path.dirname(__filename); +if (!fs.existsSync(scriptsDir)) { + fs.mkdirSync(scriptsDir, { recursive: true }); +} + +// Check if .env exists in root +const rootEnvPath = path.join(__dirname, "..", ".env"); +const exampleEnvPath = path.join(__dirname, "..", ".env.example"); + +if (!fs.existsSync(rootEnvPath)) { + if (fs.existsSync(exampleEnvPath)) { + console.log("📋 Copying .env.example to .env..."); + fs.copyFileSync(exampleEnvPath, rootEnvPath); + console.log("✅ Created .env file from .env.example"); + } else { + console.log( + "❌ No .env.example file found. Please create a .env file manually." + ); + process.exit(1); + } +} else { + console.log("✅ .env file already exists"); +} + +// Create symbolic link for frontend +const frontendEnvPath = path.join(__dirname, "..", "app", "frontend", ".env"); +const relativeRootEnvPath = "../../.env"; + +try { + // Remove existing symlink or file if it exists + if ( + fs.existsSync(frontendEnvPath) || + fs.lstatSync(frontendEnvPath).isSymbolicLink() + ) { + fs.unlinkSync(frontendEnvPath); + } +} catch (error) { + // File doesn't exist, which is fine +} + +try { + console.log( + "🔗 Creating symbolic link for frontend environment variables..." + ); + fs.symlinkSync(relativeRootEnvPath, frontendEnvPath); + console.log("✅ Created symbolic link: app/frontend/.env -> ../../.env"); +} catch (error) { + console.log("⚠️ Could not create symbolic link:", error.message); + console.log( + " You may need to manually ensure environment variables are available to the frontend." + ); +} + +console.log("🎉 Environment setup complete!"); +console.log(""); +console.log("Next steps:"); +console.log(" 1. Review and update .env file with your configuration"); +console.log(" 2. Run: npm run dev"); From a01a7dab65df94a84c6d4df99a42ec5d440fc66c Mon Sep 17 00:00:00 2001 From: Javed Hussain Date: Sat, 23 Aug 2025 12:46:56 +0530 Subject: [PATCH 06/13] Fixed empty string validations for non required attributes --- .../src/components/forms/FuelLogForm.svelte | 4 ++-- .../src/components/forms/InsuranceForm.svelte | 4 ++-- .../src/components/forms/MaintenanceLogForm.svelte | 4 ++-- .../forms/PollutionCertificateForm.svelte | 3 ++- .../src/components/forms/VehicleForm.svelte | 4 +++- app/frontend/src/lib/utils/formatting.ts | 13 +++++++++++++ 6 files changed, 24 insertions(+), 8 deletions(-) diff --git a/app/frontend/src/components/forms/FuelLogForm.svelte b/app/frontend/src/components/forms/FuelLogForm.svelte index 351676dd..b30c1750 100644 --- a/app/frontend/src/components/forms/FuelLogForm.svelte +++ b/app/frontend/src/components/forms/FuelLogForm.svelte @@ -4,7 +4,7 @@ import { env } from '$env/dynamic/public'; import { handleApiError } from '$lib/models/Error'; import type { Status } from '$lib/models/status'; - import { getCurrencySymbol } from '$lib/utils/formatting'; + import { cleanup, getCurrencySymbol } from '$lib/utils/formatting'; import FormField from '../common/FormField.svelte'; import { Calendar1, Gauge, Fuel, FileText, BadgeDollarSign } from '@lucide/svelte'; @@ -58,7 +58,7 @@ 'Content-Type': 'application/json', 'X-User-PIN': localStorage.getItem('userPin') || '' }, - body: JSON.stringify(refill) + body: JSON.stringify(cleanup(refill)) } ); if (response.ok) { diff --git a/app/frontend/src/components/forms/InsuranceForm.svelte b/app/frontend/src/components/forms/InsuranceForm.svelte index a25a2131..4183a6f3 100644 --- a/app/frontend/src/components/forms/InsuranceForm.svelte +++ b/app/frontend/src/components/forms/InsuranceForm.svelte @@ -5,7 +5,7 @@ import { env } from '$env/dynamic/public'; import { handleApiError } from '$lib/models/Error'; import type { Status } from '$lib/models/status'; - import { getCurrencySymbol } from '$lib/utils/formatting'; + import { cleanup, getCurrencySymbol } from '$lib/utils/formatting'; import { BadgeDollarSign, Building2, Calendar1, IdCard, Notebook } from '@lucide/svelte'; let { @@ -61,7 +61,7 @@ 'Content-Type': 'application/json', 'X-User-PIN': localStorage.getItem('userPin') || '' }, - body: JSON.stringify(insurance) + body: JSON.stringify(cleanup(insurance)) } ); diff --git a/app/frontend/src/components/forms/MaintenanceLogForm.svelte b/app/frontend/src/components/forms/MaintenanceLogForm.svelte index fe78c854..87eccacc 100644 --- a/app/frontend/src/components/forms/MaintenanceLogForm.svelte +++ b/app/frontend/src/components/forms/MaintenanceLogForm.svelte @@ -5,7 +5,7 @@ import { env } from '$env/dynamic/public'; import { handleApiError } from '$lib/models/Error'; import type { Status } from '$lib/models/status'; - import { getCurrencySymbol, getDistanceUnit } from '$lib/utils/formatting'; + import { cleanup, getCurrencySymbol, getDistanceUnit } from '$lib/utils/formatting'; import { BadgeDollarSign, Calendar1, Gauge, Hammer, Notebook } from '@lucide/svelte'; let { @@ -54,7 +54,7 @@ 'Content-Type': 'application/json', 'X-User-PIN': localStorage.getItem('userPin') || '' }, - body: JSON.stringify(log) + body: JSON.stringify(cleanup(log)) } ); diff --git a/app/frontend/src/components/forms/PollutionCertificateForm.svelte b/app/frontend/src/components/forms/PollutionCertificateForm.svelte index 89311bae..39f5079e 100644 --- a/app/frontend/src/components/forms/PollutionCertificateForm.svelte +++ b/app/frontend/src/components/forms/PollutionCertificateForm.svelte @@ -5,6 +5,7 @@ import { env } from '$env/dynamic/public'; import { handleApiError } from '$lib/models/Error'; import type { Status } from '$lib/models/status'; + import { cleanup } from '$lib/utils/formatting'; import { Calendar1, IdCard, Notebook, TestTube, TestTube2 } from '@lucide/svelte'; let { @@ -58,7 +59,7 @@ 'Content-Type': 'application/json', 'X-User-PIN': localStorage.getItem('userPin') || '' }, - body: JSON.stringify(certificate) + body: JSON.stringify(cleanup(certificate)) } ); diff --git a/app/frontend/src/components/forms/VehicleForm.svelte b/app/frontend/src/components/forms/VehicleForm.svelte index 78d29fe3..35ee748e 100644 --- a/app/frontend/src/components/forms/VehicleForm.svelte +++ b/app/frontend/src/components/forms/VehicleForm.svelte @@ -17,6 +17,7 @@ import StatusBlock from '$components/common/StatusBlock.svelte'; import type { Status } from '$lib/models/status'; import { handleApiError, type ApiError } from '$lib/models/Error'; + import { cleanup } from '$lib/utils/formatting'; let { vehicleToEdit = null, editMode = false, modalVisibility = $bindable(), loading } = $props(); @@ -65,7 +66,7 @@ 'Content-Type': 'application/json', 'X-User-PIN': localStorage.getItem('userPin') || '' }, - body: JSON.stringify(vehicle) + body: JSON.stringify(cleanup(vehicle)) } ); @@ -90,6 +91,7 @@ status = handleApiError(data, editMode); } } catch (e) { + console.error('Error persisting vehicle:', e); status = { message: 'Failed to connect to the server.', type: 'ERROR' diff --git a/app/frontend/src/lib/utils/formatting.ts b/app/frontend/src/lib/utils/formatting.ts index 3e2b4fca..9e212702 100644 --- a/app/frontend/src/lib/utils/formatting.ts +++ b/app/frontend/src/lib/utils/formatting.ts @@ -106,3 +106,16 @@ export { getMileageUnit, formatMileage }; + +export const cleanup = (obj: Record): Record => { + const result: Record = { ...obj }; + for (const key in result) { + if ( + result.hasOwnProperty(key) && + (String(result[key]).trim() === '' || result[key] === undefined) + ) { + result[key] = null; + } + } + return result; +}; \ No newline at end of file From e5f774ca5d323ab4ee53dc388a2e67660ccf5305 Mon Sep 17 00:00:00 2001 From: Javed Hussain Date: Sat, 23 Aug 2025 12:48:19 +0530 Subject: [PATCH 07/13] Refactor environment variable settings to use NODE_ENV consistently --- docker/Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index 1df45100..7a29327c 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -15,7 +15,7 @@ WORKDIR /app/backend COPY app/backend/package*.json ./ RUN npm install COPY app/backend/ ./ -ENV ENVIRONMENT=production +ENV NODE_ENV=production ENV DB_PATH=./tracktor.db RUN npm run build @@ -39,7 +39,7 @@ WORKDIR /app/backend EXPOSE 3000 -ENV ENVIRONMENT=production +ENV NODE_ENV=production ENV PORT=3000 ENV DB_PATH=/data/tracktor.db ENTRYPOINT ["/app/entrypoint.sh"] From 9074d3a20b4b613531b780437d765d67fbdd4544 Mon Sep 17 00:00:00 2001 From: Javed Hussain Date: Sat, 23 Aug 2025 13:54:03 +0530 Subject: [PATCH 08/13] Refactor environment variable usage to standardize on NODE_ENV across the application --- .env.example | 2 +- app/backend/index.ts | 2 +- app/backend/nodemon.json | 2 +- app/backend/src/config/env.ts | 10 +++++----- app/frontend/src/components/lists/FuelLogList.svelte | 12 +++++------- .../src/components/lists/InsuranceList.svelte | 12 +++++------- .../src/components/lists/MaintenanceLogList.svelte | 12 +++++------- .../components/lists/PollutionCertificateList.svelte | 12 +++++------- docker/Dockerfile | 2 +- docker/docker-compose.yml | 8 ++------ docs/ENVIRONMENT.md | 8 ++++---- 11 files changed, 35 insertions(+), 47 deletions(-) diff --git a/.env.example b/.env.example index 74fca1eb..09be4751 100644 --- a/.env.example +++ b/.env.example @@ -6,7 +6,7 @@ # ----------------------------------------------------------------------------- # Application Environment # ----------------------------------------------------------------------------- -ENVIRONMENT=development +NODE_ENV=development # ----------------------------------------------------------------------------- # Server Configuration diff --git a/app/backend/index.ts b/app/backend/index.ts index 909811c3..157b6137 100644 --- a/app/backend/index.ts +++ b/app/backend/index.ts @@ -63,7 +63,7 @@ initializeDatabase() console.log( `🚀 Server running at http://${env.SERVER_HOST}:${env.SERVER_PORT}` ); - console.log(`Environment: ${env.ENVIRONMENT}`); + console.log(`Environment: ${env.NODE_ENV}`); console.log(`Database: ${env.DATABASE_PATH}`); console.log(`Demo Mode: ${env.DEMO_MODE ? "Enabled" : "Disabled"}`); console.log( diff --git a/app/backend/nodemon.json b/app/backend/nodemon.json index 6acb793f..0ed08a2a 100644 --- a/app/backend/nodemon.json +++ b/app/backend/nodemon.json @@ -5,6 +5,6 @@ "ext": "ts", "exec": "tsx index.ts", "env": { - "ENVIRONMENT": "development" + "NODE_ENV": "development" } } \ No newline at end of file diff --git a/app/backend/src/config/env.ts b/app/backend/src/config/env.ts index 2d0112a2..a9864e97 100644 --- a/app/backend/src/config/env.ts +++ b/app/backend/src/config/env.ts @@ -13,7 +13,7 @@ const getOrigins = (): string[] => { const origins = process.env.CORS_ORIGINS; if (!origins) { // Allow localhost by default in development - if (process.env.ENVIRONMENT === "development") { + if (process.env.NODE_ENV === "development") { return ["*"]; } else { return [ @@ -30,7 +30,7 @@ const getOrigins = (): string[] => { export const env = { // Application Environment - ENVIRONMENT: process.env.NODE_ENV || "development", + NODE_ENV: process.env.NODE_ENV || "development", // Server Configuration SERVER_HOST: process.env.SERVER_HOST || "0.0.0.0", @@ -50,9 +50,9 @@ export const env = { LOG_REQUESTS: process.env.LOG_REQUESTS === "true", // Helper methods - isDevelopment: () => !process.env.ENVIRONMENT || process.env.ENVIRONMENT === "development", - isProduction: () => process.env.ENVIRONMENT === "production", - isTest: () => process.env.ENVIRONMENT === "test", + isDevelopment: () => !process.env.NODE_ENV || process.env.NODE_ENV === "development", + isProduction: () => process.env.NODE_ENV === "production", + isTest: () => process.env.NODE_ENV === "test", } as const; export default env; diff --git a/app/frontend/src/components/lists/FuelLogList.svelte b/app/frontend/src/components/lists/FuelLogList.svelte index fa0d40a2..8eb91ec6 100644 --- a/app/frontend/src/components/lists/FuelLogList.svelte +++ b/app/frontend/src/components/lists/FuelLogList.svelte @@ -12,6 +12,7 @@ import { Jumper } from 'svelte-loading-spinners'; import IconButton from '$components/common/IconButton.svelte'; import DeleteConfirmation from '$components/common/DeleteConfirmation.svelte'; + import { getApiUrl } from '$lib/utils/api'; const { vehicleId } = $props(); @@ -44,14 +45,11 @@ loading = true; error = ''; try { - const response = await fetch( - `${env.PUBLIC_API_BASE_URL || 'http://localhost:3000'}/api/vehicles/${vehicleId}/fuel-logs`, - { - headers: { - 'X-User-PIN': localStorage.getItem('userPin') || '' - } + const response = await fetch(getApiUrl(`/api/vehicles/${vehicleId}/fuel-logs`), { + headers: { + 'X-User-PIN': localStorage.getItem('userPin') || '' } - ); + }); if (response.ok) { fuelLogs = await response.json(); } else { diff --git a/app/frontend/src/components/lists/InsuranceList.svelte b/app/frontend/src/components/lists/InsuranceList.svelte index e3c256ea..073f4331 100644 --- a/app/frontend/src/components/lists/InsuranceList.svelte +++ b/app/frontend/src/components/lists/InsuranceList.svelte @@ -8,6 +8,7 @@ import { Jumper } from 'svelte-loading-spinners'; import IconButton from '$components/common/IconButton.svelte'; import DeleteConfirmation from '$components/common/DeleteConfirmation.svelte'; + import { getApiUrl } from '$lib/utils/api'; let { vehicleId } = $props(); @@ -44,14 +45,11 @@ loading = true; error = ''; try { - const response = await fetch( - `${env.PUBLIC_API_BASE_URL || 'http://localhost:3000'}/api/vehicles/${vehicleId}/insurance`, - { - headers: { - 'X-User-PIN': browser ? localStorage.getItem('userPin') || '' : '' - } + const response = await fetch(getApiUrl(`/api/vehicles/${vehicleId}/insurance`), { + headers: { + 'X-User-PIN': browser ? localStorage.getItem('userPin') || '' : '' } - ); + }); if (response.ok) { insurances = await response.json(); console.log('Insurance : ', JSON.stringify(insurances)); diff --git a/app/frontend/src/components/lists/MaintenanceLogList.svelte b/app/frontend/src/components/lists/MaintenanceLogList.svelte index bcd61dda..a106340a 100644 --- a/app/frontend/src/components/lists/MaintenanceLogList.svelte +++ b/app/frontend/src/components/lists/MaintenanceLogList.svelte @@ -8,6 +8,7 @@ import { Jumper } from 'svelte-loading-spinners'; import IconButton from '$components/common/IconButton.svelte'; import DeleteConfirmation from '$components/common/DeleteConfirmation.svelte'; + import { getApiUrl } from '$lib/utils/api'; let { vehicleId } = $props(); @@ -43,14 +44,11 @@ loading = true; error = ''; try { - const response = await fetch( - `${env.PUBLIC_API_BASE_URL || 'http://localhost:3000'}/api/vehicles/${vehicleId}/maintenance-logs`, - { - headers: { - 'X-User-PIN': browser ? localStorage.getItem('userPin') || '' : '' - } + const response = await fetch(getApiUrl(`/api/vehicles/${vehicleId}/maintenance-logs`), { + headers: { + 'X-User-PIN': browser ? localStorage.getItem('userPin') || '' : '' } - ); + }); if (response.ok) { const data = await response.json(); maintenanceLogs = data; diff --git a/app/frontend/src/components/lists/PollutionCertificateList.svelte b/app/frontend/src/components/lists/PollutionCertificateList.svelte index 2a188b49..86acc80f 100644 --- a/app/frontend/src/components/lists/PollutionCertificateList.svelte +++ b/app/frontend/src/components/lists/PollutionCertificateList.svelte @@ -7,6 +7,7 @@ import { Jumper } from 'svelte-loading-spinners'; import IconButton from '$components/common/IconButton.svelte'; import DeleteConfirmation from '$components/common/DeleteConfirmation.svelte'; + import { getApiUrl } from '$lib/utils/api'; let { vehicleId } = $props(); @@ -42,14 +43,11 @@ loading = true; error = ''; try { - const response = await fetch( - `${env.PUBLIC_API_BASE_URL || 'http://localhost:3000'}/api/vehicles/${vehicleId}/pucc`, - { - headers: { - 'X-User-PIN': browser ? localStorage.getItem('userPin') || '' : '' - } + const response = await fetch(getApiUrl(`/api/vehicles/${vehicleId}/pucc`), { + headers: { + 'X-User-PIN': browser ? localStorage.getItem('userPin') || '' : '' } - ); + }); if (response.ok) { pollutionCertificates = await response.json(); } else { diff --git a/docker/Dockerfile b/docker/Dockerfile index 7a29327c..5056868c 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -6,7 +6,7 @@ RUN npm install COPY app/frontend/ ./ ENV API_BASE_URL=/ ENV PUBLIC_DEMO_MODE=false -ENV ENVIRONMENT=production +ENV NODE_ENV=production RUN npm run build # Stage 2: Build the Node.js backend diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 238d1a3a..f6a86779 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -6,13 +6,9 @@ services: ports: - "3333:3000" volumes: - - tracktor-app-data:/data + - ../app/backend/tracktor.db:/data/tracktor.db environment: - - ENVIRONMENT=production - - DATABASE_PATH=/data/tracktor.db - - API_BASE_URL="http://localhost:3333" - - DEMO_MODE=false - - CORS_ORIGINS=* + - PUBLIC_API_BASE_URL=/ volumes: tracktor-app-data: null diff --git a/docs/ENVIRONMENT.md b/docs/ENVIRONMENT.md index dd996e87..83235f6d 100644 --- a/docs/ENVIRONMENT.md +++ b/docs/ENVIRONMENT.md @@ -23,7 +23,7 @@ Both backend and frontend applications load their configuration from the root `. | Variable | Default | Description | | ---------- | ------------- | ------------------------------------------------------- | -| `ENVIRONMENT` | `development` | Application environment (development, production, test) | +| `NODE_ENV` | `development` | Application environment (development, production, test) | ### Server Configuration @@ -72,7 +72,7 @@ Both backend and frontend applications load their configuration from the root `. ### Development ```bash -ENVIRONMENT=development +NODE_ENV=development SERVER_PORT=3000 CLIENT_PORT=5173 API_BASE_URL=http://localhost:3000 @@ -83,7 +83,7 @@ DEMO_MODE=false ### Production ```bash -ENVIRONMENT=production +NODE_ENV=production SERVER_PORT=3000 SERVER_HOST=0.0.0.0 DATABASE_PATH=/data/tracktor.db @@ -98,7 +98,7 @@ The Docker configuration automatically sets production-appropriate values: ```yaml environment: - - ENVIRONMENT=production + - NODE_ENV=production - SERVER_PORT=3000 - SERVER_HOST=0.0.0.0 - DATABASE_PATH=/data/tracktor.db From a84baf731fb4a5e68753891ea564137353cbb238 Mon Sep 17 00:00:00 2001 From: Javed Hussain Date: Sat, 23 Aug 2025 14:50:21 +0530 Subject: [PATCH 09/13] feat: add ESLint configuration for backend and frontend - Introduced ESLint configuration files for both backend and frontend to enforce coding standards and improve code quality. - Updated package.json scripts to include linting and formatting commands that utilize ESLint and Prettier. - Modified various TypeScript files to adhere to the new linting rules, including: - Removed unused variables and imports. - Adjusted error handling to log errors to the console. - Ensured consistent formatting and style across the codebase. - Enhanced CORS configuration in the backend to support development and production environments. - Refactored several components in the frontend to improve performance and maintainability. --- app/backend/eslint.config.js | 39 ++++++++++ app/backend/index.ts | 40 +++++------ app/backend/nodemon.json | 6 +- app/backend/package.json | 12 +++- app/backend/src/config/env.ts | 15 ++-- .../controllers/MaintenanceLogController.ts | 2 +- app/backend/src/controllers/PUCCController.ts | 3 +- app/backend/src/db/seeders/index.ts | 8 +-- app/backend/src/exceptions/ServiceError.ts | 2 +- app/backend/src/middleware/async-handler.ts | 4 +- app/backend/src/middleware/error-handler.ts | 3 +- app/backend/src/models/Insurance.ts | 3 +- app/backend/src/models/MaintenanceLog.ts | 1 - app/backend/src/models/PUCC.ts | 2 +- app/backend/src/models/Vehicle.ts | 7 +- app/backend/src/routes/puccRoutes.ts | 2 +- app/backend/src/services/configService.ts | 2 +- app/backend/src/services/fuelLogService.ts | 2 +- app/backend/src/services/insuranceService.ts | 3 +- .../src/services/maintenanceLogService.ts | 2 +- app/backend/src/services/pinService.ts | 2 +- .../services/pollutionCertificateService.ts | 3 +- app/backend/src/services/vehicleService.ts | 2 +- app/frontend/eslint.config.js | 71 +++++++++++++++++++ app/frontend/package.json | 15 +++- .../components/chart/DashboardCharts.svelte | 15 +--- .../components/common/ModalContainer.svelte | 4 +- .../src/components/common/StatusBlock.svelte | 2 - .../src/components/forms/ConfigForm.svelte | 2 +- .../src/components/forms/FuelLogForm.svelte | 5 +- .../src/components/forms/InsuranceForm.svelte | 3 +- .../forms/MaintenanceLogForm.svelte | 3 +- .../forms/PollutionCertificateForm.svelte | 5 +- .../src/components/lists/InsuranceList.svelte | 13 ++-- .../lists/MaintenanceLogList.svelte | 13 ++-- .../lists/PollutionCertificateList.svelte | 12 ++-- .../src/components/modals/ConfigModal.svelte | 3 - app/frontend/src/lib/stores/auth.ts | 5 +- app/frontend/src/lib/stores/config.ts | 1 - app/frontend/src/lib/stores/vehicle.ts | 5 +- app/frontend/src/lib/utils/formatting.ts | 2 +- app/frontend/src/routes/+layout.svelte | 5 +- app/frontend/src/routes/login/+page.svelte | 3 +- app/frontend/svelte.config.js | 1 - package.json | 6 +- 45 files changed, 232 insertions(+), 127 deletions(-) create mode 100644 app/backend/eslint.config.js create mode 100644 app/frontend/eslint.config.js diff --git a/app/backend/eslint.config.js b/app/backend/eslint.config.js new file mode 100644 index 00000000..330344ba --- /dev/null +++ b/app/backend/eslint.config.js @@ -0,0 +1,39 @@ +import js from "@eslint/js"; +import ts from "@typescript-eslint/eslint-plugin"; +import tsParser from "@typescript-eslint/parser"; +import globals from "globals"; +import unusedImports from "eslint-plugin-unused-imports"; + +export default [ + js.configs.recommended, + { + files: ["**/*.{js,ts}"], + languageOptions: { + parser: tsParser, + parserOptions: { + sourceType: "module", + ecmaVersion: 2020, + }, + globals: { + ...globals.node, + ...globals.es2017, + }, + }, + plugins: { + "@typescript-eslint": ts, + "unused-imports": unusedImports, + }, + rules: { + ...ts.configs.recommended.rules, + "@typescript-eslint/no-unused-vars": "off", + "unused-imports/no-unused-imports": "error", + "unused-imports/no-unused-vars": ["error", { argsIgnorePattern: "^_" }], + "no-unused-vars": "off", + "@typescript-eslint/no-explicit-any": "off", + "@typescript-eslint/no-empty-object-type": "off", + }, + }, + { + ignores: ["node_modules/", "dist/", "*.config.js", "*.config.ts"], + }, +]; diff --git a/app/backend/index.ts b/app/backend/index.ts index 157b6137..9b83653b 100644 --- a/app/backend/index.ts +++ b/app/backend/index.ts @@ -15,21 +15,21 @@ const app = express(); // Configure CORS - simplified for development const corsOptions = env.isDevelopment() ? { - // In development, allow all origins for easier debugging - origin: true, - credentials: true, - methods: ["GET", "POST", "PUT", "DELETE", "OPTIONS"], - allowedHeaders: ["Content-Type", "Authorization", "X-User-PIN"], - optionsSuccessStatus: 200, - } + // In development, allow all origins for easier debugging + origin: true, + credentials: true, + methods: ["GET", "POST", "PUT", "DELETE", "OPTIONS"], + allowedHeaders: ["Content-Type", "Authorization", "X-User-PIN"], + optionsSuccessStatus: 200, + } : { - // In production, use strict origin checking - origin: env.CORS_ORIGINS, - credentials: true, - methods: ["GET", "POST", "PUT", "DELETE", "OPTIONS"], - allowedHeaders: ["Content-Type", "Authorization", "X-User-PIN"], - optionsSuccessStatus: 200, - }; + // In production, use strict origin checking + origin: env.CORS_ORIGINS, + credentials: true, + methods: ["GET", "POST", "PUT", "DELETE", "OPTIONS"], + allowedHeaders: ["Content-Type", "Authorization", "X-User-PIN"], + optionsSuccessStatus: 200, + }; app.use(cors(corsOptions)); @@ -43,15 +43,9 @@ app.use("/api/vehicles", vehicleRoutes); app.use("/api/config", configRoutes); if (env.isProduction()) { - // @ts-ignore + // @ts-expect-error -- Ignore import error for dynamic import const { handler } = await import("../frontend/build/handler.js"); app.use(handler); -} else { - // In dev, redirect to SvelteKit dev server - app.use("/", (req, res) => { - const clientPort = process.env.CLIENT_PORT || 5173; - res.redirect(`http://localhost:${clientPort}${req.originalUrl}`); - }); } app.use(errorHandler); @@ -61,13 +55,13 @@ initializeDatabase() app.listen(env.SERVER_PORT, env.SERVER_HOST, () => { console.log("─".repeat(75)); console.log( - `🚀 Server running at http://${env.SERVER_HOST}:${env.SERVER_PORT}` + `🚀 Server running at http://${env.SERVER_HOST}:${env.SERVER_PORT}`, ); console.log(`Environment: ${env.NODE_ENV}`); console.log(`Database: ${env.DATABASE_PATH}`); console.log(`Demo Mode: ${env.DEMO_MODE ? "Enabled" : "Disabled"}`); console.log( - `CORS: ${env.isDevelopment() ? "Permissive (Development)" : "Strict (Production)"}` + `CORS: ${env.isDevelopment() ? "Permissive (Development)" : "Strict (Production)"}`, ); if (!env.isDevelopment()) { console.log(`Allowed origins: ${env.CORS_ORIGINS.join(", ")}`); diff --git a/app/backend/nodemon.json b/app/backend/nodemon.json index 0ed08a2a..ce986a7f 100644 --- a/app/backend/nodemon.json +++ b/app/backend/nodemon.json @@ -1,10 +1,8 @@ { - "watch": [ - "./**/*.ts" - ], + "watch": ["./**/*.ts"], "ext": "ts", "exec": "tsx index.ts", "env": { "NODE_ENV": "development" } -} \ No newline at end of file +} diff --git a/app/backend/package.json b/app/backend/package.json index 29f82d2a..7859c458 100644 --- a/app/backend/package.json +++ b/app/backend/package.json @@ -12,7 +12,8 @@ "db:migrate": "tsx src/db/migrate.ts migrate", "db:seed": "tsx src/db/migrate.ts seed", "db:status": "tsx src/db/status.ts", - "format": "prettier --write .", + "format": "(eslint --fix . || true) && prettier --write .", + "lint": "eslint . && prettier --check .", "clean": "rm -rf dist", "reset": "npm run clean && rm -rf tracktor.db node_modules && npm install" }, @@ -33,12 +34,19 @@ "umzug": "^3.8.2" }, "devDependencies": { + "@eslint/js": "^9.34.0", "@types/bcrypt": "^5.0.2", "@types/cors": "^2.8.19", "@types/express": "^4.17.21", "@types/node": "^24.0.13", "@types/sequelize": "^4.28.20", "@types/sqlite3": "^3.1.11", + "@typescript-eslint/eslint-plugin": "^8.40.0", + "@typescript-eslint/parser": "^8.40.0", + "eslint": "^9.34.0", + "eslint-config-prettier": "^10.1.8", + "eslint-plugin-unused-imports": "^4.2.0", + "globals": "^14.0.0", "nodemon": "^3.1.10", "prettier": "^3.6.2", "ts-node": "^10.9.2", @@ -46,4 +54,4 @@ "tsx": "^4.20.3", "typescript": "^5.8.3" } -} \ No newline at end of file +} diff --git a/app/backend/src/config/env.ts b/app/backend/src/config/env.ts index a9864e97..42b0159d 100644 --- a/app/backend/src/config/env.ts +++ b/app/backend/src/config/env.ts @@ -8,7 +8,6 @@ config({ quiet: true, }); - const getOrigins = (): string[] => { const origins = process.env.CORS_ORIGINS; if (!origins) { @@ -21,12 +20,15 @@ const getOrigins = (): string[] => { "http://localhost:3000", "http://127.0.0.1:5173", "http://127.0.0.1:3000", - ] + ]; } } - return origins.split(",").map(origin => origin.trim()).filter(Boolean); -} + return origins + .split(",") + .map((origin) => origin.trim()) + .filter(Boolean); +}; export const env = { // Application Environment @@ -50,7 +52,8 @@ export const env = { LOG_REQUESTS: process.env.LOG_REQUESTS === "true", // Helper methods - isDevelopment: () => !process.env.NODE_ENV || process.env.NODE_ENV === "development", + isDevelopment: () => + !process.env.NODE_ENV || process.env.NODE_ENV === "development", isProduction: () => process.env.NODE_ENV === "production", isTest: () => process.env.NODE_ENV === "test", } as const; @@ -83,4 +86,4 @@ export function validateEnvironment(): void { } console.log("✅ Environment validation passed"); -} \ No newline at end of file +} diff --git a/app/backend/src/controllers/MaintenanceLogController.ts b/app/backend/src/controllers/MaintenanceLogController.ts index 0bb907e3..18445867 100644 --- a/app/backend/src/controllers/MaintenanceLogController.ts +++ b/app/backend/src/controllers/MaintenanceLogController.ts @@ -54,7 +54,7 @@ export const getMaintenanceLogById = async (req: Request, res: Response) => { export const updateMaintenanceLog = async (req: Request, res: Response) => { const { id } = req.params; - const { date, odometer, service, cost, notes } = req.body; + const { date, odometer, service, cost } = req.body; if (!id) { throw new MaintenanceLogError( diff --git a/app/backend/src/controllers/PUCCController.ts b/app/backend/src/controllers/PUCCController.ts index aa6338b5..34777342 100644 --- a/app/backend/src/controllers/PUCCController.ts +++ b/app/backend/src/controllers/PUCCController.ts @@ -45,8 +45,7 @@ export const updatePollutionCertificate = async ( res: Response, ) => { const { vehicleId, id } = req.params; - const { certificateNumber, issueDate, expiryDate, testingCenter, notes } = - req.body; + const { certificateNumber, issueDate, expiryDate, testingCenter } = req.body; if (!vehicleId || !id) { throw new PollutionCertificateError( diff --git a/app/backend/src/db/seeders/index.ts b/app/backend/src/db/seeders/index.ts index 8319e284..d0696fe9 100644 --- a/app/backend/src/db/seeders/index.ts +++ b/app/backend/src/db/seeders/index.ts @@ -122,7 +122,7 @@ export const seedDemoData = async () => { }, ]); - const vehicle1FuelLogs: any = [ + const vehicle1FuelLogs = [ { vehicleId: vehicle1.id, date: "2024-01-15", @@ -140,7 +140,7 @@ export const seedDemoData = async () => { }, ]; - const vehicle2FuelLogs: any = [ + const vehicle2FuelLogs = [ { vehicleId: vehicle2.id, date: "2024-01-20", @@ -167,7 +167,7 @@ export const seedDemoData = async () => { const cost = Math.random() * 16 + 50; vehicle1FuelLogs.push({ vehicleId: vehicle1.id, - date: currentDate1.toISOString().split("T")[0], + date: currentDate1.toISOString().split("T")[0] || "", odometer: currentOdometer1, fuelAmount: parseFloat(fuelAmount.toFixed(2)), cost: parseFloat(cost.toFixed(2)), @@ -183,7 +183,7 @@ export const seedDemoData = async () => { const cost = Math.random() * 16 + 50; vehicle2FuelLogs.push({ vehicleId: vehicle2.id, - date: currentDate2.toISOString().split("T")[0], + date: currentDate2.toISOString().split("T")[0] || "", odometer: currentOdometer2, fuelAmount: parseFloat(fuelAmount.toFixed(2)), cost: parseFloat(cost.toFixed(2)), diff --git a/app/backend/src/exceptions/ServiceError.ts b/app/backend/src/exceptions/ServiceError.ts index bc6d68fa..716c01bc 100644 --- a/app/backend/src/exceptions/ServiceError.ts +++ b/app/backend/src/exceptions/ServiceError.ts @@ -17,7 +17,7 @@ export class ServiceError extends Error { } } -export const statusFromError = (error: any) => { +export const statusFromError = (error: Error) => { if (error instanceof ServiceError) return error.status; if (error instanceof ValidationError) return Status.BAD_REQUEST; if (error instanceof UniqueConstraintError) return Status.CONFLICT; diff --git a/app/backend/src/middleware/async-handler.ts b/app/backend/src/middleware/async-handler.ts index 0e28254b..fa3893c7 100644 --- a/app/backend/src/middleware/async-handler.ts +++ b/app/backend/src/middleware/async-handler.ts @@ -1,7 +1,9 @@ import { Request, Response, NextFunction } from "express"; // Wrapper to catch async errors and pass them to Express error handler -export const asyncHandler = (fn: Function) => { +export const asyncHandler = ( + fn: (req: Request, res: Response, next: NextFunction) => void, +) => { return (req: Request, res: Response, next: NextFunction) => { Promise.resolve(fn(req, res, next)).catch(next); }; diff --git a/app/backend/src/middleware/error-handler.ts b/app/backend/src/middleware/error-handler.ts index d6fb3351..9dc676c2 100644 --- a/app/backend/src/middleware/error-handler.ts +++ b/app/backend/src/middleware/error-handler.ts @@ -1,11 +1,10 @@ import { Request, Response, NextFunction } from "express"; -import { ValidationError } from "sequelize"; export const errorHandler = ( err: any, req: Request, res: Response, - next: NextFunction + _: NextFunction, ) => { // Log the error for debugging console.error(`Error in ${req.method} ${req.path}:`, err); diff --git a/app/backend/src/models/Insurance.ts b/app/backend/src/models/Insurance.ts index 59623c0f..58ecbbb1 100644 --- a/app/backend/src/models/Insurance.ts +++ b/app/backend/src/models/Insurance.ts @@ -1,6 +1,5 @@ import { DataTypes, Model, Optional } from "sequelize"; import { db } from "@db/index.js"; -import Vehicle from "./Vehicle.js"; import { InsuranceError } from "@exceptions/InsuranceError.js"; import { Status } from "@exceptions/ServiceError.js"; @@ -68,7 +67,7 @@ Insurance.init( msg: "Policy Number must be between length 3 to 50.", }, is: { - args: "^[0-9A-Za-z\s\-]*$", + args: "^[0-9A-Za-z- ]*$", msg: "Only number and characters with space and hyphen are allowed in policy number.", }, }, diff --git a/app/backend/src/models/MaintenanceLog.ts b/app/backend/src/models/MaintenanceLog.ts index 8a1fac96..b8b24604 100644 --- a/app/backend/src/models/MaintenanceLog.ts +++ b/app/backend/src/models/MaintenanceLog.ts @@ -1,6 +1,5 @@ import { DataTypes, Model, Optional } from "sequelize"; import { db } from "@db/index.js"; -import Vehicle from "./Vehicle.js"; interface MaintenanceLogAttributes { id: string; diff --git a/app/backend/src/models/PUCC.ts b/app/backend/src/models/PUCC.ts index 77b33189..a7064951 100644 --- a/app/backend/src/models/PUCC.ts +++ b/app/backend/src/models/PUCC.ts @@ -58,7 +58,7 @@ PollutionCertificate.init( msg: "Certificate Number must be between length 3 to 50.", }, is: { - args: "^[0-9A-Za-z\s\-]*$", + args: "^[0-9A-Za-z- ]*$", msg: "Only number and characters with space and hyphen are allowed in certificate number.", }, }, diff --git a/app/backend/src/models/Vehicle.ts b/app/backend/src/models/Vehicle.ts index 8415da32..070b6f58 100644 --- a/app/backend/src/models/Vehicle.ts +++ b/app/backend/src/models/Vehicle.ts @@ -15,11 +15,12 @@ interface VehicleAttributes { odometer?: number; } -interface VehicleCreationAttributes extends Optional { } +interface VehicleCreationAttributes extends Optional {} class Vehicle extends Model - implements VehicleAttributes { + implements VehicleAttributes +{ declare public id: string; declare public make: string; declare public model: string; @@ -79,7 +80,7 @@ Vehicle.init( unique: true, validate: { is: { - args: "^[A-Z0-9\- ]{2,10}$", + args: "^[A-Z0-9- ]{2,25}$", msg: "Licence Plate format is incorrect.", }, }, diff --git a/app/backend/src/routes/puccRoutes.ts b/app/backend/src/routes/puccRoutes.ts index 8f6b13b8..e5d65d69 100644 --- a/app/backend/src/routes/puccRoutes.ts +++ b/app/backend/src/routes/puccRoutes.ts @@ -16,7 +16,7 @@ router.put("/:id", authenticatePin, asyncHandler(updatePollutionCertificate)); router.delete( "/:id", authenticatePin, - asyncHandler(deletePollutionCertificate) + asyncHandler(deletePollutionCertificate), ); export default router; diff --git a/app/backend/src/services/configService.ts b/app/backend/src/services/configService.ts index 5a89799e..ba4f4ae7 100644 --- a/app/backend/src/services/configService.ts +++ b/app/backend/src/services/configService.ts @@ -1,5 +1,5 @@ import { ConfigError } from "@exceptions/ConfigError.js"; -import { Status, statusFromError } from "@exceptions/ServiceError.js"; +import { Status } from "@exceptions/ServiceError.js"; import Config from "@models/Config.js"; export const getAppConfig = async () => { diff --git a/app/backend/src/services/fuelLogService.ts b/app/backend/src/services/fuelLogService.ts index ddc307cd..4b8970d1 100644 --- a/app/backend/src/services/fuelLogService.ts +++ b/app/backend/src/services/fuelLogService.ts @@ -1,5 +1,5 @@ import { FuelLogError } from "@exceptions/FuelLogError.js"; -import { Status, statusFromError } from "@exceptions/ServiceError.js"; +import { Status } from "@exceptions/ServiceError.js"; import { VehicleError } from "@exceptions/VehicleError.js"; import { Vehicle, FuelLog } from "@models/index.js"; diff --git a/app/backend/src/services/insuranceService.ts b/app/backend/src/services/insuranceService.ts index 7be26fbb..a6d952ca 100644 --- a/app/backend/src/services/insuranceService.ts +++ b/app/backend/src/services/insuranceService.ts @@ -1,7 +1,6 @@ import { InsuranceError } from "@exceptions/InsuranceError.js"; -import { Status, statusFromError } from "@exceptions/ServiceError.js"; +import { Status } from "@exceptions/ServiceError.js"; import { Insurance, Vehicle } from "@models/index.js"; -import { UniqueConstraintError } from "sequelize"; export const addInsurance = async (vehicleId: string, insuranceData: any) => { const vehicle = await Vehicle.findByPk(vehicleId); diff --git a/app/backend/src/services/maintenanceLogService.ts b/app/backend/src/services/maintenanceLogService.ts index 9f6b54de..b5f574c6 100644 --- a/app/backend/src/services/maintenanceLogService.ts +++ b/app/backend/src/services/maintenanceLogService.ts @@ -1,6 +1,6 @@ import { MaintenanceLog, Vehicle } from "@models/index.js"; import { MaintenanceLogError } from "@exceptions/MaintenanceLogError.js"; -import { Status, statusFromError } from "@exceptions/ServiceError.js"; +import { Status } from "@exceptions/ServiceError.js"; export const addMaintenanceLog = async ( vehicleId: string, diff --git a/app/backend/src/services/pinService.ts b/app/backend/src/services/pinService.ts index bde13fc5..1d496d5b 100644 --- a/app/backend/src/services/pinService.ts +++ b/app/backend/src/services/pinService.ts @@ -1,7 +1,7 @@ import bcrypt from "bcrypt"; import { Auth } from "@models/index.js"; import { AuthError } from "@exceptions/AuthError.js"; -import { Status, statusFromError } from "@exceptions/ServiceError.js"; +import { Status } from "@exceptions/ServiceError.js"; export const setPin = async (pin: string) => { const hash = await bcrypt.hash(pin, 10); diff --git a/app/backend/src/services/pollutionCertificateService.ts b/app/backend/src/services/pollutionCertificateService.ts index d42ce863..7e98371b 100644 --- a/app/backend/src/services/pollutionCertificateService.ts +++ b/app/backend/src/services/pollutionCertificateService.ts @@ -1,7 +1,6 @@ import { Vehicle, PollutionCertificate } from "@models/index.js"; import { PollutionCertificateError } from "@exceptions/PollutionCertificateError.js"; -import { UniqueConstraintError } from "sequelize"; -import { Status, statusFromError } from "@exceptions/ServiceError.js"; +import { Status } from "@exceptions/ServiceError.js"; export const addPollutionCertificate = async ( vehicleId: string, diff --git a/app/backend/src/services/vehicleService.ts b/app/backend/src/services/vehicleService.ts index 7fb7eabf..85b61007 100644 --- a/app/backend/src/services/vehicleService.ts +++ b/app/backend/src/services/vehicleService.ts @@ -1,4 +1,4 @@ -import { Status, statusFromError } from "@exceptions/ServiceError.js"; +import { Status } from "@exceptions/ServiceError.js"; import { VehicleError } from "@exceptions/VehicleError.js"; import { Insurance, PollutionCertificate, Vehicle } from "@models/index.js"; diff --git a/app/frontend/eslint.config.js b/app/frontend/eslint.config.js new file mode 100644 index 00000000..8966ab4b --- /dev/null +++ b/app/frontend/eslint.config.js @@ -0,0 +1,71 @@ +import js from '@eslint/js'; +import ts from '@typescript-eslint/eslint-plugin'; +import tsParser from '@typescript-eslint/parser'; +import svelte from 'eslint-plugin-svelte'; +import svelteParser from 'svelte-eslint-parser'; +import globals from 'globals'; +import unusedImports from 'eslint-plugin-unused-imports'; + +export default [ + js.configs.recommended, + { + files: ['**/*.{js,ts}'], + languageOptions: { + parser: tsParser, + parserOptions: { + sourceType: 'module', + ecmaVersion: 2020 + }, + globals: { + ...globals.browser, + ...globals.node, + ...globals.es2017 + } + }, + plugins: { + '@typescript-eslint': ts, + 'unused-imports': unusedImports + }, + rules: { + ...ts.configs.recommended.rules, + '@typescript-eslint/no-unused-vars': 'off', + 'unused-imports/no-unused-imports': 'error', + 'unused-imports/no-unused-vars': ['error', { argsIgnorePattern: '^_' }], + '@typescript-eslint/no-explicit-any': 'off', + 'no-unused-vars': 'off', + 'no-prototype-builtins': 'off' + } + }, + { + files: ['**/*.svelte'], + languageOptions: { + parser: svelteParser, + parserOptions: { + parser: tsParser + }, + globals: { + ...globals.browser, + ...globals.node, + ...globals.es2017, + $state: 'readonly' + } + }, + plugins: { + svelte, + '@typescript-eslint': ts, + 'unused-imports': unusedImports + }, + rules: { + ...svelte.configs.recommended.rules, + '@typescript-eslint/no-unused-vars': 'off', + 'unused-imports/no-unused-imports': 'error', + 'unused-imports/no-unused-vars': ['error', { argsIgnorePattern: '^_' }], + '@typescript-eslint/no-explicit-any': 'off', + 'no-unused-vars': 'off', + 'no-prototype-builtins': 'off' + } + }, + { + ignores: ['node_modules/', '.svelte-kit/', 'build/', 'dist/', '*.config.js', '*.config.ts'] + } +]; diff --git a/app/frontend/package.json b/app/frontend/package.json index cf615fe4..b8702cbd 100644 --- a/app/frontend/package.json +++ b/app/frontend/package.json @@ -9,25 +9,34 @@ "preview": "vite preview", "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", - "format": "prettier --write .", - "lint": "prettier --check .", + "format": "(eslint --fix . || true) && prettier --write .", + "lint": "eslint . && prettier --check .", "clean": "rm -rf build .svelte-kit", "reset": "npm run clean && rm -rf node_modules && npm install" }, "devDependencies": { + "@eslint/js": "^9.34.0", "@sveltejs/adapter-node": "^5.2.13", "@sveltejs/kit": "^2.22.0", "@sveltejs/vite-plugin-svelte": "^6.0.0", "@tailwindcss/forms": "^0.5.9", "@tailwindcss/typography": "^0.5.15", "@tailwindcss/vite": "^4.0.0", + "@typescript-eslint/eslint-plugin": "^8.40.0", + "@typescript-eslint/parser": "^8.40.0", "chart.js": "^4.5.0", "date-fns": "^4.1.0", + "eslint": "^9.34.0", + "eslint-config-prettier": "^10.1.8", + "eslint-plugin-svelte": "^3.11.0", + "eslint-plugin-unused-imports": "^4.2.0", + "globals": "^14.0.0", "prettier": "^3.4.2", "prettier-plugin-svelte": "^3.3.3", "prettier-plugin-tailwindcss": "^0.6.11", "svelte": "^5.0.0", "svelte-check": "^4.0.0", + "svelte-eslint-parser": "^1.3.1", "tailwindcss": "^4.0.0", "typescript": "^5.0.0", "vite": "^7.0.4" @@ -38,4 +47,4 @@ "svelte-loading-spinners": "^0.3.6", "svelte5-chartjs": "^1.0.0" } -} \ No newline at end of file +} diff --git a/app/frontend/src/components/chart/DashboardCharts.svelte b/app/frontend/src/components/chart/DashboardCharts.svelte index 03664f35..3f3c05bd 100644 --- a/app/frontend/src/components/chart/DashboardCharts.svelte +++ b/app/frontend/src/components/chart/DashboardCharts.svelte @@ -1,20 +1,9 @@

diff --git a/app/frontend/src/components/common/StatusBlock.svelte b/app/frontend/src/components/common/StatusBlock.svelte index 86b5080c..83f687eb 100644 --- a/app/frontend/src/components/common/StatusBlock.svelte +++ b/app/frontend/src/components/common/StatusBlock.svelte @@ -1,6 +1,4 @@