From 0fb9eda3bd3c5bdbba892e07bf346dc4882d0d7b Mon Sep 17 00:00:00 2001 From: Javed Hussain Date: Fri, 8 Aug 2025 10:21:25 +0000 Subject: [PATCH 1/6] Updated validations --- app/server/src/models/Vehicle.ts | 57 +++++++++++++++++++++++++++----- 1 file changed, 49 insertions(+), 8 deletions(-) diff --git a/app/server/src/models/Vehicle.ts b/app/server/src/models/Vehicle.ts index 424235f2..226e9239 100644 --- a/app/server/src/models/Vehicle.ts +++ b/app/server/src/models/Vehicle.ts @@ -1,5 +1,6 @@ import { DataTypes, Model, Optional } from "sequelize"; import { db } from "../db/index.js"; +import { VehicleExistsError, VehicleServiceError } from "../exceptions/VehicleErrors.js"; interface VehicleAttributes { id: string; @@ -39,14 +40,20 @@ Vehicle.init( type: DataTypes.STRING, allowNull: false, validate: { - notEmpty: true, + len: { + args: [3, 50], + msg: "Manufacturer must be between length 3 to 50." + } }, }, model: { type: DataTypes.STRING, allowNull: false, validate: { - notEmpty: true, + len: { + args: [3, 50], + msg: "Model must be between length 3 to 50." + } }, }, year: { @@ -54,8 +61,14 @@ Vehicle.init( allowNull: false, validate: { isInt: true, - min: 1886, // The year the first car was invented - max: new Date().getFullYear() + 1, + min: { + args: [1886], + msg: "Year should be grater than 1886(when first car was invented)." + }, + max: { + args: [new Date().getFullYear()], + msg: "Year should be less than current year." + }, }, }, licensePlate: { @@ -63,25 +76,53 @@ Vehicle.init( allowNull: false, unique: true, validate: { - notEmpty: true, - len: [1, 15], // Assuming a maximum length for license plates + is: { + args: "^[A-Z0-9\- ]{2,10}$", + msg: "Licence Plate format is incorrect." + } }, }, vin: { type: DataTypes.STRING, - unique: true, allowNull: true, + validate: { + is: { + args: "^[A-HJ-NPR-Z0-9]{17}$", + msg: "VIN number format is incorrect." + }, + async isUnique(value: string) { + if (!value) return; + const existingVehicles = await db.getQueryInterface().select(null, "vehicles", { + where: { + vin: value + } + }); + if (existingVehicles.length > 0) { + throw new VehicleExistsError(); + } + } + } + }, color: { type: DataTypes.STRING, allowNull: true, + validate: { + is: { + args: "^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$", + msg: "Only hex color codes are allowed." + } + } }, odometer: { type: DataTypes.INTEGER, allowNull: true, validate: { isInt: true, - min: 0, // Odometer cannot be negative + min: { + args: [0], + msg: "Odometer must always be non negative." + } }, }, }, From 705533c9e295ae7e1a0262cc96173ce48ab50d8b Mon Sep 17 00:00:00 2001 From: Javed Hussain Date: Fri, 8 Aug 2025 17:40:38 +0000 Subject: [PATCH 2/6] fixed validations for api's --- .../src/components/forms/VehicleForm.svelte | 10 +-- .../src/components/lists/InsuranceList.svelte | 2 +- .../lists/PollutionCertificateList.svelte | 2 +- app/client/src/lib/models/vehicle.ts | 16 ++--- app/server/index.ts | 26 ++++--- app/server/src/db/db.ts | 11 ++- app/server/src/db/index.ts | 43 ++++++----- app/server/src/models/Config.ts | 9 ++- app/server/src/models/FuelLog.ts | 52 +++++++++++--- app/server/src/models/Insurance.ts | 63 ++++++++++++++-- app/server/src/models/Vehicle.ts | 72 ++++++++++++------- app/server/src/models/index.ts | 4 +- app/server/src/services/insuranceService.ts | 2 + app/server/src/services/vehicleService.ts | 51 ++++++++----- app/server/tsconfig.json | 1 - 15 files changed, 246 insertions(+), 118 deletions(-) diff --git a/app/client/src/components/forms/VehicleForm.svelte b/app/client/src/components/forms/VehicleForm.svelte index a6377646..5f5592c0 100644 --- a/app/client/src/components/forms/VehicleForm.svelte +++ b/app/client/src/components/forms/VehicleForm.svelte @@ -20,12 +20,12 @@ let { vehicleToEdit = null, editMode = false, modalVisibility = $bindable(), loading } = $props(); const vehicle: NewVehicle = $state({ - make: '', - model: '', + make: null, + model: null, year: null, - licensePlate: '', - vin: '', - color: '', + licensePlate: null, + vin: null, + color: null, odometer: null }); diff --git a/app/client/src/components/lists/InsuranceList.svelte b/app/client/src/components/lists/InsuranceList.svelte index 4f7823ed..add59550 100644 --- a/app/client/src/components/lists/InsuranceList.svelte +++ b/app/client/src/components/lists/InsuranceList.svelte @@ -101,7 +101,7 @@ {:else} {#each insurances as ins (ins.id)}
diff --git a/app/client/src/components/lists/PollutionCertificateList.svelte b/app/client/src/components/lists/PollutionCertificateList.svelte index de270990..9b1f1049 100644 --- a/app/client/src/components/lists/PollutionCertificateList.svelte +++ b/app/client/src/components/lists/PollutionCertificateList.svelte @@ -100,7 +100,7 @@ {:else} {#each pollutionCertificates as pucc (pucc.id)}
diff --git a/app/client/src/lib/models/vehicle.ts b/app/client/src/lib/models/vehicle.ts index 083237ef..59aca8bb 100644 --- a/app/client/src/lib/models/vehicle.ts +++ b/app/client/src/lib/models/vehicle.ts @@ -1,16 +1,16 @@ export interface NewVehicle { - make: string; - model: string; + make: string | null; + model: string | null; year: number | null; - licensePlate: string; - vin?: string; - color?: string; - odometer?: number | null; + licensePlate: string | null; + vin: string | null; + color: string | null; + odometer: number | null; } export interface Vehicle extends NewVehicle { vehicleType: string; id: string; - insuranceStatus?: string; - puccStatus?: string; + insuranceStatus: string | null; + puccStatus?: string | null; } diff --git a/app/server/index.ts b/app/server/index.ts index 5063802f..7fafacaa 100644 --- a/app/server/index.ts +++ b/app/server/index.ts @@ -16,13 +16,13 @@ app.use("/api", pinRoutes); app.use("/api/vehicles", vehicleRoutes); app.use("/api/config", configRoutes); -if (process.env.NODE_ENV === 'production') { +if (process.env.NODE_ENV === "production") { // @ts-ignore - const { handler } = await import('../client/build/handler.js'); + const { handler } = await import("../client/build/handler.js"); app.use(handler); } else { // In dev, redirect to SvelteKit dev server - app.use('/', (req, res) => { + app.use("/", (req, res) => { res.redirect(`http://localhost:5173${req.originalUrl}`); }); } @@ -30,15 +30,19 @@ if (process.env.NODE_ENV === 'production') { performDbMigrations() .then(() => { console.log("DB Migration is Successfull!!!"); - seedData().then(() => { - console.log("Data Seeded Successfully!!!"); - app.listen(PORT, HOST, () => { - console.log(`🖥️ Server running @ http://${HOST}:${PORT}`); + seedData() + .then(() => { + console.log("Data Seeded Successfully!!!"); + app.listen(PORT, HOST, () => { + console.log( + "---------------------------------------------------------------------------", + ); + console.log(`Server started -> http://${HOST}:${PORT}`); + }); + }) + .catch((err) => { + console.error("Error while seeding : ", err); }); - }).catch((err) => { - console.error("Error while seeding : ", err); - }) - }) .catch((err) => { console.error("Error while running db migrations : ", err); diff --git a/app/server/src/db/db.ts b/app/server/src/db/db.ts index 69d7817a..167ceddb 100644 --- a/app/server/src/db/db.ts +++ b/app/server/src/db/db.ts @@ -3,13 +3,12 @@ import { Sequelize } from "sequelize"; config({ quiet: false, debug: true }); -const showSql = process.env.SHOW_SQL === 'true' || false -console.log(`--------------------------------- SHOW SQL : ${showSql} --------------------------------------`) +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 - logging: showSql, + dialect: "sqlite", + storage: `${process.env.DB_PATH || "./vehicles.db"}`, // Use environment variable for database path + logging: showSql, }); -export { db }; \ No newline at end of file +export { db }; diff --git a/app/server/src/db/index.ts b/app/server/src/db/index.ts index 6b77508a..0df19e2f 100644 --- a/app/server/src/db/index.ts +++ b/app/server/src/db/index.ts @@ -1,14 +1,22 @@ import { Umzug, SequelizeStorage } from "umzug"; import bcrypt from "bcrypt"; -import { Auth, Config, FuelLog, Insurance, MaintenanceLog, PollutionCertificate, Vehicle } from "../models/index.js"; +import { + Auth, + Config, + FuelLog, + Insurance, + MaintenanceLog, + PollutionCertificate, + Vehicle, +} from "../models/index.js"; import { db } from "./db.js"; const umzug = new Umzug({ - migrations: { glob: 'migrations/*.ts' }, + migrations: { glob: "migrations/*.ts" }, context: db.getQueryInterface(), storage: new SequelizeStorage({ tableName: "migrations", - sequelize: db + sequelize: db, }), logger: console, }); @@ -17,12 +25,12 @@ const umzug = new Umzug({ type Migration = typeof umzug._types.migration; const performDbMigrations = async () => { - await db.sync({ alter: true }) + await db.sync({ alter: true }); return umzug.up({}); -} +}; const seedData = async () => { - const demoMode = process.env.DEMO_MODE === 'true' + const demoMode = process.env.DEMO_MODE === "true"; if (demoMode) { await db.dropAllSchemas({}); @@ -30,21 +38,20 @@ const seedData = async () => { } else { await setupPinAndConfigs(); } - -} +}; const setupPinAndConfigs = async () => { - const pin = process.env.AUTH_PIN + const pin = process.env.AUTH_PIN; if (pin) await generatePin(pin); await generateConfigs(); -} +}; const insertDummyData = async () => { try { await db.sync({ force: true }); console.log("Database synchronized!"); - await generatePin('123456'); + await generatePin("123456"); await generateConfigs(); const { vehicle1, vehicle2 } = await generateVehicles(); await generateInsurances(vehicle1, vehicle2); @@ -55,7 +62,7 @@ const insertDummyData = async () => { } catch (error) { console.error("Error populating database:", error); } -} +}; async function generateFuelLogData(vehicle1: Vehicle, vehicle2: Vehicle) { const vehicle1FuelLogs: any = [ @@ -200,9 +207,9 @@ async function generateVehicles() { make: "Honda", model: "Civic", year: 2022, - licensePlate: "TS-07-JA-1997", + licensePlate: "TS07JA1997", vin: "1HGFC1F7XNA000001", - color: "Black", + color: "#000", odometer: 15000, }); @@ -210,9 +217,9 @@ async function generateVehicles() { make: "Toyota", model: "Corolla", year: 2021, - licensePlate: "AP-28-DX-2000", + licensePlate: "AP28DX2000", vin: "1NXBU4EE4AZ000002", - color: "White", + color: "#FFF", odometer: 25000, }); @@ -239,7 +246,7 @@ async function generateConfigs() { }, ]; await Config.bulkCreate(configData, { - ignoreDuplicates: true + ignoreDuplicates: true, }); console.log("Configuration data created."); } @@ -251,4 +258,4 @@ async function generatePin(pin: string) { console.log(`User created with PIN: ${pin}`); } -export { Migration, performDbMigrations, seedData, db } \ No newline at end of file +export { Migration, performDbMigrations, seedData, db }; diff --git a/app/server/src/models/Config.ts b/app/server/src/models/Config.ts index 800870e7..8a002853 100644 --- a/app/server/src/models/Config.ts +++ b/app/server/src/models/Config.ts @@ -7,11 +7,12 @@ interface ConfigAttributes { description?: string; } -interface ConfigCreationAttributes extends Optional { } +interface ConfigCreationAttributes extends Optional {} class Config extends Model - implements ConfigAttributes { + implements ConfigAttributes +{ declare public key: string; declare public value: string; declare public description: string; @@ -27,13 +28,11 @@ Config.init( }, value: { type: DataTypes.STRING, - allowNull: true, - defaultValue: "", + allowNull: false, }, description: { type: DataTypes.STRING, allowNull: true, - defaultValue: "", }, }, { diff --git a/app/server/src/models/FuelLog.ts b/app/server/src/models/FuelLog.ts index d0a87511..1e7b7540 100644 --- a/app/server/src/models/FuelLog.ts +++ b/app/server/src/models/FuelLog.ts @@ -1,6 +1,8 @@ import { DataTypes, Model, Optional } from "sequelize"; import { db } from "../db/index.js"; import Vehicle from "./Vehicle.js"; +import { VehicleServiceError } from "../exceptions/VehicleErrors.js"; +import { FuelLogError } from "../exceptions/FuelLogError.js"; interface FuelLogAttributes { id: string; @@ -12,11 +14,12 @@ interface FuelLogAttributes { notes?: string; } -interface FuelLogCreationAttributes extends Optional { } +interface FuelLogCreationAttributes extends Optional {} class FuelLog extends Model - implements FuelLogAttributes { + implements FuelLogAttributes +{ declare public id: string; declare public vehicleId: string; declare public date: string; @@ -45,36 +48,63 @@ FuelLog.init( date: { type: DataTypes.DATEONLY, allowNull: false, - validate: { - isDate: true, - notEmpty: true, - }, }, odometer: { type: DataTypes.INTEGER, allowNull: false, validate: { - isInt: true, - min: 0, + async validateOdometer(value: number) { + const maxOdometer: number = await FuelLog.max("odometer", { + where: { + vehicleId: this.vehicleId as string, + }, + }); + + const vehicle = await Vehicle.findOne({ + where: { + id: this.vehicleId as string, + }, + attributes: ["odometer"], + }); + + const vehicleOdometer = vehicle?.odometer || 0; + + if (value <= maxOdometer || value <= vehicleOdometer) { + throw new FuelLogError( + "Odometer Reading must be greater than current vehicle odometer.", + ); + } + }, }, }, fuelAmount: { type: DataTypes.FLOAT, allowNull: false, + validate: { + min: { + args: [0], + msg: "Fuel Amount must be greater than 0.", + }, + }, }, cost: { type: DataTypes.FLOAT, allowNull: false, validate: { - isFloat: true, - min: 0, + min: { + args: [0], + msg: "Fuel Amount must be greater than 0.", + }, }, }, notes: { type: DataTypes.STRING, allowNull: true, validate: { - len: [0, 500], + len: { + args: [0, 500], + msg: "Notes must be lesser than 500 length.", + }, }, }, }, diff --git a/app/server/src/models/Insurance.ts b/app/server/src/models/Insurance.ts index 60709ad2..46c80d2f 100644 --- a/app/server/src/models/Insurance.ts +++ b/app/server/src/models/Insurance.ts @@ -1,6 +1,7 @@ import { DataTypes, Model, Optional } from "sequelize"; import { db } from "../db/index.js"; import Vehicle from "./Vehicle.js"; +import { InsuranceServiceError } from "../exceptions/InsuranceError.js"; interface InsuranceAttributes { id: string; @@ -10,14 +11,16 @@ interface InsuranceAttributes { startDate: string; endDate: string; cost: number; + notes?: string; } interface InsuranceCreationAttributes - extends Optional { } + extends Optional {} class Insurance extends Model - implements InsuranceAttributes { + implements InsuranceAttributes +{ declare public id: string; declare public vehicleId: string; declare public provider: string; @@ -25,6 +28,7 @@ class Insurance declare public startDate: string; declare public endDate: string; declare public cost: number; + declare public notes?: string; } Insurance.init( @@ -47,14 +51,24 @@ Insurance.init( type: DataTypes.STRING, allowNull: false, validate: { - notEmpty: true, + len: { + args: [3, 50], + msg: "Provider must be between length 3 to 50.", + }, }, }, policyNumber: { type: DataTypes.STRING, allowNull: false, validate: { - notEmpty: true, + len: { + args: [3, 50], + msg: "Provider must be between length 3 to 50.", + }, + is: { + args: "^[0-9A-Za-z\s\-]*$", + msg: "Only number and characters with space and hyphen are allowed in policy number.", + }, }, }, startDate: { @@ -75,8 +89,20 @@ Insurance.init( type: DataTypes.FLOAT, allowNull: false, validate: { - isFloat: true, - min: 0, + min: { + args: [0], + msg: "Fuel Amount must be greater than 0.", + }, + }, + }, + notes: { + type: DataTypes.STRING, + allowNull: true, + validate: { + len: { + args: [0, 500], + msg: "Notes must be lesser than 500 length.", + }, }, }, }, @@ -85,6 +111,31 @@ Insurance.init( timestamps: true, underscored: true, sequelize: db, + validate: { + async validateDates() { + const previousInsEndDate = await Insurance.max("endDate", { + where: { + vehicleId: this.vehicleId as string, + }, + }); + + const sDate = new Date(this.startDate as string); + const eDate = new Date(this.endDate as string); + const maxEndDate = new Date(previousInsEndDate as string); + + if (sDate >= eDate) { + throw new InsuranceServiceError( + "Start date must always be before end date.", + ); + } + + if (sDate < maxEndDate) { + throw new InsuranceServiceError( + "Start date must always be after previous insurance end date.", + ); + } + }, + }, }, ); diff --git a/app/server/src/models/Vehicle.ts b/app/server/src/models/Vehicle.ts index 226e9239..3e7f674f 100644 --- a/app/server/src/models/Vehicle.ts +++ b/app/server/src/models/Vehicle.ts @@ -1,6 +1,10 @@ import { DataTypes, Model, Optional } from "sequelize"; import { db } from "../db/index.js"; -import { VehicleExistsError, VehicleServiceError } from "../exceptions/VehicleErrors.js"; +import { + VehicleExistsError, + VehicleServiceError, +} from "../exceptions/VehicleErrors.js"; +import FuelLog from "./FuelLog.js"; interface VehicleAttributes { id: string; @@ -13,11 +17,12 @@ interface VehicleAttributes { odometer?: number; } -interface VehicleCreationAttributes extends Optional { } +interface VehicleCreationAttributes extends Optional {} class Vehicle extends Model - implements VehicleAttributes { + implements VehicleAttributes +{ declare public id: string; declare public make: string; declare public model: string; @@ -42,8 +47,8 @@ Vehicle.init( validate: { len: { args: [3, 50], - msg: "Manufacturer must be between length 3 to 50." - } + msg: "Manufacturer must be between length 3 to 50.", + }, }, }, model: { @@ -52,8 +57,8 @@ Vehicle.init( validate: { len: { args: [3, 50], - msg: "Model must be between length 3 to 50." - } + msg: "Model must be between length 3 to 50.", + }, }, }, year: { @@ -63,11 +68,11 @@ Vehicle.init( isInt: true, min: { args: [1886], - msg: "Year should be grater than 1886(when first car was invented)." + msg: "Year should be grater than 1886(when first car was invented).", }, max: { args: [new Date().getFullYear()], - msg: "Year should be less than current year." + msg: "Year should be less than current year.", }, }, }, @@ -78,31 +83,35 @@ Vehicle.init( validate: { is: { args: "^[A-Z0-9\- ]{2,10}$", - msg: "Licence Plate format is incorrect." - } + msg: "Licence Plate format is incorrect.", + }, }, }, vin: { type: DataTypes.STRING, allowNull: true, validate: { + notEmpty: { + msg: "VIN number can't be an empty string.", + }, is: { args: "^[A-HJ-NPR-Z0-9]{17}$", - msg: "VIN number format is incorrect." + msg: "VIN number format is incorrect.", }, async isUnique(value: string) { if (!value) return; - const existingVehicles = await db.getQueryInterface().select(null, "vehicles", { - where: { - vin: value - } - }); + const existingVehicles = await db + .getQueryInterface() + .select(null, "vehicles", { + where: { + vin: value, + }, + }); if (existingVehicles.length > 0) { throw new VehicleExistsError(); } - } - } - + }, + }, }, color: { type: DataTypes.STRING, @@ -110,19 +119,28 @@ Vehicle.init( validate: { is: { args: "^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$", - msg: "Only hex color codes are allowed." - } - } + msg: "Only hex color codes are allowed.", + }, + }, }, odometer: { type: DataTypes.INTEGER, allowNull: true, validate: { isInt: true, - min: { - args: [0], - msg: "Odometer must always be non negative." - } + async validateOdometer(value: number) { + const minOdometer: number = await FuelLog.min("odometer", { + where: { + vehicleId: this.id as string, + }, + }); + + if (minOdometer && value > minOdometer) { + throw new VehicleServiceError( + "Initial Odometer Reading must be lesser than first fuel log odometer.", + ); + } + }, }, }, }, diff --git a/app/server/src/models/index.ts b/app/server/src/models/index.ts index a6b53708..6e7a1e21 100644 --- a/app/server/src/models/index.ts +++ b/app/server/src/models/index.ts @@ -7,10 +7,10 @@ import Auth from "./Auth.js"; import Config from "./Config.js"; // Associations -Vehicle.hasOne(Insurance, { foreignKey: "vehicleId", as: "insurance" }); +Vehicle.hasMany(Insurance, { foreignKey: "vehicleId", as: "insurance" }); Insurance.belongsTo(Vehicle, { foreignKey: "vehicleId", as: "vehicle" }); -Vehicle.hasOne(PollutionCertificate, { +Vehicle.hasMany(PollutionCertificate, { foreignKey: "vehicleId", as: "pollutionCertificate", }); diff --git a/app/server/src/services/insuranceService.ts b/app/server/src/services/insuranceService.ts index 3b5980ad..6aaee769 100644 --- a/app/server/src/services/insuranceService.ts +++ b/app/server/src/services/insuranceService.ts @@ -22,6 +22,7 @@ export const addInsurance = async (vehicleId: string, insuranceData: any) => { message: "Insurance details added successfully.", }; } catch (error: unknown) { + console.error("Error adding fuel log: ", error); if (error instanceof UniqueConstraintError) { throw new InsuranceExistsError(); } @@ -42,6 +43,7 @@ export const getInsurances = async (vehicleId: string) => { } return insurance; } catch (error: unknown) { + console.error("Error getting insurance: ", error); if (error instanceof InsuranceNotFoundError) { throw error; } diff --git a/app/server/src/services/vehicleService.ts b/app/server/src/services/vehicleService.ts index 4436f9da..ef6320a0 100644 --- a/app/server/src/services/vehicleService.ts +++ b/app/server/src/services/vehicleService.ts @@ -1,5 +1,9 @@ -import { VehicleExistsError, VehicleServiceError, VehicleNotFoundError } from "../exceptions/VehicleErrors.js"; -import { Vehicle } from "../models/index.js"; +import { + VehicleExistsError, + VehicleServiceError, + VehicleNotFoundError, +} from "../exceptions/VehicleErrors.js"; +import { Insurance, PollutionCertificate, Vehicle } from "../models/index.js"; import { UniqueConstraintError } from "sequelize"; export const addVehicle = async (vehicleData: any) => { @@ -19,31 +23,46 @@ export const getAllVehicles = async () => { try { const vehicles = await Vehicle.findAll({ include: [ - { association: 'insurance' }, - { association: 'pollutionCertificate' } - ] + { association: "insurance" }, + { association: "pollutionCertificate" }, + ], }); - return vehicles.map(vehicle => { - const insurance = (vehicle as any).insurance; - const pollutionCertificate = (vehicle as any).pollutionCertificate; + // console.log(JSON.stringify(vehicles, null, 4)); + + return vehicles.map((vehicle) => { + const insurances: Insurance[] = (vehicle as any).insurance; + const pollutionCertificates: PollutionCertificate[] = (vehicle as any) + .pollutionCertificate; let insuranceStatus = "N/A"; - if (insurance) { - const endDate = new Date(insurance.endDate); - insuranceStatus = endDate > new Date() ? "Active" : "Expired"; + if (insurances && insurances.length > 0) { + insuranceStatus = "Expired"; + insurances.forEach((insurance) => { + const endDate = new Date(insurance.endDate); + const today = new Date(); + if (endDate > today) { + insuranceStatus = "Active"; + } + }); } let puccStatus = "N/A"; - if (pollutionCertificate) { - const expiryDate = new Date(pollutionCertificate.expiryDate); - puccStatus = expiryDate > new Date() ? "Active" : "Expired"; + if (pollutionCertificates && pollutionCertificates.length > 0) { + puccStatus = "Expired"; + pollutionCertificates.forEach((pucc) => { + const expiryDate = new Date(pucc.expiryDate); + const today = new Date(); + if (expiryDate > today) { + puccStatus = "Active"; + } + }); } return { ...vehicle.toJSON(), insuranceStatus, - puccStatus + puccStatus, }; }); } catch (error: unknown) { @@ -93,7 +112,7 @@ export const deleteVehicle = async (id: string) => { try { const result = await Vehicle.destroy({ where: { id: id }, - cascade: true + cascade: true, }); if (result === 0) { throw new VehicleNotFoundError(); diff --git a/app/server/tsconfig.json b/app/server/tsconfig.json index 28b84bb8..68385ebb 100644 --- a/app/server/tsconfig.json +++ b/app/server/tsconfig.json @@ -16,7 +16,6 @@ "moduleResolution": "nodenext", "noUncheckedIndexedAccess": true, "declaration": true - }, "include": ["**/*.ts"], "exclude": ["node_modules", "dist"] From 07b41d630628382309d4a666dd64a273d35a7bbf Mon Sep 17 00:00:00 2001 From: Javed Hussain Date: Sat, 9 Aug 2025 12:17:12 +0000 Subject: [PATCH 3/6] Engance UI and error handling --- .../components/chart/DashboardCharts.svelte | 10 +- .../src/components/common/IconButton.svelte | 26 ++++ .../src/components/common/StatusBlock.svelte | 24 +++ .../src/components/common/ThemeToggle.svelte | 8 +- .../src/components/common/VehicleCard.svelte | 89 +++++------ .../src/components/lists/FuelLogList.svelte | 27 +--- .../src/components/lists/InsuranceList.svelte | 32 +--- .../lists/MaintenanceLogList.svelte | 27 +--- .../lists/PollutionCertificateList.svelte | 32 +--- .../src/components/lists/VehicleList.svelte | 1 - .../src/components/tabs/DashboardTab.svelte | 2 +- app/client/src/lib/models/status.ts | 4 + app/client/src/routes/+layout.svelte | 50 +++--- app/client/src/routes/dashboard/+page.svelte | 2 + app/client/src/routes/login/+page.svelte | 54 ++++--- .../src/controllers/ConfigController.ts | 28 ++-- .../src/controllers/FuelLogController.ts | 111 +++++-------- .../src/controllers/InsuranceController.ts | 111 +++++-------- .../controllers/MaintenanceLogController.ts | 147 ++++++++---------- app/server/src/controllers/PUCCController.ts | 121 ++++++-------- app/server/src/controllers/PinController.ts | 33 ++-- .../src/controllers/VehicleController.ts | 77 +++++---- app/server/src/db/index.ts | 6 +- app/server/src/exceptions/AuthError.ts | 7 + app/server/src/exceptions/ConfigError.ts | 7 + app/server/src/exceptions/FuelLogError.ts | 18 +-- app/server/src/exceptions/InsuranceError.ts | 21 +-- .../src/exceptions/MaintenanceLogError.ts | 7 + .../src/exceptions/MaintenanceLogErrors.ts | 13 -- app/server/src/exceptions/PinErrors.ts | 6 - .../exceptions/PollutionCertificateError.ts | 7 + .../exceptions/PollutionCertificateErrors.ts | 20 --- app/server/src/exceptions/ServiceError.ts | 25 +++ app/server/src/exceptions/VehicleError.ts | 7 + app/server/src/exceptions/VehicleErrors.ts | 20 --- app/server/src/models/FuelLog.ts | 3 +- app/server/src/models/Insurance.ts | 22 ++- app/server/src/models/MaintenanceLog.ts | 38 +++-- app/server/src/models/PUCC.ts | 71 ++++++++- app/server/src/models/Vehicle.ts | 14 +- app/server/src/services/configService.ts | 57 +++++-- app/server/src/services/fuelLogService.ts | 62 ++++---- app/server/src/services/insuranceService.ts | 69 ++++---- .../src/services/maintenanceLogService.ts | 72 +++++---- app/server/src/services/pinService.ts | 35 ++--- .../services/pollutionCertificateService.ts | 81 ++++------ app/server/src/services/vehicleService.ts | 70 ++++----- app/server/tsconfig.json | 2 +- 48 files changed, 870 insertions(+), 906 deletions(-) create mode 100644 app/client/src/components/common/IconButton.svelte create mode 100644 app/client/src/components/common/StatusBlock.svelte create mode 100644 app/client/src/lib/models/status.ts create mode 100644 app/server/src/exceptions/AuthError.ts create mode 100644 app/server/src/exceptions/ConfigError.ts create mode 100644 app/server/src/exceptions/MaintenanceLogError.ts delete mode 100644 app/server/src/exceptions/MaintenanceLogErrors.ts delete mode 100644 app/server/src/exceptions/PinErrors.ts create mode 100644 app/server/src/exceptions/PollutionCertificateError.ts delete mode 100644 app/server/src/exceptions/PollutionCertificateErrors.ts create mode 100644 app/server/src/exceptions/ServiceError.ts create mode 100644 app/server/src/exceptions/VehicleError.ts delete mode 100644 app/server/src/exceptions/VehicleErrors.ts diff --git a/app/client/src/components/chart/DashboardCharts.svelte b/app/client/src/components/chart/DashboardCharts.svelte index d7431a7a..7cf4a31a 100644 --- a/app/client/src/components/chart/DashboardCharts.svelte +++ b/app/client/src/components/chart/DashboardCharts.svelte @@ -85,7 +85,7 @@ { label: `Total Fuel Cost (${getCurrencySymbol()})`, data: costData, - fill: false, + fill: true, borderColor: 'rgb(75, 192, 192)', tension: 0.3, borderWidth: 2, @@ -102,9 +102,13 @@ { label: `Mileage (${getMileageUnit()})`, data: mileageDataPoints, - fill: true, borderColor: 'rgb(255, 99, 132)', - tension: 0.1 + fill: true, + tension: 0.3, + borderWidth: 2, + borderCapStyle: 'round', + pointStyle: 'circle', + pointRadius: 2 } ] }; diff --git a/app/client/src/components/common/IconButton.svelte b/app/client/src/components/common/IconButton.svelte new file mode 100644 index 00000000..a0051c9c --- /dev/null +++ b/app/client/src/components/common/IconButton.svelte @@ -0,0 +1,26 @@ + + + diff --git a/app/client/src/components/common/StatusBlock.svelte b/app/client/src/components/common/StatusBlock.svelte new file mode 100644 index 00000000..bac6dc23 --- /dev/null +++ b/app/client/src/components/common/StatusBlock.svelte @@ -0,0 +1,24 @@ + + +{#if message} +

+ {message} +

+{/if} diff --git a/app/client/src/components/common/ThemeToggle.svelte b/app/client/src/components/common/ThemeToggle.svelte index 0a77f26c..417177e8 100644 --- a/app/client/src/components/common/ThemeToggle.svelte +++ b/app/client/src/components/common/ThemeToggle.svelte @@ -31,17 +31,17 @@
{#if darkMode} - + {:else} - + {/if}
diff --git a/app/client/src/components/common/VehicleCard.svelte b/app/client/src/components/common/VehicleCard.svelte index 92357dfe..e13c1231 100644 --- a/app/client/src/components/common/VehicleCard.svelte +++ b/app/client/src/components/common/VehicleCard.svelte @@ -20,6 +20,7 @@ import { puccModelStore } from '$lib/stores/pucc'; import { browser } from '$app/environment'; import { env } from '$env/dynamic/public'; + import IconButton from './IconButton.svelte'; const { vehicle, updateCallback } = $props(); @@ -28,8 +29,7 @@ return; } try { - const response = await fetch( - `${env.PUBLIC_API_BASE_URL || ''}/api/vehicles/${vehicleId}`, { + const response = await fetch(`${env.PUBLIC_API_BASE_URL || ''}/api/vehicles/${vehicleId}`, { method: 'DELETE', headers: { 'X-User-PIN': localStorage.getItem('userPin') || '' @@ -117,64 +117,53 @@ {/if}
-
- - - - + ariaLabel="Pollution Certificate" + />
- - + ariaLabel="Delete" + />
diff --git a/app/client/src/components/lists/FuelLogList.svelte b/app/client/src/components/lists/FuelLogList.svelte index 3bac2d6e..0e343cb5 100644 --- a/app/client/src/components/lists/FuelLogList.svelte +++ b/app/client/src/components/lists/FuelLogList.svelte @@ -12,6 +12,7 @@ import { fuelLogModelStore } from '$lib/stores/fuel-log'; import { Jumper } from 'svelte-loading-spinners'; + import IconButton from '$components/common/IconButton.svelte'; const { vehicleId } = $props(); @@ -138,27 +139,13 @@ > {log.notes || '-'} - - + ariaLabel="Delete" + /> {/each} diff --git a/app/client/src/components/lists/InsuranceList.svelte b/app/client/src/components/lists/InsuranceList.svelte index add59550..3d04ddd4 100644 --- a/app/client/src/components/lists/InsuranceList.svelte +++ b/app/client/src/components/lists/InsuranceList.svelte @@ -6,6 +6,7 @@ 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'; let { vehicleId } = $props(); @@ -109,30 +110,13 @@ {ins.provider}
- - + deleteInsurance(ins.id)} + ariaLabel="Delete" + />
diff --git a/app/client/src/components/lists/MaintenanceLogList.svelte b/app/client/src/components/lists/MaintenanceLogList.svelte index 5033569f..f1ecc49d 100644 --- a/app/client/src/components/lists/MaintenanceLogList.svelte +++ b/app/client/src/components/lists/MaintenanceLogList.svelte @@ -6,6 +6,7 @@ import { Pencil, Trash2 } from '@lucide/svelte'; import { maintenanceModelStore } from '$lib/stores/maintenance'; import { Jumper } from 'svelte-loading-spinners'; + import IconButton from '$components/common/IconButton.svelte'; let { vehicleId } = $props(); @@ -126,27 +127,13 @@ {formatCurrency(log.cost)} {log.notes || '-'} - - + ariaLabel="Delete" + /> {/each} diff --git a/app/client/src/components/lists/PollutionCertificateList.svelte b/app/client/src/components/lists/PollutionCertificateList.svelte index 9b1f1049..2e061cab 100644 --- a/app/client/src/components/lists/PollutionCertificateList.svelte +++ b/app/client/src/components/lists/PollutionCertificateList.svelte @@ -6,6 +6,7 @@ import { formatDate } from '$lib/utils/formatting'; import { puccModelStore } from '$lib/stores/pucc'; import { Jumper } from 'svelte-loading-spinners'; + import IconButton from '$components/common/IconButton.svelte'; let { vehicleId } = $props(); @@ -110,30 +111,13 @@ >
- - + deletePollutionCertificate(pucc.id)} + ariaLabel="Delete" + />
diff --git a/app/client/src/components/lists/VehicleList.svelte b/app/client/src/components/lists/VehicleList.svelte index c188e27f..4e30c8f3 100644 --- a/app/client/src/components/lists/VehicleList.svelte +++ b/app/client/src/components/lists/VehicleList.svelte @@ -1,7 +1,6 @@ - + diff --git a/app/client/src/lib/models/status.ts b/app/client/src/lib/models/status.ts new file mode 100644 index 00000000..42372587 --- /dev/null +++ b/app/client/src/lib/models/status.ts @@ -0,0 +1,4 @@ +export type Status = { + message?: string; + type: 'ERROR' | 'SUCCESS' | 'INFO'; +}; diff --git a/app/client/src/routes/+layout.svelte b/app/client/src/routes/+layout.svelte index bc178086..1b27dc91 100644 --- a/app/client/src/routes/+layout.svelte +++ b/app/client/src/routes/+layout.svelte @@ -11,6 +11,7 @@ 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(); @@ -52,11 +53,12 @@ {#if demoMode}
- 🚜 Demo Mode: This is a demo instance. Data will be reset periodically and is not saved - permanently. Please avoid adding any persoanl info. + ⚠️ 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 @@ -78,28 +80,26 @@ Tracktor -
+
-
- { - configModelStore.show((status: boolean) => { - fetchVehicles(); - }); - }} - /> -
-
- -
+ { + configModelStore.show((status: boolean) => { + fetchVehicles(); + }); + }} + ariaLabel="Settings" + /> +
diff --git a/app/client/src/routes/dashboard/+page.svelte b/app/client/src/routes/dashboard/+page.svelte index aba20e6d..4a01b971 100644 --- a/app/client/src/routes/dashboard/+page.svelte +++ b/app/client/src/routes/dashboard/+page.svelte @@ -34,6 +34,8 @@ selectedVehicleId = data.selectedVehicleId; if (vehicles.length > 0) { selectedVehicleId = selectedVehicleId || vehicles[0].id; + } else { + selectedVehicleId = undefined; } }); diff --git a/app/client/src/routes/login/+page.svelte b/app/client/src/routes/login/+page.svelte index 17e1cfef..6136337d 100644 --- a/app/client/src/routes/login/+page.svelte +++ b/app/client/src/routes/login/+page.svelte @@ -5,18 +5,21 @@ import ThemeToggle from '$components/common/ThemeToggle.svelte'; import { env } from '$env/dynamic/public'; import { ShieldEllipsis, ShieldPlus, Tractor } from '@lucide/svelte'; - import { DoubleBounce, Jumper, Shadow } from 'svelte-loading-spinners'; + import { Jumper } from 'svelte-loading-spinners'; import { simulateNetworkDelay } from '$lib/utils/dev'; + import StatusBlock from '$components/common/StatusBlock.svelte'; + import type { Status } from '$lib/models/status'; let loading = $state(false); - let error = $state(''); - let pinExists = $state(false); // New state to track if PIN exists on server - let checkingPinStatus = $state(true); // New state to track initial status check + let status = $state({ + message: undefined, + type: 'INFO' + }); + let pinExists = $state(false); + let checkingPinStatus = $state(true); - // If the user is already logged in, redirect to the dashboard. $effect(() => { if (browser) { - // Check if PIN exists on the server async function checkPinStatus() { try { const response = await fetch(`${env.PUBLIC_API_BASE_URL || ''}/api/pin/status`); @@ -24,10 +27,16 @@ const data = await response.json(); pinExists = data.exists; } else { - error = 'Failed to check PIN status.'; + status = { + message: 'Failed to check PIN status.', + type: 'ERROR' + }; } } catch (e) { - error = 'Failed to connect to the server. Please check your connection.'; + status = { + message: 'Unknown Server Error Occurred.', + type: 'ERROR' + }; } finally { checkingPinStatus = false; } @@ -43,12 +52,15 @@ async function handlePinComplete(pin: string) { loading = true; - error = ''; - // await simulateNetworkDelay(1000); // Simulate network delay for development + status.message = ''; + await simulateNetworkDelay(1000); // Simulate network delay for development try { await endpointCall(pin, pinExists); } catch (e) { - error = 'Failed to connect to the server. Please check your connection.'; + status = { + message: 'Failed to connect to the server. Please check your connection.', + type: 'ERROR' + }; } finally { loading = false; } @@ -69,10 +81,20 @@ if (browser) { localStorage.setItem('userPin', pin); } + loading = false; + status = { + message: 'PIN Verified Successfully', + type: 'SUCCESS' + }; + await simulateNetworkDelay(1000); goto('/dashboard', { replaceState: true }); } else { const data = await response.json(); - error = data.message || (pinExists ? 'Invalid PIN. Please try again.' : 'Failed to set PIN.'); + status = { + message: + data.message || (pinExists ? 'Invalid PIN. Please try again.' : 'Failed to set PIN.'), + type: 'ERROR' + }; } }; @@ -123,12 +145,6 @@

{/if} - {#if error} -

- {error} -

- {/if} +
diff --git a/app/server/src/controllers/ConfigController.ts b/app/server/src/controllers/ConfigController.ts index 13925906..3669102d 100644 --- a/app/server/src/controllers/ConfigController.ts +++ b/app/server/src/controllers/ConfigController.ts @@ -4,13 +4,15 @@ import { getAppConfigByKey, updateAppConfig, } from "../services/configService.js"; +import { ConfigError } from "../exceptions/ConfigError.js"; +import { Status } from "../exceptions/ServiceError.js"; export const getConfig = async (req: Request, res: Response) => { try { const config = await getAppConfig(); - res.json(config); - } catch (error) { - res.status(500).json({ message: "Error fetching configuration" }); + res.status(200).json(config); + } catch (error: any) { + res.status(error.status.valueOf).json({ message: error.message }); } }; @@ -18,15 +20,12 @@ export const getConfigByKey = async (req: Request, res: Response) => { try { const key = req.params.key; if (!key) { - return res.status(400).json({ message: "Key parameter is required" }); + throw new ConfigError("Key parameter is required", Status.BAD_REQUEST); } const config = await getAppConfigByKey(key); - if (!config) { - return res.status(404).json({ message: "Configuration not found" }); - } - res.json(config); - } catch (error) { - res.status(500).json({ message: "Error fetching configuration" }); + res.status(200).json(config); + } catch (error: any) { + res.status(error.status.valueOf).json({ message: error.message }); } }; @@ -34,19 +33,16 @@ export const updateConfig = async (req: Request, res: Response) => { try { const configs: { key: string; value: string }[] = req.body; if (!Array.isArray(configs) || configs.length === 0) { - return res.status(400).json({ message: "Invalid configuration data" }); + throw new ConfigError("Invalid configuration data", Status.BAD_REQUEST); } const updatedConfigs = await Promise.all( configs.map(async (config) => { const { key, value } = config; - if (!key || value === undefined) { - throw new Error("Key and value are required for each configuration"); - } return await updateAppConfig(key, value); }), ); res.json(updatedConfigs); - } catch (error) { - res.status(500).json({ message: "Error updating configuration" }); + } catch (error: any) { + res.status(error.status.valueOf).json({ message: error.message }); } }; diff --git a/app/server/src/controllers/FuelLogController.ts b/app/server/src/controllers/FuelLogController.ts index 991ee939..0abe395c 100644 --- a/app/server/src/controllers/FuelLogController.ts +++ b/app/server/src/controllers/FuelLogController.ts @@ -1,112 +1,85 @@ - import { Request, Response } from "express"; import * as fuelLogService from "../services/fuelLogService.js"; -import { VehicleNotFoundError } from "../exceptions/VehicleErrors.js"; -import { FuelLogNotFoundError } from "../exceptions/FuelLogError.js"; +import { FuelLogError } from "../exceptions/FuelLogError.js"; +import { Status } from "../exceptions/ServiceError.js"; export const addFuelLog = async (req: Request, res: Response) => { - const { vehicleId } = req.params; - const { date, odometer, fuelAmount, cost } = req.body; + try { + const { vehicleId } = req.params; + const { date, odometer, fuelAmount, cost } = req.body; - if (!date || !odometer || !fuelAmount || !cost) { - return res - .status(400) - .json({ message: "Date, Odometer, Fuel Amount, and Cost are required." }); - } - if (!vehicleId) { - console.log("Vehicle ID is missing in request parameters.", JSON.stringify(req.path)); - return res - .status(400) - .json({ message: "Vehicle ID is required." }); - } + if (!date || !odometer || !fuelAmount || !cost) { + throw new FuelLogError( + "Date, Odometer, Fuel Amount, and Cost are required in request body.", + Status.BAD_REQUEST, + ); + } + if (!vehicleId) { + throw new FuelLogError("Vehicle id is required.", Status.BAD_REQUEST); + } - try { const result = await fuelLogService.addFuelLog(vehicleId, req.body); res.status(201).json(result); } catch (error: any) { - console.error("Error adding fuel log:", error); - if (error instanceof VehicleNotFoundError) { - return res.status(404).json({ message: error.message }); - } - res.status(500).json({ message: error.message }); + res.status(error.status.valueOf()).json({ message: error.message }); } }; export const getFuelLogs = async (req: Request, res: Response) => { - const { vehicleId } = req.params; - if (!vehicleId) { - return res - .status(400) - .json({ message: "Vehicle ID is required." }); - } try { + const { vehicleId } = req.params; + if (!vehicleId) { + throw new FuelLogError("Vehicle id is required.", Status.BAD_REQUEST); + } const fuelLogs = await fuelLogService.getFuelLogs(vehicleId); res.status(200).json(fuelLogs); } catch (error: any) { - console.error("Error fetching fuel logs:", error); - res.status(500).json({ message: error.message }); + res.status(error.status.valueOf()).json({ message: error.message }); } }; export const getFuelLogById = async (req: Request, res: Response) => { - const { id } = req.params; - if (!id) { - return res - .status(400) - .json({ message: "Fuel log ID is required." }); - } try { + const { id } = req.params; + if (!id) { + throw new FuelLogError("Fuel Log id is required.", Status.BAD_REQUEST); + } const fuelLog = await fuelLogService.getFuelLogById(id); res.status(200).json(fuelLog); } catch (error: any) { - if (error instanceof FuelLogNotFoundError) { - return res.status(404).json({ message: error.message }); - } - res.status(500).json({ message: error.message }); + res.status(error.status.valueOf()).json({ message: error.message }); } }; export const updateFuelLog = async (req: Request, res: Response) => { - const { id } = req.params; - const { date, odometer, fuelAmount, cost } = req.body; - - if (!date || !odometer || !fuelAmount || !cost) { - return res - .status(400) - .json({ message: "Date, Odometer, Fuel Amount, and Cost are required." }); - } - if (!id) { - return res - .status(400) - .json({ message: "Fuel log ID is required." }); - } try { + const { id } = req.params; + const { date, odometer, fuelAmount, cost } = req.body; + if (!date || !odometer || !fuelAmount || !cost) { + throw new FuelLogError( + "Date, Odometer, Fuel Amount, and Cost are required.", + Status.BAD_REQUEST, + ); + } + if (!id) { + throw new FuelLogError("Fuel log ID is required.", Status.BAD_REQUEST); + } const result = await fuelLogService.updateFuelLog(id, req.body); res.status(200).json(result); } catch (error: any) { - console.error("Error updating fuel log:", error); - if (error instanceof FuelLogNotFoundError) { - return res.status(404).json({ message: error.message }); - } - res.status(500).json({ message: error.message }); + res.status(error.status.valueOf()).json({ message: error.message }); } }; export const deleteFuelLog = async (req: Request, res: Response) => { - const { id } = req.params; - if (!id) { - return res - .status(400) - .json({ message: "Fuel log ID is required." }); - } try { + const { id } = req.params; + if (!id) { + throw new FuelLogError("Fuel Log id is required.", Status.BAD_REQUEST); + } const result = await fuelLogService.deleteFuelLog(id); res.status(200).json(result); } catch (error: any) { - console.error("Error deleting fuel log:", error); - if (error instanceof FuelLogNotFoundError) { - return res.status(404).json({ message: error.message }); - } - res.status(500).json({ message: error.message }); + res.status(error.status.valueOf()).json({ message: error.message }); } }; diff --git a/app/server/src/controllers/InsuranceController.ts b/app/server/src/controllers/InsuranceController.ts index 8cbaae2c..a55f2790 100644 --- a/app/server/src/controllers/InsuranceController.ts +++ b/app/server/src/controllers/InsuranceController.ts @@ -1,78 +1,59 @@ import { Request, Response } from "express"; import * as insuranceService from "../services/insuranceService.js"; -import { - InsuranceNotFoundError, - InsuranceExistsError, - InsuranceServiceError, -} from "../exceptions/InsuranceError.js"; +import { InsuranceError } from "../exceptions/InsuranceError.js"; +import { Status } from "../exceptions/ServiceError.js"; export const addInsurance = async (req: Request, res: Response) => { - const { vehicleId } = req.params; - const { provider, policyNumber, startDate, endDate, cost } = req.body; + try { + const { vehicleId } = req.params; + const { provider, policyNumber, startDate, endDate, cost } = req.body; - if (!vehicleId) { - return res.status(400).json({ message: "Vehicle ID is required." }); - } - if (!provider || !policyNumber || !startDate || !endDate || !cost) { - return res.status(400).json({ - message: + if (!vehicleId) { + throw new InsuranceError("Vehicle ID is required.", Status.BAD_REQUEST); + } + if (!provider || !policyNumber || !startDate || !endDate || !cost) { + throw new InsuranceError( "Provider, Policy Number, Start Date, End Date, and Cost are required.", - }); - } - - try { + Status.BAD_REQUEST, + ); + } const result = await insuranceService.addInsurance(vehicleId, req.body); res.status(201).json(result); } catch (error: any) { - if (error instanceof InsuranceNotFoundError) { - return res.status(404).json({ message: error.message }); - } - if (error instanceof InsuranceExistsError) { - return res.status(409).json({ message: error.message }); - } - if (error instanceof InsuranceServiceError) { - return res.status(500).json({ message: error.message }); - } - res.status(500).json({ message: error.message }); + res.status(error.status.valueOf()).json({ message: error.message }); } }; export const getInsurances = async (req: Request, res: Response) => { - const { vehicleId } = req.params; - if (!vehicleId) { - return res.status(400).json({ message: "Vehicle ID is required." }); - } try { + const { vehicleId } = req.params; + if (!vehicleId) { + throw new InsuranceError("Vehicle ID is required.", Status.BAD_REQUEST); + } const insurances = await insuranceService.getInsurances(vehicleId); res.status(200).json(insurances); } catch (error: any) { - if (error instanceof InsuranceNotFoundError) { - return res.status(404).json({ message: error.message }); - } - if (error instanceof InsuranceServiceError) { - return res.status(500).json({ message: error.message }); - } - res.status(500).json({ message: error.message }); + res.status(error.status.valueOf()).json({ message: error.message }); } }; export const updateInsurance = async (req: Request, res: Response) => { - const { vehicleId, id } = req.params; - const { provider, policyNumber, startDate, endDate, cost } = req.body; + try { + const { vehicleId, id } = req.params; + const { provider, policyNumber, startDate, endDate, cost } = req.body; - if (!vehicleId || !id) { - return res - .status(400) - .json({ message: "Vehicle ID and Insurance Id is required." }); - } - if (!provider || !policyNumber || !startDate || !endDate || !cost) { - return res.status(400).json({ - message: + if (!vehicleId || !id) { + throw new InsuranceError( + "Vehicle ID and Insurance Id is required.", + Status.BAD_REQUEST, + ); + } + if (!provider || !policyNumber || !startDate || !endDate || !cost) { + throw new InsuranceError( "Provider, Policy Number, Start Date, End Date, and Cost are required.", - }); - } - - try { + Status.BAD_REQUEST, + ); + } const result = await insuranceService.updateInsurance( vehicleId, id, @@ -80,31 +61,19 @@ export const updateInsurance = async (req: Request, res: Response) => { ); res.status(200).json(result); } catch (error: any) { - if (error instanceof InsuranceNotFoundError) { - return res.status(404).json({ message: error.message }); - } - if (error instanceof InsuranceServiceError) { - return res.status(500).json({ message: error.message }); - } - res.status(500).json({ message: error.message }); + res.status(error.status.valueOf()).json({ message: error.message }); } }; export const deleteInsurance = async (req: Request, res: Response) => { - const { vehicleId } = req.params; - if (!vehicleId) { - return res.status(400).json({ message: "Vehicle ID is required." }); - } try { - const result = await insuranceService.deleteInsurance(vehicleId); + const { id } = req.params; + if (!id) { + throw new InsuranceError("Insurance ID is required.", Status.BAD_REQUEST); + } + const result = await insuranceService.deleteInsurance(id); res.status(200).json(result); } catch (error: any) { - if (error instanceof InsuranceNotFoundError) { - return res.status(404).json({ message: error.message }); - } - if (error instanceof InsuranceServiceError) { - return res.status(500).json({ message: error.message }); - } - res.status(500).json({ message: error.message }); + res.status(error.status.valueOf()).json({ message: error.message }); } }; diff --git a/app/server/src/controllers/MaintenanceLogController.ts b/app/server/src/controllers/MaintenanceLogController.ts index d5d50ceb..34e7baa6 100644 --- a/app/server/src/controllers/MaintenanceLogController.ts +++ b/app/server/src/controllers/MaintenanceLogController.ts @@ -1,119 +1,110 @@ import { Request, Response } from "express"; import * as maintenanceLogService from "../services/maintenanceLogService.js"; -import { - MaintenanceLogNotFoundError, - MaintenanceLogServiceError -} from "../exceptions/MaintenanceLogErrors.js"; +import { MaintenanceLogError } from "../exceptions/MaintenanceLogError.js"; +import { Status } from "../exceptions/ServiceError.js"; export const addMaintenanceLog = async (req: Request, res: Response) => { - const { vehicleId } = req.params; - const { date, odometer, service, cost } = req.body; - - if (!vehicleId) { - return res.status(400).json({ message: "Vehicle ID is required." }); - } - if (!date || !odometer || !service || !cost) { - return res - .status(400) - .json({ message: "Date, Odometer, Service, and Cost are required." }); - } - try { + const { vehicleId } = req.params; + const { date, odometer, service, cost } = req.body; + + if (!vehicleId) { + throw new MaintenanceLogError( + "Vehicle ID is required.", + Status.BAD_REQUEST, + ); + } + if (!date || !odometer || !service || !cost) { + throw new MaintenanceLogError( + "Date, Odometer, Service, and Cost are required.", + Status.BAD_REQUEST, + ); + } const result = await maintenanceLogService.addMaintenanceLog( vehicleId, - req.body + req.body, ); res.status(201).json(result); } catch (error: any) { - if (error instanceof MaintenanceLogNotFoundError) { - return res.status(404).json({ message: error.message }); - } - if (error instanceof MaintenanceLogServiceError) { - return res.status(500).json({ message: error.message }); - } - res.status(500).json({ message: error.message }); + res.status(error.status.valueOf).json({ message: error.message }); } }; export const getMaintenanceLogs = async (req: Request, res: Response) => { - const { vehicleId } = req.params; - if (!vehicleId) { - return res.status(400).json({ message: "Vehicle ID is required." }); - } try { - const maintenanceLogs = await maintenanceLogService.getMaintenanceLogs( - vehicleId - ); + const { vehicleId } = req.params; + if (!vehicleId) { + throw new MaintenanceLogError( + "Vehicle ID is required.", + Status.BAD_REQUEST, + ); + } + const maintenanceLogs = + await maintenanceLogService.getMaintenanceLogs(vehicleId); res.status(200).json(maintenanceLogs); } catch (error: any) { - if (error instanceof MaintenanceLogServiceError) { - return res.status(500).json({ message: error.message }); - } - res.status(500).json({ message: error.message }); + res.status(error.status.valueOf).json({ message: error.message }); } }; export const getMaintenanceLogById = async (req: Request, res: Response) => { - const { id } = req.params; - if (!id) { - return res.status(400).json({ message: "Maintenance Log ID is required." }); - } try { - const maintenanceLog = await maintenanceLogService.getMaintenanceLogById(id); + const { id } = req.params; + if (!id) { + throw new MaintenanceLogError( + "Maintenance Log ID is required.", + Status.BAD_REQUEST, + ); + } + + const maintenanceLog = + await maintenanceLogService.getMaintenanceLogById(id); res.status(200).json(maintenanceLog); } catch (error: any) { - if (error instanceof MaintenanceLogNotFoundError) { - return res.status(404).json({ message: error.message }); - } - if (error instanceof MaintenanceLogServiceError) { - return res.status(500).json({ message: error.message }); - } - res.status(500).json({ message: error.message }); + res.status(error.status.valueOf).json({ message: error.message }); } }; export const updateMaintenanceLog = async (req: Request, res: Response) => { - const { id } = req.params; - const { date, odometer, service, cost, notes } = req.body; - - if (!id) { - return res.status(400).json({ message: "Maintenance Log ID is required." }); - } - if (!date || !odometer || !service || !cost) { - return res - .status(400) - .json({ message: "Date, Odometer, Service, and Cost are required." }); - } - try { - const result = await maintenanceLogService.updateMaintenanceLog(id, req.body); - res.status(200).json(result); - } catch (error: any) { - if (error instanceof MaintenanceLogNotFoundError) { - return res.status(404).json({ message: error.message }); + const { id } = req.params; + const { date, odometer, service, cost, notes } = req.body; + + if (!id) { + throw new MaintenanceLogError( + "Maintenance Log ID is required.", + Status.BAD_REQUEST, + ); } - if (error instanceof MaintenanceLogServiceError) { - return res.status(500).json({ message: error.message }); + if (!date || !odometer || !service || !cost) { + throw new MaintenanceLogError( + "Date, Odometer, Service, and Cost are required.", + Status.BAD_REQUEST, + ); } - res.status(500).json({ message: error.message }); + const result = await maintenanceLogService.updateMaintenanceLog( + id, + req.body, + ); + res.status(200).json(result); + } catch (error: any) { + res.status(error.status.valueOf).json({ message: error.message }); } }; export const deleteMaintenanceLog = async (req: Request, res: Response) => { - const { id } = req.params; - if (!id) { - return res.status(400).json({ message: "Maintenance Log ID is required." }); - } try { + const { id } = req.params; + if (!id) { + throw new MaintenanceLogError( + "Maintenance Log ID is required.", + Status.BAD_REQUEST, + ); + } + const result = await maintenanceLogService.deleteMaintenanceLog(id); res.status(200).json(result); } catch (error: any) { - if (error instanceof MaintenanceLogNotFoundError) { - return res.status(404).json({ message: error.message }); - } - if (error instanceof MaintenanceLogServiceError) { - return res.status(500).json({ message: error.message }); - } - res.status(500).json({ message: error.message }); + res.status(error.status.valueOf).json({ message: error.message }); } }; diff --git a/app/server/src/controllers/PUCCController.ts b/app/server/src/controllers/PUCCController.ts index ab049b6d..52aac6f4 100644 --- a/app/server/src/controllers/PUCCController.ts +++ b/app/server/src/controllers/PUCCController.ts @@ -1,63 +1,51 @@ import { Request, Response } from "express"; import * as pollutionCertificateService from "../services/pollutionCertificateService.js"; -import { - PollutionCertificateNotFoundError, - PollutionCertificateExistsError, - PollutionCertificateServiceError, -} from "../exceptions/PollutionCertificateErrors.js"; +import { PollutionCertificateError } from "../exceptions/PollutionCertificateError.js"; +import { Status } from "../exceptions/ServiceError.js"; export const addPollutionCertificate = async (req: Request, res: Response) => { - const { vehicleId } = req.params; - const { certificateNumber, issueDate, expiryDate, testingCenter, notes } = - req.body; + try { + const { vehicleId } = req.params; + const { certificateNumber, issueDate, expiryDate, testingCenter } = + req.body; - if (!vehicleId) { - return res.status(400).json({ message: "Vehicle ID is required." }); - } - if (!certificateNumber || !issueDate || !expiryDate || !testingCenter) { - return res.status(400).json({ - message: + if (!vehicleId) { + throw new PollutionCertificateError( + "Vehicle ID is required.", + Status.BAD_REQUEST, + ); + } + if (!certificateNumber || !issueDate || !expiryDate || !testingCenter) { + throw new PollutionCertificateError( "Certificate Number, Issue Date, Expiry Date, and Testing Center are required.", - }); - } + Status.BAD_REQUEST, + ); + } - try { const result = await pollutionCertificateService.addPollutionCertificate( vehicleId, req.body, ); res.status(201).json(result); } catch (error: any) { - if (error instanceof PollutionCertificateNotFoundError) { - return res.status(404).json({ message: error.message }); - } - if (error instanceof PollutionCertificateExistsError) { - return res.status(409).json({ message: error.message }); - } - if (error instanceof PollutionCertificateServiceError) { - return res.status(500).json({ message: error.message }); - } - res.status(500).json({ message: error.message }); + res.status(error.status.valueOf()).json({ message: error.message }); } }; export const getPollutionCertificates = async (req: Request, res: Response) => { - const { vehicleId } = req.params; - if (!vehicleId) { - return res.status(400).json({ message: "Vehicle ID is required." }); - } try { + const { vehicleId } = req.params; + if (!vehicleId) { + throw new PollutionCertificateError( + "Vehicle ID is required.", + Status.BAD_REQUEST, + ); + } const pollutionCertificates = await pollutionCertificateService.getPollutionCertificates(vehicleId); res.status(200).json(pollutionCertificates); } catch (error: any) { - if (error instanceof PollutionCertificateNotFoundError) { - return res.status(404).json({ message: error.message }); - } - if (error instanceof PollutionCertificateServiceError) { - return res.status(500).json({ message: error.message }); - } - res.status(500).json({ message: error.message }); + res.status(error.status.valueOf()).json({ message: error.message }); } }; @@ -65,23 +53,23 @@ export const updatePollutionCertificate = async ( req: Request, res: Response, ) => { - const { vehicleId, id } = req.params; - const { certificateNumber, issueDate, expiryDate, testingCenter, notes } = - req.body; + try { + const { vehicleId, id } = req.params; + const { certificateNumber, issueDate, expiryDate, testingCenter, notes } = + req.body; - if (!vehicleId || !id) { - return res - .status(400) - .json({ message: "Vehicle ID and certificate ID are required." }); - } - if (!certificateNumber || !issueDate || !expiryDate || !testingCenter) { - return res.status(400).json({ - message: + if (!vehicleId || !id) { + throw new PollutionCertificateError( + "Vehicle ID and certificate ID are required.", + Status.BAD_REQUEST, + ); + } + if (!certificateNumber || !issueDate || !expiryDate || !testingCenter) { + throw new PollutionCertificateError( "Certificate Number, Issue Date, Expiry Date, and Testing Center are required.", - }); - } - - try { + Status.BAD_REQUEST, + ); + } const result = await pollutionCertificateService.updatePollutionCertificate( vehicleId, id, @@ -89,13 +77,7 @@ export const updatePollutionCertificate = async ( ); res.status(200).json(result); } catch (error: any) { - if (error instanceof PollutionCertificateNotFoundError) { - return res.status(404).json({ message: error.message }); - } - if (error instanceof PollutionCertificateServiceError) { - return res.status(500).json({ message: error.message }); - } - res.status(500).json({ message: error.message }); + res.status(error.status.valueOf()).json({ message: error.message }); } }; @@ -103,21 +85,18 @@ export const deletePollutionCertificate = async ( req: Request, res: Response, ) => { - const { vehicleId } = req.params; - if (!vehicleId) { - return res.status(400).json({ message: "Vehicle ID is required." }); - } try { + const { vehicleId } = req.params; + if (!vehicleId) { + throw new PollutionCertificateError( + "Vehicle ID is required.", + Status.BAD_REQUEST, + ); + } const result = await pollutionCertificateService.deletePollutionCertificate(vehicleId); res.status(200).json(result); } catch (error: any) { - if (error instanceof PollutionCertificateNotFoundError) { - return res.status(404).json({ message: error.message }); - } - if (error instanceof PollutionCertificateServiceError) { - return res.status(500).json({ message: error.message }); - } - res.status(500).json({ message: error.message }); + res.status(error.status.valueOf()).json({ message: error.message }); } }; diff --git a/app/server/src/controllers/PinController.ts b/app/server/src/controllers/PinController.ts index 37f51cad..f824448f 100644 --- a/app/server/src/controllers/PinController.ts +++ b/app/server/src/controllers/PinController.ts @@ -1,34 +1,31 @@ - import { Request, Response } from "express"; import * as pinService from "../services/pinService.js"; +import { AuthError } from "../exceptions/AuthError.js"; +import { Status } from "../exceptions/ServiceError.js"; export const setPin = async (req: Request, res: Response) => { - const { pin } = req.body; - - if (!pin || pin.length !== 6 || !/^\d+$/.test(pin)) { - return res.status(400).json({ message: "PIN must be a 6-digit number." }); - } - try { + const { pin } = req.body; + if (!pin || pin.length !== 6 || !/^\d+$/.test(pin)) { + throw new AuthError("PIN must be a 6-digit number.", Status.BAD_REQUEST); + } const result = await pinService.setPin(pin); - res.status(result.status).json({ message: result.message }); + res.status(200).json({ message: result.message }); } catch (error: any) { - res.status(500).json({ message: error.message }); + res.status(error.status.valueOf()).json({ message: error.message }); } }; export const verifyPin = async (req: Request, res: Response) => { - const { pin } = req.body; - - if (!pin) { - return res.status(400).json({ message: "PIN is required." }); - } - try { + const { pin } = req.body; + if (!pin) { + throw new AuthError("PIN is required.", Status.BAD_REQUEST); + } const result = await pinService.verifyPin(pin); - res.status(result.status).json({ message: result.message }); + res.status(200).json({ message: result.message }); } catch (error: any) { - res.status(500).json({ message: error.message }); + res.status(error.status.valueOf()).json({ message: error.message }); } }; @@ -37,6 +34,6 @@ export const getPinStatus = async (req: Request, res: Response) => { const result = await pinService.getPinStatus(); res.status(200).json(result); } catch (error: any) { - res.status(500).json({ message: error.message }); + res.status(error.status.valueOf()).json({ message: error.message }); } }; diff --git a/app/server/src/controllers/VehicleController.ts b/app/server/src/controllers/VehicleController.ts index 06a4b08b..db7e9fa8 100644 --- a/app/server/src/controllers/VehicleController.ts +++ b/app/server/src/controllers/VehicleController.ts @@ -1,21 +1,21 @@ import { Request, Response } from "express"; import * as vehicleService from "../services/vehicleService.js"; -import { VehicleNotFoundError } from "../exceptions/VehicleErrors.js"; +import { VehicleError } from "../exceptions/VehicleError.js"; +import { Status } from "../exceptions/ServiceError.js"; export const addVehicle = async (req: Request, res: Response) => { - const { make, model, year, licensePlate } = req.body; - - if (!make || !model || !year || !licensePlate) { - return res - .status(400) - .json({ message: "Make, Model, Year, and License Plate are required." }); - } - try { + const { make, model, year, licensePlate } = req.body; + if (!make || !model || !year || !licensePlate) { + new VehicleError( + "Make, Model, Year, and License Plate are required.", + Status.BAD_REQUEST, + ); + } const result = await vehicleService.addVehicle(req.body); res.status(201).json(result); } catch (error: any) { - res.status(500).json({ message: error.message }); + res.status(error.status.valueOf()).json({ message: error.message }); } }; @@ -24,61 +24,54 @@ export const getAllVehicles = async (req: Request, res: Response) => { const vehicles = await vehicleService.getAllVehicles(); res.status(200).json(vehicles); } catch (error: any) { - res.status(500).json({ message: error.message }); + res.status(error.status.valueOf()).json({ message: error.message }); } }; export const getVehicleById = async (req: Request, res: Response) => { - const { id } = req.params; - if (!id) { - return res.status(400).json({ message: "Vehicle ID is required." }); - } try { + const { id } = req.params; + if (!id) { + throw new VehicleError("Vehicle ID is required.", Status.BAD_REQUEST); + } const vehicle = await vehicleService.getVehicleById(id); res.status(200).json(vehicle); } catch (error: any) { - if (error instanceof VehicleNotFoundError) { - return res.status(404).json({ message: error.message }); - } - res.status(500).json({ message: error.message }); + res.status(error.status.valueOf()).json({ message: error.message }); } }; export const updateVehicle = async (req: Request, res: Response) => { - const { id } = req.params; - const { make, model, year, licensePlate } = req.body; - - if (!make || !model || !year || !licensePlate) { - return res - .status(400) - .json({ message: "Make, Model, Year, and License Plate are required." }); - } - if (!id) { - return res.status(400).json({ message: "Vehicle ID is required." }); - } try { + const { id } = req.params; + const { make, model, year, licensePlate } = req.body; + + if (!make || !model || !year || !licensePlate) { + throw new VehicleError( + "Make, Model, Year, and License Plate are required.", + Status.BAD_REQUEST, + ); + } + if (!id) { + throw new VehicleError("Vehicle ID is required.", Status.BAD_REQUEST); + } const result = await vehicleService.updateVehicle(id, req.body); res.status(200).json(result); } catch (error: any) { - if (error instanceof VehicleNotFoundError) { - return res.status(404).json({ message: error.message }); - } - res.status(500).json({ message: error.message }); + res.status(error.status.valueOf()).json({ message: error.message }); } }; export const deleteVehicle = async (req: Request, res: Response) => { - const { id } = req.params; - if (!id) { - return res.status(400).json({ message: "Vehicle ID is required." }); - } try { + const { id } = req.params; + if (!id) { + throw new VehicleError("Vehicle ID is required.", Status.BAD_REQUEST); + } + const result = await vehicleService.deleteVehicle(id); res.status(200).json(result); } catch (error: any) { - if (error instanceof VehicleNotFoundError) { - return res.status(404).json({ message: error.message }); - } - res.status(500).json({ message: error.message }); + res.status(error.status.valueOf()).json({ message: error.message }); } }; diff --git a/app/server/src/db/index.ts b/app/server/src/db/index.ts index 0df19e2f..b50562c9 100644 --- a/app/server/src/db/index.ts +++ b/app/server/src/db/index.ts @@ -164,15 +164,17 @@ async function generateMaintenenceLogs(vehicle1: Vehicle, vehicle2: Vehicle) { vehicleId: vehicle1.id, date: "2023-06-01", odometer: 18000, - service: "Oil Change", + serviceCenter: "Test Center", cost: 50, + notes: "Oil Change", }, { vehicleId: vehicle2.id, date: "2023-07-01", odometer: 28000, - service: "Tire Rotation", + serviceCenter: "Test Center", cost: 30, + notes: "Tire Rotation", }, ]); diff --git a/app/server/src/exceptions/AuthError.ts b/app/server/src/exceptions/AuthError.ts new file mode 100644 index 00000000..38317a16 --- /dev/null +++ b/app/server/src/exceptions/AuthError.ts @@ -0,0 +1,7 @@ +import { ServiceError, Status } from "./ServiceError.js"; + +export class AuthError extends ServiceError { + constructor(message: string, status = Status.INTERNAL_SERVER_ERROR) { + super(AuthError.name, message, status); + } +} diff --git a/app/server/src/exceptions/ConfigError.ts b/app/server/src/exceptions/ConfigError.ts new file mode 100644 index 00000000..ab55b4e1 --- /dev/null +++ b/app/server/src/exceptions/ConfigError.ts @@ -0,0 +1,7 @@ +import { ServiceError, Status } from "./ServiceError.js"; + +export class ConfigError extends ServiceError { + constructor(message: string, status = Status.INTERNAL_SERVER_ERROR) { + super(ConfigError.name, message, status); + } +} diff --git a/app/server/src/exceptions/FuelLogError.ts b/app/server/src/exceptions/FuelLogError.ts index ff2d2465..643879ed 100644 --- a/app/server/src/exceptions/FuelLogError.ts +++ b/app/server/src/exceptions/FuelLogError.ts @@ -1,13 +1,7 @@ -export class FuelLogError extends Error { - constructor(message: string) { - super(message); - this.name = "FuelLogError"; - } -} +import { ServiceError, Status } from "./ServiceError.js"; -export class FuelLogNotFoundError extends Error { - constructor() { - super("Fuel log not found."); - this.name = "FuelLogNotFoundError"; - } -} \ No newline at end of file +export class FuelLogError extends ServiceError { + constructor(message: string, status = Status.INTERNAL_SERVER_ERROR) { + super(FuelLogError.name, message, status); + } +} diff --git a/app/server/src/exceptions/InsuranceError.ts b/app/server/src/exceptions/InsuranceError.ts index 8bd59683..46ff0e91 100644 --- a/app/server/src/exceptions/InsuranceError.ts +++ b/app/server/src/exceptions/InsuranceError.ts @@ -1,20 +1,7 @@ -export class InsuranceNotFoundError extends Error { - constructor(message = "Insurance details not found.") { - super(message); - this.name = "InsuranceNotFoundError"; - } -} +import { ServiceError, Status } from "./ServiceError.js"; -export class InsuranceExistsError extends Error { - constructor(message = "Insurance details already exist for this vehicle.") { - super(message); - this.name = "InsuranceExistsError"; +export class InsuranceError extends ServiceError { + constructor(message: string, status = Status.INTERNAL_SERVER_ERROR) { + super(InsuranceError.name, message, status); } } - -export class InsuranceServiceError extends Error { - constructor(message = "Insurance service error.") { - super(message); - this.name = "InsuranceServiceError"; - } -} \ No newline at end of file diff --git a/app/server/src/exceptions/MaintenanceLogError.ts b/app/server/src/exceptions/MaintenanceLogError.ts new file mode 100644 index 00000000..f7801442 --- /dev/null +++ b/app/server/src/exceptions/MaintenanceLogError.ts @@ -0,0 +1,7 @@ +import { ServiceError, Status } from "./ServiceError.js"; + +export class MaintenanceLogError extends ServiceError { + constructor(message: string, status = Status.INTERNAL_SERVER_ERROR) { + super(MaintenanceLogError.name, message, status); + } +} diff --git a/app/server/src/exceptions/MaintenanceLogErrors.ts b/app/server/src/exceptions/MaintenanceLogErrors.ts deleted file mode 100644 index f578db23..00000000 --- a/app/server/src/exceptions/MaintenanceLogErrors.ts +++ /dev/null @@ -1,13 +0,0 @@ -export class MaintenanceLogNotFoundError extends Error { - constructor(message = "Maintenance log not found.") { - super(message); - this.name = "MaintenanceLogNotFoundError"; - } -} - -export class MaintenanceLogServiceError extends Error { - constructor(message = "Maintenance log service error.") { - super(message); - this.name = "MaintenanceLogServiceError"; - } -} diff --git a/app/server/src/exceptions/PinErrors.ts b/app/server/src/exceptions/PinErrors.ts deleted file mode 100644 index b8d37e2c..00000000 --- a/app/server/src/exceptions/PinErrors.ts +++ /dev/null @@ -1,6 +0,0 @@ -export class PinError extends Error { - constructor(message: string) { - super(message); - this.name = "PinError"; - } -} diff --git a/app/server/src/exceptions/PollutionCertificateError.ts b/app/server/src/exceptions/PollutionCertificateError.ts new file mode 100644 index 00000000..3646ec5d --- /dev/null +++ b/app/server/src/exceptions/PollutionCertificateError.ts @@ -0,0 +1,7 @@ +import { ServiceError, Status } from "./ServiceError.js"; + +export class PollutionCertificateError extends ServiceError { + constructor(message: string, status = Status.INTERNAL_SERVER_ERROR) { + super(PollutionCertificateError.name, message, status); + } +} diff --git a/app/server/src/exceptions/PollutionCertificateErrors.ts b/app/server/src/exceptions/PollutionCertificateErrors.ts deleted file mode 100644 index 246ba2eb..00000000 --- a/app/server/src/exceptions/PollutionCertificateErrors.ts +++ /dev/null @@ -1,20 +0,0 @@ -export class PollutionCertificateNotFoundError extends Error { - constructor(message = "Pollution certificate not found.") { - super(message); - this.name = "PollutionCertificateNotFoundError"; - } -} - -export class PollutionCertificateExistsError extends Error { - constructor(message = "Pollution certificate already exists for this vehicle or certificate number is not unique.") { - super(message); - this.name = "PollutionCertificateExistsError"; - } -} - -export class PollutionCertificateServiceError extends Error { - constructor(message = "Pollution certificate service error.") { - super(message); - this.name = "PollutionCertificateServiceError"; - } -} diff --git a/app/server/src/exceptions/ServiceError.ts b/app/server/src/exceptions/ServiceError.ts new file mode 100644 index 00000000..bc6d68fa --- /dev/null +++ b/app/server/src/exceptions/ServiceError.ts @@ -0,0 +1,25 @@ +import { UniqueConstraintError, ValidationError } from "sequelize"; + +export enum Status { + BAD_REQUEST = 400, + UNAUTHORIZED = 403, + NOT_FOUND = 404, + CONFLICT = 409, + INTERNAL_SERVER_ERROR = 500, +} + +export class ServiceError extends Error { + status: Status; + constructor(name: string, message: string, status: Status) { + super(message); + this.name = name; + this.status = status; + } +} + +export const statusFromError = (error: any) => { + if (error instanceof ServiceError) return error.status; + if (error instanceof ValidationError) return Status.BAD_REQUEST; + if (error instanceof UniqueConstraintError) return Status.CONFLICT; + return Status.INTERNAL_SERVER_ERROR; +}; diff --git a/app/server/src/exceptions/VehicleError.ts b/app/server/src/exceptions/VehicleError.ts new file mode 100644 index 00000000..4936de80 --- /dev/null +++ b/app/server/src/exceptions/VehicleError.ts @@ -0,0 +1,7 @@ +import { ServiceError, Status } from "./ServiceError.js"; + +export class VehicleError extends ServiceError { + constructor(message: string, status = Status.INTERNAL_SERVER_ERROR) { + super(VehicleError.name, message, status); + } +} diff --git a/app/server/src/exceptions/VehicleErrors.ts b/app/server/src/exceptions/VehicleErrors.ts deleted file mode 100644 index ffa63548..00000000 --- a/app/server/src/exceptions/VehicleErrors.ts +++ /dev/null @@ -1,20 +0,0 @@ -export class VehicleNotFoundError extends Error { - constructor(message = "Vehicle not found.") { - super(message); - this.name = "VehicleNotFoundError"; - } -} - -export class VehicleExistsError extends Error { - constructor(message = "Vehicle with this license plate or VIN already exists.") { - super(message); - this.name = "VehicleExistsError"; - } -} - -export class VehicleServiceError extends Error { - constructor(message = "Vehicle service error.") { - super(message); - this.name = "VehicleServiceError"; - } -} \ No newline at end of file diff --git a/app/server/src/models/FuelLog.ts b/app/server/src/models/FuelLog.ts index 1e7b7540..4c0c1db5 100644 --- a/app/server/src/models/FuelLog.ts +++ b/app/server/src/models/FuelLog.ts @@ -1,8 +1,8 @@ import { DataTypes, Model, Optional } from "sequelize"; import { db } from "../db/index.js"; import Vehicle from "./Vehicle.js"; -import { VehicleServiceError } from "../exceptions/VehicleErrors.js"; import { FuelLogError } from "../exceptions/FuelLogError.js"; +import { Status } from "../exceptions/ServiceError.js"; interface FuelLogAttributes { id: string; @@ -72,6 +72,7 @@ FuelLog.init( if (value <= maxOdometer || value <= vehicleOdometer) { throw new FuelLogError( "Odometer Reading must be greater than current vehicle odometer.", + Status.BAD_REQUEST, ); } }, diff --git a/app/server/src/models/Insurance.ts b/app/server/src/models/Insurance.ts index 46c80d2f..a414e3e8 100644 --- a/app/server/src/models/Insurance.ts +++ b/app/server/src/models/Insurance.ts @@ -1,7 +1,8 @@ import { DataTypes, Model, Optional } from "sequelize"; import { db } from "../db/index.js"; import Vehicle from "./Vehicle.js"; -import { InsuranceServiceError } from "../exceptions/InsuranceError.js"; +import { InsuranceError } from "../exceptions/InsuranceError.js"; +import { Status } from "../exceptions/ServiceError.js"; interface InsuranceAttributes { id: string; @@ -60,10 +61,11 @@ Insurance.init( policyNumber: { type: DataTypes.STRING, allowNull: false, + unique: true, validate: { len: { args: [3, 50], - msg: "Provider must be between length 3 to 50.", + msg: "Policy Number must be between length 3 to 50.", }, is: { args: "^[0-9A-Za-z\s\-]*$", @@ -75,14 +77,20 @@ Insurance.init( type: DataTypes.DATEONLY, allowNull: false, validate: { - isDate: true, + isDate: { + args: true, + msg: "Start date format is not correct.", + }, }, }, endDate: { type: DataTypes.DATEONLY, allowNull: false, validate: { - isDate: true, + isDate: { + args: true, + msg: "End date format is not correct.", + }, }, }, cost: { @@ -124,14 +132,16 @@ Insurance.init( const maxEndDate = new Date(previousInsEndDate as string); if (sDate >= eDate) { - throw new InsuranceServiceError( + throw new InsuranceError( "Start date must always be before end date.", + Status.BAD_REQUEST, ); } if (sDate < maxEndDate) { - throw new InsuranceServiceError( + throw new InsuranceError( "Start date must always be after previous insurance end date.", + Status.BAD_REQUEST, ); } }, diff --git a/app/server/src/models/MaintenanceLog.ts b/app/server/src/models/MaintenanceLog.ts index 28464e5b..d314861e 100644 --- a/app/server/src/models/MaintenanceLog.ts +++ b/app/server/src/models/MaintenanceLog.ts @@ -7,22 +7,23 @@ interface MaintenanceLogAttributes { vehicleId: string; date: string; odometer: number; - service: string; + serviceCenter: string; cost: number; notes?: string; } interface MaintenanceLogCreationAttributes - extends Optional { } + extends Optional {} class MaintenanceLog extends Model - implements MaintenanceLogAttributes { + implements MaintenanceLogAttributes +{ declare public id: string; declare public vehicleId: string; declare public date: string; declare public odometer: number; - declare public service: string; + declare public serviceCenter: string; declare public cost: number; declare public notes: string; } @@ -47,37 +48,50 @@ MaintenanceLog.init( type: DataTypes.DATEONLY, allowNull: false, validate: { - isDate: true, + isDate: { + args: true, + msg: "Date format is not correct", + }, }, }, odometer: { type: DataTypes.INTEGER, allowNull: false, validate: { - isInt: true, - min: 0, + min: { + args: [0], + msg: "Odometer reading must be greater than 0.", + }, }, }, - service: { + serviceCenter: { type: DataTypes.STRING, allowNull: false, validate: { - notEmpty: true, + len: { + args: [3, 50], + msg: "Service Center must be between length 3 to 50.", + }, }, }, cost: { type: DataTypes.FLOAT, allowNull: false, validate: { - isFloat: true, - min: 0, + min: { + args: [0], + msg: "Cost must be greater than 0.", + }, }, }, notes: { type: DataTypes.STRING, allowNull: true, validate: { - notEmpty: true, + len: { + args: [0, 500], + msg: "Notes must be lesser than 500 length.", + }, }, }, }, diff --git a/app/server/src/models/PUCC.ts b/app/server/src/models/PUCC.ts index 068c5120..9a684dff 100644 --- a/app/server/src/models/PUCC.ts +++ b/app/server/src/models/PUCC.ts @@ -1,6 +1,8 @@ import { DataTypes, Model, Optional } from "sequelize"; import { db } from "../db/index.js"; import Vehicle from "./Vehicle.js"; +import { PollutionCertificateError } from "../exceptions/PollutionCertificateError.js"; +import { Status } from "../exceptions/ServiceError.js"; interface PollutionCertificateAttributes { id: string; @@ -13,14 +15,15 @@ interface PollutionCertificateAttributes { } interface PollutionCertificateCreationAttributes - extends Optional { } + extends Optional {} class PollutionCertificate extends Model< PollutionCertificateAttributes, PollutionCertificateCreationAttributes > - implements PollutionCertificateAttributes { + implements PollutionCertificateAttributes +{ declare public id: string; declare public vehicleId: string; declare public certificateNumber: string; @@ -41,7 +44,7 @@ PollutionCertificate.init( vehicleId: { type: DataTypes.UUIDV4, references: { - model: Vehicle, // Use string reference + model: Vehicle, key: "id", }, allowNull: false, @@ -51,40 +54,92 @@ PollutionCertificate.init( allowNull: false, unique: true, validate: { - notEmpty: true, + len: { + args: [3, 50], + msg: "Certificate Number must be between length 3 to 50.", + }, + is: { + args: "^[0-9A-Za-z\s\-]*$", + msg: "Only number and characters with space and hyphen are allowed in certificate number.", + }, }, }, issueDate: { type: DataTypes.DATEONLY, allowNull: false, validate: { - isDate: true, + isDate: { + args: true, + msg: "Issue date format is not correct.", + }, }, }, expiryDate: { type: DataTypes.DATEONLY, allowNull: false, validate: { - isDate: true, + isDate: { + args: true, + msg: "Expiry date format is not correct.", + }, }, }, testingCenter: { type: DataTypes.STRING, allowNull: false, validate: { - notEmpty: true, + len: { + args: [3, 50], + msg: "Testing Center must be between length 3 to 50.", + }, }, }, notes: { type: DataTypes.STRING, allowNull: true, + validate: { + len: { + args: [0, 500], + msg: "Notes must be lesser than 500 length.", + }, + }, }, }, { tableName: "pollution_certificates", timestamps: true, underscored: true, - sequelize: db + sequelize: db, + validate: { + async validateDates() { + const previousInsEndDate = await PollutionCertificate.max( + "expiryDate", + { + where: { + vehicleId: this.vehicleId as string, + }, + }, + ); + + const sDate = new Date(this.issueDate as string); + const eDate = new Date(this.expiryDate as string); + const maxEndDate = new Date(previousInsEndDate as string); + + if (sDate >= eDate) { + throw new PollutionCertificateError( + "Issue date must always be before Expiry date.", + Status.BAD_REQUEST, + ); + } + + if (sDate < maxEndDate) { + throw new PollutionCertificateError( + "Issue date must always be after previous PUCC Expiry date.", + Status.BAD_REQUEST, + ); + } + }, + }, }, ); diff --git a/app/server/src/models/Vehicle.ts b/app/server/src/models/Vehicle.ts index 3e7f674f..aa2486ca 100644 --- a/app/server/src/models/Vehicle.ts +++ b/app/server/src/models/Vehicle.ts @@ -1,10 +1,8 @@ import { DataTypes, Model, Optional } from "sequelize"; import { db } from "../db/index.js"; -import { - VehicleExistsError, - VehicleServiceError, -} from "../exceptions/VehicleErrors.js"; import FuelLog from "./FuelLog.js"; +import { VehicleError } from "../exceptions/VehicleError.js"; +import { Status } from "../exceptions/ServiceError.js"; interface VehicleAttributes { id: string; @@ -108,7 +106,10 @@ Vehicle.init( }, }); if (existingVehicles.length > 0) { - throw new VehicleExistsError(); + throw new VehicleError( + "VIN NUmber already exists.", + Status.CONFLICT, + ); } }, }, @@ -136,8 +137,9 @@ Vehicle.init( }); if (minOdometer && value > minOdometer) { - throw new VehicleServiceError( + throw new VehicleError( "Initial Odometer Reading must be lesser than first fuel log odometer.", + Status.BAD_REQUEST, ); } }, diff --git a/app/server/src/services/configService.ts b/app/server/src/services/configService.ts index b86774d1..1f1ddea3 100644 --- a/app/server/src/services/configService.ts +++ b/app/server/src/services/configService.ts @@ -1,26 +1,55 @@ +import { ConfigError } from "../exceptions/ConfigError.js"; +import { Status, statusFromError } from "../exceptions/ServiceError.js"; import Config from "../models/Config.js"; export const getAppConfig = async () => { - let config = await Config.findAll({ - attributes: ["key", "value", "description"], - }); - if (!config) { - config = []; + try { + let config = await Config.findAll({ + attributes: ["key", "value", "description"], + }); + if (!config) { + config = []; + } + return config; + } catch (error: any) { + console.error("Get Config : ", error); + throw new ConfigError(error.message, statusFromError(error)); } - return config; }; export const getAppConfigByKey = async (key: string) => { - const config = await Config.findOne({ where: { key } }); - return config; + try { + const config = await Config.findOne({ where: { key } }); + if (!config) { + throw new ConfigError( + `No config found for key : ${key}`, + Status.NOT_FOUND, + ); + } + return config; + } catch (error: any) { + console.error("Get Config By Key : ", error); + throw new ConfigError(error.messge, statusFromError(error)); + } }; export const updateAppConfig = async (key: string, value: string) => { - const config = await getAppConfigByKey(key); - if (!config) { - return await Config.create({ key, value }); + try { + if (!key || value === undefined) { + throw new ConfigError( + "Key and value are required for each configuration", + Status.BAD_REQUEST, + ); + } + const config = await getAppConfigByKey(key); + if (!config) { + return await Config.create({ key, value }); + } + config.value = value; + await config.save(); + return config; + } catch (error: any) { + console.error("Update Config : ", error); + throw new ConfigError(error.message, statusFromError(error)); } - config.value = value; - await config.save(); - return config; }; diff --git a/app/server/src/services/fuelLogService.ts b/app/server/src/services/fuelLogService.ts index 77a08b93..e2ca2558 100644 --- a/app/server/src/services/fuelLogService.ts +++ b/app/server/src/services/fuelLogService.ts @@ -1,25 +1,25 @@ -import { FuelLogError, FuelLogNotFoundError } from "../exceptions/FuelLogError.js"; -import { VehicleNotFoundError } from "../exceptions/VehicleErrors.js"; -import {Vehicle, FuelLog} from "../models/index.js"; +import { FuelLogError } from "../exceptions/FuelLogError.js"; +import { Status, statusFromError } from "../exceptions/ServiceError.js"; +import { VehicleError } from "../exceptions/VehicleError.js"; +import { Vehicle, FuelLog } from "../models/index.js"; export const addFuelLog = async (vehicleId: string, fuelLogData: any) => { try { const vehicle = await Vehicle.findByPk(vehicleId); if (!vehicle) { - throw new VehicleNotFoundError("Vehicle not found."); + throw new VehicleError( + `No vehicle found for id : ${vehicleId}`, + Status.NOT_FOUND, + ); } - const fuelLog = await FuelLog.create({ ...fuelLogData, vehicleId: vehicleId, }); return { id: fuelLog.id, message: "Fuel log added successfully." }; } catch (error: any) { - console.error("Error adding fuel log: ", error); - if (error instanceof VehicleNotFoundError) { - throw error; - } - throw new FuelLogError("Error adding fuel log."); + console.error("Add Fuel Log : ", error); + throw new FuelLogError(error.message, statusFromError(error)); } }; @@ -41,14 +41,14 @@ export const getFuelLogs = async (vehicleId: string) => { return { ...log.toJSON(), mileage: null }; } const distance = log.odometer - prevLog.odometer; - const mileage = distance / log.fuelAmount; // km/L or miles/gallon + const mileage = distance / log.fuelAmount; return { ...log.toJSON(), mileage: parseFloat(mileage.toFixed(2)) }; } return { ...log.toJSON(), mileage: null }; }); } catch (error: any) { - console.error("Error fetching fuel logs: ", error); - throw new FuelLogError("Error fetching fuel logs."); + console.error("Get Fuel Log : ", error); + throw new FuelLogError(error.message, statusFromError(error)); } }; @@ -56,15 +56,15 @@ export const getFuelLogById = async (id: string) => { try { const fuelLog = await FuelLog.findByPk(id); if (!fuelLog) { - throw new FuelLogNotFoundError(); + throw new FuelLogError( + `No Fuel Logs found for id : ${id}`, + Status.NOT_FOUND, + ); } return fuelLog; } catch (error: any) { - console.error("Error fetching fuel log: ", error); - if (error instanceof FuelLogNotFoundError) { - throw error; - } - throw new FuelLogError("Error fetching fuel log."); + console.error("Get Fuel Log By Key: ", error); + throw new FuelLogError(error.message, statusFromError(error)); } }; @@ -72,17 +72,17 @@ export const updateFuelLog = async (id: string, fuelLogData: any) => { try { const fuelLog = await FuelLog.findByPk(id); if (!fuelLog) { - throw new FuelLogNotFoundError(); + throw new FuelLogError( + `No Fuel Logs found for id : ${id}`, + Status.NOT_FOUND, + ); } await fuelLog.update(fuelLogData); return { message: "Fuel log updated successfully." }; } catch (error: any) { - console.error("Error updating fuel log: ", error); - if (error instanceof FuelLogNotFoundError) { - throw error; - } - throw new Error("Error updating fuel log."); + console.error("Update Fuel Log: ", error); + throw new FuelLogError(error.message, statusFromError(error)); } }; @@ -92,14 +92,14 @@ export const deleteFuelLog = async (id: string) => { where: { id: id }, }); if (result === 0) { - throw new FuelLogNotFoundError(); + throw new FuelLogError( + `No Fuel Logs found for id : ${id}`, + Status.NOT_FOUND, + ); } return { message: "Fuel log deleted successfully." }; } catch (error: any) { - console.error("Error deleting fuel log: ", error); - if (error instanceof FuelLogNotFoundError) { - throw error; - } - throw new Error("Error deleting fuel log."); + console.error("Delete Fuel Log: ", error); + throw new FuelLogError(error.message, statusFromError(error)); } }; diff --git a/app/server/src/services/insuranceService.ts b/app/server/src/services/insuranceService.ts index 6aaee769..ace2e4b1 100644 --- a/app/server/src/services/insuranceService.ts +++ b/app/server/src/services/insuranceService.ts @@ -1,8 +1,5 @@ -import { - InsuranceNotFoundError, - InsuranceExistsError, - InsuranceServiceError, -} from "../exceptions/InsuranceError.js"; +import { InsuranceError } from "../exceptions/InsuranceError.js"; +import { Status, statusFromError } from "../exceptions/ServiceError.js"; import { Insurance, Vehicle } from "../models/index.js"; import { UniqueConstraintError } from "sequelize"; @@ -10,9 +7,11 @@ export const addInsurance = async (vehicleId: string, insuranceData: any) => { try { const vehicle = await Vehicle.findByPk(vehicleId); if (!vehicle) { - throw new InsuranceNotFoundError("Vehicle not found."); + throw new InsuranceError( + `No Vehicle found for id : ${vehicleId}`, + Status.NOT_FOUND, + ); } - const insurance = await Insurance.create({ ...insuranceData, vehicleId: vehicleId, @@ -21,15 +20,9 @@ export const addInsurance = async (vehicleId: string, insuranceData: any) => { id: insurance.id, message: "Insurance details added successfully.", }; - } catch (error: unknown) { - console.error("Error adding fuel log: ", error); - if (error instanceof UniqueConstraintError) { - throw new InsuranceExistsError(); - } - if (error instanceof InsuranceNotFoundError) { - throw error; - } - throw new InsuranceServiceError("Error adding insurance details."); + } catch (error: any) { + console.error("Add Insurance: ", error); + throw new InsuranceError(error.message, statusFromError(error)); } }; @@ -39,15 +32,15 @@ export const getInsurances = async (vehicleId: string) => { where: { vehicleId: vehicleId }, }); if (!insurance) { - throw new InsuranceNotFoundError(); + throw new InsuranceError( + `No Insurances found for vehicle id : ${vehicleId}`, + Status.NOT_FOUND, + ); } return insurance; - } catch (error: unknown) { - console.error("Error getting insurance: ", error); - if (error instanceof InsuranceNotFoundError) { - throw error; - } - throw new InsuranceServiceError("Error fetching insurance details."); + } catch (error: any) { + console.error("Get Insurances: ", error); + throw new InsuranceError(error.message, statusFromError(error)); } }; @@ -61,32 +54,32 @@ export const updateInsurance = async ( where: { vehicleId: vehicleId, id }, }); if (!insurance) { - throw new InsuranceNotFoundError(); + throw new InsuranceError( + `No Insurances found for id: ${id}`, + Status.NOT_FOUND, + ); } - await insurance.update(insuranceData); return { message: "Insurance details updated successfully." }; - } catch (error: unknown) { - if (error instanceof InsuranceNotFoundError) { - throw error; - } - throw new InsuranceServiceError("Error updating insurance details."); + } catch (error: any) { + console.error("Update Insurance: ", error); + throw new InsuranceError(error.message, statusFromError(error)); } }; -export const deleteInsurance = async (vehicleId: string) => { +export const deleteInsurance = async (id: string) => { try { const result = await Insurance.destroy({ - where: { vehicleId: vehicleId }, + where: { id }, }); if (result === 0) { - throw new InsuranceNotFoundError(); + throw new InsuranceError( + `No Insurances found for id: ${id}`, + Status.NOT_FOUND, + ); } return { message: "Insurance details deleted successfully." }; - } catch (error: unknown) { - if (error instanceof InsuranceNotFoundError) { - throw error; - } - throw new InsuranceServiceError("Error deleting insurance details."); + } catch (error: any) { + throw new InsuranceError(error.message, statusFromError(error)); } }; diff --git a/app/server/src/services/maintenanceLogService.ts b/app/server/src/services/maintenanceLogService.ts index 8c5a00f1..3bb50142 100644 --- a/app/server/src/services/maintenanceLogService.ts +++ b/app/server/src/services/maintenanceLogService.ts @@ -1,17 +1,18 @@ import { MaintenanceLog, Vehicle } from "../models/index.js"; -import { - MaintenanceLogNotFoundError, - MaintenanceLogServiceError -} from "../exceptions/MaintenanceLogErrors.js"; +import { MaintenanceLogError } from "../exceptions/MaintenanceLogError.js"; +import { Status, statusFromError } from "../exceptions/ServiceError.js"; export const addMaintenanceLog = async ( vehicleId: string, - maintenanceLogData: any + maintenanceLogData: any, ) => { try { const vehicle = await Vehicle.findByPk(vehicleId); if (!vehicle) { - throw new MaintenanceLogNotFoundError("Vehicle not found."); + throw new MaintenanceLogError( + `No vehicle found for id ${vehicleId}`, + Status.NOT_FOUND, + ); } const maintenanceLog = await MaintenanceLog.create({ @@ -22,12 +23,9 @@ export const addMaintenanceLog = async ( id: maintenanceLog.id, message: "Maintenance log added successfully.", }; - } catch (error: unknown) { - console.error("Error adding maintenance log: ", error); - if (error instanceof MaintenanceLogNotFoundError) { - throw error; - } - throw new MaintenanceLogServiceError("Error adding maintenance log."); + } catch (error: any) { + console.error("Add Maintenance log: ", error); + throw new MaintenanceLogError(error.message, statusFromError(error)); } }; @@ -41,9 +39,9 @@ export const getMaintenanceLogs = async (vehicleId: string) => { ], }); return maintenanceLogs; - } catch (error: unknown) { - console.error("Error fetching maintenance logs: ", error); - throw new MaintenanceLogServiceError("Error fetching maintenance logs."); + } catch (error: any) { + console.error("Get Maintenance logs: ", error); + throw new MaintenanceLogError(error.message, statusFromError(error)); } }; @@ -51,36 +49,36 @@ export const getMaintenanceLogById = async (id: string) => { try { const maintenanceLog = await MaintenanceLog.findByPk(id); if (!maintenanceLog) { - throw new MaintenanceLogNotFoundError(); + throw new MaintenanceLogError( + `No Maintenence log found for id : ${id}`, + Status.NOT_FOUND, + ); } return maintenanceLog; - } catch (error: unknown) { - console.error("Error fetching maintenance log: ", error); - if (error instanceof MaintenanceLogNotFoundError) { - throw error; - } - throw new MaintenanceLogServiceError("Error fetching maintenance log."); + } catch (error: any) { + console.error("Get maintenance log By Id: ", error); + throw new MaintenanceLogError(error.message, statusFromError(error)); } }; export const updateMaintenanceLog = async ( id: string, - maintenanceLogData: any + maintenanceLogData: any, ) => { try { const maintenanceLog = await MaintenanceLog.findByPk(id); if (!maintenanceLog) { - throw new MaintenanceLogNotFoundError(); + throw new MaintenanceLogError( + `No Maintenence log found for id : ${id}`, + Status.NOT_FOUND, + ); } await maintenanceLog.update(maintenanceLogData); return { message: "Maintenance log updated successfully." }; - } catch (error: unknown) { - console.error("Error updating maintenance log: ", error); - if (error instanceof MaintenanceLogNotFoundError) { - throw error; - } - throw new MaintenanceLogServiceError("Error updating maintenance log."); + } catch (error: any) { + console.error("Update Maintenance log: ", error); + throw new MaintenanceLogError(error.message, statusFromError(error)); } }; @@ -90,14 +88,14 @@ export const deleteMaintenanceLog = async (id: string) => { where: { id: id }, }); if (result === 0) { - throw new MaintenanceLogNotFoundError(); + throw new MaintenanceLogError( + `No Maintenence log found for id : ${id}`, + Status.NOT_FOUND, + ); } return { message: "Maintenance log deleted successfully." }; - } catch (error: unknown) { - console.error("Error deleting maintenance log: ", error); - if (error instanceof MaintenanceLogNotFoundError) { - throw error; - } - throw new MaintenanceLogServiceError("Error deleting maintenance log."); + } catch (error: any) { + console.error("Delete maintenance log: ", error); + throw new MaintenanceLogError(error.message, statusFromError(error)); } }; diff --git a/app/server/src/services/pinService.ts b/app/server/src/services/pinService.ts index 1a3734d1..5cfff6f0 100644 --- a/app/server/src/services/pinService.ts +++ b/app/server/src/services/pinService.ts @@ -1,7 +1,7 @@ - import bcrypt from "bcrypt"; -import {Auth} from "../models/index.js"; -import { PinError } from "../exceptions/PinErrors.js"; +import { Auth } from "../models/index.js"; +import { AuthError } from "../exceptions/AuthError.js"; +import { Status, statusFromError } from "../exceptions/ServiceError.js"; export const setPin = async (pin: string) => { try { @@ -15,13 +15,13 @@ export const setPin = async (pin: string) => { if (!created) { auth.hash = hash; await auth.save(); - return { status: 200, message: "PIN updated successfully." }; + return { message: "PIN updated successfully." }; } else { - return { status: 201, message: "PIN set successfully." }; + return { message: "PIN set successfully." }; } } catch (error: any) { - console.error("Error hashing or setting PIN:", error); - throw new PinError("Error hashing or setting PIN."); + console.error("Set PIN:", error); + throw new AuthError(error.message, statusFromError(error)); } }; @@ -29,21 +29,20 @@ export const verifyPin = async (pin: string) => { try { const auth = await Auth.findByPk(1); if (!auth) { - return { - status: 404, - message: "PIN not set. Please set a PIN first.", - }; + throw new AuthError("PIN is not set yet. Please set PIN first."); } - const match = await bcrypt.compare(pin, auth.get("hash")); if (match) { - return { status: 200, message: "PIN verified successfully." }; + return { message: "PIN verified successfully." }; } else { - return { status: 401, message: "Invalid PIN." }; + throw new AuthError( + "Incorrect PIN provided. Please try again with correct PIN", + Status.UNAUTHORIZED, + ); } } catch (error: any) { - console.error("Error verifying PIN:", error); - throw new PinError("Error while verifying PIN."); + console.error("Verify PIN:", error); + throw new AuthError(error.message, statusFromError(error)); } }; @@ -52,7 +51,7 @@ export const getPinStatus = async () => { const auth = await Auth.findByPk(1); return { exists: !!auth }; } catch (error: any) { - console.error("Error checking PIN status:", error); - throw new PinError("Error checking PIN status."); + console.error("PIN status:", error); + throw new AuthError(error.message, statusFromError(error)); } }; diff --git a/app/server/src/services/pollutionCertificateService.ts b/app/server/src/services/pollutionCertificateService.ts index 75000f36..47f0b397 100644 --- a/app/server/src/services/pollutionCertificateService.ts +++ b/app/server/src/services/pollutionCertificateService.ts @@ -1,10 +1,7 @@ import { Vehicle, PollutionCertificate } from "../models/index.js"; -import { - PollutionCertificateNotFoundError, - PollutionCertificateExistsError, - PollutionCertificateServiceError, -} from "../exceptions/PollutionCertificateErrors.js"; +import { PollutionCertificateError } from "../exceptions/PollutionCertificateError.js"; import { UniqueConstraintError } from "sequelize"; +import { Status, statusFromError } from "../exceptions/ServiceError.js"; export const addPollutionCertificate = async ( vehicleId: string, @@ -13,9 +10,11 @@ export const addPollutionCertificate = async ( try { const vehicle = await Vehicle.findByPk(vehicleId); if (!vehicle) { - throw new PollutionCertificateNotFoundError("Vehicle not found."); + throw new PollutionCertificateError( + `No vehicle found for id : ${vehicleId}`, + Status.NOT_FOUND, + ); } - const pollutionCertificate = await PollutionCertificate.create({ ...pollutionCertificateData, vehicleId: vehicleId, @@ -24,35 +23,27 @@ export const addPollutionCertificate = async ( id: pollutionCertificate.id, message: "Pollution certificate added successfully.", }; - } catch (error: unknown) { - if (error instanceof UniqueConstraintError) { - throw new PollutionCertificateExistsError(); - } - if (error instanceof PollutionCertificateNotFoundError) { - throw error; - } - throw new PollutionCertificateServiceError( - "Error adding pollution certificate.", - ); + } catch (error: any) { + console.error("Add PUCC: ", error); + throw new PollutionCertificateError(error.message, statusFromError(error)); } }; export const getPollutionCertificates = async (vehicleId: string) => { try { - const pollutionCertificate = await PollutionCertificate.findAll({ + const pollutionCertificates = await PollutionCertificate.findAll({ where: { vehicleId: vehicleId }, }); - if (!pollutionCertificate) { - throw new PollutionCertificateNotFoundError(); + if (!pollutionCertificates) { + throw new PollutionCertificateError( + `No PUCC found for vehicle id : ${vehicleId}`, + Status.NOT_FOUND, + ); } - return pollutionCertificate; - } catch (error: unknown) { - if (error instanceof PollutionCertificateNotFoundError) { - throw error; - } - throw new PollutionCertificateServiceError( - "Error fetching pollution certificate.", - ); + return pollutionCertificates; + } catch (error: any) { + console.error("Get PUCCs: ", error); + throw new PollutionCertificateError(error.message, statusFromError(error)); } }; @@ -66,37 +57,33 @@ export const updatePollutionCertificate = async ( where: { vehicleId: vehicleId, id: id }, }); if (!pollutionCertificate) { - throw new PollutionCertificateNotFoundError(); + throw new PollutionCertificateError( + `No PUCC found for id : ${id}`, + Status.NOT_FOUND, + ); } await pollutionCertificate.update(pollutionCertificateData); return { message: "Pollution certificate updated successfully." }; - } catch (error: unknown) { - console.error(error); - if (error instanceof PollutionCertificateNotFoundError) { - throw error; - } - throw new PollutionCertificateServiceError( - "Error updating pollution certificate.", - ); + } catch (error: any) { + console.error("Update PUCC: ", error); + throw new PollutionCertificateError(error.message, statusFromError(error)); } }; -export const deletePollutionCertificate = async (vehicleId: string) => { +export const deletePollutionCertificate = async (id: string) => { try { const result = await PollutionCertificate.destroy({ - where: { vehicleId: vehicleId }, + where: { id }, }); if (result === 0) { - throw new PollutionCertificateNotFoundError(); + throw new PollutionCertificateError( + `No PUCC found for id : ${id}`, + Status.NOT_FOUND, + ); } return { message: "Pollution certificate deleted successfully." }; - } catch (error: unknown) { - if (error instanceof PollutionCertificateNotFoundError) { - throw error; - } - throw new PollutionCertificateServiceError( - "Error deleting pollution certificate.", - ); + } catch (error: any) { + throw new PollutionCertificateError(error.message, statusFromError(error)); } }; diff --git a/app/server/src/services/vehicleService.ts b/app/server/src/services/vehicleService.ts index ef6320a0..f1001484 100644 --- a/app/server/src/services/vehicleService.ts +++ b/app/server/src/services/vehicleService.ts @@ -1,21 +1,14 @@ -import { - VehicleExistsError, - VehicleServiceError, - VehicleNotFoundError, -} from "../exceptions/VehicleErrors.js"; +import { Status, statusFromError } from "../exceptions/ServiceError.js"; +import { VehicleError } from "../exceptions/VehicleError.js"; import { Insurance, PollutionCertificate, Vehicle } from "../models/index.js"; -import { UniqueConstraintError } from "sequelize"; export const addVehicle = async (vehicleData: any) => { try { const vehicle = await Vehicle.create(vehicleData); return { id: vehicle.id, message: "Vehicle added successfully." }; - } catch (error: unknown) { - console.error("Error adding vehicle: ", error); - if (error instanceof UniqueConstraintError) { - throw new VehicleExistsError(); - } - throw new VehicleServiceError("Error adding vehicle."); + } catch (error: any) { + console.error("Add vehicle: ", error); + throw new VehicleError(error.message, statusFromError(error)); } }; @@ -28,8 +21,6 @@ export const getAllVehicles = async () => { ], }); - // console.log(JSON.stringify(vehicles, null, 4)); - return vehicles.map((vehicle) => { const insurances: Insurance[] = (vehicle as any).insurance; const pollutionCertificates: PollutionCertificate[] = (vehicle as any) @@ -65,9 +56,9 @@ export const getAllVehicles = async () => { puccStatus, }; }); - } catch (error: unknown) { - console.error("Error fetching vehicles: ", error); - throw new VehicleServiceError("Error fetching vehicles."); + } catch (error: any) { + console.error("Get vehicles: ", error); + throw new VehicleError(error.message, statusFromError(error)); } }; @@ -75,15 +66,15 @@ export const getVehicleById = async (id: string) => { try { const vehicle = await Vehicle.findByPk(id); if (!vehicle) { - throw new VehicleNotFoundError(); + throw new VehicleError( + `No vehicle found for id : ${id}`, + Status.NOT_FOUND, + ); } return vehicle; - } catch (error: unknown) { - console.error(`Error fetching vehicle(${id}):`, error); - if (error instanceof VehicleNotFoundError) { - throw error; - } - throw new VehicleServiceError("Error fetching vehicle."); + } catch (error: any) { + console.error(`Get vehicle:`, error); + throw new VehicleError(error.message, statusFromError(error)); } }; @@ -91,20 +82,17 @@ export const updateVehicle = async (id: string, vehicleData: any) => { try { const vehicle = await Vehicle.findByPk(id); if (!vehicle) { - throw new VehicleNotFoundError(); + throw new VehicleError( + `No vehicle found for id : ${id}`, + Status.NOT_FOUND, + ); } await vehicle.update(vehicleData); return { message: "Vehicle updated successfully." }; - } catch (error: unknown) { - console.error(`Error updating vehicle(${id}):`, error); - if (error instanceof UniqueConstraintError) { - throw new VehicleExistsError(); - } - if (error instanceof VehicleNotFoundError) { - throw error; - } - throw new VehicleServiceError("Error updating vehicle."); + } catch (error: any) { + console.error(`Update vehicle:`, error); + throw new VehicleError(error.message, statusFromError(error)); } }; @@ -115,14 +103,14 @@ export const deleteVehicle = async (id: string) => { cascade: true, }); if (result === 0) { - throw new VehicleNotFoundError(); + throw new VehicleError( + `No vehicle found for id : ${id}`, + Status.NOT_FOUND, + ); } return { message: "Vehicle deleted successfully." }; - } catch (error: unknown) { - console.error(`Error deleting vehicle(${id}):`, error); - if (error instanceof VehicleNotFoundError) { - throw error; - } - throw new VehicleServiceError("Error deleting vehicle."); + } catch (error: any) { + console.error(`Delete vehicle:`, error); + throw new VehicleError(error.message, statusFromError(error)); } }; diff --git a/app/server/tsconfig.json b/app/server/tsconfig.json index 68385ebb..80527aab 100644 --- a/app/server/tsconfig.json +++ b/app/server/tsconfig.json @@ -18,5 +18,5 @@ "declaration": true }, "include": ["**/*.ts"], - "exclude": ["node_modules", "dist"] + "exclude": ["node_modules", "./dist"] } From 7141d8837c58d087f32326b10e81802fe911c55c Mon Sep 17 00:00:00 2001 From: Javed Hussain Date: Sat, 9 Aug 2025 15:06:47 +0000 Subject: [PATCH 4/6] UI Enhancements --- .../src/components/common/Button.svelte | 46 +++++++++++++++++++ .../components/common/FormSubmitButton.svelte | 20 -------- .../src/components/forms/ConfigForm.svelte | 6 +-- .../src/components/forms/FuelLogForm.svelte | 4 +- .../src/components/forms/InsuranceForm.svelte | 4 +- .../forms/MaintenanceLogForm.svelte | 4 +- .../forms/PollutionCertificateForm.svelte | 4 +- .../src/components/forms/VehicleForm.svelte | 8 ++-- app/client/src/routes/dashboard/+page.svelte | 14 +++--- 9 files changed, 67 insertions(+), 43 deletions(-) create mode 100644 app/client/src/components/common/Button.svelte delete mode 100644 app/client/src/components/common/FormSubmitButton.svelte diff --git a/app/client/src/components/common/Button.svelte b/app/client/src/components/common/Button.svelte new file mode 100644 index 00000000..0ce4637e --- /dev/null +++ b/app/client/src/components/common/Button.svelte @@ -0,0 +1,46 @@ + + +
+ {#if !loading} + + {:else} + + {/if} +
diff --git a/app/client/src/components/common/FormSubmitButton.svelte b/app/client/src/components/common/FormSubmitButton.svelte deleted file mode 100644 index 64d7607e..00000000 --- a/app/client/src/components/common/FormSubmitButton.svelte +++ /dev/null @@ -1,20 +0,0 @@ - - -
- {#if !loading} - - {:else} - - {/if} -
diff --git a/app/client/src/components/forms/ConfigForm.svelte b/app/client/src/components/forms/ConfigForm.svelte index 03533087..565966d2 100644 --- a/app/client/src/components/forms/ConfigForm.svelte +++ b/app/client/src/components/forms/ConfigForm.svelte @@ -1,5 +1,5 @@ @@ -105,7 +105,7 @@ {/if}
{/each} - + -

- {title} -

-
+
+

+ {title} +

+ +
+
{@render children()}
diff --git a/app/client/src/components/forms/FuelLogForm.svelte b/app/client/src/components/forms/FuelLogForm.svelte index a5c7e99f..863c5e31 100644 --- a/app/client/src/components/forms/FuelLogForm.svelte +++ b/app/client/src/components/forms/FuelLogForm.svelte @@ -1,8 +1,7 @@
- - - - +
+ + +
+
+ + +
+