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/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/FormField.svelte b/app/client/src/components/common/FormField.svelte index 34b06ae6..47c626ee 100644 --- a/app/client/src/components/common/FormField.svelte +++ b/app/client/src/components/common/FormField.svelte @@ -6,6 +6,7 @@ value = $bindable(), icon = null, required = false, + label = undefined, ariaLabel = '', disabled = false, inputClass = '', @@ -14,14 +15,19 @@ const Icon = icon; -
+
+ {#if label} + + {/if}
{#if icon}
-
- - - - + ariaLabel="Pollution Certificate" + />
- - + ariaLabel="Delete" + />
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} - + - + { + 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..c0237cdf 100644 --- a/app/client/src/routes/dashboard/+page.svelte +++ b/app/client/src/routes/dashboard/+page.svelte @@ -1,5 +1,5 @@ @@ -123,12 +145,6 @@

{/if} - {#if error} -

- {error} -

- {/if} + diff --git a/app/server/index.ts b/app/server/index.ts index 5063802f..9612faf4 100644 --- a/app/server/index.ts +++ b/app/server/index.ts @@ -4,6 +4,7 @@ import pinRoutes from "./src/routes/pinRoutes.js"; import vehicleRoutes from "./src/routes/vehicleRoutes.js"; import configRoutes from "./src/routes/configRoutes.js"; import { performDbMigrations, seedData } from "./src/db/index.js"; +import { errorHandler } from "./src/middleware/error-handler.js"; const app = express(); const PORT = Number(process.env.APP_PORT) || 3000; @@ -16,29 +17,35 @@ 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}`); }); } +app.use(errorHandler); + 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/controllers/ConfigController.ts b/app/server/src/controllers/ConfigController.ts index 13925906..7d5afe72 100644 --- a/app/server/src/controllers/ConfigController.ts +++ b/app/server/src/controllers/ConfigController.ts @@ -4,49 +4,33 @@ 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" }); - } + const config = await getAppConfig(); + res.status(200).json(config); }; 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" }); - } - 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" }); + const key = req.params.key; + if (!key) { + throw new ConfigError("Key parameter is required", Status.BAD_REQUEST); } + const config = await getAppConfigByKey(key); + res.status(200).json(config); }; 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" }); - } - 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" }); + const configs: { key: string; value: string }[] = req.body; + if (!Array.isArray(configs) || configs.length === 0) { + throw new ConfigError("Invalid configuration data", Status.BAD_REQUEST); } + const updatedConfigs = await Promise.all( + configs.map(async (config) => { + const { key, value } = config; + return await updateAppConfig(key, value); + }), + ); + res.json(updatedConfigs); }; diff --git a/app/server/src/controllers/FuelLogController.ts b/app/server/src/controllers/FuelLogController.ts index 991ee939..7f72dc27 100644 --- a/app/server/src/controllers/FuelLogController.ts +++ b/app/server/src/controllers/FuelLogController.ts @@ -1,112 +1,66 @@ - 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; if (!date || !odometer || !fuelAmount || !cost) { - return res - .status(400) - .json({ message: "Date, Odometer, Fuel Amount, and Cost are required." }); + throw new FuelLogError( + "Date, Odometer, Fuel Amount, and Cost are required in request body.", + Status.BAD_REQUEST, + ); } 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." }); + 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 }); - } + const result = await fuelLogService.addFuelLog(vehicleId, req.body); + res.status(201).json(result); }; 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 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 }); + throw new FuelLogError("Vehicle id is required.", Status.BAD_REQUEST); } + const fuelLogs = await fuelLogService.getFuelLogs(vehicleId); + res.status(200).json(fuelLogs); }; 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 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 }); + throw new FuelLogError("Fuel Log id is required.", Status.BAD_REQUEST); } + const fuelLog = await fuelLogService.getFuelLogById(id); + res.status(200).json(fuelLog); }; 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." }); + throw new FuelLogError( + "Date, Odometer, Fuel Amount, and Cost are required.", + Status.BAD_REQUEST, + ); } if (!id) { - return res - .status(400) - .json({ message: "Fuel log ID is required." }); - } - try { - 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 }); + throw new FuelLogError("Fuel log ID is required.", Status.BAD_REQUEST); } + const result = await fuelLogService.updateFuelLog(id, req.body); + res.status(200).json(result); }; 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 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 }); + throw new FuelLogError("Fuel Log id is required.", Status.BAD_REQUEST); } + const result = await fuelLogService.deleteFuelLog(id); + res.status(200).json(result); }; diff --git a/app/server/src/controllers/InsuranceController.ts b/app/server/src/controllers/InsuranceController.ts index 8cbaae2c..e1e925e2 100644 --- a/app/server/src/controllers/InsuranceController.ts +++ b/app/server/src/controllers/InsuranceController.ts @@ -1,59 +1,32 @@ 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; if (!vehicleId) { - return res.status(400).json({ message: "Vehicle ID is required." }); + throw new InsuranceError("Vehicle ID is required.", Status.BAD_REQUEST); } if (!provider || !policyNumber || !startDate || !endDate || !cost) { - return res.status(400).json({ - message: - "Provider, Policy Number, Start Date, End Date, and Cost are required.", - }); - } - - try { - 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 }); + throw new InsuranceError( + "Provider, Policy Number, Start Date, End Date, and Cost are required.", + Status.BAD_REQUEST, + ); } + const result = await insuranceService.addInsurance(vehicleId, req.body); + res.status(201).json(result); }; 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 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 }); + throw new InsuranceError("Vehicle ID is required.", Status.BAD_REQUEST); } + const insurances = await insuranceService.getInsurances(vehicleId); + res.status(200).json(insurances); }; export const updateInsurance = async (req: Request, res: Response) => { @@ -61,50 +34,30 @@ export const updateInsurance = async (req: Request, res: Response) => { const { provider, policyNumber, startDate, endDate, cost } = req.body; if (!vehicleId || !id) { - return res - .status(400) - .json({ message: "Vehicle ID and Insurance Id is required." }); + throw new InsuranceError( + "Vehicle ID and Insurance Id is required.", + Status.BAD_REQUEST, + ); } if (!provider || !policyNumber || !startDate || !endDate || !cost) { - return res.status(400).json({ - message: - "Provider, Policy Number, Start Date, End Date, and Cost are required.", - }); - } - - try { - const result = await insuranceService.updateInsurance( - vehicleId, - id, - req.body, + throw new InsuranceError( + "Provider, Policy Number, Start Date, End Date, and Cost are required.", + Status.BAD_REQUEST, ); - 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 }); } + const result = await insuranceService.updateInsurance( + vehicleId, + id, + req.body, + ); + res.status(200).json(result); }; 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); - 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 }); + 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); }; diff --git a/app/server/src/controllers/MaintenanceLogController.ts b/app/server/src/controllers/MaintenanceLogController.ts index d5d50ceb..2ada6eb6 100644 --- a/app/server/src/controllers/MaintenanceLogController.ts +++ b/app/server/src/controllers/MaintenanceLogController.ts @@ -1,75 +1,56 @@ 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; + const { date, odometer, serviceCenter, 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." }); + throw new MaintenanceLogError( + "Vehicle ID is required.", + Status.BAD_REQUEST, + ); } - - try { - const result = await maintenanceLogService.addMaintenanceLog( - vehicleId, - req.body + if (!date || !odometer || !serviceCenter || !cost) { + throw new MaintenanceLogError( + "Date, Odometer, ServiceCenter, and Cost are required.", + Status.BAD_REQUEST, ); - 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 }); } + const result = await maintenanceLogService.addMaintenanceLog( + vehicleId, + req.body, + ); + res.status(201).json(result); }; 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 + throw new MaintenanceLogError( + "Vehicle ID is required.", + Status.BAD_REQUEST, ); - 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 }); } + const maintenanceLogs = + await maintenanceLogService.getMaintenanceLogs(vehicleId); + res.status(200).json(maintenanceLogs); }; 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); - 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 }); + throw new MaintenanceLogError( + "Maintenance Log ID is required.", + Status.BAD_REQUEST, + ); } + + const maintenanceLog = + await maintenanceLogService.getMaintenanceLogById(id); + res.status(200).json(maintenanceLog); }; export const updateMaintenanceLog = async (req: Request, res: Response) => { @@ -77,43 +58,33 @@ export const updateMaintenanceLog = async (req: Request, res: Response) => { const { date, odometer, service, cost, notes } = req.body; if (!id) { - return res.status(400).json({ message: "Maintenance Log ID is required." }); + throw new MaintenanceLogError( + "Maintenance Log ID is required.", + Status.BAD_REQUEST, + ); } 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 }); - } - if (error instanceof MaintenanceLogServiceError) { - return res.status(500).json({ message: error.message }); - } - res.status(500).json({ message: error.message }); + throw new MaintenanceLogError( + "Date, Odometer, Service, and Cost are required.", + Status.BAD_REQUEST, + ); } + const result = await maintenanceLogService.updateMaintenanceLog( + id, + req.body, + ); + res.status(200).json(result); }; 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 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 }); + throw new MaintenanceLogError( + "Maintenance Log ID is required.", + Status.BAD_REQUEST, + ); } + + const result = await maintenanceLogService.deleteMaintenanceLog(id); + res.status(200).json(result); }; diff --git a/app/server/src/controllers/PUCCController.ts b/app/server/src/controllers/PUCCController.ts index ab049b6d..47312b59 100644 --- a/app/server/src/controllers/PUCCController.ts +++ b/app/server/src/controllers/PUCCController.ts @@ -1,64 +1,44 @@ 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 } = + const { certificateNumber, issueDate, expiryDate, testingCenter } = req.body; if (!vehicleId) { - return res.status(400).json({ message: "Vehicle ID is required." }); + throw new PollutionCertificateError( + "Vehicle ID is required.", + Status.BAD_REQUEST, + ); } if (!certificateNumber || !issueDate || !expiryDate || !testingCenter) { - return res.status(400).json({ - message: - "Certificate Number, Issue Date, Expiry Date, and Testing Center are required.", - }); - } - - try { - const result = await pollutionCertificateService.addPollutionCertificate( - vehicleId, - req.body, + throw new PollutionCertificateError( + "Certificate Number, Issue Date, Expiry Date, and Testing Center are required.", + Status.BAD_REQUEST, ); - 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 }); } + + const result = await pollutionCertificateService.addPollutionCertificate( + vehicleId, + req.body, + ); + res.status(201).json(result); }; 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 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 }); + throw new PollutionCertificateError( + "Vehicle ID is required.", + Status.BAD_REQUEST, + ); } + const pollutionCertificates = + await pollutionCertificateService.getPollutionCertificates(vehicleId); + res.status(200).json(pollutionCertificates); }; export const updatePollutionCertificate = async ( @@ -70,33 +50,23 @@ export const updatePollutionCertificate = async ( req.body; if (!vehicleId || !id) { - return res - .status(400) - .json({ message: "Vehicle ID and certificate ID are required." }); + throw new PollutionCertificateError( + "Vehicle ID and certificate ID are required.", + Status.BAD_REQUEST, + ); } if (!certificateNumber || !issueDate || !expiryDate || !testingCenter) { - return res.status(400).json({ - message: - "Certificate Number, Issue Date, Expiry Date, and Testing Center are required.", - }); - } - - try { - const result = await pollutionCertificateService.updatePollutionCertificate( - vehicleId, - id, - req.body, + throw new PollutionCertificateError( + "Certificate Number, Issue Date, Expiry Date, and Testing Center are required.", + Status.BAD_REQUEST, ); - 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 }); } + const result = await pollutionCertificateService.updatePollutionCertificate( + vehicleId, + id, + req.body, + ); + res.status(200).json(result); }; export const deletePollutionCertificate = async ( @@ -105,19 +75,12 @@ export const deletePollutionCertificate = async ( ) => { const { vehicleId } = req.params; if (!vehicleId) { - return res.status(400).json({ message: "Vehicle ID is required." }); - } - try { - 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 }); + throw new PollutionCertificateError( + "Vehicle ID is required.", + Status.BAD_REQUEST, + ); } + const result = + await pollutionCertificateService.deletePollutionCertificate(vehicleId); + res.status(200).json(result); }; diff --git a/app/server/src/controllers/PinController.ts b/app/server/src/controllers/PinController.ts index 37f51cad..2e024022 100644 --- a/app/server/src/controllers/PinController.ts +++ b/app/server/src/controllers/PinController.ts @@ -1,42 +1,27 @@ - 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 result = await pinService.setPin(pin); - res.status(result.status).json({ message: result.message }); - } catch (error: any) { - res.status(500).json({ message: error.message }); + throw new AuthError("PIN must be a 6-digit number.", Status.BAD_REQUEST); } + const result = await pinService.setPin(pin); + res.status(200).json({ message: result.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 result = await pinService.verifyPin(pin); - res.status(result.status).json({ message: result.message }); - } catch (error: any) { - res.status(500).json({ message: error.message }); + throw new AuthError("PIN is required.", Status.BAD_REQUEST); } + const result = await pinService.verifyPin(pin); + res.status(200).json({ message: result.message }); }; export const getPinStatus = async (req: Request, res: Response) => { - try { - const result = await pinService.getPinStatus(); - res.status(200).json(result); - } catch (error: any) { - res.status(500).json({ message: error.message }); - } + const result = await pinService.getPinStatus(); + res.status(200).json(result); }; diff --git a/app/server/src/controllers/VehicleController.ts b/app/server/src/controllers/VehicleController.ts index 06a4b08b..2559a24c 100644 --- a/app/server/src/controllers/VehicleController.ts +++ b/app/server/src/controllers/VehicleController.ts @@ -1,47 +1,32 @@ 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 result = await vehicleService.addVehicle(req.body); - res.status(201).json(result); - } catch (error: any) { - res.status(500).json({ message: error.message }); + 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); }; export const getAllVehicles = async (req: Request, res: Response) => { - try { - const vehicles = await vehicleService.getAllVehicles(); - res.status(200).json(vehicles); - } catch (error: any) { - res.status(500).json({ message: error.message }); - } + const vehicles = await vehicleService.getAllVehicles(); + res.status(200).json(vehicles); }; 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 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 }); + throw new VehicleError("Vehicle ID is required.", Status.BAD_REQUEST); } + const vehicle = await vehicleService.getVehicleById(id); + res.status(200).json(vehicle); }; export const updateVehicle = async (req: Request, res: Response) => { @@ -49,36 +34,24 @@ export const updateVehicle = 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." }); + throw new VehicleError( + "Make, Model, Year, and License Plate are required.", + Status.BAD_REQUEST, + ); } if (!id) { - return res.status(400).json({ message: "Vehicle ID is required." }); - } - try { - 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 }); + throw new VehicleError("Vehicle ID is required.", Status.BAD_REQUEST); } + const result = await vehicleService.updateVehicle(id, req.body); + res.status(200).json(result); }; 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 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 }); + throw new VehicleError("Vehicle ID is required.", Status.BAD_REQUEST); } + + const result = await vehicleService.deleteVehicle(id); + res.status(200).json(result); }; diff --git a/app/server/src/db/db.ts b/app/server/src/db/db.ts index 69d7817a..2da8a6c3 100644 --- a/app/server/src/db/db.ts +++ b/app/server/src/db/db.ts @@ -1,15 +1,14 @@ import { config } from "dotenv"; import { Sequelize } from "sequelize"; -config({ quiet: false, debug: true }); +config({ quiet: true, debug: false }); -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..ad188ff7 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: false }); 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 = [ @@ -157,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", }, ]); @@ -200,9 +209,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 +219,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 +248,7 @@ async function generateConfigs() { }, ]; await Config.bulkCreate(configData, { - ignoreDuplicates: true + ignoreDuplicates: true, }); console.log("Configuration data created."); } @@ -251,4 +260,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/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/middleware/error-handler.ts b/app/server/src/middleware/error-handler.ts new file mode 100644 index 00000000..2f2e72d3 --- /dev/null +++ b/app/server/src/middleware/error-handler.ts @@ -0,0 +1,36 @@ +import { Request, Response } from "express"; +import { ValidationError } from "sequelize"; + +export const errorHandler = ( + err: any, + req: Request, + res: Response, + next: any, +) => { + res.setHeader("Content-Type", "application/json"); + let body = {}; + switch (err.name) { + case "SequelizeValidationError": + body = { + type: "ValidationError", + errors: err.errors.map((e: any) => { + return { + message: e.message, + path: e.path + } + }) + }; + break; + default: + body = { + type: err.name, + errors: [ + { message: err.message } + ] + } + } + + console.error(err.name) + res.status(500); + res.send(JSON.stringify(body)); +}; 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..592e29fa 100644 --- a/app/server/src/models/FuelLog.ts +++ b/app/server/src/models/FuelLog.ts @@ -12,11 +12,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 +46,45 @@ FuelLog.init( date: { type: DataTypes.DATEONLY, allowNull: false, - validate: { - isDate: true, - notEmpty: true, - }, }, odometer: { type: DataTypes.INTEGER, allowNull: false, validate: { - isInt: true, - min: 0, + min: { + args: [0], + msg: "Odometer reading must be greater than 0.", + }, }, }, 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..a414e3e8 100644 --- a/app/server/src/models/Insurance.ts +++ b/app/server/src/models/Insurance.ts @@ -1,6 +1,8 @@ import { DataTypes, Model, Optional } from "sequelize"; import { db } from "../db/index.js"; import Vehicle from "./Vehicle.js"; +import { InsuranceError } from "../exceptions/InsuranceError.js"; +import { Status } from "../exceptions/ServiceError.js"; interface InsuranceAttributes { id: string; @@ -10,14 +12,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 +29,7 @@ class Insurance declare public startDate: string; declare public endDate: string; declare public cost: number; + declare public notes?: string; } Insurance.init( @@ -47,36 +52,65 @@ 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, + unique: true, validate: { - notEmpty: true, + len: { + args: [3, 50], + msg: "Policy 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 policy number.", + }, }, }, startDate: { 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: { 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 +119,33 @@ 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 InsuranceError( + "Start date must always be before end date.", + Status.BAD_REQUEST, + ); + } + + if (sDate < maxEndDate) { + 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 424235f2..aa2486ca 100644 --- a/app/server/src/models/Vehicle.ts +++ b/app/server/src/models/Vehicle.ts @@ -1,5 +1,8 @@ import { DataTypes, Model, Optional } from "sequelize"; import { db } from "../db/index.js"; +import FuelLog from "./FuelLog.js"; +import { VehicleError } from "../exceptions/VehicleError.js"; +import { Status } from "../exceptions/ServiceError.js"; interface VehicleAttributes { id: string; @@ -12,11 +15,12 @@ interface VehicleAttributes { odometer?: number; } -interface VehicleCreationAttributes extends Optional { } +interface VehicleCreationAttributes extends Optional {} class Vehicle extends Model - implements VehicleAttributes { + implements VehicleAttributes +{ declare public id: string; declare public make: string; declare public model: string; @@ -39,14 +43,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 +64,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 +79,70 @@ 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: { + notEmpty: { + msg: "VIN number can't be an empty string.", + }, + 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 VehicleError( + "VIN NUmber already exists.", + Status.CONFLICT, + ); + } + }, + }, }, 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 + async validateOdometer(value: number) { + const minOdometer: number = await FuelLog.min("odometer", { + where: { + vehicleId: this.id as string, + }, + }); + + if (minOdometer && value > minOdometer) { + throw new VehicleError( + "Initial Odometer Reading must be lesser than first fuel log odometer.", + Status.BAD_REQUEST, + ); + } + }, }, }, }, 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/configService.ts b/app/server/src/services/configService.ts index b86774d1..3f7df42d 100644 --- a/app/server/src/services/configService.ts +++ b/app/server/src/services/configService.ts @@ -1,3 +1,5 @@ +import { ConfigError } from "../exceptions/ConfigError.js"; +import { Status, statusFromError } from "../exceptions/ServiceError.js"; import Config from "../models/Config.js"; export const getAppConfig = async () => { @@ -12,10 +14,22 @@ export const getAppConfig = async () => { export const getAppConfigByKey = async (key: string) => { const config = await Config.findOne({ where: { key } }); + if (!config) { + throw new ConfigError( + `No config found for key : ${key}`, + Status.NOT_FOUND, + ); + } return config; }; export const updateAppConfig = async (key: string, value: string) => { + 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 }); diff --git a/app/server/src/services/fuelLogService.ts b/app/server/src/services/fuelLogService.ts index 77a08b93..8f1c7e08 100644 --- a/app/server/src/services/fuelLogService.ts +++ b/app/server/src/services/fuelLogService.ts @@ -1,105 +1,87 @@ -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."); - } - - 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."); + const vehicle = await Vehicle.findByPk(vehicleId); + if (!vehicle) { + 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." }; }; export const getFuelLogs = async (vehicleId: string) => { - try { - const fuelLogs = await FuelLog.findAll({ - where: { vehicleId: vehicleId }, - order: [ - ["date", "ASC"], - ["odometer", "ASC"], - ], - }); - // Calculate mileage - return fuelLogs.map((log, index, arr) => { - if (index > 0) { - const prevLog = arr[index - 1]; - if (!prevLog) { - return { ...log.toJSON(), mileage: null }; - } - const distance = log.odometer - prevLog.odometer; - const mileage = distance / log.fuelAmount; // km/L or miles/gallon - return { ...log.toJSON(), mileage: parseFloat(mileage.toFixed(2)) }; + const fuelLogs = await FuelLog.findAll({ + where: { vehicleId: vehicleId }, + order: [ + ["date", "ASC"], + ["odometer", "ASC"], + ], + }); + + // Calculate mileage + return fuelLogs.map((log, index, arr) => { + if (index > 0) { + const prevLog = arr[index - 1]; + if (!prevLog) { + return { ...log.toJSON(), mileage: null }; } - return { ...log.toJSON(), mileage: null }; - }); - } catch (error: any) { - console.error("Error fetching fuel logs: ", error); - throw new FuelLogError("Error fetching fuel logs."); - } + const distance = log.odometer - prevLog.odometer; + const mileage = distance / log.fuelAmount; + return { ...log.toJSON(), mileage: parseFloat(mileage.toFixed(2)) }; + } + return { ...log.toJSON(), mileage: null }; + }); + }; export const getFuelLogById = async (id: string) => { - try { - const fuelLog = await FuelLog.findByPk(id); - if (!fuelLog) { - throw new FuelLogNotFoundError(); - } - 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."); + + const fuelLog = await FuelLog.findByPk(id); + if (!fuelLog) { + throw new FuelLogError( + `No Fuel Logs found for id : ${id}`, + Status.NOT_FOUND, + ); } + return fuelLog; + }; export const updateFuelLog = async (id: string, fuelLogData: any) => { - try { - const fuelLog = await FuelLog.findByPk(id); - if (!fuelLog) { - throw new FuelLogNotFoundError(); - } - 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."); + const fuelLog = await FuelLog.findByPk(id); + if (!fuelLog) { + throw new FuelLogError( + `No Fuel Logs found for id : ${id}`, + Status.NOT_FOUND, + ); } + + await fuelLog.update(fuelLogData); + return { message: "Fuel log updated successfully." }; + }; export const deleteFuelLog = async (id: string) => { - try { - const result = await FuelLog.destroy({ - where: { id: id }, - }); - if (result === 0) { - throw new FuelLogNotFoundError(); - } - 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."); + + const result = await FuelLog.destroy({ + where: { id: id }, + }); + if (result === 0) { + throw new FuelLogError( + `No Fuel Logs found for id : ${id}`, + Status.NOT_FOUND, + ); } + return { message: "Fuel log deleted successfully." }; }; diff --git a/app/server/src/services/insuranceService.ts b/app/server/src/services/insuranceService.ts index 3b5980ad..8d79747f 100644 --- a/app/server/src/services/insuranceService.ts +++ b/app/server/src/services/insuranceService.ts @@ -1,52 +1,41 @@ -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"; export const addInsurance = async (vehicleId: string, insuranceData: any) => { - try { - const vehicle = await Vehicle.findByPk(vehicleId); - if (!vehicle) { - throw new InsuranceNotFoundError("Vehicle not found."); - } - const insurance = await Insurance.create({ - ...insuranceData, - vehicleId: vehicleId, - }); - return { - id: insurance.id, - message: "Insurance details added successfully.", - }; - } catch (error: unknown) { - if (error instanceof UniqueConstraintError) { - throw new InsuranceExistsError(); - } - if (error instanceof InsuranceNotFoundError) { - throw error; - } - throw new InsuranceServiceError("Error adding insurance details."); + const vehicle = await Vehicle.findByPk(vehicleId); + if (!vehicle) { + throw new InsuranceError( + `No Vehicle found for id : ${vehicleId}`, + Status.NOT_FOUND, + ); } + const insurance = await Insurance.create({ + ...insuranceData, + vehicleId: vehicleId, + }); + return { + id: insurance.id, + message: "Insurance details added successfully.", + }; + }; export const getInsurances = async (vehicleId: string) => { - try { - const insurance = await Insurance.findAll({ - where: { vehicleId: vehicleId }, - }); - if (!insurance) { - throw new InsuranceNotFoundError(); - } - return insurance; - } catch (error: unknown) { - if (error instanceof InsuranceNotFoundError) { - throw error; - } - throw new InsuranceServiceError("Error fetching insurance details."); + + const insurance = await Insurance.findAll({ + where: { vehicleId: vehicleId }, + }); + if (!insurance) { + throw new InsuranceError( + `No Insurances found for vehicle id : ${vehicleId}`, + Status.NOT_FOUND, + ); } + return insurance; + }; export const updateInsurance = async ( @@ -54,37 +43,31 @@ export const updateInsurance = async ( id: string, insuranceData: any, ) => { - try { - const insurance = await Insurance.findOne({ - where: { vehicleId: vehicleId, id }, - }); - if (!insurance) { - throw new InsuranceNotFoundError(); - } - 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."); + const insurance = await Insurance.findOne({ + where: { vehicleId: vehicleId, id }, + }); + if (!insurance) { + throw new InsuranceError( + `No Insurances found for id: ${id}`, + Status.NOT_FOUND, + ); } + await insurance.update(insuranceData); + return { message: "Insurance details updated successfully." }; + }; -export const deleteInsurance = async (vehicleId: string) => { - try { - const result = await Insurance.destroy({ - where: { vehicleId: vehicleId }, - }); - if (result === 0) { - throw new InsuranceNotFoundError(); - } - return { message: "Insurance details deleted successfully." }; - } catch (error: unknown) { - if (error instanceof InsuranceNotFoundError) { - throw error; - } - throw new InsuranceServiceError("Error deleting insurance details."); +export const deleteInsurance = async (id: string) => { + + const result = await Insurance.destroy({ + where: { id }, + }); + if (result === 0) { + throw new InsuranceError( + `No Insurances found for id: ${id}`, + Status.NOT_FOUND, + ); } -}; + return { message: "Insurance details deleted successfully." }; +} diff --git a/app/server/src/services/maintenanceLogService.ts b/app/server/src/services/maintenanceLogService.ts index 8c5a00f1..f661b2ab 100644 --- a/app/server/src/services/maintenanceLogService.ts +++ b/app/server/src/services/maintenanceLogService.ts @@ -1,103 +1,86 @@ 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."); - } - - const maintenanceLog = await MaintenanceLog.create({ - ...maintenanceLogData, - vehicleId: vehicleId, - }); - return { - 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."); + + const vehicle = await Vehicle.findByPk(vehicleId); + if (!vehicle) { + throw new MaintenanceLogError( + `No vehicle found for id ${vehicleId}`, + Status.NOT_FOUND, + ); } + + const maintenanceLog = await MaintenanceLog.create({ + ...maintenanceLogData, + vehicleId: vehicleId, + }); + return { + id: maintenanceLog.id, + message: "Maintenance log added successfully.", + }; + }; export const getMaintenanceLogs = async (vehicleId: string) => { - try { - const maintenanceLogs = await MaintenanceLog.findAll({ - where: { vehicleId: vehicleId }, - order: [ - ["date", "ASC"], - ["odometer", "ASC"], - ], - }); - return maintenanceLogs; - } catch (error: unknown) { - console.error("Error fetching maintenance logs: ", error); - throw new MaintenanceLogServiceError("Error fetching maintenance logs."); - } + + const maintenanceLogs = await MaintenanceLog.findAll({ + where: { vehicleId: vehicleId }, + order: [ + ["date", "ASC"], + ["odometer", "ASC"], + ], + }); + return maintenanceLogs; + }; export const getMaintenanceLogById = async (id: string) => { - try { - const maintenanceLog = await MaintenanceLog.findByPk(id); - if (!maintenanceLog) { - throw new MaintenanceLogNotFoundError(); - } - 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."); + + const maintenanceLog = await MaintenanceLog.findByPk(id); + if (!maintenanceLog) { + throw new MaintenanceLogError( + `No Maintenence log found for id : ${id}`, + Status.NOT_FOUND, + ); } + return maintenanceLog; + }; export const updateMaintenanceLog = async ( id: string, - maintenanceLogData: any + maintenanceLogData: any, ) => { - try { - const maintenanceLog = await MaintenanceLog.findByPk(id); - if (!maintenanceLog) { - throw new MaintenanceLogNotFoundError(); - } - - 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."); + + const maintenanceLog = await MaintenanceLog.findByPk(id); + if (!maintenanceLog) { + throw new MaintenanceLogError( + `No Maintenence log found for id : ${id}`, + Status.NOT_FOUND, + ); } + + await maintenanceLog.update(maintenanceLogData); + return { message: "Maintenance log updated successfully." }; + }; export const deleteMaintenanceLog = async (id: string) => { - try { - const result = await MaintenanceLog.destroy({ - where: { id: id }, - }); - if (result === 0) { - throw new MaintenanceLogNotFoundError(); - } - 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."); + + const result = await MaintenanceLog.destroy({ + where: { id: id }, + }); + if (result === 0) { + throw new MaintenanceLogError( + `No Maintenence log found for id : ${id}`, + Status.NOT_FOUND, + ); } + return { message: "Maintenance log deleted successfully." }; + }; diff --git a/app/server/src/services/pinService.ts b/app/server/src/services/pinService.ts index 1a3734d1..466b99ef 100644 --- a/app/server/src/services/pinService.ts +++ b/app/server/src/services/pinService.ts @@ -1,58 +1,48 @@ - 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 { - const hash = await bcrypt.hash(pin, 10); - - const [auth, created] = await Auth.findOrCreate({ - where: { id: 1 }, - defaults: { hash: hash }, - }); - - if (!created) { - auth.hash = hash; - await auth.save(); - return { status: 200, message: "PIN updated successfully." }; - } else { - return { status: 201, message: "PIN set successfully." }; - } - } catch (error: any) { - console.error("Error hashing or setting PIN:", error); - throw new PinError("Error hashing or setting PIN."); + + const hash = await bcrypt.hash(pin, 10); + + const [auth, created] = await Auth.findOrCreate({ + where: { id: 1 }, + defaults: { hash: hash }, + }); + + if (!created) { + auth.hash = hash; + await auth.save(); + return { message: "PIN updated successfully." }; + } else { + return { message: "PIN set successfully." }; } + }; 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.", - }; - } - - const match = await bcrypt.compare(pin, auth.get("hash")); - if (match) { - return { status: 200, message: "PIN verified successfully." }; - } else { - return { status: 401, message: "Invalid PIN." }; - } - } catch (error: any) { - console.error("Error verifying PIN:", error); - throw new PinError("Error while verifying PIN."); + + const auth = await Auth.findByPk(1); + if (!auth) { + throw new AuthError("PIN is not set yet. Please set PIN first."); } + const match = await bcrypt.compare(pin, auth.get("hash")); + if (match) { + return { message: "PIN verified successfully." }; + } else { + throw new AuthError( + "Incorrect PIN provided. Please try again with correct PIN", + Status.UNAUTHORIZED, + ); + } + }; export const getPinStatus = async () => { - try { - 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."); - } + + const auth = await Auth.findByPk(1); + return { exists: !!auth }; + }; diff --git a/app/server/src/services/pollutionCertificateService.ts b/app/server/src/services/pollutionCertificateService.ts index 75000f36..1c1ac1bf 100644 --- a/app/server/src/services/pollutionCertificateService.ts +++ b/app/server/src/services/pollutionCertificateService.ts @@ -1,59 +1,44 @@ 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, pollutionCertificateData: any, ) => { - try { - const vehicle = await Vehicle.findByPk(vehicleId); - if (!vehicle) { - throw new PollutionCertificateNotFoundError("Vehicle not found."); - } - const pollutionCertificate = await PollutionCertificate.create({ - ...pollutionCertificateData, - vehicleId: vehicleId, - }); - return { - 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.", + const vehicle = await Vehicle.findByPk(vehicleId); + if (!vehicle) { + throw new PollutionCertificateError( + `No vehicle found for id : ${vehicleId}`, + Status.NOT_FOUND, ); } + const pollutionCertificate = await PollutionCertificate.create({ + ...pollutionCertificateData, + vehicleId: vehicleId, + }); + return { + id: pollutionCertificate.id, + message: "Pollution certificate added successfully.", + }; + }; export const getPollutionCertificates = async (vehicleId: string) => { - try { - const pollutionCertificate = await PollutionCertificate.findAll({ - where: { vehicleId: vehicleId }, - }); - if (!pollutionCertificate) { - throw new PollutionCertificateNotFoundError(); - } - return pollutionCertificate; - } catch (error: unknown) { - if (error instanceof PollutionCertificateNotFoundError) { - throw error; - } - throw new PollutionCertificateServiceError( - "Error fetching pollution certificate.", + + const pollutionCertificates = await PollutionCertificate.findAll({ + where: { vehicleId: vehicleId }, + }); + if (!pollutionCertificates) { + throw new PollutionCertificateError( + `No PUCC found for vehicle id : ${vehicleId}`, + Status.NOT_FOUND, ); } + return pollutionCertificates; + }; export const updatePollutionCertificate = async ( @@ -61,42 +46,32 @@ export const updatePollutionCertificate = async ( id: string, pollutionCertificateData: any, ) => { - try { - const pollutionCertificate = await PollutionCertificate.findOne({ - where: { vehicleId: vehicleId, id: id }, - }); - if (!pollutionCertificate) { - throw new PollutionCertificateNotFoundError(); - } - 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.", + const pollutionCertificate = await PollutionCertificate.findOne({ + where: { vehicleId: vehicleId, id: id }, + }); + if (!pollutionCertificate) { + throw new PollutionCertificateError( + `No PUCC found for id : ${id}`, + Status.NOT_FOUND, ); } + + await pollutionCertificate.update(pollutionCertificateData); + return { message: "Pollution certificate updated successfully." }; + }; -export const deletePollutionCertificate = async (vehicleId: string) => { - try { - const result = await PollutionCertificate.destroy({ - where: { vehicleId: vehicleId }, - }); - if (result === 0) { - throw new PollutionCertificateNotFoundError(); - } - return { message: "Pollution certificate deleted successfully." }; - } catch (error: unknown) { - if (error instanceof PollutionCertificateNotFoundError) { - throw error; - } - throw new PollutionCertificateServiceError( - "Error deleting pollution certificate.", +export const deletePollutionCertificate = async (id: string) => { + + const result = await PollutionCertificate.destroy({ + where: { id }, + }); + if (result === 0) { + throw new PollutionCertificateError( + `No PUCC found for id : ${id}`, + Status.NOT_FOUND, ); } -}; + return { message: "Pollution certificate deleted successfully." }; +} diff --git a/app/server/src/services/vehicleService.ts b/app/server/src/services/vehicleService.ts index 4436f9da..d7234bea 100644 --- a/app/server/src/services/vehicleService.ts +++ b/app/server/src/services/vehicleService.ts @@ -1,109 +1,95 @@ -import { VehicleExistsError, VehicleServiceError, VehicleNotFoundError } from "../exceptions/VehicleErrors.js"; -import { Vehicle } from "../models/index.js"; -import { UniqueConstraintError } from "sequelize"; +import { Status, statusFromError } from "../exceptions/ServiceError.js"; +import { VehicleError } from "../exceptions/VehicleError.js"; +import { Insurance, PollutionCertificate, Vehicle } from "../models/index.js"; 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."); - } + const vehicle = await Vehicle.create(vehicleData); + return { id: vehicle.id, message: "Vehicle added successfully." }; }; export const getAllVehicles = async () => { - try { - const vehicles = await Vehicle.findAll({ - include: [ - { association: 'insurance' }, - { association: 'pollutionCertificate' } - ] - }); - - return vehicles.map(vehicle => { - const insurance = (vehicle as any).insurance; - const pollutionCertificate = (vehicle as any).pollutionCertificate; - - let insuranceStatus = "N/A"; - if (insurance) { + + const vehicles = await Vehicle.findAll({ + include: [ + { association: "insurance" }, + { association: "pollutionCertificate" }, + ], + }); + + return vehicles.map((vehicle) => { + const insurances: Insurance[] = (vehicle as any).insurance; + const pollutionCertificates: PollutionCertificate[] = (vehicle as any) + .pollutionCertificate; + + let insuranceStatus = "Not Available"; + if (insurances && insurances.length > 0) { + insuranceStatus = "Expired"; + insurances.forEach((insurance) => { const endDate = new Date(insurance.endDate); - insuranceStatus = endDate > new Date() ? "Active" : "Expired"; - } - - let puccStatus = "N/A"; - if (pollutionCertificate) { - const expiryDate = new Date(pollutionCertificate.expiryDate); - puccStatus = expiryDate > new Date() ? "Active" : "Expired"; - } - - return { - ...vehicle.toJSON(), - insuranceStatus, - puccStatus - }; - }); - } catch (error: unknown) { - console.error("Error fetching vehicles: ", error); - throw new VehicleServiceError("Error fetching vehicles."); - } + const today = new Date(); + if (endDate > today) { + insuranceStatus = "Active"; + } + }); + } + + let puccStatus = "Not Available"; + 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, + }; + }); + }; export const getVehicleById = async (id: string) => { - try { - const vehicle = await Vehicle.findByPk(id); - if (!vehicle) { - throw new VehicleNotFoundError(); - } - return vehicle; - } catch (error: unknown) { - console.error(`Error fetching vehicle(${id}):`, error); - if (error instanceof VehicleNotFoundError) { - throw error; - } - throw new VehicleServiceError("Error fetching vehicle."); + + const vehicle = await Vehicle.findByPk(id); + if (!vehicle) { + throw new VehicleError( + `No vehicle found for id : ${id}`, + Status.NOT_FOUND, + ); } + return vehicle; + }; export const updateVehicle = async (id: string, vehicleData: any) => { - try { - const vehicle = await Vehicle.findByPk(id); - if (!vehicle) { - throw new VehicleNotFoundError(); - } - 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."); + const vehicle = await Vehicle.findByPk(id); + if (!vehicle) { + throw new VehicleError(`No vehicle found for id : ${id}`, Status.NOT_FOUND); } + + await vehicle.update(vehicleData); + return { message: "Vehicle updated successfully." }; }; export const deleteVehicle = async (id: string) => { - try { - const result = await Vehicle.destroy({ - where: { id: id }, - cascade: true - }); - if (result === 0) { - throw new VehicleNotFoundError(); - } - 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."); + + const result = await Vehicle.destroy({ + where: { id: id }, + cascade: true, + }); + if (result === 0) { + throw new VehicleError( + `No vehicle found for id : ${id}`, + Status.NOT_FOUND, + ); } + return { message: "Vehicle deleted successfully." }; + }; diff --git a/app/server/tsconfig.json b/app/server/tsconfig.json index 28b84bb8..80527aab 100644 --- a/app/server/tsconfig.json +++ b/app/server/tsconfig.json @@ -16,8 +16,7 @@ "moduleResolution": "nodenext", "noUncheckedIndexedAccess": true, "declaration": true - }, "include": ["**/*.ts"], - "exclude": ["node_modules", "dist"] + "exclude": ["node_modules", "./dist"] }