diff --git a/.env.example b/.env.example
index b86c3bbf..09be4751 100644
--- a/.env.example
+++ b/.env.example
@@ -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
@@ -34,14 +29,11 @@ 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
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
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/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 95663794..2b4a4973 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";
@@ -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));
@@ -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");
});
}
@@ -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));
});
})
diff --git a/app/backend/package.json b/app/backend/package.json
index d8363b11..f1de08a4 100644
--- a/app/backend/package.json
+++ b/app/backend/package.json
@@ -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": "",
@@ -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"
}
diff --git a/app/backend/src/config/env.ts b/app/backend/src/config/env.ts
index e1406db5..42f6e17a 100644
--- a/app/backend/src/config/env.ts
+++ b/app/backend/src/config/env.ts
@@ -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
@@ -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;
@@ -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);
}
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/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/db/index.ts b/app/backend/src/db/index.ts
index ab2e0cc2..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/*.ts"),
+ 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/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
new file mode 100644
index 00000000..fa3893c7
--- /dev/null
+++ b/app/backend/src/middleware/async-handler.ts
@@ -0,0 +1,10 @@
+import { Request, Response, NextFunction } from "express";
+
+// Wrapper to catch async errors and pass them to Express error handler
+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 e8345c64..764ea79d 100644
--- a/app/backend/src/middleware/error-handler.ts
+++ b/app/backend/src/middleware/error-handler.ts
@@ -1,34 +1,46 @@
-import { Request, Response } from "express";
-import { ValidationError } from "sequelize";
+import { Request, Response, NextFunction } from "express";
export const errorHandler = (
err: any,
req: Request,
res: Response,
- next: any,
+ _: NextFunction,
) => {
+ // Log the error for debugging
+ console.error("Error in %s %s:", 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/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 22f3e8f4..070b6f58 100644
--- a/app/backend/src/models/Vehicle.ts
+++ b/app/backend/src/models/Vehicle.ts
@@ -80,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.",
},
},
@@ -93,7 +93,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/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..41b73355 100644
--- a/app/backend/src/routes/pinRoutes.ts
+++ b/app/backend/src/routes/pinRoutes.ts
@@ -1,10 +1,19 @@
import { Router } from "express";
import { setPin, verifyPin, getPinStatus } from "@controllers/PinController.js";
+import { asyncHandler } from "@middleware/async-handler.js";
+import rateLimit from "express-rate-limit";
const router = Router();
-router.post("/pin", setPin);
-router.post("/pin/verify", verifyPin);
-router.get("/pin/status", getPinStatus);
+// Rate limiter for /pin/verify to prevent brute-force/DoS attacks
+const pinVerifyLimiter = rateLimit({
+ windowMs: 5 * 60 * 1000, // 5 minutes
+ max: 5, // limit each IP to 5 requests per windowMs
+ message: "Too many PIN verification attempts, please try again later."
+});
+
+router.post("/pin", asyncHandler(setPin));
+router.post("/pin/verify", pinVerifyLimiter, 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..e5d65d69 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/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/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/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 70219272..b8702cbd 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",
@@ -9,24 +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 .",
- "clean": "rm -rf build .svelte-kit"
+ "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"
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 ce3858d2..83f687eb 100644
--- a/app/frontend/src/components/common/StatusBlock.svelte
+++ b/app/frontend/src/components/common/StatusBlock.svelte
@@ -1,6 +1,4 @@
{#if message}
-
+
{#each message.split('\n') as error}
{error}
{/each}
diff --git a/app/frontend/src/components/forms/ConfigForm.svelte b/app/frontend/src/components/forms/ConfigForm.svelte
index 565966d2..80ec3e4b 100644
--- a/app/frontend/src/components/forms/ConfigForm.svelte
+++ b/app/frontend/src/components/forms/ConfigForm.svelte
@@ -105,7 +105,7 @@
{/if}
{/each}
-
+
{#if status.message}
-
+
diff --git a/app/frontend/src/components/forms/InsuranceForm.svelte b/app/frontend/src/components/forms/InsuranceForm.svelte
index a25a2131..7667d235 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))
}
);
@@ -83,6 +83,7 @@
status = handleApiError(data, editMode);
}
} catch (e) {
+ console.error(e);
status = {
message: 'Failed to connect to the server.',
type: 'ERROR'
@@ -166,6 +167,6 @@
required={false}
ariaLabel="Notes"
/>
-
+
diff --git a/app/frontend/src/components/forms/MaintenanceLogForm.svelte b/app/frontend/src/components/forms/MaintenanceLogForm.svelte
index fe78c854..56dce9e1 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))
}
);
@@ -76,6 +76,7 @@
status = handleApiError(data, editMode);
}
} catch (e) {
+ console.error(e);
status = {
message: 'Failed to connect to the server.',
type: 'ERROR'
@@ -150,6 +151,6 @@
required={false}
ariaLabel="Additional Notes"
/>
-
+
diff --git a/app/frontend/src/components/forms/PollutionCertificateForm.svelte b/app/frontend/src/components/forms/PollutionCertificateForm.svelte
index 89311bae..018ac95b 100644
--- a/app/frontend/src/components/forms/PollutionCertificateForm.svelte
+++ b/app/frontend/src/components/forms/PollutionCertificateForm.svelte
@@ -5,7 +5,8 @@
import { env } from '$env/dynamic/public';
import { handleApiError } from '$lib/models/Error';
import type { Status } from '$lib/models/status';
- import { Calendar1, IdCard, Notebook, TestTube, TestTube2 } from '@lucide/svelte';
+ import { cleanup } from '$lib/utils/formatting';
+ import { Calendar1, IdCard, Notebook, TestTube2 } from '@lucide/svelte';
let {
vehicleId,
@@ -58,7 +59,7 @@
'Content-Type': 'application/json',
'X-User-PIN': localStorage.getItem('userPin') || ''
},
- body: JSON.stringify(certificate)
+ body: JSON.stringify(cleanup(certificate))
}
);
@@ -80,6 +81,7 @@
status = handleApiError(data, editMode);
}
} catch (e) {
+ console.error(e);
status = {
message: 'Failed to connect to the server.',
type: 'ERROR'
@@ -152,6 +154,6 @@
required={false}
ariaLabel="Notes"
/>
-
+
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/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..c82294fa 100644
--- a/app/frontend/src/components/lists/InsuranceList.svelte
+++ b/app/frontend/src/components/lists/InsuranceList.svelte
@@ -2,12 +2,12 @@
import { onMount } from 'svelte';
import { browser } from '$app/environment';
import { env } from '$env/dynamic/public';
- import { Shield, Calendar, Hash, DollarSign, Pencil, Trash2, Notebook } from '@lucide/svelte';
+ import { Shield, Calendar, Hash, DollarSign, Trash2, Notebook } from '@lucide/svelte';
import { formatCurrency, formatDate } from '$lib/utils/formatting';
- import { insuranceModelStore } from '$lib/stores/insurance';
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,21 +44,19 @@
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));
} else {
error = 'Failed to fetch insurance data.';
}
- } catch (err) {
+ } catch (e) {
+ console.error(e);
error = 'Network error. Please try again.';
} finally {
loading = false;
@@ -83,10 +81,11 @@
await fetchInsuranceDetails();
} else {
const data = await response.json();
- alert(data.message || 'Failed to delete insurance details.');
+ error = data.message || 'Failed to delete insurance details.';
}
- } catch (err) {
- alert('Network error. Failed to delete insurance details.');
+ } catch (e) {
+ console.error(e);
+ error = 'Network error. Failed to delete insurance details.';
}
}
diff --git a/app/frontend/src/components/lists/MaintenanceLogList.svelte b/app/frontend/src/components/lists/MaintenanceLogList.svelte
index bcd61dda..b7efe5b8 100644
--- a/app/frontend/src/components/lists/MaintenanceLogList.svelte
+++ b/app/frontend/src/components/lists/MaintenanceLogList.svelte
@@ -3,11 +3,11 @@
import { browser } from '$app/environment';
import { env } from '$env/dynamic/public';
import { formatCurrency, formatDate, formatDistance } from '$lib/utils/formatting';
- import { Pencil, Trash2 } from '@lucide/svelte';
- import { maintenanceModelStore } from '$lib/stores/maintenance';
+ import { Trash2 } from '@lucide/svelte';
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,21 +43,19 @@
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;
} else {
error = 'Failed to fetch maintenance logs.';
}
- } catch (err) {
+ } catch (e) {
+ console.error(e);
error = 'Network error. Please try again.';
} finally {
loading = false;
@@ -82,10 +80,11 @@
await fetchMaintenanceLogs();
} else {
const data = await response.json();
- alert(data.message || 'Failed to delete maintenance log.');
+ error = data.message || 'Failed to delete maintenance log.';
}
- } catch (err) {
- alert('Network error. Failed to delete maintenance log.');
+ } catch (e) {
+ console.error(e);
+ error = 'Network error. Failed to delete maintenance log.';
}
}
diff --git a/app/frontend/src/components/lists/PollutionCertificateList.svelte b/app/frontend/src/components/lists/PollutionCertificateList.svelte
index 2a188b49..5c2b66ca 100644
--- a/app/frontend/src/components/lists/PollutionCertificateList.svelte
+++ b/app/frontend/src/components/lists/PollutionCertificateList.svelte
@@ -2,11 +2,12 @@
import { onMount } from 'svelte';
import { browser } from '$app/environment';
import { env } from '$env/dynamic/public';
- import { FileText, Calendar, MapPin, Pencil, Trash2, BadgeCheck } from '@lucide/svelte';
+ import { FileText, Calendar, MapPin, Trash2, BadgeCheck } from '@lucide/svelte';
import { formatDate } from '$lib/utils/formatting';
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,20 +43,18 @@
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 {
error = 'Failed to fetch Pollution Certificates.';
}
- } catch (err) {
+ } catch (e) {
+ console.error(e);
error = 'Network error. Please try again.';
} finally {
loading = false;
@@ -80,10 +79,11 @@
await fetchPollutionCertificateDetails();
} else {
const data = await response.json();
- alert(data.message || 'Failed to delete pollution certificate.');
+ error = data.message || 'Failed to delete pollution certificate.';
}
- } catch (err) {
- alert('Network error. Failed to delete pollution certificate.');
+ } catch (e) {
+ console.error(e);
+ error = 'Network error. Failed to delete pollution certificate.';
}
}
diff --git a/app/frontend/src/components/modals/ConfigModal.svelte b/app/frontend/src/components/modals/ConfigModal.svelte
index 4bf1f564..ddd1c8a0 100644
--- a/app/frontend/src/components/modals/ConfigModal.svelte
+++ b/app/frontend/src/components/modals/ConfigModal.svelte
@@ -3,11 +3,8 @@
import ModalContainer from '$components/common/ModalContainer.svelte';
import { configModelStore } from '$lib/stores/config';
- let logToEdit = $state(null);
let showModal = $state(false);
- let editMode = $state(false);
let loading = $state(false);
- let vehicleId = $state(undefined);
let callback = $state();
configModelStore.subscribe((data) => {
diff --git a/app/frontend/src/lib/stores/auth.ts b/app/frontend/src/lib/stores/auth.ts
index 5a10d295..2427cc90 100644
--- a/app/frontend/src/lib/stores/auth.ts
+++ b/app/frontend/src/lib/stores/auth.ts
@@ -2,11 +2,10 @@ import { browser } from '$app/environment';
export class AuthState {
private static instance: AuthState;
- private isAuthenticated: boolean = $state(false);
+ private isAuthenticated: boolean = false;
private constructor(currentRoute: string) {
- // Private constructor to prevent instantiation from outside
- this.isAuthenticated = false; // Default value
+ this.isAuthenticated = false;
if (browser) {
if (currentRoute !== '/login') {
const pin = localStorage.getItem('userPin');
diff --git a/app/frontend/src/lib/stores/config.ts b/app/frontend/src/lib/stores/config.ts
index 2e8d0ebb..722d1efe 100644
--- a/app/frontend/src/lib/stores/config.ts
+++ b/app/frontend/src/lib/stores/config.ts
@@ -1,6 +1,6 @@
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 +13,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 +33,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..324104ca 100644
--- a/app/frontend/src/lib/stores/vehicle.ts
+++ b/app/frontend/src/lib/stores/vehicle.ts
@@ -1,8 +1,6 @@
-import { goto, replaceState } from '$app/navigation';
-import { env } from '$env/dynamic/public';
+import { goto } from '$app/navigation';
import type { Vehicle } from '$lib/models/vehicle';
-import { simulateNetworkDelay } from '$lib/utils/dev';
-import { redirect } from '@sveltejs/kit';
+import { getApiUrl } from '$lib/utils/api';
import { writable } from 'svelte/store';
const createVehicleModalStore = () => {
@@ -66,14 +64,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/lib/utils/formatting.ts b/app/frontend/src/lib/utils/formatting.ts
index 3e2b4fca..24312632 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;
+};
diff --git a/app/frontend/src/routes/+layout.svelte b/app/frontend/src/routes/+layout.svelte
index 1b27dc91..ee7c2be1 100644
--- a/app/frontend/src/routes/+layout.svelte
+++ b/app/frontend/src/routes/+layout.svelte
@@ -3,14 +3,13 @@
import { goto } from '$app/navigation';
import { browser } from '$app/environment';
import '../styles/app.css';
- import { onMount, tick } from 'svelte';
+ import { tick } from 'svelte';
import { LogOut, Tractor, Settings } from '@lucide/svelte';
import ThemeToggle from '$components/common/ThemeToggle.svelte';
import { env } from '$env/dynamic/public';
import { Jumper } from 'svelte-loading-spinners';
import { configModelStore } from '$lib/stores/config';
import { vehiclesStore } from '$lib/stores/vehicle';
- import { darkModeStore } from '$lib/stores/dark-mode';
import IconButton from '$components/common/IconButton.svelte';
let { children } = $props();
@@ -22,6 +21,7 @@
$effect(() => {
demoMode = env.PUBLIC_DEMO_MODE === 'true';
+ console.log('env', env);
if (browser) {
const pin = localStorage.getItem('userPin');
isAuthenticated = !!pin;
@@ -57,8 +57,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
@@ -88,7 +87,7 @@
icon={Settings}
onclick={() => {
configModelStore.show((status: boolean) => {
- fetchVehicles();
+ status && fetchVehicles();
});
}}
ariaLabel="Settings"
diff --git a/app/frontend/src/routes/login/+page.svelte b/app/frontend/src/routes/login/+page.svelte
index 0557d82a..c9ad8cf0 100644
--- a/app/frontend/src/routes/login/+page.svelte
+++ b/app/frontend/src/routes/login/+page.svelte
@@ -3,7 +3,7 @@
import { browser } from '$app/environment';
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 +22,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;
@@ -35,6 +33,7 @@
};
}
} catch (e) {
+ console.error(e);
status = {
message: 'Unknown Server Error Occurred.',
type: 'ERROR'
@@ -55,10 +54,11 @@
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) {
+ console.error(e);
status = {
message: 'Failed to connect to the server. Please check your connection.',
type: 'ERROR'
@@ -70,16 +70,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/docker/Dockerfile b/docker/Dockerfile
index a2cc7bac..5056868c 100644
--- a/docker/Dockerfile
+++ b/docker/Dockerfile
@@ -4,7 +4,7 @@ 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
RUN npm run build
@@ -16,7 +16,7 @@ COPY app/backend/package*.json ./
RUN npm install
COPY app/backend/ ./
ENV NODE_ENV=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 NODE_ENV=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 84a2f22d..f6a86779 100644
--- a/docker/docker-compose.yml
+++ b/docker/docker-compose.yml
@@ -1,22 +1,14 @@
services:
app:
- image: ghcr.io/javedh-dev/tracktor:latest
+ image: ghcr.io/javedh-dev/tracktor:dev
container_name: tracktor-app
restart: always
ports:
- "3333:3000"
volumes:
- - tracktor-app-data:/data
+ - ../app/backend/tracktor.db:/data/tracktor.db
environment:
- - NODE_ENV=production
- - SERVER_PORT=3000
- - SERVER_HOST=0.0.0.0
- - DATABASE_PATH=/data/vehicles.db
- - PUBLIC_API_BASE_URL=
- - PUBLIC_DEMO_MODE=false
- - DEMO_MODE=false
- - LOG_LEVEL=info
- - CORS_ORIGINS=*
+ - PUBLIC_API_BASE_URL=/
volumes:
tracktor-app-data: null
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"
diff --git a/docs/ENVIRONMENT.md b/docs/ENVIRONMENT.md
index 25d3195b..83235f6d 100644
--- a/docs/ENVIRONMENT.md
+++ b/docs/ENVIRONMENT.md
@@ -39,13 +39,13 @@ 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
| Variable | Default | Description |
| --------------- | --------------- | ------------------------- |
-| `DATABASE_PATH` | `./vehicles.db` | SQLite database file path |
+| `DATABASE_PATH` | `./tracktor.db` | SQLite database file path |
### Application Features
@@ -76,7 +76,7 @@ NODE_ENV=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
```
@@ -86,8 +86,8 @@ DEMO_MODE=false
NODE_ENV=production
SERVER_PORT=3000
SERVER_HOST=0.0.0.0
-DATABASE_PATH=/data/vehicles.db
-PUBLIC_API_BASE_URL=https://your-domain.com
+DATABASE_PATH=/data/tracktor.db
+API_BASE_URL=https://your-domain.com
DEMO_MODE=false
LOG_LEVEL=warn
```
@@ -101,7 +101,7 @@ environment:
- NODE_ENV=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
```
@@ -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-lock.json b/package-lock.json
index 0e56fb32..a433b00f 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -6,16 +6,14 @@
"": {
"name": "tracktor",
"workspaces": [
- "app/*",
- "docs"
+ "app/*"
],
"devDependencies": {
"concurrently": "^9.2.0"
}
},
"app/backend": {
- "name": "server",
- "version": "1.0.0",
+ "version": "0.0.1",
"license": "ISC",
"dependencies": {
"@types/swagger-jsdoc": "^6.0.4",
@@ -31,15 +29,23 @@
"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",
+ "tsc-alias": "^1.8.16",
"tsx": "^4.20.3",
"typescript": "^5.8.3"
}
@@ -406,7 +412,6 @@
}
},
"app/frontend": {
- "name": "client",
"version": "0.0.1",
"dependencies": {
"@lucide/svelte": "^0.525.0",
@@ -415,19 +420,28 @@
"svelte5-chartjs": "^1.0.0"
},
"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"
@@ -1302,6 +1316,171 @@
"node": ">=18"
}
},
+ "node_modules/@eslint-community/eslint-utils": {
+ "version": "4.7.0",
+ "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz",
+ "integrity": "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "eslint-visitor-keys": "^3.4.3"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0"
+ }
+ },
+ "node_modules/@eslint-community/regexpp": {
+ "version": "4.12.1",
+ "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz",
+ "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^12.0.0 || ^14.0.0 || >=16.0.0"
+ }
+ },
+ "node_modules/@eslint/config-array": {
+ "version": "0.21.0",
+ "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.0.tgz",
+ "integrity": "sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@eslint/object-schema": "^2.1.6",
+ "debug": "^4.3.1",
+ "minimatch": "^3.1.2"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/@eslint/config-helpers": {
+ "version": "0.3.1",
+ "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.3.1.tgz",
+ "integrity": "sha512-xR93k9WhrDYpXHORXpxVL5oHj3Era7wo6k/Wd8/IsQNnZUTzkGS29lyn3nAT05v6ltUuTFVCCYDEGfy2Or/sPA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/@eslint/core": {
+ "version": "0.15.2",
+ "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.2.tgz",
+ "integrity": "sha512-78Md3/Rrxh83gCxoUc0EiciuOHsIITzLy53m3d9UyiW8y9Dj2D29FeETqyKA+BRK76tnTp6RXWb3pCay8Oyomg==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@types/json-schema": "^7.0.15"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/@eslint/eslintrc": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz",
+ "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ajv": "^6.12.4",
+ "debug": "^4.3.2",
+ "espree": "^10.0.1",
+ "globals": "^14.0.0",
+ "ignore": "^5.2.0",
+ "import-fresh": "^3.2.1",
+ "js-yaml": "^4.1.0",
+ "minimatch": "^3.1.2",
+ "strip-json-comments": "^3.1.1"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/@eslint/eslintrc/node_modules/ajv": {
+ "version": "6.12.6",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
+ "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fast-deep-equal": "^3.1.1",
+ "fast-json-stable-stringify": "^2.0.0",
+ "json-schema-traverse": "^0.4.1",
+ "uri-js": "^4.2.2"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/epoberezkin"
+ }
+ },
+ "node_modules/@eslint/eslintrc/node_modules/json-schema-traverse": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@eslint/eslintrc/node_modules/strip-json-comments": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
+ "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/@eslint/js": {
+ "version": "9.34.0",
+ "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.34.0.tgz",
+ "integrity": "sha512-EoyvqQnBNsV1CWaEJ559rxXL4c8V92gxirbawSmVUOWXlsRxxQXl6LmCpdUblgxgSkDIqKnhzba2SjRTI/A5Rw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://eslint.org/donate"
+ }
+ },
+ "node_modules/@eslint/object-schema": {
+ "version": "2.1.6",
+ "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz",
+ "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/@eslint/plugin-kit": {
+ "version": "0.3.5",
+ "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.5.tgz",
+ "integrity": "sha512-Z5kJ+wU3oA7MMIqVR9tyZRtjYPr4OC004Q4Rw7pgOKUOKkJfZ3O24nz3WYfGRpMDNmcOi3TwQOmgm7B7Tpii0w==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@eslint/core": "^0.15.2",
+ "levn": "^0.4.1"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
"node_modules/@gar/promisify": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz",
@@ -1309,6 +1488,72 @@
"license": "MIT",
"optional": true
},
+ "node_modules/@humanfs/core": {
+ "version": "0.19.1",
+ "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
+ "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=18.18.0"
+ }
+ },
+ "node_modules/@humanfs/node": {
+ "version": "0.16.6",
+ "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz",
+ "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@humanfs/core": "^0.19.1",
+ "@humanwhocodes/retry": "^0.3.0"
+ },
+ "engines": {
+ "node": ">=18.18.0"
+ }
+ },
+ "node_modules/@humanfs/node/node_modules/@humanwhocodes/retry": {
+ "version": "0.3.1",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz",
+ "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=18.18"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/nzakas"
+ }
+ },
+ "node_modules/@humanwhocodes/module-importer": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
+ "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=12.22"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/nzakas"
+ }
+ },
+ "node_modules/@humanwhocodes/retry": {
+ "version": "0.4.3",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz",
+ "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=18.18"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/nzakas"
+ }
+ },
"node_modules/@iconify-json/simple-icons": {
"version": "1.2.48",
"resolved": "https://registry.npmjs.org/@iconify-json/simple-icons/-/simple-icons-1.2.48.tgz",
@@ -2801,130 +3046,401 @@
"dev": true,
"license": "MIT"
},
- "node_modules/@ungap/structured-clone": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz",
- "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==",
- "dev": true,
- "license": "ISC"
- },
- "node_modules/@vue/compiler-core": {
- "version": "3.5.19",
- "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.19.tgz",
- "integrity": "sha512-/afpyvlkrSNYbPo94Qu8GtIOWS+g5TRdOvs6XZNw6pWQQmj5pBgSZvEPOIZlqWq0YvoUhDDQaQ2TnzuJdOV4hA==",
+ "node_modules/@typescript-eslint/eslint-plugin": {
+ "version": "8.40.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.40.0.tgz",
+ "integrity": "sha512-w/EboPlBwnmOBtRbiOvzjD+wdiZdgFeo17lkltrtn7X37vagKKWJABvyfsJXTlHe6XBzugmYgd4A4nW+k8Mixw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/parser": "^7.28.3",
- "@vue/shared": "3.5.19",
- "entities": "^4.5.0",
- "estree-walker": "^2.0.2",
- "source-map-js": "^1.2.1"
+ "@eslint-community/regexpp": "^4.10.0",
+ "@typescript-eslint/scope-manager": "8.40.0",
+ "@typescript-eslint/type-utils": "8.40.0",
+ "@typescript-eslint/utils": "8.40.0",
+ "@typescript-eslint/visitor-keys": "8.40.0",
+ "graphemer": "^1.4.0",
+ "ignore": "^7.0.0",
+ "natural-compare": "^1.4.0",
+ "ts-api-utils": "^2.1.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "@typescript-eslint/parser": "^8.40.0",
+ "eslint": "^8.57.0 || ^9.0.0",
+ "typescript": ">=4.8.4 <6.0.0"
}
},
- "node_modules/@vue/compiler-dom": {
- "version": "3.5.19",
- "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.19.tgz",
- "integrity": "sha512-Drs6rPHQZx/pN9S6ml3Z3K/TWCIRPvzG2B/o5kFK9X0MNHt8/E+38tiRfojufrYBfA6FQUFB2qBBRXlcSXWtOA==",
+ "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": {
+ "version": "7.0.5",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz",
+ "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==",
"dev": true,
"license": "MIT",
- "dependencies": {
- "@vue/compiler-core": "3.5.19",
- "@vue/shared": "3.5.19"
+ "engines": {
+ "node": ">= 4"
}
},
- "node_modules/@vue/compiler-sfc": {
- "version": "3.5.19",
- "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.19.tgz",
- "integrity": "sha512-YWCm1CYaJ+2RvNmhCwI7t3I3nU+hOrWGWMsn+Z/kmm1jy5iinnVtlmkiZwbLlbV1SRizX7vHsc0/bG5dj0zRTg==",
+ "node_modules/@typescript-eslint/parser": {
+ "version": "8.40.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.40.0.tgz",
+ "integrity": "sha512-jCNyAuXx8dr5KJMkecGmZ8KI61KBUhkCob+SD+C+I5+Y1FWI2Y3QmY4/cxMCC5WAsZqoEtEETVhUiUMIGCf6Bw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/parser": "^7.28.3",
- "@vue/compiler-core": "3.5.19",
- "@vue/compiler-dom": "3.5.19",
- "@vue/compiler-ssr": "3.5.19",
- "@vue/shared": "3.5.19",
- "estree-walker": "^2.0.2",
- "magic-string": "^0.30.17",
- "postcss": "^8.5.6",
- "source-map-js": "^1.2.1"
+ "@typescript-eslint/scope-manager": "8.40.0",
+ "@typescript-eslint/types": "8.40.0",
+ "@typescript-eslint/typescript-estree": "8.40.0",
+ "@typescript-eslint/visitor-keys": "8.40.0",
+ "debug": "^4.3.4"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^8.57.0 || ^9.0.0",
+ "typescript": ">=4.8.4 <6.0.0"
}
},
- "node_modules/@vue/compiler-ssr": {
- "version": "3.5.19",
- "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.19.tgz",
- "integrity": "sha512-/wx0VZtkWOPdiQLWPeQeqpHWR/LuNC7bHfSX7OayBTtUy8wur6vT6EQIX6Et86aED6J+y8tTw43qo2uoqGg5sw==",
+ "node_modules/@typescript-eslint/project-service": {
+ "version": "8.40.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.40.0.tgz",
+ "integrity": "sha512-/A89vz7Wf5DEXsGVvcGdYKbVM9F7DyFXj52lNYUDS1L9yJfqjW/fIp5PgMuEJL/KeqVTe2QSbXAGUZljDUpArw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@vue/compiler-dom": "3.5.19",
- "@vue/shared": "3.5.19"
+ "@typescript-eslint/tsconfig-utils": "^8.40.0",
+ "@typescript-eslint/types": "^8.40.0",
+ "debug": "^4.3.4"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.8.4 <6.0.0"
}
},
- "node_modules/@vue/devtools-api": {
- "version": "7.7.7",
- "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-7.7.7.tgz",
- "integrity": "sha512-lwOnNBH2e7x1fIIbVT7yF5D+YWhqELm55/4ZKf45R9T8r9dE2AIOy8HKjfqzGsoTHFbWbr337O4E0A0QADnjBg==",
+ "node_modules/@typescript-eslint/scope-manager": {
+ "version": "8.40.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.40.0.tgz",
+ "integrity": "sha512-y9ObStCcdCiZKzwqsE8CcpyuVMwRouJbbSrNuThDpv16dFAj429IkM6LNb1dZ2m7hK5fHyzNcErZf7CEeKXR4w==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@vue/devtools-kit": "^7.7.7"
+ "@typescript-eslint/types": "8.40.0",
+ "@typescript-eslint/visitor-keys": "8.40.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
}
},
- "node_modules/@vue/devtools-kit": {
- "version": "7.7.7",
- "resolved": "https://registry.npmjs.org/@vue/devtools-kit/-/devtools-kit-7.7.7.tgz",
- "integrity": "sha512-wgoZtxcTta65cnZ1Q6MbAfePVFxfM+gq0saaeytoph7nEa7yMXoi6sCPy4ufO111B9msnw0VOWjPEFCXuAKRHA==",
+ "node_modules/@typescript-eslint/tsconfig-utils": {
+ "version": "8.40.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.40.0.tgz",
+ "integrity": "sha512-jtMytmUaG9d/9kqSl/W3E3xaWESo4hFDxAIHGVW/WKKtQhesnRIJSAJO6XckluuJ6KDB5woD1EiqknriCtAmcw==",
"dev": true,
"license": "MIT",
- "dependencies": {
- "@vue/devtools-shared": "^7.7.7",
- "birpc": "^2.3.0",
- "hookable": "^5.5.3",
- "mitt": "^3.0.1",
- "perfect-debounce": "^1.0.0",
- "speakingurl": "^14.0.1",
- "superjson": "^2.2.2"
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.8.4 <6.0.0"
}
},
- "node_modules/@vue/devtools-shared": {
- "version": "7.7.7",
- "resolved": "https://registry.npmjs.org/@vue/devtools-shared/-/devtools-shared-7.7.7.tgz",
- "integrity": "sha512-+udSj47aRl5aKb0memBvcUG9koarqnxNM5yjuREvqwK6T3ap4mn3Zqqc17QrBFTqSMjr3HK1cvStEZpMDpfdyw==",
+ "node_modules/@typescript-eslint/type-utils": {
+ "version": "8.40.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.40.0.tgz",
+ "integrity": "sha512-eE60cK4KzAc6ZrzlJnflXdrMqOBaugeukWICO2rB0KNvwdIMaEaYiywwHMzA1qFpTxrLhN9Lp4E/00EgWcD3Ow==",
"dev": true,
"license": "MIT",
"dependencies": {
- "rfdc": "^1.4.1"
+ "@typescript-eslint/types": "8.40.0",
+ "@typescript-eslint/typescript-estree": "8.40.0",
+ "@typescript-eslint/utils": "8.40.0",
+ "debug": "^4.3.4",
+ "ts-api-utils": "^2.1.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^8.57.0 || ^9.0.0",
+ "typescript": ">=4.8.4 <6.0.0"
}
},
- "node_modules/@vue/reactivity": {
- "version": "3.5.19",
- "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.19.tgz",
- "integrity": "sha512-4bueZg2qs5MSsK2dQk3sssV0cfvxb/QZntTC8v7J448GLgmfPkQ+27aDjlt40+XFqOwUq5yRxK5uQh14Fc9eVA==",
+ "node_modules/@typescript-eslint/types": {
+ "version": "8.40.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.40.0.tgz",
+ "integrity": "sha512-ETdbFlgbAmXHyFPwqUIYrfc12ArvpBhEVgGAxVYSwli26dn8Ko+lIo4Su9vI9ykTZdJn+vJprs/0eZU0YMAEQg==",
"dev": true,
"license": "MIT",
- "dependencies": {
- "@vue/shared": "3.5.19"
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
}
},
- "node_modules/@vue/runtime-core": {
- "version": "3.5.19",
- "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.19.tgz",
- "integrity": "sha512-TaooCr8Hge1sWjLSyhdubnuofs3shhzZGfyD11gFolZrny76drPwBVQj28/z/4+msSFb18tOIg6VVVgf9/IbIA==",
+ "node_modules/@typescript-eslint/typescript-estree": {
+ "version": "8.40.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.40.0.tgz",
+ "integrity": "sha512-k1z9+GJReVVOkc1WfVKs1vBrR5MIKKbdAjDTPvIK3L8De6KbFfPFt6BKpdkdk7rZS2GtC/m6yI5MYX+UsuvVYQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@vue/reactivity": "3.5.19",
- "@vue/shared": "3.5.19"
- }
- },
- "node_modules/@vue/runtime-dom": {
- "version": "3.5.19",
- "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.19.tgz",
- "integrity": "sha512-qmahqeok6ztuUTmV8lqd7N9ymbBzctNF885n8gL3xdCC1u2RnM/coX16Via0AiONQXUoYpxPojL3U1IsDgSWUQ==",
- "dev": true,
- "license": "MIT",
+ "@typescript-eslint/project-service": "8.40.0",
+ "@typescript-eslint/tsconfig-utils": "8.40.0",
+ "@typescript-eslint/types": "8.40.0",
+ "@typescript-eslint/visitor-keys": "8.40.0",
+ "debug": "^4.3.4",
+ "fast-glob": "^3.3.2",
+ "is-glob": "^4.0.3",
+ "minimatch": "^9.0.4",
+ "semver": "^7.6.0",
+ "ts-api-utils": "^2.1.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.8.4 <6.0.0"
+ }
+ },
+ "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
+ "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0"
+ }
+ },
+ "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": {
+ "version": "9.0.5",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
+ "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/@typescript-eslint/utils": {
+ "version": "8.40.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.40.0.tgz",
+ "integrity": "sha512-Cgzi2MXSZyAUOY+BFwGs17s7ad/7L+gKt6Y8rAVVWS+7o6wrjeFN4nVfTpbE25MNcxyJ+iYUXflbs2xR9h4UBg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@eslint-community/eslint-utils": "^4.7.0",
+ "@typescript-eslint/scope-manager": "8.40.0",
+ "@typescript-eslint/types": "8.40.0",
+ "@typescript-eslint/typescript-estree": "8.40.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^8.57.0 || ^9.0.0",
+ "typescript": ">=4.8.4 <6.0.0"
+ }
+ },
+ "node_modules/@typescript-eslint/visitor-keys": {
+ "version": "8.40.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.40.0.tgz",
+ "integrity": "sha512-8CZ47QwalyRjsypfwnbI3hKy5gJDPmrkLjkgMxhi0+DZZ2QNx2naS6/hWoVYUHU7LU2zleF68V9miaVZvhFfTA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/types": "8.40.0",
+ "eslint-visitor-keys": "^4.2.1"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz",
+ "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/@ungap/structured-clone": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz",
+ "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/@vue/compiler-core": {
+ "version": "3.5.19",
+ "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.19.tgz",
+ "integrity": "sha512-/afpyvlkrSNYbPo94Qu8GtIOWS+g5TRdOvs6XZNw6pWQQmj5pBgSZvEPOIZlqWq0YvoUhDDQaQ2TnzuJdOV4hA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.28.3",
+ "@vue/shared": "3.5.19",
+ "entities": "^4.5.0",
+ "estree-walker": "^2.0.2",
+ "source-map-js": "^1.2.1"
+ }
+ },
+ "node_modules/@vue/compiler-dom": {
+ "version": "3.5.19",
+ "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.19.tgz",
+ "integrity": "sha512-Drs6rPHQZx/pN9S6ml3Z3K/TWCIRPvzG2B/o5kFK9X0MNHt8/E+38tiRfojufrYBfA6FQUFB2qBBRXlcSXWtOA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vue/compiler-core": "3.5.19",
+ "@vue/shared": "3.5.19"
+ }
+ },
+ "node_modules/@vue/compiler-sfc": {
+ "version": "3.5.19",
+ "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.19.tgz",
+ "integrity": "sha512-YWCm1CYaJ+2RvNmhCwI7t3I3nU+hOrWGWMsn+Z/kmm1jy5iinnVtlmkiZwbLlbV1SRizX7vHsc0/bG5dj0zRTg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.28.3",
+ "@vue/compiler-core": "3.5.19",
+ "@vue/compiler-dom": "3.5.19",
+ "@vue/compiler-ssr": "3.5.19",
+ "@vue/shared": "3.5.19",
+ "estree-walker": "^2.0.2",
+ "magic-string": "^0.30.17",
+ "postcss": "^8.5.6",
+ "source-map-js": "^1.2.1"
+ }
+ },
+ "node_modules/@vue/compiler-ssr": {
+ "version": "3.5.19",
+ "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.19.tgz",
+ "integrity": "sha512-/wx0VZtkWOPdiQLWPeQeqpHWR/LuNC7bHfSX7OayBTtUy8wur6vT6EQIX6Et86aED6J+y8tTw43qo2uoqGg5sw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vue/compiler-dom": "3.5.19",
+ "@vue/shared": "3.5.19"
+ }
+ },
+ "node_modules/@vue/devtools-api": {
+ "version": "7.7.7",
+ "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-7.7.7.tgz",
+ "integrity": "sha512-lwOnNBH2e7x1fIIbVT7yF5D+YWhqELm55/4ZKf45R9T8r9dE2AIOy8HKjfqzGsoTHFbWbr337O4E0A0QADnjBg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vue/devtools-kit": "^7.7.7"
+ }
+ },
+ "node_modules/@vue/devtools-kit": {
+ "version": "7.7.7",
+ "resolved": "https://registry.npmjs.org/@vue/devtools-kit/-/devtools-kit-7.7.7.tgz",
+ "integrity": "sha512-wgoZtxcTta65cnZ1Q6MbAfePVFxfM+gq0saaeytoph7nEa7yMXoi6sCPy4ufO111B9msnw0VOWjPEFCXuAKRHA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vue/devtools-shared": "^7.7.7",
+ "birpc": "^2.3.0",
+ "hookable": "^5.5.3",
+ "mitt": "^3.0.1",
+ "perfect-debounce": "^1.0.0",
+ "speakingurl": "^14.0.1",
+ "superjson": "^2.2.2"
+ }
+ },
+ "node_modules/@vue/devtools-shared": {
+ "version": "7.7.7",
+ "resolved": "https://registry.npmjs.org/@vue/devtools-shared/-/devtools-shared-7.7.7.tgz",
+ "integrity": "sha512-+udSj47aRl5aKb0memBvcUG9koarqnxNM5yjuREvqwK6T3ap4mn3Zqqc17QrBFTqSMjr3HK1cvStEZpMDpfdyw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "rfdc": "^1.4.1"
+ }
+ },
+ "node_modules/@vue/reactivity": {
+ "version": "3.5.19",
+ "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.19.tgz",
+ "integrity": "sha512-4bueZg2qs5MSsK2dQk3sssV0cfvxb/QZntTC8v7J448GLgmfPkQ+27aDjlt40+XFqOwUq5yRxK5uQh14Fc9eVA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vue/shared": "3.5.19"
+ }
+ },
+ "node_modules/@vue/runtime-core": {
+ "version": "3.5.19",
+ "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.19.tgz",
+ "integrity": "sha512-TaooCr8Hge1sWjLSyhdubnuofs3shhzZGfyD11gFolZrny76drPwBVQj28/z/4+msSFb18tOIg6VVVgf9/IbIA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vue/reactivity": "3.5.19",
+ "@vue/shared": "3.5.19"
+ }
+ },
+ "node_modules/@vue/runtime-dom": {
+ "version": "3.5.19",
+ "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.19.tgz",
+ "integrity": "sha512-qmahqeok6ztuUTmV8lqd7N9ymbBzctNF885n8gL3xdCC1u2RnM/coX16Via0AiONQXUoYpxPojL3U1IsDgSWUQ==",
+ "dev": true,
+ "license": "MIT",
"dependencies": {
"@vue/reactivity": "3.5.19",
"@vue/runtime-core": "3.5.19",
@@ -3092,6 +3608,16 @@
"node": ">=0.4.0"
}
},
+ "node_modules/acorn-jsx": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
+ "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
+ }
+ },
"node_modules/acorn-walk": {
"version": "8.3.4",
"resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz",
@@ -3321,6 +3847,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",
@@ -3330,6 +3866,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",
@@ -3582,6 +4122,16 @@
"integrity": "sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ==",
"license": "MIT"
},
+ "node_modules/callsites": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
+ "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/ccount": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz",
@@ -3711,10 +4261,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",
@@ -3939,6 +4485,21 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/cross-spawn": {
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
+ "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "path-key": "^3.1.0",
+ "shebang-command": "^2.0.0",
+ "which": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
"node_modules/cssesc": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
@@ -4011,6 +4572,13 @@
"node": ">=4.0.0"
}
},
+ "node_modules/deep-is": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
+ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/deepmerge": {
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz",
@@ -4097,6 +4665,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
@@ -4315,20 +4896,253 @@
"@esbuild/win32-x64": "0.25.9"
}
},
- "node_modules/escalade": {
- "version": "3.2.0",
- "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
- "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
+ "node_modules/escalade": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
+ "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/escape-html": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
+ "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
+ "license": "MIT"
+ },
+ "node_modules/escape-string-regexp": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
+ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/eslint": {
+ "version": "9.34.0",
+ "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.34.0.tgz",
+ "integrity": "sha512-RNCHRX5EwdrESy3Jc9o8ie8Bog+PeYvvSR8sDGoZxNFTvZ4dlxUB3WzQ3bQMztFrSRODGrLLj8g6OFuGY/aiQg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@eslint-community/eslint-utils": "^4.2.0",
+ "@eslint-community/regexpp": "^4.12.1",
+ "@eslint/config-array": "^0.21.0",
+ "@eslint/config-helpers": "^0.3.1",
+ "@eslint/core": "^0.15.2",
+ "@eslint/eslintrc": "^3.3.1",
+ "@eslint/js": "9.34.0",
+ "@eslint/plugin-kit": "^0.3.5",
+ "@humanfs/node": "^0.16.6",
+ "@humanwhocodes/module-importer": "^1.0.1",
+ "@humanwhocodes/retry": "^0.4.2",
+ "@types/estree": "^1.0.6",
+ "@types/json-schema": "^7.0.15",
+ "ajv": "^6.12.4",
+ "chalk": "^4.0.0",
+ "cross-spawn": "^7.0.6",
+ "debug": "^4.3.2",
+ "escape-string-regexp": "^4.0.0",
+ "eslint-scope": "^8.4.0",
+ "eslint-visitor-keys": "^4.2.1",
+ "espree": "^10.4.0",
+ "esquery": "^1.5.0",
+ "esutils": "^2.0.2",
+ "fast-deep-equal": "^3.1.3",
+ "file-entry-cache": "^8.0.0",
+ "find-up": "^5.0.0",
+ "glob-parent": "^6.0.2",
+ "ignore": "^5.2.0",
+ "imurmurhash": "^0.1.4",
+ "is-glob": "^4.0.0",
+ "json-stable-stringify-without-jsonify": "^1.0.1",
+ "lodash.merge": "^4.6.2",
+ "minimatch": "^3.1.2",
+ "natural-compare": "^1.4.0",
+ "optionator": "^0.9.3"
+ },
+ "bin": {
+ "eslint": "bin/eslint.js"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://eslint.org/donate"
+ },
+ "peerDependencies": {
+ "jiti": "*"
+ },
+ "peerDependenciesMeta": {
+ "jiti": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/eslint-config-prettier": {
+ "version": "10.1.8",
+ "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.8.tgz",
+ "integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "eslint-config-prettier": "bin/cli.js"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint-config-prettier"
+ },
+ "peerDependencies": {
+ "eslint": ">=7.0.0"
+ }
+ },
+ "node_modules/eslint-plugin-svelte": {
+ "version": "3.11.0",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-svelte/-/eslint-plugin-svelte-3.11.0.tgz",
+ "integrity": "sha512-KliWlkieHyEa65aQIkRwUFfHzT5Cn4u3BQQsu3KlkJOs7c1u7ryn84EWaOjEzilbKgttT4OfBURA8Uc4JBSQIw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@eslint-community/eslint-utils": "^4.6.1",
+ "@jridgewell/sourcemap-codec": "^1.5.0",
+ "esutils": "^2.0.3",
+ "globals": "^16.0.0",
+ "known-css-properties": "^0.37.0",
+ "postcss": "^8.4.49",
+ "postcss-load-config": "^3.1.4",
+ "postcss-safe-parser": "^7.0.0",
+ "semver": "^7.6.3",
+ "svelte-eslint-parser": "^1.3.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ota-meshi"
+ },
+ "peerDependencies": {
+ "eslint": "^8.57.1 || ^9.0.0",
+ "svelte": "^3.37.0 || ^4.0.0 || ^5.0.0"
+ },
+ "peerDependenciesMeta": {
+ "svelte": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/eslint-plugin-svelte/node_modules/globals": {
+ "version": "16.3.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-16.3.0.tgz",
+ "integrity": "sha512-bqWEnJ1Nt3neqx2q5SFfGS8r/ahumIakg3HcwtNlrVlwXIeNumWn/c7Pn/wKzGhf6SaW6H6uWXLqC30STCMchQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/eslint-plugin-unused-imports": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-unused-imports/-/eslint-plugin-unused-imports-4.2.0.tgz",
+ "integrity": "sha512-hLbJ2/wnjKq4kGA9AUaExVFIbNzyxYdVo49QZmKCnhk5pc9wcYRbfgLHvWJ8tnsdcseGhoUAddm9gn/lt+d74w==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "@typescript-eslint/eslint-plugin": "^8.0.0-0 || ^7.0.0 || ^6.0.0 || ^5.0.0",
+ "eslint": "^9.0.0 || ^8.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@typescript-eslint/eslint-plugin": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/eslint-scope": {
+ "version": "8.4.0",
+ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz",
+ "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "esrecurse": "^4.3.0",
+ "estraverse": "^5.2.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/eslint-visitor-keys": {
+ "version": "3.4.3",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
+ "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/eslint/node_modules/ajv": {
+ "version": "6.12.6",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
+ "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
"dev": true,
"license": "MIT",
+ "dependencies": {
+ "fast-deep-equal": "^3.1.1",
+ "fast-json-stable-stringify": "^2.0.0",
+ "json-schema-traverse": "^0.4.1",
+ "uri-js": "^4.2.2"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/epoberezkin"
+ }
+ },
+ "node_modules/eslint/node_modules/eslint-visitor-keys": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz",
+ "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==",
+ "dev": true,
+ "license": "Apache-2.0",
"engines": {
- "node": ">=6"
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
}
},
- "node_modules/escape-html": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
- "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
+ "node_modules/eslint/node_modules/glob-parent": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
+ "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "is-glob": "^4.0.3"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
+ "node_modules/eslint/node_modules/json-schema-traverse": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
+ "dev": true,
"license": "MIT"
},
"node_modules/esm-env": {
@@ -4337,6 +5151,50 @@
"integrity": "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==",
"license": "MIT"
},
+ "node_modules/espree": {
+ "version": "10.4.0",
+ "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz",
+ "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "acorn": "^8.15.0",
+ "acorn-jsx": "^5.3.2",
+ "eslint-visitor-keys": "^4.2.1"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/espree/node_modules/eslint-visitor-keys": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz",
+ "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/esquery": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz",
+ "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "estraverse": "^5.1.0"
+ },
+ "engines": {
+ "node": ">=0.10"
+ }
+ },
"node_modules/esrap": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/esrap/-/esrap-2.1.0.tgz",
@@ -4346,6 +5204,29 @@
"@jridgewell/sourcemap-codec": "^1.4.15"
}
},
+ "node_modules/esrecurse": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
+ "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "estraverse": "^5.2.0"
+ },
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/estraverse": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
+ "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
"node_modules/estree-walker": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
@@ -4455,6 +5336,20 @@
"node": ">=8.6.0"
}
},
+ "node_modules/fast-json-stable-stringify": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
+ "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/fast-levenshtein": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
+ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/fastq": {
"version": "1.19.1",
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz",
@@ -4482,6 +5377,19 @@
}
}
},
+ "node_modules/file-entry-cache": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz",
+ "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "flat-cache": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=16.0.0"
+ }
+ },
"node_modules/file-uri-to-path": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
@@ -4518,6 +5426,44 @@
"node": ">= 0.8"
}
},
+ "node_modules/find-up": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
+ "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "locate-path": "^6.0.0",
+ "path-exists": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/flat-cache": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz",
+ "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "flatted": "^3.2.9",
+ "keyv": "^4.5.4"
+ },
+ "engines": {
+ "node": ">=16"
+ }
+ },
+ "node_modules/flatted": {
+ "version": "3.3.3",
+ "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz",
+ "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==",
+ "dev": true,
+ "license": "ISC"
+ },
"node_modules/focus-trap": {
"version": "7.6.5",
"resolved": "https://registry.npmjs.org/focus-trap/-/focus-trap-7.6.5.tgz",
@@ -4547,6 +5493,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",
@@ -4730,6 +5680,40 @@
"node": ">= 6"
}
},
+ "node_modules/globals": {
+ "version": "14.0.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz",
+ "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/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",
@@ -4748,6 +5732,13 @@
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
"license": "ISC"
},
+ "node_modules/graphemer": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz",
+ "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/has-flag": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
@@ -4948,6 +5939,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",
@@ -4955,6 +5956,23 @@
"dev": true,
"license": "ISC"
},
+ "node_modules/import-fresh": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
+ "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "parent-module": "^1.0.0",
+ "resolve-from": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/import-lazy": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-4.0.0.tgz",
@@ -4968,8 +5986,8 @@
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
"integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
+ "devOptional": true,
"license": "MIT",
- "optional": true,
"engines": {
"node": ">=0.8.19"
}
@@ -5158,8 +6176,8 @@
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
- "license": "ISC",
- "optional": true
+ "devOptional": true,
+ "license": "ISC"
},
"node_modules/jiti": {
"version": "2.5.1",
@@ -5189,12 +6207,26 @@
"js-yaml": "bin/js-yaml.js"
}
},
+ "node_modules/json-buffer": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
+ "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/json-schema-traverse": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
"license": "MIT"
},
+ "node_modules/json-stable-stringify-without-jsonify": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
+ "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/jsonfile": {
"version": "6.2.0",
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz",
@@ -5207,6 +6239,16 @@
"graceful-fs": "^4.1.6"
}
},
+ "node_modules/keyv": {
+ "version": "4.5.4",
+ "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
+ "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "json-buffer": "3.0.1"
+ }
+ },
"node_modules/kleur": {
"version": "4.1.5",
"resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz",
@@ -5217,6 +6259,27 @@
"node": ">=6"
}
},
+ "node_modules/known-css-properties": {
+ "version": "0.37.0",
+ "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.37.0.tgz",
+ "integrity": "sha512-JCDrsP4Z1Sb9JwG0aJ8Eo2r7k4Ou5MwmThS/6lcIe1ICyb7UBJKGRIUUdqc2ASdE/42lgz6zFUnzAIhtXnBVrQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/levn": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
+ "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "prelude-ls": "^1.2.1",
+ "type-check": "~0.4.0"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
"node_modules/lightningcss": {
"version": "1.30.1",
"resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.1.tgz",
@@ -5456,12 +6519,38 @@
"url": "https://opencollective.com/parcel"
}
},
+ "node_modules/lilconfig": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz",
+ "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ }
+ },
"node_modules/locate-character": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/locate-character/-/locate-character-3.0.0.tgz",
"integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==",
"license": "MIT"
},
+ "node_modules/locate-path": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
+ "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-locate": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/lodash": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
@@ -6025,6 +7114,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",
@@ -6050,6 +7153,13 @@
"integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==",
"license": "MIT"
},
+ "node_modules/natural-compare": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
+ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/negotiator": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz",
@@ -6266,18 +7376,68 @@
"dev": true,
"license": "MIT",
"dependencies": {
- "emoji-regex-xs": "^1.0.0",
- "regex": "^6.0.1",
- "regex-recursion": "^6.0.2"
+ "emoji-regex-xs": "^1.0.0",
+ "regex": "^6.0.1",
+ "regex-recursion": "^6.0.2"
+ }
+ },
+ "node_modules/openapi-types": {
+ "version": "12.1.3",
+ "resolved": "https://registry.npmjs.org/openapi-types/-/openapi-types-12.1.3.tgz",
+ "integrity": "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==",
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/optionator": {
+ "version": "0.9.4",
+ "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
+ "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "deep-is": "^0.1.3",
+ "fast-levenshtein": "^2.0.6",
+ "levn": "^0.4.1",
+ "prelude-ls": "^1.2.1",
+ "type-check": "^0.4.0",
+ "word-wrap": "^1.2.5"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/p-limit": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
+ "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "yocto-queue": "^0.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/p-locate": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
+ "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-limit": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/openapi-types": {
- "version": "12.1.3",
- "resolved": "https://registry.npmjs.org/openapi-types/-/openapi-types-12.1.3.tgz",
- "integrity": "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==",
- "license": "MIT",
- "peer": true
- },
"node_modules/p-map": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz",
@@ -6294,6 +7454,19 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/parent-module": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
+ "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "callsites": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/parseurl": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
@@ -6303,6 +7476,16 @@
"node": ">= 0.8"
}
},
+ "node_modules/path-exists": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
+ "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/path-is-absolute": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
@@ -6312,6 +7495,16 @@
"node": ">=0.10.0"
}
},
+ "node_modules/path-key": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
+ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/path-parse": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
@@ -6328,6 +7521,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 +7564,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",
@@ -6399,6 +7615,100 @@
"node": "^10 || ^12 || >=14"
}
},
+ "node_modules/postcss-load-config": {
+ "version": "3.1.4",
+ "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-3.1.4.tgz",
+ "integrity": "sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "lilconfig": "^2.0.5",
+ "yaml": "^1.10.2"
+ },
+ "engines": {
+ "node": ">= 10"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ "peerDependencies": {
+ "postcss": ">=8.0.9",
+ "ts-node": ">=9.0.0"
+ },
+ "peerDependenciesMeta": {
+ "postcss": {
+ "optional": true
+ },
+ "ts-node": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/postcss-load-config/node_modules/yaml": {
+ "version": "1.10.2",
+ "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz",
+ "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/postcss-safe-parser": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/postcss-safe-parser/-/postcss-safe-parser-7.0.1.tgz",
+ "integrity": "sha512-0AioNCJZ2DPYz5ABT6bddIqlhgwhpHZ/l65YAYo0BCIn0xiDpsnTHz0gnoTGk0OXZW0JRs+cDwL8u/teRdz+8A==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss-safe-parser"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">=18.0"
+ },
+ "peerDependencies": {
+ "postcss": "^8.4.31"
+ }
+ },
+ "node_modules/postcss-scss": {
+ "version": "4.0.9",
+ "resolved": "https://registry.npmjs.org/postcss-scss/-/postcss-scss-4.0.9.tgz",
+ "integrity": "sha512-AjKOeiwAitL/MXxQW2DliT28EKukvvbEWx3LBmJIRN8KfBGZbRTxNYW0kSqi1COiTZ57nZ9NW06S6ux//N1c9A==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss-scss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.0"
+ },
+ "peerDependencies": {
+ "postcss": "^8.4.29"
+ }
+ },
"node_modules/postcss-selector-parser": {
"version": "6.0.10",
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz",
@@ -6450,6 +7760,16 @@
"node": ">=10"
}
},
+ "node_modules/prelude-ls": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
+ "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
"node_modules/prettier": {
"version": "3.6.2",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz",
@@ -6651,6 +7971,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",
@@ -6817,6 +8147,16 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/resolve-from": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
+ "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
"node_modules/resolve-pkg-maps": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz",
@@ -7136,10 +8476,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",
@@ -7160,6 +8496,29 @@
"integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
"license": "ISC"
},
+ "node_modules/shebang-command": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
+ "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "shebang-regex": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/shebang-regex": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
+ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/shell-quote": {
"version": "1.8.3",
"resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.3.tgz",
@@ -7342,6 +8701,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",
@@ -7686,6 +9055,62 @@
"url": "https://paulmillr.com/funding/"
}
},
+ "node_modules/svelte-eslint-parser": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/svelte-eslint-parser/-/svelte-eslint-parser-1.3.1.tgz",
+ "integrity": "sha512-0Iztj5vcOVOVkhy1pbo5uA9r+d3yaVoE5XPc9eABIWDOSJZ2mOsZ4D+t45rphWCOr0uMw3jtSG2fh2e7GvKnPg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "eslint-scope": "^8.2.0",
+ "eslint-visitor-keys": "^4.0.0",
+ "espree": "^10.0.0",
+ "postcss": "^8.4.49",
+ "postcss-scss": "^4.0.9",
+ "postcss-selector-parser": "^7.0.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ota-meshi"
+ },
+ "peerDependencies": {
+ "svelte": "^3.37.0 || ^4.0.0 || ^5.0.0"
+ },
+ "peerDependenciesMeta": {
+ "svelte": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/svelte-eslint-parser/node_modules/eslint-visitor-keys": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz",
+ "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/svelte-eslint-parser/node_modules/postcss-selector-parser": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz",
+ "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "cssesc": "^3.0.0",
+ "util-deprecate": "^1.0.2"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
"node_modules/svelte-loading-spinners": {
"version": "0.3.6",
"resolved": "https://registry.npmjs.org/svelte-loading-spinners/-/svelte-loading-spinners-0.3.6.tgz",
@@ -8017,6 +9442,19 @@
"url": "https://github.com/sponsors/wooorm"
}
},
+ "node_modules/ts-api-utils": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz",
+ "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18.12"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.8.4"
+ }
+ },
"node_modules/ts-node": {
"version": "10.9.2",
"resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz",
@@ -8061,6 +9499,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",
@@ -8100,6 +9570,19 @@
"node": "*"
}
},
+ "node_modules/type-check": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
+ "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "prelude-ls": "^1.2.1"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
"node_modules/type-fest": {
"version": "4.41.0",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz",
@@ -9036,8 +10519,8 @@
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+ "devOptional": true,
"license": "ISC",
- "optional": true,
"dependencies": {
"isexe": "^2.0.0"
},
@@ -9067,6 +10550,16 @@
"@types/node": "*"
}
},
+ "node_modules/word-wrap": {
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
+ "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/wrap-ansi": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
@@ -9146,6 +10639,19 @@
"node": ">=6"
}
},
+ "node_modules/yocto-queue": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
+ "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/z-schema": {
"version": "5.0.5",
"resolved": "https://registry.npmjs.org/z-schema/-/z-schema-5.0.5.tgz",
diff --git a/package.json b/package.json
index b7d02b6b..a2f675ee 100644
--- a/package.json
+++ b/package.json
@@ -2,33 +2,33 @@
"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 --workspaces --if-present",
+ "lint": "npm run lint --workspaces --if-present",
+ "check": "npm run check --workspaces --if-present",
"docs:dev": "npm run docs:dev --workspace=docs",
"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": {
"concurrently": "^9.2.0"
}
-}
+}
\ No newline at end of file
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");