Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 1 addition & 9 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -22,26 +22,18 @@ 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

# -----------------------------------------------------------------------------
# Database Configuration
# -----------------------------------------------------------------------------
# Database file path (SQLite)
DATABASE_PATH=./vehicles.db
DATABASE_PATH=./tracktor.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

Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/docker-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion app/backend/.gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
node_modules
.env
vehicles.db
tracktor.db
dist/
39 changes: 39 additions & 0 deletions app/backend/eslint.config.js
Original file line number Diff line number Diff line change
@@ -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"],
},
];
58 changes: 18 additions & 40 deletions app/backend/index.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -18,24 +12,14 @@ validateEnvironment();

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 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,
};
// Configure CORS - use explicit origins in all environments
const corsOptions = {
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));

Expand All @@ -49,14 +33,12 @@ app.use("/api/vehicles", vehicleRoutes);
app.use("/api/config", configRoutes);

if (env.isProduction()) {
// @ts-ignore
const { handler } = await import("@frontend/build/handler.js");
// @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.get("/", (req, res) => {
res.redirect("http://localhost:5173");
});
}

Expand All @@ -67,17 +49,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}`,
);
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)"}`,
`🚀 Server running at http://${env.SERVER_HOST}:${env.SERVER_PORT}`
);
if (!env.isDevelopment()) {
console.log(`📋 Allowed origins: ${env.CORS_ORIGINS.join(", ")}`);
}
console.log(`Environment: ${env.NODE_ENV}`);
console.log(`Database: ${env.DATABASE_PATH}`);
console.log(`Demo Mode: ${env.DEMO_MODE ? "Enabled" : "Disabled"}`);
console.log(`CORS: Explicit origins only`);
console.log(`Allowed origins: [ ${env.CORS_ORIGINS.join(", ")} ]`);
console.log("─".repeat(75));
});
})
Expand Down
21 changes: 16 additions & 5 deletions app/backend/package.json
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
{
"name": "server",
"name": "backend",
"version": "0.0.1",
"description": "Tractor backend",
"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",
"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 .",
"clean": "rm -rf dist"
"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"
},
"keywords": [],
"author": "",
Expand All @@ -29,18 +31,27 @@
"sqlite3": "^5.1.7",
"swagger-jsdoc": "^6.2.8",
"swagger-ui-express": "^5.0.1",
"umzug": "^3.8.2"
"umzug": "^3.8.2",
"express-rate-limit": "^8.0.1"
},
"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",
"tsc-alias": "^1.8.16",
"tsx": "^4.20.3",
"typescript": "^5.8.3"
}
Expand Down
50 changes: 33 additions & 17 deletions app/backend/src/config/env.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,29 @@
/**
* 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) {
return [
"http://localhost:5173",
"http://127.0.0.1:5173",
"http://localhost:3000",
"http://127.0.0.1:3000",
];
}

return origins
.split(",")
.map((origin) => origin.trim())
.filter(Boolean);
};

export const env = {
// Application Environment
Expand All @@ -12,27 +34,21 @@ 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.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",
isDevelopment: () =>
!process.env.NODE_ENV || process.env.NODE_ENV === "development",
isProduction: () => process.env.NODE_ENV === "production",
isTest: () => process.env.NODE_ENV === "test",
} as const;
Expand All @@ -43,13 +59,13 @@ 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) {
console.error(
"❌ Missing required environment variables:",
missing.join(", "),
missing.join(", ")
);
process.exit(1);
}
Expand Down
2 changes: 1 addition & 1 deletion app/backend/src/controllers/MaintenanceLogController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
3 changes: 1 addition & 2 deletions app/backend/src/controllers/PUCCController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
2 changes: 1 addition & 1 deletion app/backend/src/db/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
2 changes: 1 addition & 1 deletion app/backend/src/db/db.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
});

Expand Down
8 changes: 6 additions & 2 deletions app/backend/src/db/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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/*.ts"),
glob: path.join(__dirname, `migrations/*.${migrationExtension}`),
resolve: ({ name, path: migrationPath }) => {
return {
name,
Expand Down Expand Up @@ -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);
Expand Down
Loading