From 6b844c48b45c14b1e1837c66de840eeb560fe7ee Mon Sep 17 00:00:00 2001 From: Kevin Cantrell Date: Sun, 6 Jul 2025 19:25:59 +0900 Subject: [PATCH 1/5] massive update to remove ioc.config --- README.md | 2 +- package.json | 2 - pnpm-lock.yaml | 67 -- src/hooks.server.ts | 1 - src/lib/errors/ErrorHandlingService.ts | 136 ++-- src/lib/repositories/AirDataRepository.ts | 158 +++-- .../repositories/DeviceOwnersRepository.ts | 531 ++++++++-------- src/lib/repositories/DeviceRepository.ts | 400 ++++++------ src/lib/repositories/DeviceTypeRepository.ts | 399 ++++++------ src/lib/repositories/LocationRepository.ts | 8 +- .../repositories/NotifierTypeRepository.ts | 8 +- .../repositories/ReportTemplateRepository.ts | 40 +- src/lib/repositories/RuleRepository.ts | 597 +++++++++--------- src/lib/repositories/UserRepository.ts | 44 +- src/lib/server/ioc.config.ts | 87 --- src/lib/server/ioc.types.ts | 29 - src/lib/services/DeviceOwnersService.ts | 374 +++++------ src/lib/services/LocationService.ts | 7 +- src/lib/services/ReportTemplateService.ts | 47 +- src/lib/services/RuleService.ts | 313 +++++---- src/routes/+page.server.ts | 26 +- .../account/update-password/+page.server.ts | 104 ++- src/routes/api/auth/login/+server.ts | 4 +- src/routes/api/auth/logout/+server.ts | 5 +- src/routes/api/auth/register/+server.ts | 94 +-- src/routes/api/auth/status/+server.ts | 69 +- src/routes/api/devices/[devEui]/+server.ts | 161 ++--- .../api/devices/[devEui]/downlink/+server.ts | 126 ++-- .../devices/[devEui]/permissions/+server.ts | 135 ++-- .../permissions/[permissionId]/+server.ts | 197 +++--- .../api/devices/[devEui]/rules/+server.ts | 98 +-- .../rules/[ruleGroupId]/criteria/+server.ts | 88 +-- .../[devEui]/rules/[ruleId]/+server.ts | 159 ++--- .../rules/criteria/[criteriaId]/+server.ts | 94 +-- src/routes/api/locations/+server.ts | 86 ++- .../locations/[locationId]/devices/+server.ts | 143 ++--- .../[locationId]/permissions/+server.ts | 124 ---- src/routes/api/permissions/levels/+server.ts | 55 +- src/routes/api/users/+server.ts | 44 +- .../location/[location_id]/+layout.server.ts | 6 +- .../location/[location_id]/+page.server.ts | 6 +- .../[location_id]/devices/+page.server.ts | 6 +- .../devices/[devEui]/settings/+page.server.ts | 1 - .../settings/permissions/+page.server.ts | 1 - .../[devEui]/settings/rules/+page.server.ts | 19 +- .../[location_id]/settings/+page.server.ts | 17 +- .../auth/forgot-password/+page.server.ts | 107 ++-- src/routes/auth/register/+page.server.ts | 26 +- static/build-info.json | 8 +- 49 files changed, 2409 insertions(+), 2850 deletions(-) delete mode 100644 src/lib/server/ioc.config.ts delete mode 100644 src/lib/server/ioc.types.ts diff --git a/README.md b/README.md index 2f124dc4..d9427807 100644 --- a/README.md +++ b/README.md @@ -78,7 +78,7 @@ Link : https://app.CropWatch.io - PWA Framework: Svelte, SvelteKit - UI Components: @cropwatchdevelopment/cwui, bits-ui, @revolist/svelte-datagrid - Styling: Tailwind CSS, @layerstack/tailwind, @tailwindcss/forms, @tailwindcss/typography -- State & IoC: @stencil/store, inversify +- State Management: @stencil/store - Data Visualization: D3.js, ApexCharts - Mapping: Leaflet - PDF Generation: jspdf, pdfkit, html2canvas diff --git a/package.json b/package.json index 6b25d84c..7f4ce99b 100644 --- a/package.json +++ b/package.json @@ -78,13 +78,11 @@ "d3-axis": "^3.0.0", "d3-scale": "^4.0.2", "d3-selection": "^3.0.0", - "inversify": "7.0.0-alpha.5", "jspdf": "^3.0.1", "leaflet": "2.0.0-alpha", "lodash": "^4.17.21", "luxon": "^3.6.1", "pdfkit": "^0.17.1", - "reflect-metadata": "^0.2.2", "stripe": "^18.3.0", "svelte-i18n": "^4.0.1", "svelte-ux": "^1.0.4", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8d428598..a9888816 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -56,9 +56,6 @@ importers: d3-selection: specifier: ^3.0.0 version: 3.0.0 - inversify: - specifier: 7.0.0-alpha.5 - version: 7.0.0-alpha.5(reflect-metadata@0.2.2) jspdf: specifier: ^3.0.1 version: 3.0.1 @@ -74,9 +71,6 @@ importers: pdfkit: specifier: ^0.17.1 version: 0.17.1 - reflect-metadata: - specifier: ^0.2.2 - version: 0.2.2 stripe: specifier: ^18.3.0 version: 18.3.0(@types/node@24.0.3) @@ -1157,25 +1151,6 @@ packages: '@internationalized/date@3.8.2': resolution: {integrity: sha512-/wENk7CbvLbkUvX1tu0mwq49CVkkWpkXubGel6birjRPyo6uQ4nQpnq5xZu823zRCwwn82zgHrvgF1vZyvmVgA==} - '@inversifyjs/common@1.5.0': - resolution: {integrity: sha512-Qj5BELk11AfI2rgZEAaLPmOftmQRLLmoCXgAjmaF0IngQN5vHomVT5ML7DZ3+CA5fgGcEVMcGbUDAun+Rz+oNg==} - - '@inversifyjs/container@1.5.2': - resolution: {integrity: sha512-z0h5iwzA/Gv/1K1ZFHj4VAXpyShOez61g4vWMaWpY4gKidsIbnn01ArRbKbsTKzwW63DniAGMhtnUO+y3htcpQ==} - peerDependencies: - reflect-metadata: ~0.2.2 - - '@inversifyjs/core@4.0.0': - resolution: {integrity: sha512-uNK3aFEAn5mnQ1TbPqDDG/4BMNGPZ8Jb+cDW2ualjNCiMqtIDWP2gBQf0zahxSbZnc5iVrkk7ftjX3Raze8BLQ==} - - '@inversifyjs/prototype-utils@0.1.0': - resolution: {integrity: sha512-lNz1yyajMRDXBHLvJsYYX81FcmeD15e5Qz1zAZ/3zeYTl+u7ZF/GxNRKJzNOloeMPMtuR8BnvzHA1SZxjR+J9w==} - - '@inversifyjs/reflect-metadata-utils@1.1.0': - resolution: {integrity: sha512-jmuAuC3eL1GnFAYfJGJOMKRDL9q1mgzOyrban6zxfM8Yg1FUHsj25h27bW2G7p8X1Amvhg3MLkaOuogszkrofA==} - peerDependencies: - reflect-metadata: 0.2.2 - '@isaacs/cliui@8.0.2': resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} engines: {node: '>=12'} @@ -3243,11 +3218,6 @@ packages: invariant@2.2.4: resolution: {integrity: sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==} - inversify@7.0.0-alpha.5: - resolution: {integrity: sha512-fl09CyoTZXUc26m0GK65Cxx8B3MNjVb2vhdD/o03Vn9yXGFiQuRInvc3WC64j4QZl2UtRD9kK3Spk1junADT8g==} - peerDependencies: - reflect-metadata: ~0.2.2 - is-alphabetical@1.0.4: resolution: {integrity: sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==} @@ -4265,9 +4235,6 @@ packages: redux@5.0.1: resolution: {integrity: sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==} - reflect-metadata@0.2.2: - resolution: {integrity: sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==} - reflect.getprototypeof@1.0.10: resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==} engines: {node: '>= 0.4'} @@ -6290,31 +6257,6 @@ snapshots: dependencies: '@swc/helpers': 0.5.17 - '@inversifyjs/common@1.5.0': {} - - '@inversifyjs/container@1.5.2(reflect-metadata@0.2.2)': - dependencies: - '@inversifyjs/common': 1.5.0 - '@inversifyjs/core': 4.0.0(reflect-metadata@0.2.2) - '@inversifyjs/reflect-metadata-utils': 1.1.0(reflect-metadata@0.2.2) - reflect-metadata: 0.2.2 - - '@inversifyjs/core@4.0.0(reflect-metadata@0.2.2)': - dependencies: - '@inversifyjs/common': 1.5.0 - '@inversifyjs/prototype-utils': 0.1.0 - '@inversifyjs/reflect-metadata-utils': 1.1.0(reflect-metadata@0.2.2) - transitivePeerDependencies: - - reflect-metadata - - '@inversifyjs/prototype-utils@0.1.0': - dependencies: - '@inversifyjs/common': 1.5.0 - - '@inversifyjs/reflect-metadata-utils@1.1.0(reflect-metadata@0.2.2)': - dependencies: - reflect-metadata: 0.2.2 - '@isaacs/cliui@8.0.2': dependencies: string-width: 5.1.2 @@ -8887,13 +8829,6 @@ snapshots: dependencies: loose-envify: 1.4.0 - inversify@7.0.0-alpha.5(reflect-metadata@0.2.2): - dependencies: - '@inversifyjs/common': 1.5.0 - '@inversifyjs/container': 1.5.2(reflect-metadata@0.2.2) - '@inversifyjs/core': 4.0.0(reflect-metadata@0.2.2) - reflect-metadata: 0.2.2 - is-alphabetical@1.0.4: {} is-alphanumerical@1.0.4: @@ -9772,8 +9707,6 @@ snapshots: redux@5.0.1: {} - reflect-metadata@0.2.2: {} - reflect.getprototypeof@1.0.10: dependencies: call-bind: 1.0.8 diff --git a/src/hooks.server.ts b/src/hooks.server.ts index 4f58d343..bdcbb0d8 100644 --- a/src/hooks.server.ts +++ b/src/hooks.server.ts @@ -1,4 +1,3 @@ -import 'reflect-metadata'; import { error, redirect, type Handle } from '@sveltejs/kit'; import { createServerClient } from '@supabase/ssr'; import { PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY } from '$env/static/public'; diff --git a/src/lib/errors/ErrorHandlingService.ts b/src/lib/errors/ErrorHandlingService.ts index 2844aa0f..a7342f2d 100644 --- a/src/lib/errors/ErrorHandlingService.ts +++ b/src/lib/errors/ErrorHandlingService.ts @@ -1,4 +1,3 @@ -import { injectable } from 'inversify'; import { AppError } from './AppError'; import { DatabaseError, NotFoundError } from './SpecificErrors'; import type { PostgrestError } from '@supabase/supabase-js'; @@ -6,76 +5,75 @@ import type { PostgrestError } from '@supabase/supabase-js'; /** * Service for handling errors in a centralized way */ -@injectable() export class ErrorHandlingService { - /** - * Handle Supabase database errors - * Transforms Supabase errors into domain-specific errors - * @param error The Supabase error to handle - * @param customMessage Optional custom error message - */ - handleDatabaseError(error: PostgrestError | null, customMessage?: string): never { - if (!error) { - throw new DatabaseError('Unknown database error occurred'); - } + /** + * Handle Supabase database errors + * Transforms Supabase errors into domain-specific errors + * @param error The Supabase error to handle + * @param customMessage Optional custom error message + */ + handleDatabaseError(error: PostgrestError | null, customMessage?: string): never { + if (!error) { + throw new DatabaseError('Unknown database error occurred'); + } - // Handle specific PostgreSQL error codes - switch (error.code) { - case '23505': // unique_violation - throw new DatabaseError(customMessage || 'Duplicate entry found', error); - case '23503': // foreign_key_violation - throw new DatabaseError(customMessage || 'Referenced record not found', error); - case '42P01': // undefined_table - throw new DatabaseError(customMessage || 'Table not found', error); - case '42703': // undefined_column - throw new DatabaseError(customMessage || 'Column not found', error); - default: - throw new DatabaseError( - customMessage || `Database error: ${error.message || 'Unknown error'}`, - error - ); - } - } + // Handle specific PostgreSQL error codes + switch (error.code) { + case '23505': // unique_violation + throw new DatabaseError(customMessage || 'Duplicate entry found', error); + case '23503': // foreign_key_violation + throw new DatabaseError(customMessage || 'Referenced record not found', error); + case '42P01': // undefined_table + throw new DatabaseError(customMessage || 'Table not found', error); + case '42703': // undefined_column + throw new DatabaseError(customMessage || 'Column not found', error); + default: + throw new DatabaseError( + customMessage || `Database error: ${error.message || 'Unknown error'}`, + error + ); + } + } - /** - * Handle case when no record is found - * @param entityName Name of the entity that wasn't found (e.g., 'Device') - * @param identifier Identifier that was used to look up the entity - */ - handleNotFound(entityName: string, identifier: string | number): never { - throw new NotFoundError(`${entityName} not found with identifier: ${identifier}`); - } + /** + * Handle case when no record is found + * @param entityName Name of the entity that wasn't found (e.g., 'Device') + * @param identifier Identifier that was used to look up the entity + */ + handleNotFound(entityName: string, identifier: string | number): never { + throw new NotFoundError(`${entityName} not found with identifier: ${identifier}`); + } - /** - * Log errors to the appropriate logging service - * Can be expanded to send errors to external monitoring services - * @param error The error to log - */ - logError(error: Error | AppError): void { - const isAppError = error instanceof AppError; - - // Log different levels based on error type - if (isAppError && error.statusCode >= 500) { - console.error('[SERVER ERROR]', { - message: error.message, - errorCode: (error as AppError).errorCode, - stack: error.stack, - isOperational: (error as AppError).isOperational - }); - } else if (isAppError && error.statusCode >= 400) { - console.warn('[CLIENT ERROR]', { - message: error.message, - errorCode: (error as AppError).errorCode, - isOperational: (error as AppError).isOperational - }); - } else { - console.error('[UNEXPECTED ERROR]', { - message: error.message, - stack: error.stack - }); - } + /** + * Log errors to the appropriate logging service + * Can be expanded to send errors to external monitoring services + * @param error The error to log + */ + logError(error: Error | AppError): void { + const isAppError = error instanceof AppError; - // Here you could add additional logging to external services - // like Sentry, LogRocket, etc. - } -} \ No newline at end of file + // Log different levels based on error type + if (isAppError && error.statusCode >= 500) { + console.error('[SERVER ERROR]', { + message: error.message, + errorCode: (error as AppError).errorCode, + stack: error.stack, + isOperational: (error as AppError).isOperational + }); + } else if (isAppError && error.statusCode >= 400) { + console.warn('[CLIENT ERROR]', { + message: error.message, + errorCode: (error as AppError).errorCode, + isOperational: (error as AppError).isOperational + }); + } else { + console.error('[UNEXPECTED ERROR]', { + message: error.message, + stack: error.stack + }); + } + + // Here you could add additional logging to external services + // like Sentry, LogRocket, etc. + } +} diff --git a/src/lib/repositories/AirDataRepository.ts b/src/lib/repositories/AirDataRepository.ts index 0e5594de..dc66085c 100644 --- a/src/lib/repositories/AirDataRepository.ts +++ b/src/lib/repositories/AirDataRepository.ts @@ -1,95 +1,89 @@ -import { inject, injectable } from 'inversify'; import type { SupabaseClient } from '@supabase/supabase-js'; import { BaseRepository } from './BaseRepository'; import type { AirData } from '../models/AirData'; -import { TYPES } from '../server/ioc.types'; import { ErrorHandlingService } from '../errors/ErrorHandlingService'; /** * Repository for air data access */ -@injectable() export class AirDataRepository extends BaseRepository { - protected tableName = 'cw_air_data'; - protected primaryKey = 'dev_eui'; - protected entityName = 'AirData'; + protected tableName = 'cw_air_data'; + protected primaryKey = 'dev_eui'; + protected entityName = 'AirData'; - /** - * Constructor with Supabase client dependency - */ - constructor( - @inject(TYPES.SupabaseClient) supabase: SupabaseClient, - @inject(ErrorHandlingService) errorHandler: ErrorHandlingService - ) { - super(supabase, errorHandler); - } + /** + * Constructor with Supabase client dependency + */ + constructor(supabase: SupabaseClient, errorHandler: ErrorHandlingService) { + super(supabase, errorHandler); + } - /** - * Find air data by device EUI - * @param devEui The device EUI - */ - async findByDeviceEui(devEui: string): Promise { - const { data, error } = await this.supabase - .from(this.tableName) - .select('*') - .eq('dev_eui', devEui) - .order('created_at', { ascending: false }); - - if (error) { - this.errorHandler.handleDatabaseError( - error, - `Error finding air data by device EUI: ${devEui}` - ); - } - - return data as AirData[] || []; - } + /** + * Find air data by device EUI + * @param devEui The device EUI + */ + async findByDeviceEui(devEui: string): Promise { + const { data, error } = await this.supabase + .from(this.tableName) + .select('*') + .eq('dev_eui', devEui) + .order('created_at', { ascending: false }); - /** - * Find latest air data for a device - * @param devEui The device EUI - */ - async findLatestByDeviceEui(devEui: string): Promise { - const { data, error } = await this.supabase - .from(this.tableName) - .select('*') - .eq('dev_eui', devEui) - .order('created_at', { ascending: false }) - .limit(1) - .single(); - - if (error) { - this.errorHandler.handleDatabaseError( - error, - `Error finding latest air data by device EUI: ${devEui}` - ); - } - - return data as AirData; - } + if (error) { + this.errorHandler.handleDatabaseError( + error, + `Error finding air data by device EUI: ${devEui}` + ); + } - /** - * Find air data by date range - * @param devEui The device EUI - * @param startDate The start date - * @param endDate The end date - */ - async findByDateRange(devEui: string, startDate: Date, endDate: Date): Promise { - const { data, error } = await this.supabase - .from(this.tableName) - .select('*') - .eq('dev_eui', devEui) - .gte('created_at', startDate.toISOString()) - .lte('created_at', endDate.toISOString()) - .order('created_at', { ascending: true }); - - if (error) { - this.errorHandler.handleDatabaseError( - error, - `Error finding air data by date range for device: ${devEui}` - ); - } - - return data as AirData[] || []; - } -} \ No newline at end of file + return (data as AirData[]) || []; + } + + /** + * Find latest air data for a device + * @param devEui The device EUI + */ + async findLatestByDeviceEui(devEui: string): Promise { + const { data, error } = await this.supabase + .from(this.tableName) + .select('*') + .eq('dev_eui', devEui) + .order('created_at', { ascending: false }) + .limit(1) + .single(); + + if (error) { + this.errorHandler.handleDatabaseError( + error, + `Error finding latest air data by device EUI: ${devEui}` + ); + } + + return data as AirData; + } + + /** + * Find air data by date range + * @param devEui The device EUI + * @param startDate The start date + * @param endDate The end date + */ + async findByDateRange(devEui: string, startDate: Date, endDate: Date): Promise { + const { data, error } = await this.supabase + .from(this.tableName) + .select('*') + .eq('dev_eui', devEui) + .gte('created_at', startDate.toISOString()) + .lte('created_at', endDate.toISOString()) + .order('created_at', { ascending: true }); + + if (error) { + this.errorHandler.handleDatabaseError( + error, + `Error finding air data by date range for device: ${devEui}` + ); + } + + return (data as AirData[]) || []; + } +} diff --git a/src/lib/repositories/DeviceOwnersRepository.ts b/src/lib/repositories/DeviceOwnersRepository.ts index abe11a1c..22de6dc1 100644 --- a/src/lib/repositories/DeviceOwnersRepository.ts +++ b/src/lib/repositories/DeviceOwnersRepository.ts @@ -1,98 +1,92 @@ import type { SupabaseClient } from '@supabase/supabase-js'; import { BaseRepository } from './BaseRepository'; -import type { DeviceOwner, DeviceOwnerInsert, DeviceOwnerUpdate, DeviceOwnerWithProfile } from '../models/DeviceOwner'; +import type { + DeviceOwner, + DeviceOwnerInsert, + DeviceOwnerUpdate, + DeviceOwnerWithProfile +} from '../models/DeviceOwner'; import { ErrorHandlingService } from '../errors/ErrorHandlingService'; -import { injectable, inject } from 'inversify'; -import { TYPES } from '$lib/server/ioc.types'; /** * Repository for device owner data access */ -@injectable() export class DeviceOwnersRepository extends BaseRepository { - protected tableName = 'cw_device_owners'; - protected primaryKey = 'id'; - protected entityName = 'DeviceOwner'; - - /** - * Constructor with Supabase client and error handler dependencies - */ - constructor( - @inject(TYPES.SupabaseClient) supabase: SupabaseClient, - @inject(TYPES.ErrorHandlingService) errorHandler: ErrorHandlingService - ) { - super(supabase, errorHandler); - } - - /** - * Find all device owners - */ - async findAll(): Promise { - const { data, error } = await this.supabase - .from(this.tableName) - .select('*') - .order('id'); - - if (error) { - this.errorHandler.handleDatabaseError( - error, - 'Error finding all device owners' - ); - } - - return data as DeviceOwner[] || []; - } - - /** - * Find device owners by device EUI - * @param devEui The device EUI - */ - async findByDeviceEui(devEui: string): Promise { - const { data, error } = await this.supabase - .from(this.tableName) - .select('*') - .eq('dev_eui', devEui) - .order('id'); - - if (error) { - this.errorHandler.handleDatabaseError( - error, - `Error finding device owners by device EUI: ${devEui}` - ); - } - - return data as DeviceOwner[] || []; - } - - /** - * Find device owners by user ID - * @param userId The user ID - */ - async findByUserId(userId: string): Promise { - const { data, error } = await this.supabase - .from(this.tableName) - .select('*') - .eq('user_id', userId) - .order('id'); - - if (error) { - this.errorHandler.handleDatabaseError( - error, - `Error finding device owners by user ID: ${userId}` - ); - } - - return data as DeviceOwner[] || []; - } - - /** - * Find device owners with profile information by device EUI - * @param devEui The device EUI - */ - async findWithProfilesByDeviceEui(devEui: string): Promise { - const { data, error } = await this.supabase - .from(this.tableName) - .select(` + protected tableName = 'cw_device_owners'; + protected primaryKey = 'id'; + protected entityName = 'DeviceOwner'; + + /** + * Constructor with Supabase client and error handler dependencies + */ + constructor(supabase: SupabaseClient, errorHandler: ErrorHandlingService) { + super(supabase, errorHandler); + } + + /** + * Find all device owners + */ + async findAll(): Promise { + const { data, error } = await this.supabase.from(this.tableName).select('*').order('id'); + + if (error) { + this.errorHandler.handleDatabaseError(error, 'Error finding all device owners'); + } + + return (data as DeviceOwner[]) || []; + } + + /** + * Find device owners by device EUI + * @param devEui The device EUI + */ + async findByDeviceEui(devEui: string): Promise { + const { data, error } = await this.supabase + .from(this.tableName) + .select('*') + .eq('dev_eui', devEui) + .order('id'); + + if (error) { + this.errorHandler.handleDatabaseError( + error, + `Error finding device owners by device EUI: ${devEui}` + ); + } + + return (data as DeviceOwner[]) || []; + } + + /** + * Find device owners by user ID + * @param userId The user ID + */ + async findByUserId(userId: string): Promise { + const { data, error } = await this.supabase + .from(this.tableName) + .select('*') + .eq('user_id', userId) + .order('id'); + + if (error) { + this.errorHandler.handleDatabaseError( + error, + `Error finding device owners by user ID: ${userId}` + ); + } + + return (data as DeviceOwner[]) || []; + } + + /** + * Find device owners with profile information by device EUI + * @param devEui The device EUI + */ + async findWithProfilesByDeviceEui(devEui: string): Promise { + const { data, error } = await this.supabase + .from(this.tableName) + .select( + ` *, profiles!user_id( id, @@ -100,87 +94,94 @@ export class DeviceOwnersRepository extends BaseRepository email, username ) - `) - .eq('dev_eui', devEui) - .order('permission_level', { ascending: false }); - - if (error) { - this.errorHandler.handleDatabaseError( - error, - `Error finding device owners with profiles by device EUI: ${devEui}` - ); - } - - // Transform the data to match our DeviceOwnerWithProfile interface - const transformedData = data?.map(item => ({ - ...item, - profile: Array.isArray(item.profiles) ? item.profiles[0] : item.profiles - })) as DeviceOwnerWithProfile[] || []; - - return transformedData; - } - - /** - * Find a specific device owner entry - * @param devEui The device EUI - * @param userId The user ID - */ - async findByDeviceAndUser(devEui: string, userId: string): Promise { - const { data, error } = await this.supabase - .from(this.tableName) - .select('*') - .eq('dev_eui', devEui) - .eq('user_id', userId) - .single(); - - if (error) { - // Don't log error for "not found" cases as they might be expected - if (error.code !== 'PGRST116') { - this.errorHandler.handleDatabaseError( - error, - `Error finding device owner by device EUI ${devEui} and user ID ${userId}` - ); - } - return null; - } - - return data as DeviceOwner; - } - - /** - * Check if a user has permission for a device - * @param devEui The device EUI - * @param userId The user ID - * @param minimumPermission The minimum permission level required (optional) - */ - async hasPermission(devEui: string, userId: string, minimumPermission?: number): Promise { - const { data, error } = await this.supabase - .from(this.tableName) - .select('permission_level') - .eq('dev_eui', devEui) - .eq('user_id', userId) - .single(); - - if (error || !data) { - return false; - } - - if (minimumPermission !== undefined) { - // Lower numbers = higher permissions (1=Admin, 2=Editor, 3=Viewer) - return data.permission_level <= minimumPermission; - } - - return true; - } - - /** - * Get devices owned by a user - * @param userId The user ID - */ - async getDevicesByUserId(userId: string): Promise { - const { data, error } = await this.supabase - .from(this.tableName) - .select(` + ` + ) + .eq('dev_eui', devEui) + .order('permission_level', { ascending: false }); + + if (error) { + this.errorHandler.handleDatabaseError( + error, + `Error finding device owners with profiles by device EUI: ${devEui}` + ); + } + + // Transform the data to match our DeviceOwnerWithProfile interface + const transformedData = + (data?.map((item) => ({ + ...item, + profile: Array.isArray(item.profiles) ? item.profiles[0] : item.profiles + })) as DeviceOwnerWithProfile[]) || []; + + return transformedData; + } + + /** + * Find a specific device owner entry + * @param devEui The device EUI + * @param userId The user ID + */ + async findByDeviceAndUser(devEui: string, userId: string): Promise { + const { data, error } = await this.supabase + .from(this.tableName) + .select('*') + .eq('dev_eui', devEui) + .eq('user_id', userId) + .single(); + + if (error) { + // Don't log error for "not found" cases as they might be expected + if (error.code !== 'PGRST116') { + this.errorHandler.handleDatabaseError( + error, + `Error finding device owner by device EUI ${devEui} and user ID ${userId}` + ); + } + return null; + } + + return data as DeviceOwner; + } + + /** + * Check if a user has permission for a device + * @param devEui The device EUI + * @param userId The user ID + * @param minimumPermission The minimum permission level required (optional) + */ + async hasPermission( + devEui: string, + userId: string, + minimumPermission?: number + ): Promise { + const { data, error } = await this.supabase + .from(this.tableName) + .select('permission_level') + .eq('dev_eui', devEui) + .eq('user_id', userId) + .single(); + + if (error || !data) { + return false; + } + + if (minimumPermission !== undefined) { + // Lower numbers = higher permissions (1=Admin, 2=Editor, 3=Viewer) + return data.permission_level <= minimumPermission; + } + + return true; + } + + /** + * Get devices owned by a user + * @param userId The user ID + */ + async getDevicesByUserId(userId: string): Promise { + const { data, error } = await this.supabase + .from(this.tableName) + .select( + ` *, cw_devices!dev_eui( dev_eui, @@ -188,108 +189,94 @@ export class DeviceOwnersRepository extends BaseRepository description, location_id ) - `) - .eq('user_id', userId) - .order('permission_level', { ascending: false }); - - if (error) { - this.errorHandler.handleDatabaseError( - error, - `Error finding devices by user ID: ${userId}` - ); - } - - return data as DeviceOwner[] || []; - } - - /** - * Create a new device owner entry - * @param deviceOwner The device owner data to insert - */ - async create(deviceOwner: DeviceOwnerInsert): Promise { - const { data, error } = await this.supabase - .from(this.tableName) - .insert(deviceOwner) - .select() - .single(); - - if (error) { - this.errorHandler.handleDatabaseError( - error, - 'Error creating device owner' - ); - } - - return data as DeviceOwner; - } - - /** - * Update a device owner entry - * @param id The device owner ID - * @param updates The updates to apply - */ - async update(id: number, updates: DeviceOwnerUpdate): Promise { - const { data, error } = await this.supabase - .from(this.tableName) - .update(updates) - .eq(this.primaryKey, id) - .select() - .single(); - - if (error) { - this.errorHandler.handleDatabaseError( - error, - `Error updating device owner with ID: ${id}` - ); - } - - return data as DeviceOwner; - } - - /** - * Delete a device owner entry - * @param id The device owner ID - */ - async delete(id: number): Promise { - const { error } = await this.supabase - .from(this.tableName) - .delete() - .eq(this.primaryKey, id); - - if (error) { - this.errorHandler.handleDatabaseError( - error, - `Error deleting device owner with ID: ${id}` - ); - } - } - - /** - * Delete device owner entries by device EUI and user ID - * @param devEui The device EUI - * @param userId The user ID - */ - async deleteByDeviceAndUser(devEui: string, userId: string): Promise { - const { error } = await this.supabase - .from(this.tableName) - .delete() - .eq('dev_eui', devEui) - .eq('user_id', userId); - - if (error) { - this.errorHandler.handleDatabaseError( - error, - `Error deleting device owner by device EUI ${devEui} and user ID ${userId}` - ); - } - } - - /** - * Update permission level for a device owner - * @param id The device owner ID - * @param permissionLevel The new permission level - */ - async updatePermission(id: number, permissionLevel: number): Promise { - return this.update(id, { permission_level: permissionLevel }); - } + ` + ) + .eq('user_id', userId) + .order('permission_level', { ascending: false }); + + if (error) { + this.errorHandler.handleDatabaseError(error, `Error finding devices by user ID: ${userId}`); + } + + return (data as DeviceOwner[]) || []; + } + + /** + * Create a new device owner entry + * @param deviceOwner The device owner data to insert + */ + async create(deviceOwner: DeviceOwnerInsert): Promise { + const { data, error } = await this.supabase + .from(this.tableName) + .insert(deviceOwner) + .select() + .single(); + + if (error) { + this.errorHandler.handleDatabaseError(error, 'Error creating device owner'); + } + + return data as DeviceOwner; + } + + /** + * Update a device owner entry + * @param id The device owner ID + * @param updates The updates to apply + */ + async update(id: number, updates: DeviceOwnerUpdate): Promise { + const { data, error } = await this.supabase + .from(this.tableName) + .update(updates) + .eq(this.primaryKey, id) + .select() + .single(); + + if (error) { + this.errorHandler.handleDatabaseError(error, `Error updating device owner with ID: ${id}`); + } + + return data as DeviceOwner; + } + + /** + * Delete a device owner entry + * @param id The device owner ID + */ + async delete(id: number): Promise { + const { error } = await this.supabase.from(this.tableName).delete().eq(this.primaryKey, id); + + if (error) { + this.errorHandler.handleDatabaseError(error, `Error deleting device owner with ID: ${id}`); + } + } + + /** + * Delete device owner entries by device EUI and user ID + * @param devEui The device EUI + * @param userId The user ID + */ + async deleteByDeviceAndUser(devEui: string, userId: string): Promise { + const { error } = await this.supabase + .from(this.tableName) + .delete() + .eq('dev_eui', devEui) + .eq('user_id', userId); + + if (error) { + this.errorHandler.handleDatabaseError( + error, + `Error deleting device owner by device EUI ${devEui} and user ID ${userId}` + ); + } + } + + /** + * Update permission level for a device owner + * @param id The device owner ID + * @param permissionLevel The new permission level + */ + async updatePermission(id: number, permissionLevel: number): Promise { + return this.update(id, { permission_level: permissionLevel }); + } } diff --git a/src/lib/repositories/DeviceRepository.ts b/src/lib/repositories/DeviceRepository.ts index 662c19b1..b078d812 100644 --- a/src/lib/repositories/DeviceRepository.ts +++ b/src/lib/repositories/DeviceRepository.ts @@ -3,228 +3,214 @@ import { BaseRepository } from './BaseRepository'; import type { Device, DeviceWithType, DeviceLocation } from '../models/Device'; import { ErrorHandlingService } from '../errors/ErrorHandlingService'; import type { Database } from '../../../database.types'; -import { injectable, inject } from 'inversify'; -import { TYPES } from '$lib/server/ioc.types'; /** * Type for device with joined location and device type data */ export type DeviceWithJoins = Device & { - cw_device_type: Database['public']['Tables']['cw_device_type']['Row']; - cw_locations: (DeviceLocation & { - cw_location_owners: Database['public']['Tables']['cw_location_owners']['Row'][]; - })[]; + cw_device_type: Database['public']['Tables']['cw_device_type']['Row']; + cw_locations: (DeviceLocation & { + cw_location_owners: Database['public']['Tables']['cw_location_owners']['Row'][]; + })[]; }; /** * Repository for device data access */ -@injectable() export class DeviceRepository extends BaseRepository { - protected tableName = 'cw_devices'; - protected primaryKey = 'dev_eui'; - protected entityName = 'Device'; - - /** - * Constructor with Supabase client and error handler dependencies - */ - constructor( - @inject(TYPES.SupabaseClient) supabase: SupabaseClient, - @inject(TYPES.ErrorHandlingService) errorHandler: ErrorHandlingService - ) { - super(supabase, errorHandler); - } - - /** - * Get a device with its type information - * @param devEui The device EUI - */ - async getDeviceWithType(devEui: string): Promise { - const { data, error } = await this.supabase - .from(this.tableName) - .select(` + protected tableName = 'cw_devices'; + protected primaryKey = 'dev_eui'; + protected entityName = 'Device'; + + /** + * Constructor with Supabase client and error handler dependencies + */ + constructor(supabase: SupabaseClient, errorHandler: ErrorHandlingService) { + super(supabase, errorHandler); + } + + /** + * Get a device with its type information + * @param devEui The device EUI + */ + async getDeviceWithType(devEui: string): Promise { + const { data, error } = await this.supabase + .from(this.tableName) + .select( + ` *, cw_device_type(*), ip_log(*) - `) - .eq(this.primaryKey, devEui) - .single(); - - if (error) { - this.errorHandler.handleDatabaseError( - error, - `Error finding device with type by EUI: ${devEui}` - ); - } - - return data as DeviceWithType; - } - - /** - * Get devices by location ID - * @param locationId The location ID - */ - async findByLocation(locationId: number): Promise { - const session = await this.supabase.auth.getSession(); - - // Define the query with proper typing - const query = this.supabase - .from(this.tableName) - .select(` + ` + ) + .eq(this.primaryKey, devEui) + .single(); + + if (error) { + this.errorHandler.handleDatabaseError( + error, + `Error finding device with type by EUI: ${devEui}` + ); + } + + return data as DeviceWithType; + } + + /** + * Get devices by location ID + * @param locationId The location ID + */ + async findByLocation(locationId: number): Promise { + const session = await this.supabase.auth.getSession(); + + // Define the query with proper typing + const query = this.supabase + .from(this.tableName) + .select( + ` *, cw_device_type(*), cw_locations(*, cw_location_owners(*)), ip_log(*) - `) - .eq('location_id', locationId) - .eq('cw_locations.cw_location_owners.user_id', session.data.session?.user.id); - - const { data, error } = await query; - - if (error) { - this.errorHandler.handleDatabaseError( - error, - `Error finding devices by location: ${locationId}` - ); - } - - return data as DeviceWithJoins[] || []; - } - - /** - * Get all devices for a location without auth filtering - * @param locationId The location ID - */ - async getAllDevicesForLocation(locationId: number): Promise { - // Define the query with proper typing - const query = this.supabase - .from(this.tableName) - .select('*') - .eq('location_id', locationId); - - const { data, error } = await query; - - if (error) { - this.errorHandler.handleDatabaseError( - error, - `Error finding all devices by location: ${locationId}` - ); - } - - return data as Device[] || []; - } - - /** - * Get devices by type ID - * @param typeId The device type ID - */ - async findByType(typeId: number): Promise { - const { data, error } = await this.supabase - .from(this.tableName) - .select('*') - .eq('type', typeId); - - if (error) { - this.errorHandler.handleDatabaseError( - error, - `Error finding devices by type: ${typeId}` - ); - } - - return data as Device[] || []; - } - - /** - * Find a device owner entry - * @param devEui The device EUI - * @param userId The user ID - */ - async findDeviceOwner(devEui: string, userId: string): Promise<{ id: number | string } | null> { - - const { data: ownerData, error: ownerError } = await this.supabase - .from('cw_devices') - .select('user_id') - .eq('dev_eui', devEui) - .eq('user_id', userId) - .single(); - - if (ownerData) { - return ownerData.user_id; - } - - const { data, error } = await this.supabase - .from('cw_device_owners') - .select('id') - .eq('dev_eui', devEui) - .eq('user_id', userId) - .single(); - - if (error) { - return null; - } - - return data; - } - - /** - * Add a user to a device with a specified permission level - * @param devEui The device EUI - * @param userId The user ID - * @param permissionLevel The permission level to assign - */ - async addUserToDevice(devEui: string, userId: string, permissionLevel: number): Promise { - const { error } = await this.supabase - .from('cw_device_owners') - .insert({ - dev_eui: devEui, - user_id: userId, - permission_level: permissionLevel - }); - - if (error) { - this.errorHandler.handleDatabaseError( - error, - `Error adding user ${userId} to device ${devEui}` - ); - } - } - - /** - * Update a user's permission for a device - * @param deviceOwnerId The device owner entry ID - * @param permissionLevel The new permission level - */ - async updateDevicePermission(deviceOwnerId: number, permissionLevel: number): Promise { - const { error } = await this.supabase - .from('cw_device_owners') - .update({ permission_level: permissionLevel }) - .eq('id', deviceOwnerId); - - if (error) { - this.errorHandler.handleDatabaseError( - error, - `Error updating permission for device owner ${deviceOwnerId}` - ); - } - } - - /** - * Remove a user from a device - * @param devEui The device EUI - * @param userId The user ID to remove - */ - async removeUserFromDevice(devEui: string, userId: string): Promise { - const { error } = await this.supabase - .from('cw_device_owners') - .delete() - .eq('dev_eui', devEui) - .eq('user_id', userId); - - if (error) { - this.errorHandler.handleDatabaseError( - error, - `Error removing user ${userId} from device ${devEui}` - ); - } - } -} \ No newline at end of file + ` + ) + .eq('location_id', locationId) + .eq('cw_locations.cw_location_owners.user_id', session.data.session?.user.id); + + const { data, error } = await query; + + if (error) { + this.errorHandler.handleDatabaseError( + error, + `Error finding devices by location: ${locationId}` + ); + } + + return (data as DeviceWithJoins[]) || []; + } + + /** + * Get all devices for a location without auth filtering + * @param locationId The location ID + */ + async getAllDevicesForLocation(locationId: number): Promise { + // Define the query with proper typing + const query = this.supabase.from(this.tableName).select('*').eq('location_id', locationId); + + const { data, error } = await query; + + if (error) { + this.errorHandler.handleDatabaseError( + error, + `Error finding all devices by location: ${locationId}` + ); + } + + return (data as Device[]) || []; + } + + /** + * Get devices by type ID + * @param typeId The device type ID + */ + async findByType(typeId: number): Promise { + const { data, error } = await this.supabase.from(this.tableName).select('*').eq('type', typeId); + + if (error) { + this.errorHandler.handleDatabaseError(error, `Error finding devices by type: ${typeId}`); + } + + return (data as Device[]) || []; + } + + /** + * Find a device owner entry + * @param devEui The device EUI + * @param userId The user ID + */ + async findDeviceOwner(devEui: string, userId: string): Promise<{ id: number | string } | null> { + const { data: ownerData, error: ownerError } = await this.supabase + .from('cw_devices') + .select('user_id') + .eq('dev_eui', devEui) + .eq('user_id', userId) + .single(); + + if (ownerData) { + return ownerData.user_id; + } + + const { data, error } = await this.supabase + .from('cw_device_owners') + .select('id') + .eq('dev_eui', devEui) + .eq('user_id', userId) + .single(); + + if (error) { + return null; + } + + return data; + } + + /** + * Add a user to a device with a specified permission level + * @param devEui The device EUI + * @param userId The user ID + * @param permissionLevel The permission level to assign + */ + async addUserToDevice(devEui: string, userId: string, permissionLevel: number): Promise { + const { error } = await this.supabase.from('cw_device_owners').insert({ + dev_eui: devEui, + user_id: userId, + permission_level: permissionLevel + }); + + if (error) { + this.errorHandler.handleDatabaseError( + error, + `Error adding user ${userId} to device ${devEui}` + ); + } + } + + /** + * Update a user's permission for a device + * @param deviceOwnerId The device owner entry ID + * @param permissionLevel The new permission level + */ + async updateDevicePermission(deviceOwnerId: number, permissionLevel: number): Promise { + const { error } = await this.supabase + .from('cw_device_owners') + .update({ permission_level: permissionLevel }) + .eq('id', deviceOwnerId); + + if (error) { + this.errorHandler.handleDatabaseError( + error, + `Error updating permission for device owner ${deviceOwnerId}` + ); + } + } + + /** + * Remove a user from a device + * @param devEui The device EUI + * @param userId The user ID to remove + */ + async removeUserFromDevice(devEui: string, userId: string): Promise { + const { error } = await this.supabase + .from('cw_device_owners') + .delete() + .eq('dev_eui', devEui) + .eq('user_id', userId); + + if (error) { + this.errorHandler.handleDatabaseError( + error, + `Error removing user ${userId} from device ${devEui}` + ); + } + } +} diff --git a/src/lib/repositories/DeviceTypeRepository.ts b/src/lib/repositories/DeviceTypeRepository.ts index 90e9ec50..475c8714 100644 --- a/src/lib/repositories/DeviceTypeRepository.ts +++ b/src/lib/repositories/DeviceTypeRepository.ts @@ -1,219 +1,198 @@ -import { inject, injectable } from 'inversify'; import type { SupabaseClient } from '@supabase/supabase-js'; import { BaseRepository } from './BaseRepository'; import type { DeviceType } from '../models/Device'; -import { TYPES } from '../server/ioc.types'; import { ErrorHandlingService } from '../errors/ErrorHandlingService'; /** * Repository for device type data access (read-only operations) */ -@injectable() export class DeviceTypeRepository extends BaseRepository { - protected tableName = 'cw_device_type'; - protected primaryKey = 'id'; - protected entityName = 'DeviceType'; - - /** - * Constructor with Supabase client and error handler dependencies - */ - constructor( - @inject(TYPES.SupabaseClient) supabase: SupabaseClient, - @inject(ErrorHandlingService) errorHandler: ErrorHandlingService - ) { - super(supabase, errorHandler); - } - - /** - * Find all device types - */ - async findAll(): Promise { - const { data, error } = await this.supabase - .from(this.tableName) - .select('*') - .order('name'); - - if (error) { - this.errorHandler.handleDatabaseError( - error, - 'Error finding all device types' - ); - } - - return data as DeviceType[] || []; - } - - /** - * Find device type by ID - * @param id The device type ID - */ - async findById(id: number): Promise { - const { data, error } = await this.supabase - .from(this.tableName) - .select('*') - .eq('id', id) - .single(); - - if (error) { - // For "no rows found" error, return null - if (error.code === 'PGRST116') { - return null; - } - - this.errorHandler.handleDatabaseError( - error, - `Error finding device type with ID: ${id}` - ); - } - - return data as DeviceType; - } - - /** - * Find active device types - */ - async findActive(): Promise { - const { data, error } = await this.supabase - .from(this.tableName) - .select('*') - .eq('isActive', true) - .order('name'); - - if (error) { - this.errorHandler.handleDatabaseError( - error, - 'Error finding active device types' - ); - } - - return data as DeviceType[] || []; - } - - /** - * Find device types by manufacturer - * @param manufacturer The manufacturer name - */ - async findByManufacturer(manufacturer: string): Promise { - const { data, error } = await this.supabase - .from(this.tableName) - .select('*') - .ilike('manufacturer', `%${manufacturer}%`) - .order('name'); - - if (error) { - this.errorHandler.handleDatabaseError( - error, - `Error finding device types by manufacturer: ${manufacturer}` - ); - } - - return data as DeviceType[] || []; - } - - /** - * Find device types by model - * @param model The model name or pattern - */ - async findByModel(model: string): Promise { - const { data, error } = await this.supabase - .from(this.tableName) - .select('*') - .ilike('model', `%${model}%`) - .order('name'); - - if (error) { - this.errorHandler.handleDatabaseError( - error, - `Error finding device types by model: ${model}` - ); - } - - return data as DeviceType[] || []; - } - - /** - * Find device types by data table - * @param dataTable The data table name - */ - async findByDataTable(dataTable: string): Promise { - const { data, error } = await this.supabase - .from(this.tableName) - .select('*') - .eq('data_table_v2', dataTable) - .order('name'); - - if (error) { - this.errorHandler.handleDatabaseError( - error, - `Error finding device types by data table: ${dataTable}` - ); - } - - return data as DeviceType[] || []; - } - - /** - * Search for device types by name - * @param searchTerm The search term to match against the device type name - */ - async searchByName(searchTerm: string): Promise { - const { data, error } = await this.supabase - .from(this.tableName) - .select('*') - .ilike('name', `%${searchTerm}%`) - .order('name'); - - if (error) { - this.errorHandler.handleDatabaseError( - error, - `Error searching device types by name: ${searchTerm}` - ); - } - - return data as DeviceType[] || []; - } - - /** - * Get device types with default upload interval within a range - * @param minInterval Minimum upload interval - * @param maxInterval Maximum upload interval - */ - async findByUploadIntervalRange(minInterval: number, maxInterval: number): Promise { - const { data, error } = await this.supabase - .from(this.tableName) - .select('*') - .gte('default_upload_interval', minInterval) - .lte('default_upload_interval', maxInterval) - .order('default_upload_interval'); - - if (error) { - this.errorHandler.handleDatabaseError( - error, - `Error finding device types by upload interval range: ${minInterval}-${maxInterval}` - ); - } - - return data as DeviceType[] || []; - } - - /** - * Find device types by primary data type - * @param dataType Primary data type (e.g., "temperature", "humidity") - */ - async findByPrimaryDataType(dataType: string): Promise { - const { data, error } = await this.supabase - .from(this.tableName) - .select('*') - .ilike('primary_data_notation', `%${dataType}%`) - .order('name'); - - if (error) { - this.errorHandler.handleDatabaseError( - error, - `Error finding device types by primary data type: ${dataType}` - ); - } - - return data as DeviceType[] || []; - } -} \ No newline at end of file + protected tableName = 'cw_device_type'; + protected primaryKey = 'id'; + protected entityName = 'DeviceType'; + + /** + * Constructor with Supabase client and error handler dependencies + */ + constructor(supabase: SupabaseClient, errorHandler: ErrorHandlingService) { + super(supabase, errorHandler); + } + + /** + * Find all device types + */ + async findAll(): Promise { + const { data, error } = await this.supabase.from(this.tableName).select('*').order('name'); + + if (error) { + this.errorHandler.handleDatabaseError(error, 'Error finding all device types'); + } + + return (data as DeviceType[]) || []; + } + + /** + * Find device type by ID + * @param id The device type ID + */ + async findById(id: number): Promise { + const { data, error } = await this.supabase + .from(this.tableName) + .select('*') + .eq('id', id) + .single(); + + if (error) { + // For "no rows found" error, return null + if (error.code === 'PGRST116') { + return null; + } + + this.errorHandler.handleDatabaseError(error, `Error finding device type with ID: ${id}`); + } + + return data as DeviceType; + } + + /** + * Find active device types + */ + async findActive(): Promise { + const { data, error } = await this.supabase + .from(this.tableName) + .select('*') + .eq('isActive', true) + .order('name'); + + if (error) { + this.errorHandler.handleDatabaseError(error, 'Error finding active device types'); + } + + return (data as DeviceType[]) || []; + } + + /** + * Find device types by manufacturer + * @param manufacturer The manufacturer name + */ + async findByManufacturer(manufacturer: string): Promise { + const { data, error } = await this.supabase + .from(this.tableName) + .select('*') + .ilike('manufacturer', `%${manufacturer}%`) + .order('name'); + + if (error) { + this.errorHandler.handleDatabaseError( + error, + `Error finding device types by manufacturer: ${manufacturer}` + ); + } + + return (data as DeviceType[]) || []; + } + + /** + * Find device types by model + * @param model The model name or pattern + */ + async findByModel(model: string): Promise { + const { data, error } = await this.supabase + .from(this.tableName) + .select('*') + .ilike('model', `%${model}%`) + .order('name'); + + if (error) { + this.errorHandler.handleDatabaseError(error, `Error finding device types by model: ${model}`); + } + + return (data as DeviceType[]) || []; + } + + /** + * Find device types by data table + * @param dataTable The data table name + */ + async findByDataTable(dataTable: string): Promise { + const { data, error } = await this.supabase + .from(this.tableName) + .select('*') + .eq('data_table_v2', dataTable) + .order('name'); + + if (error) { + this.errorHandler.handleDatabaseError( + error, + `Error finding device types by data table: ${dataTable}` + ); + } + + return (data as DeviceType[]) || []; + } + + /** + * Search for device types by name + * @param searchTerm The search term to match against the device type name + */ + async searchByName(searchTerm: string): Promise { + const { data, error } = await this.supabase + .from(this.tableName) + .select('*') + .ilike('name', `%${searchTerm}%`) + .order('name'); + + if (error) { + this.errorHandler.handleDatabaseError( + error, + `Error searching device types by name: ${searchTerm}` + ); + } + + return (data as DeviceType[]) || []; + } + + /** + * Get device types with default upload interval within a range + * @param minInterval Minimum upload interval + * @param maxInterval Maximum upload interval + */ + async findByUploadIntervalRange(minInterval: number, maxInterval: number): Promise { + const { data, error } = await this.supabase + .from(this.tableName) + .select('*') + .gte('default_upload_interval', minInterval) + .lte('default_upload_interval', maxInterval) + .order('default_upload_interval'); + + if (error) { + this.errorHandler.handleDatabaseError( + error, + `Error finding device types by upload interval range: ${minInterval}-${maxInterval}` + ); + } + + return (data as DeviceType[]) || []; + } + + /** + * Find device types by primary data type + * @param dataType Primary data type (e.g., "temperature", "humidity") + */ + async findByPrimaryDataType(dataType: string): Promise { + const { data, error } = await this.supabase + .from(this.tableName) + .select('*') + .ilike('primary_data_notation', `%${dataType}%`) + .order('name'); + + if (error) { + this.errorHandler.handleDatabaseError( + error, + `Error finding device types by primary data type: ${dataType}` + ); + } + + return (data as DeviceType[]) || []; + } +} diff --git a/src/lib/repositories/LocationRepository.ts b/src/lib/repositories/LocationRepository.ts index 49b52c2f..57290f06 100644 --- a/src/lib/repositories/LocationRepository.ts +++ b/src/lib/repositories/LocationRepository.ts @@ -3,13 +3,10 @@ import { BaseRepository } from './BaseRepository'; import type { Location, LocationInsert, LocationUpdate } from '../models/Location'; import type { LocationUser } from '../models/LocationUser'; import { ErrorHandlingService } from '../errors/ErrorHandlingService'; -import { injectable, inject } from 'inversify'; -import { TYPES } from '$lib/server/ioc.types'; /** * Repository for location data access */ -@injectable() export class LocationRepository extends BaseRepository { protected tableName = 'cw_locations'; protected primaryKey = 'location_id'; @@ -18,10 +15,7 @@ export class LocationRepository extends BaseRepository { /** * Constructor with Supabase client and error handler dependencies */ - constructor( - @inject(TYPES.SupabaseClient) supabase: SupabaseClient, - @inject(TYPES.ErrorHandlingService) errorHandler: ErrorHandlingService - ) { + constructor(supabase: SupabaseClient, errorHandler: ErrorHandlingService) { super(supabase, errorHandler); } diff --git a/src/lib/repositories/NotifierTypeRepository.ts b/src/lib/repositories/NotifierTypeRepository.ts index 8dd6cc66..79fd368a 100644 --- a/src/lib/repositories/NotifierTypeRepository.ts +++ b/src/lib/repositories/NotifierTypeRepository.ts @@ -1,23 +1,17 @@ -import { inject, injectable } from 'inversify'; import type { SupabaseClient } from '@supabase/supabase-js'; import { BaseRepository } from './BaseRepository'; import type { NotifierType } from '../models/NotifierType'; -import { TYPES } from '../server/ioc.types'; import { ErrorHandlingService } from '../errors/ErrorHandlingService'; /** * Repository for notifier type data access */ -@injectable() export class NotifierTypeRepository extends BaseRepository { protected tableName = 'cw_notifier_types'; protected primaryKey = 'id'; protected entityName = 'NotifierType'; - constructor( - @inject(TYPES.SupabaseClient) supabase: SupabaseClient, - @inject(TYPES.ErrorHandlingService) errorHandler: ErrorHandlingService - ) { + constructor(supabase: SupabaseClient, errorHandler: ErrorHandlingService) { super(supabase, errorHandler); } diff --git a/src/lib/repositories/ReportTemplateRepository.ts b/src/lib/repositories/ReportTemplateRepository.ts index 7f46928f..bc01bfe4 100644 --- a/src/lib/repositories/ReportTemplateRepository.ts +++ b/src/lib/repositories/ReportTemplateRepository.ts @@ -1,33 +1,27 @@ import type { SupabaseClient } from '@supabase/supabase-js'; import type { Database } from '../../../database.types'; import { BaseRepository } from './BaseRepository'; -import { injectable, inject } from 'inversify'; import { ErrorHandlingService } from '../errors/ErrorHandlingService'; import type { ReportTemplate } from '../models/ReportTemplate'; -import { TYPES } from '../server/ioc.types'; -@injectable() export class ReportTemplateRepository extends BaseRepository { - protected tableName = 'reports_templates'; - protected primaryKey = 'id'; - protected entityName = 'Report'; + protected tableName = 'reports_templates'; + protected primaryKey = 'id'; + protected entityName = 'Report'; - constructor( - @inject(TYPES.SupabaseClient) supabase: SupabaseClient, - @inject(TYPES.ErrorHandlingService) errorHandler: ErrorHandlingService - ) { - super(supabase, errorHandler); - } + constructor(supabase: SupabaseClient, errorHandler: ErrorHandlingService) { + super(supabase, errorHandler); + } - async findByOwner(ownerId: string): Promise { - const { data, error } = await this.supabase - .from(this.tableName) - .select('*') - .eq('owner_id', ownerId) - .order('created_at', { ascending: false }); - if (error) { - this.errorHandler.handleDatabaseError(error, 'Error fetching user reports'); - } - return (data as ReportTemplate[]) || []; - } + async findByOwner(ownerId: string): Promise { + const { data, error } = await this.supabase + .from(this.tableName) + .select('*') + .eq('owner_id', ownerId) + .order('created_at', { ascending: false }); + if (error) { + this.errorHandler.handleDatabaseError(error, 'Error fetching user reports'); + } + return (data as ReportTemplate[]) || []; + } } diff --git a/src/lib/repositories/RuleRepository.ts b/src/lib/repositories/RuleRepository.ts index 4f48d9a7..47e516b3 100644 --- a/src/lib/repositories/RuleRepository.ts +++ b/src/lib/repositories/RuleRepository.ts @@ -1,325 +1,294 @@ -import { inject, injectable } from 'inversify'; import type { SupabaseClient } from '@supabase/supabase-js'; import { BaseRepository } from './BaseRepository'; -import type { - Rule, - RuleInsert, - RuleUpdate, - RuleCriteria, - RuleCriteriaInsert, - RuleWithCriteria, - RuleCriteriaUpdate +import type { + Rule, + RuleInsert, + RuleUpdate, + RuleCriteria, + RuleCriteriaInsert, + RuleWithCriteria, + RuleCriteriaUpdate } from '../models/Rule'; -import { TYPES } from '../server/ioc.types'; import { ErrorHandlingService } from '../errors/ErrorHandlingService'; /** * Repository for rule and rule criteria data access */ -@injectable() export class RuleRepository extends BaseRepository { - protected tableName = 'cw_rules'; - protected primaryKey = 'id'; - protected entityName = 'Rule'; - protected criteriaTable = 'cw_rule_criteria'; - - /** - * Constructor with Supabase client and error handler dependencies - */ - constructor( - @inject(TYPES.SupabaseClient) supabase: SupabaseClient, - @inject(ErrorHandlingService) errorHandler: ErrorHandlingService - ) { - super(supabase, errorHandler); - } - - /** - * Find rules by device EUI - * @param devEui The device EUI - */ - async findByDevice(devEui: string): Promise { - const { data, error } = await this.supabase - .from(this.tableName) - .select(`*, cw_rule_criteria(*)`) - .eq('dev_eui', devEui) - .order('name'); - - if (error) { - this.errorHandler.handleDatabaseError( - error, - `Error finding rules by device EUI: ${devEui}` - ); - } - - return data as Rule[] || []; - } - - /** - * Find rules and rule Criteria by device EUI - * @param devEui The device EUI - */ - async getRulesAndCriteriaByDevice(devEui: string): Promise { - const { data, error } = await this.supabase - .from(this.tableName) - .select(` + protected tableName = 'cw_rules'; + protected primaryKey = 'id'; + protected entityName = 'Rule'; + protected criteriaTable = 'cw_rule_criteria'; + + /** + * Constructor with Supabase client and error handler dependencies + */ + constructor(supabase: SupabaseClient, errorHandler: ErrorHandlingService) { + super(supabase, errorHandler); + } + + /** + * Find rules by device EUI + * @param devEui The device EUI + */ + async findByDevice(devEui: string): Promise { + const { data, error } = await this.supabase + .from(this.tableName) + .select(`*, cw_rule_criteria(*)`) + .eq('dev_eui', devEui) + .order('name'); + + if (error) { + this.errorHandler.handleDatabaseError(error, `Error finding rules by device EUI: ${devEui}`); + } + + return (data as Rule[]) || []; + } + + /** + * Find rules and rule Criteria by device EUI + * @param devEui The device EUI + */ + async getRulesAndCriteriaByDevice(devEui: string): Promise { + const { data, error } = await this.supabase + .from(this.tableName) + .select( + ` *, cw_rule_criteria(*) - `) - .eq('dev_eui', devEui) - .order('name'); - - if (error) { - this.errorHandler.handleDatabaseError( - error, - `Error finding rules by device EUI: ${devEui}` - ); - } - - return data as Rule[] || []; - } - - /** - * Find rules by profile ID - * @param profileId The profile ID - */ - async findByProfile(profileId: string): Promise { - const { data, error } = await this.supabase - .from(this.tableName) - .select('*') - .eq('profile_id', profileId) - .order('name'); - - if (error) { - this.errorHandler.handleDatabaseError( - error, - `Error finding rules by profile ID: ${profileId}` - ); - } - - return data as Rule[] || []; - } - - /** - * Find active rules - */ - async findActive(): Promise { - const { data, error } = await this.supabase - .from(this.tableName) - .select('*') - .eq('is_active', true) - .order('name'); - - if (error) { - this.errorHandler.handleDatabaseError( - error, - 'Error finding active rules' - ); - } - - return data as Rule[] || []; - } - - /** - * Find rule by rule group ID - * @param ruleGroupId The rule group ID - */ - async findByRuleGroup(ruleGroupId: string): Promise { - const { data, error } = await this.supabase - .from(this.tableName) - .select('*') - .eq('ruleGroupId', ruleGroupId) - .single(); - - if (error) { - // For "no rows found" error, return null - if (error.code === 'PGRST116') { - return null; - } - - this.errorHandler.handleDatabaseError( - error, - `Error finding rule by rule group ID: ${ruleGroupId}` - ); - } - - return data as Rule; - } - - /** - * Find criteria by rule group ID - * @param ruleGroupId The rule group ID - */ - async findCriteriaByGroup(ruleGroupId: string): Promise { - const { data, error } = await this.supabase - .from(this.criteriaTable) - .select('*') - .eq('ruleGroupId', ruleGroupId) - .order('id'); - - if (error) { - this.errorHandler.handleDatabaseError( - error, - `Error finding criteria by rule group ID: ${ruleGroupId}` - ); - } - - return data as RuleCriteria[] || []; - } - - /** - * Find rule with its criteria - * @param ruleGroupId The rule group ID - */ - async findRuleWithCriteria(ruleGroupId: string): Promise { - const rule = await this.findByRuleGroup(ruleGroupId); - - if (!rule) { - return null; - } - - const criteria = await this.findCriteriaByGroup(ruleGroupId); - - return { - ...rule, - criteria - }; - } - - /** - * Create a new rule - * @param rule The rule to create - */ - async create(rule: RuleInsert): Promise { - const { data, error } = await this.supabase - .from(this.tableName) - .insert(rule) - .select('*') - .single(); - - if (error) { - this.errorHandler.handleDatabaseError( - error, - 'Error creating rule' - ); - } - - return data as Rule; - } - - /** - * Create a new rule criteria - * @param criteria The rule criteria to create - */ - async createCriteria(criteria: RuleCriteriaInsert): Promise { - const { data, error } = await this.supabase - .from(this.criteriaTable) - .insert(criteria) - .select() - .single(); - - if (error) { - this.errorHandler.handleDatabaseError( - error, - 'Error creating rule criteria' - ); - } - - return data as RuleCriteria; - } - - /** - * Update an existing rule - * @param id The rule ID - * @param rule The rule data to update - */ - async update(id: number, rule: RuleUpdate): Promise { - const { data, error } = await this.supabase - .from(this.tableName) - .update(rule) - .eq(this.primaryKey, id) - .select('*') - .single(); - - if (error) { - // For "no rows found" error, return null - if (error.code === 'PGRST116') { - return null; - } - - this.errorHandler.handleDatabaseError( - error, - `Error updating rule with ID: ${id}` - ); - } - - return data as Rule; - } - - /** - * Update Criteria of an existing rule - * @param id The rule ID - * @param rule The rule data to update - */ - async updateCriteria(id: number, rule: RuleCriteriaUpdate): Promise { - const { data, error } = await this.supabase - .from(this.criteriaTable) - .update(rule) - .eq(this.primaryKey, id) - .select('*') - .single(); - - if (error) { - // For "no rows found" error, return null - if (error.code === 'PGRST116') { - return null; - } - - this.errorHandler.handleDatabaseError( - error, - `Error updating rule with ID: ${id}` - ); - } - - return data as RuleCriteria; - } - - /** - * Delete a rule criteria - * @param id The criteria ID to delete - */ - async deleteCriteria(id: number): Promise { - const { error } = await this.supabase - .from(this.criteriaTable) - .delete() - .eq('id', id); - - if (error) { - this.errorHandler.handleDatabaseError( - error, - `Error deleting rule criteria with ID: ${id}` - ); - } - - return true; - } - - /** - * Delete all criteria for a rule group - * @param ruleGroupId The rule group ID - */ - async deleteCriteriaByGroup(ruleGroupId: string): Promise { - const { error } = await this.supabase - .from(this.criteriaTable) - .delete() - .eq('ruleGroupId', ruleGroupId); - - if (error) { - this.errorHandler.handleDatabaseError( - error, - `Error deleting criteria for rule group: ${ruleGroupId}` - ); - } - - return true; - } -} \ No newline at end of file + ` + ) + .eq('dev_eui', devEui) + .order('name'); + + if (error) { + this.errorHandler.handleDatabaseError(error, `Error finding rules by device EUI: ${devEui}`); + } + + return (data as Rule[]) || []; + } + + /** + * Find rules by profile ID + * @param profileId The profile ID + */ + async findByProfile(profileId: string): Promise { + const { data, error } = await this.supabase + .from(this.tableName) + .select('*') + .eq('profile_id', profileId) + .order('name'); + + if (error) { + this.errorHandler.handleDatabaseError( + error, + `Error finding rules by profile ID: ${profileId}` + ); + } + + return (data as Rule[]) || []; + } + + /** + * Find active rules + */ + async findActive(): Promise { + const { data, error } = await this.supabase + .from(this.tableName) + .select('*') + .eq('is_active', true) + .order('name'); + + if (error) { + this.errorHandler.handleDatabaseError(error, 'Error finding active rules'); + } + + return (data as Rule[]) || []; + } + + /** + * Find rule by rule group ID + * @param ruleGroupId The rule group ID + */ + async findByRuleGroup(ruleGroupId: string): Promise { + const { data, error } = await this.supabase + .from(this.tableName) + .select('*') + .eq('ruleGroupId', ruleGroupId) + .single(); + + if (error) { + // For "no rows found" error, return null + if (error.code === 'PGRST116') { + return null; + } + + this.errorHandler.handleDatabaseError( + error, + `Error finding rule by rule group ID: ${ruleGroupId}` + ); + } + + return data as Rule; + } + + /** + * Find criteria by rule group ID + * @param ruleGroupId The rule group ID + */ + async findCriteriaByGroup(ruleGroupId: string): Promise { + const { data, error } = await this.supabase + .from(this.criteriaTable) + .select('*') + .eq('ruleGroupId', ruleGroupId) + .order('id'); + + if (error) { + this.errorHandler.handleDatabaseError( + error, + `Error finding criteria by rule group ID: ${ruleGroupId}` + ); + } + + return (data as RuleCriteria[]) || []; + } + + /** + * Find rule with its criteria + * @param ruleGroupId The rule group ID + */ + async findRuleWithCriteria(ruleGroupId: string): Promise { + const rule = await this.findByRuleGroup(ruleGroupId); + + if (!rule) { + return null; + } + + const criteria = await this.findCriteriaByGroup(ruleGroupId); + + return { + ...rule, + criteria + }; + } + + /** + * Create a new rule + * @param rule The rule to create + */ + async create(rule: RuleInsert): Promise { + const { data, error } = await this.supabase + .from(this.tableName) + .insert(rule) + .select('*') + .single(); + + if (error) { + this.errorHandler.handleDatabaseError(error, 'Error creating rule'); + } + + return data as Rule; + } + + /** + * Create a new rule criteria + * @param criteria The rule criteria to create + */ + async createCriteria(criteria: RuleCriteriaInsert): Promise { + const { data, error } = await this.supabase + .from(this.criteriaTable) + .insert(criteria) + .select() + .single(); + + if (error) { + this.errorHandler.handleDatabaseError(error, 'Error creating rule criteria'); + } + + return data as RuleCriteria; + } + + /** + * Update an existing rule + * @param id The rule ID + * @param rule The rule data to update + */ + async update(id: number, rule: RuleUpdate): Promise { + const { data, error } = await this.supabase + .from(this.tableName) + .update(rule) + .eq(this.primaryKey, id) + .select('*') + .single(); + + if (error) { + // For "no rows found" error, return null + if (error.code === 'PGRST116') { + return null; + } + + this.errorHandler.handleDatabaseError(error, `Error updating rule with ID: ${id}`); + } + + return data as Rule; + } + + /** + * Update Criteria of an existing rule + * @param id The rule ID + * @param rule The rule data to update + */ + async updateCriteria(id: number, rule: RuleCriteriaUpdate): Promise { + const { data, error } = await this.supabase + .from(this.criteriaTable) + .update(rule) + .eq(this.primaryKey, id) + .select('*') + .single(); + + if (error) { + // For "no rows found" error, return null + if (error.code === 'PGRST116') { + return null; + } + + this.errorHandler.handleDatabaseError(error, `Error updating rule with ID: ${id}`); + } + + return data as RuleCriteria; + } + + /** + * Delete a rule criteria + * @param id The criteria ID to delete + */ + async deleteCriteria(id: number): Promise { + const { error } = await this.supabase.from(this.criteriaTable).delete().eq('id', id); + + if (error) { + this.errorHandler.handleDatabaseError(error, `Error deleting rule criteria with ID: ${id}`); + } + + return true; + } + + /** + * Delete all criteria for a rule group + * @param ruleGroupId The rule group ID + */ + async deleteCriteriaByGroup(ruleGroupId: string): Promise { + const { error } = await this.supabase + .from(this.criteriaTable) + .delete() + .eq('ruleGroupId', ruleGroupId); + + if (error) { + this.errorHandler.handleDatabaseError( + error, + `Error deleting criteria for rule group: ${ruleGroupId}` + ); + } + + return true; + } +} diff --git a/src/lib/repositories/UserRepository.ts b/src/lib/repositories/UserRepository.ts index a0f753c1..af62e96e 100644 --- a/src/lib/repositories/UserRepository.ts +++ b/src/lib/repositories/UserRepository.ts @@ -1,39 +1,33 @@ -import { inject, injectable } from 'inversify'; import type { SupabaseClient } from '@supabase/supabase-js'; import { BaseRepository } from './BaseRepository'; import type { User } from '../models/User'; -import { TYPES } from '$lib/server/ioc.types'; import { ErrorHandlingService } from '../errors/ErrorHandlingService'; /** * Repository for user profile data access */ -@injectable() export class UserRepository extends BaseRepository { - protected tableName = 'profiles'; - protected primaryKey = 'id'; - protected entityName = 'User'; + protected tableName = 'profiles'; + protected primaryKey = 'id'; + protected entityName = 'User'; - constructor( - @inject(TYPES.SupabaseClient) supabase: SupabaseClient, - @inject(TYPES.ErrorHandlingService) errorHandler: ErrorHandlingService - ) { - super(supabase, errorHandler); - } + constructor(supabase: SupabaseClient, errorHandler: ErrorHandlingService) { + super(supabase, errorHandler); + } - /** - * Get all users ordered by full name - */ - async findAll(): Promise { - const { data, error } = await this.supabase - .from(this.tableName) - .select('id, email, full_name, username') - .order('full_name', { ascending: true }); + /** + * Get all users ordered by full name + */ + async findAll(): Promise { + const { data, error } = await this.supabase + .from(this.tableName) + .select('id, email, full_name, username') + .order('full_name', { ascending: true }); - if (error) { - this.errorHandler.handleDatabaseError(error, 'Error finding all users'); - } + if (error) { + this.errorHandler.handleDatabaseError(error, 'Error finding all users'); + } - return (data as User[]) || []; - } + return (data as User[]) || []; + } } diff --git a/src/lib/server/ioc.config.ts b/src/lib/server/ioc.config.ts deleted file mode 100644 index d96542d6..00000000 --- a/src/lib/server/ioc.config.ts +++ /dev/null @@ -1,87 +0,0 @@ -import 'reflect-metadata'; -import { Container } from 'inversify'; -import { createClient, type SupabaseClient } from '@supabase/supabase-js'; -import type { Database } from '../../database.types'; -import { TYPES } from './ioc.types'; - -// Interfaces -import type { ILocationService } from '../interfaces/ILocationService'; - -// Services -import { LocationService } from '../services/LocationService'; -import { ErrorHandlingService } from '../errors/ErrorHandlingService'; - -// Repositories -import { DeviceRepository } from '../repositories/DeviceRepository'; -import { LocationRepository } from '../repositories/LocationRepository'; -import { NotifierTypeRepository } from '../repositories/NotifierTypeRepository'; -import { UserRepository } from '../repositories/UserRepository'; -import { ReportTemplateRepository } from '../repositories/ReportTemplateRepository'; -import { ReportTemplateService } from '../services/ReportTemplateService'; -import { PUBLIC_SUPABASE_ANON_KEY, PUBLIC_SUPABASE_URL } from '$env/static/public'; -import { env } from '$env/dynamic/public'; - -// Create and configure the IoC container -const container = new Container({ defaultScope: 'Singleton' }); - -// Create a Supabase client for backend operations -container - .bind(TYPES.SupabaseClient) - .toDynamicValue(() => { - const supabaseUrl = PUBLIC_SUPABASE_URL || env.PUBLIC_SUPABASE_URL; - const supabaseAnonKey = PUBLIC_SUPABASE_ANON_KEY || env.PUBLIC_SUPABASE_ANON_KEY; - - if (!supabaseUrl || !supabaseAnonKey) { - throw new Error( - 'Supabase environment variables SUPABASE_URL and SUPABASE_ANON_KEY must be set.' - ); - } - return createClient(supabaseUrl, supabaseAnonKey, { - auth: { - persistSession: false, - autoRefreshToken: false - } - }); - }) - .inSingletonScope(); - -// Bind error handling service -container.bind(ErrorHandlingService).toSelf().inSingletonScope(); -container - .bind(TYPES.ErrorHandlingService) - .to(ErrorHandlingService) - .inSingletonScope(); - -// Bind repositories -container.bind(LocationRepository).toSelf().inSingletonScope(); -container - .bind(TYPES.LocationRepository) - .to(LocationRepository) - .inSingletonScope(); -container.bind(DeviceRepository).toSelf().inSingletonScope(); -container.bind(TYPES.DeviceRepository).to(DeviceRepository).inSingletonScope(); -container.bind(NotifierTypeRepository).toSelf().inSingletonScope(); -container - .bind(TYPES.NotifierTypeRepository) - .to(NotifierTypeRepository) - .inSingletonScope(); -container.bind(UserRepository).toSelf().inSingletonScope(); -container.bind(TYPES.UserRepository).to(UserRepository).inSingletonScope(); -container.bind(ReportTemplateRepository).toSelf().inSingletonScope(); -container - .bind(TYPES.ReportTemplateRepository) - .to(ReportTemplateRepository) - .inSingletonScope(); - -// Bind services -container.bind(LocationService).toSelf().inSingletonScope(); -container.bind(TYPES.LocationService).to(LocationService).inSingletonScope(); -container.bind(ReportTemplateService).toSelf().inSingletonScope(); -container - .bind(TYPES.ReportTemplateService) - .to(ReportTemplateService) - .inSingletonScope(); - -// Other services and repositories can be added back as needed - -export { container }; diff --git a/src/lib/server/ioc.types.ts b/src/lib/server/ioc.types.ts deleted file mode 100644 index 67041bc8..00000000 --- a/src/lib/server/ioc.types.ts +++ /dev/null @@ -1,29 +0,0 @@ -/** - * Types for dependency injection container - */ -export const TYPES = { - // Infrastructure - SupabaseClient: Symbol.for('SupabaseClient'), - ErrorHandlingService: Symbol.for('ErrorHandlingService'), - SessionService: Symbol.for('SessionService'), - - // Repositories - DeviceRepository: Symbol.for('DeviceRepository'), - DeviceOwnersRepository: Symbol.for('DeviceOwnersRepository'), - AirDataRepository: Symbol.for('AirDataRepository'), - LocationRepository: Symbol.for('LocationRepository'), - RuleRepository: Symbol.for('RuleRepository'), - NotifierTypeRepository: Symbol.for('NotifierTypeRepository'), - UserRepository: Symbol.for('UserRepository'), - ReportTemplateRepository: Symbol.for('ReportTemplateRepository'), - - // Services - DeviceService: Symbol.for('DeviceService'), - AirDataService: Symbol.for('AirDataService'), - SoilDataService: Symbol.for('SoilDataService'), - LocationService: Symbol.for('LocationService'), - RuleService: Symbol.for('RuleService'), - AuthService: Symbol.for('AuthService'), - DeviceDataService: Symbol.for('DeviceDataService'), - ReportTemplateService: Symbol.for('ReportTemplateService') -}; \ No newline at end of file diff --git a/src/lib/services/DeviceOwnersService.ts b/src/lib/services/DeviceOwnersService.ts index 07f1425a..eae2611e 100644 --- a/src/lib/services/DeviceOwnersService.ts +++ b/src/lib/services/DeviceOwnersService.ts @@ -1,189 +1,201 @@ import type { SupabaseClient } from '@supabase/supabase-js'; -import { injectable, inject } from 'inversify'; -import { TYPES } from '$lib/server/ioc.types'; import { DeviceOwnersRepository } from '../repositories/DeviceOwnersRepository'; -import type { DeviceOwner, DeviceOwnerInsert, DeviceOwnerUpdate, DeviceOwnerWithProfile } from '../models/DeviceOwner'; +import type { + DeviceOwner, + DeviceOwnerInsert, + DeviceOwnerUpdate, + DeviceOwnerWithProfile +} from '../models/DeviceOwner'; /** * Service for device owner business logic */ -@injectable() export class DeviceOwnersService { - - /** - * Constructor with DeviceOwnersRepository dependency - */ - constructor( - @inject(TYPES.DeviceOwnersRepository) private repository: DeviceOwnersRepository - ) {} - - /** - * Get a device owner by ID - * @param id The device owner ID - */ - async getById(id: number): Promise { - return await this.repository.findById(id); - } - - /** - * Get all device owners - */ - async getAll(): Promise { - return await this.repository.findAll(); - } - - /** - * Get device owners by device EUI - * @param devEui The device EUI - */ - async getByDeviceEui(devEui: string): Promise { - return await this.repository.findByDeviceEui(devEui); - } - - /** - * Get device owners by user ID - * @param userId The user ID - */ - async getByUserId(userId: string): Promise { - return await this.repository.findByUserId(userId); - } - - /** - * Get device owners with profile information by device EUI - * @param devEui The device EUI - */ - async getWithProfilesByDeviceEui(devEui: string): Promise { - return await this.repository.findWithProfilesByDeviceEui(devEui); - } - - /** - * Get a specific device owner entry - * @param devEui The device EUI - * @param userId The user ID - */ - async getByDeviceAndUser(devEui: string, userId: string): Promise { - return await this.repository.findByDeviceAndUser(devEui, userId); - } - - /** - * Check if a user has permission for a device - * @param devEui The device EUI - * @param userId The user ID - * @param minimumPermission The minimum permission level required (optional) - */ - async hasPermission(devEui: string, userId: string, minimumPermission?: number): Promise { - return await this.repository.hasPermission(devEui, userId, minimumPermission); - } - - /** - * Get devices owned by a user - * @param userId The user ID - */ - async getDevicesByUserId(userId: string): Promise { - return await this.repository.getDevicesByUserId(userId); - } - - /** - * Add a user to a device with a specified permission level - * @param devEui The device EUI - * @param userId The user ID - * @param permissionLevel The permission level to assign - */ - async addUserToDevice(devEui: string, userId: string, permissionLevel: number): Promise { - // Check if the user is already an owner of this device - const existingOwner = await this.repository.findByDeviceAndUser(devEui, userId); - if (existingOwner) { - throw new Error(`User ${userId} is already an owner of device ${devEui}`); - } - - const deviceOwner: DeviceOwnerInsert = { - dev_eui: devEui, - user_id: userId, - permission_level: permissionLevel - }; - - return await this.repository.create(deviceOwner); - } - - /** - * Update a device owner's permission level - * @param id The device owner ID - * @param permissionLevel The new permission level - */ - async updatePermission(id: number, permissionLevel: number): Promise { - return await this.repository.updatePermission(id, permissionLevel); - } - - /** - * Update a device owner entry - * @param id The device owner ID - * @param updates The updates to apply - */ - async update(id: number, updates: DeviceOwnerUpdate): Promise { - return await this.repository.update(id, updates); - } - - /** - * Remove a user from a device - * @param devEui The device EUI - * @param userId The user ID to remove - */ - async removeUserFromDevice(devEui: string, userId: string): Promise { - await this.repository.deleteByDeviceAndUser(devEui, userId); - } - - /** - * Remove a device owner by ID - * @param id The device owner ID - */ - async remove(id: number): Promise { - await this.repository.delete(id); - } - - /** - * Transfer device ownership - * @param devEui The device EUI - * @param fromUserId The current owner's user ID - * @param toUserId The new owner's user ID - * @param permissionLevel The permission level for the new owner - */ - async transferOwnership(devEui: string, fromUserId: string, toUserId: string, permissionLevel: number = 100): Promise { - // Check if the target user is already an owner - const existingOwner = await this.repository.findByDeviceAndUser(devEui, toUserId); - if (existingOwner) { - throw new Error(`User ${toUserId} is already an owner of device ${devEui}`); - } - - // Add the new owner - await this.addUserToDevice(devEui, toUserId, permissionLevel); - - // Remove the old owner - await this.removeUserFromDevice(devEui, fromUserId); - } - - /** - * Get the highest permission level for a user on a device - * @param devEui The device EUI - * @param userId The user ID - */ - async getUserPermissionLevel(devEui: string, userId: string): Promise { - const deviceOwner = await this.repository.findByDeviceAndUser(devEui, userId); - return deviceOwner?.permission_level || null; - } - - /** - * Check if a user is the primary owner (highest permission) of a device - * @param devEui The device EUI - * @param userId The user ID - */ - async isPrimaryOwner(devEui: string, userId: string): Promise { - const owners = await this.repository.findByDeviceEui(devEui); - if (owners.length === 0) return false; - - const userOwner = owners.find(owner => owner.user_id === userId); - if (!userOwner) return false; - - const maxPermission = Math.max(...owners.map(owner => owner.permission_level)); - return userOwner.permission_level === maxPermission; - } + /** + * Constructor with DeviceOwnersRepository dependency + */ + constructor(private repository: DeviceOwnersRepository) {} + + /** + * Get a device owner by ID + * @param id The device owner ID + */ + async getById(id: number): Promise { + return await this.repository.findById(id); + } + + /** + * Get all device owners + */ + async getAll(): Promise { + return await this.repository.findAll(); + } + + /** + * Get device owners by device EUI + * @param devEui The device EUI + */ + async getByDeviceEui(devEui: string): Promise { + return await this.repository.findByDeviceEui(devEui); + } + + /** + * Get device owners by user ID + * @param userId The user ID + */ + async getByUserId(userId: string): Promise { + return await this.repository.findByUserId(userId); + } + + /** + * Get device owners with profile information by device EUI + * @param devEui The device EUI + */ + async getWithProfilesByDeviceEui(devEui: string): Promise { + return await this.repository.findWithProfilesByDeviceEui(devEui); + } + + /** + * Get a specific device owner entry + * @param devEui The device EUI + * @param userId The user ID + */ + async getByDeviceAndUser(devEui: string, userId: string): Promise { + return await this.repository.findByDeviceAndUser(devEui, userId); + } + + /** + * Check if a user has permission for a device + * @param devEui The device EUI + * @param userId The user ID + * @param minimumPermission The minimum permission level required (optional) + */ + async hasPermission( + devEui: string, + userId: string, + minimumPermission?: number + ): Promise { + return await this.repository.hasPermission(devEui, userId, minimumPermission); + } + + /** + * Get devices owned by a user + * @param userId The user ID + */ + async getDevicesByUserId(userId: string): Promise { + return await this.repository.getDevicesByUserId(userId); + } + + /** + * Add a user to a device with a specified permission level + * @param devEui The device EUI + * @param userId The user ID + * @param permissionLevel The permission level to assign + */ + async addUserToDevice( + devEui: string, + userId: string, + permissionLevel: number + ): Promise { + // Check if the user is already an owner of this device + const existingOwner = await this.repository.findByDeviceAndUser(devEui, userId); + if (existingOwner) { + throw new Error(`User ${userId} is already an owner of device ${devEui}`); + } + + const deviceOwner: DeviceOwnerInsert = { + dev_eui: devEui, + user_id: userId, + permission_level: permissionLevel + }; + + return await this.repository.create(deviceOwner); + } + + /** + * Update a device owner's permission level + * @param id The device owner ID + * @param permissionLevel The new permission level + */ + async updatePermission(id: number, permissionLevel: number): Promise { + return await this.repository.updatePermission(id, permissionLevel); + } + + /** + * Update a device owner entry + * @param id The device owner ID + * @param updates The updates to apply + */ + async update(id: number, updates: DeviceOwnerUpdate): Promise { + return await this.repository.update(id, updates); + } + + /** + * Remove a user from a device + * @param devEui The device EUI + * @param userId The user ID to remove + */ + async removeUserFromDevice(devEui: string, userId: string): Promise { + await this.repository.deleteByDeviceAndUser(devEui, userId); + } + + /** + * Remove a device owner by ID + * @param id The device owner ID + */ + async remove(id: number): Promise { + await this.repository.delete(id); + } + + /** + * Transfer device ownership + * @param devEui The device EUI + * @param fromUserId The current owner's user ID + * @param toUserId The new owner's user ID + * @param permissionLevel The permission level for the new owner + */ + async transferOwnership( + devEui: string, + fromUserId: string, + toUserId: string, + permissionLevel: number = 100 + ): Promise { + // Check if the target user is already an owner + const existingOwner = await this.repository.findByDeviceAndUser(devEui, toUserId); + if (existingOwner) { + throw new Error(`User ${toUserId} is already an owner of device ${devEui}`); + } + + // Add the new owner + await this.addUserToDevice(devEui, toUserId, permissionLevel); + + // Remove the old owner + await this.removeUserFromDevice(devEui, fromUserId); + } + + /** + * Get the highest permission level for a user on a device + * @param devEui The device EUI + * @param userId The user ID + */ + async getUserPermissionLevel(devEui: string, userId: string): Promise { + const deviceOwner = await this.repository.findByDeviceAndUser(devEui, userId); + return deviceOwner?.permission_level || null; + } + + /** + * Check if a user is the primary owner (highest permission) of a device + * @param devEui The device EUI + * @param userId The user ID + */ + async isPrimaryOwner(devEui: string, userId: string): Promise { + const owners = await this.repository.findByDeviceEui(devEui); + if (owners.length === 0) return false; + + const userOwner = owners.find((owner) => owner.user_id === userId); + if (!userOwner) return false; + + const maxPermission = Math.max(...owners.map((owner) => owner.permission_level)); + return userOwner.permission_level === maxPermission; + } } diff --git a/src/lib/services/LocationService.ts b/src/lib/services/LocationService.ts index 2c979d1e..4834da67 100644 --- a/src/lib/services/LocationService.ts +++ b/src/lib/services/LocationService.ts @@ -3,8 +3,6 @@ import { LocationRepository } from '../repositories/LocationRepository'; import type { Location, LocationInsert, LocationUpdate } from '../models/Location'; import type { LocationUser } from '../models/LocationUser'; import { DeviceRepository } from '../repositories/DeviceRepository'; -import { injectable, inject } from 'inversify'; -import { TYPES } from '$lib/server/ioc.types'; import { ErrorHandlingService } from '../errors/ErrorHandlingService'; import { info } from '../utilities/logger'; @@ -12,14 +10,13 @@ import { info } from '../utilities/logger'; * Implementation of LocationService * This service handles all business logic related to locations */ -@injectable() export class LocationService implements ILocationService { /** * Constructor with repository dependencies */ constructor( - @inject(TYPES.LocationRepository) private locationRepository: LocationRepository, - @inject(TYPES.DeviceRepository) private deviceRepository: DeviceRepository, + private locationRepository: LocationRepository, + private deviceRepository: DeviceRepository, private errorHandler: ErrorHandlingService = new ErrorHandlingService() ) { info('starting location service...'); diff --git a/src/lib/services/ReportTemplateService.ts b/src/lib/services/ReportTemplateService.ts index ecfd9268..d18ee7e5 100644 --- a/src/lib/services/ReportTemplateService.ts +++ b/src/lib/services/ReportTemplateService.ts @@ -1,33 +1,34 @@ -import { injectable, inject } from 'inversify'; -import { TYPES } from '$lib/server/ioc.types'; import { ReportTemplateRepository } from '../repositories/ReportTemplateRepository'; -import type { ReportTemplate, ReportTemplateInsert, ReportTemplateUpdate } from '../models/ReportTemplate'; +import type { + ReportTemplate, + ReportTemplateInsert, + ReportTemplateUpdate +} from '../models/ReportTemplate'; import { ErrorHandlingService } from '../errors/ErrorHandlingService'; -@injectable() export class ReportTemplateService { - constructor( - @inject(TYPES.ReportTemplateRepository) private repo: ReportTemplateRepository, - private errorHandler: ErrorHandlingService = new ErrorHandlingService() - ) {} + constructor( + private repo: ReportTemplateRepository, + private errorHandler: ErrorHandlingService = new ErrorHandlingService() + ) {} - async getUserReports(userId: string): Promise { - return this.repo.findByOwner(userId); - } + async getUserReports(userId: string): Promise { + return this.repo.findByOwner(userId); + } - async getReport(id: number): Promise { - return this.repo.findById(id); - } + async getReport(id: number): Promise { + return this.repo.findById(id); + } - async createReport(report: ReportTemplateInsert): Promise { - return this.repo.create(report); - } + async createReport(report: ReportTemplateInsert): Promise { + return this.repo.create(report); + } - async updateReport(id: number, report: ReportTemplateUpdate): Promise { - return this.repo.update(id, report); - } + async updateReport(id: number, report: ReportTemplateUpdate): Promise { + return this.repo.update(id, report); + } - async deleteReport(id: number): Promise { - return this.repo.delete(id); - } + async deleteReport(id: number): Promise { + return this.repo.delete(id); + } } diff --git a/src/lib/services/RuleService.ts b/src/lib/services/RuleService.ts index 555edb72..0fd075bf 100644 --- a/src/lib/services/RuleService.ts +++ b/src/lib/services/RuleService.ts @@ -1,166 +1,165 @@ -import { inject, injectable } from 'inversify'; import type { IRuleService } from '../interfaces/IRuleService'; import { RuleRepository } from '../repositories/RuleRepository'; -import type { - Rule, - RuleInsert, - RuleUpdate, - RuleCriteria, - RuleCriteriaInsert, - RuleCriteriaUpdate, - RuleWithCriteria +import type { + Rule, + RuleInsert, + RuleUpdate, + RuleCriteria, + RuleCriteriaInsert, + RuleCriteriaUpdate, + RuleWithCriteria } from '../models/Rule'; /** * Implementation of RuleService * This service handles all business logic related to rules and criteria */ -@injectable() export class RuleService implements IRuleService { - /** - * Constructor with RuleRepository dependency - */ - constructor( - @inject(RuleRepository) private ruleRepository: RuleRepository - ) {} - - /** - * Get a rule by its ID - * @param id The rule ID - */ - async getRuleById(id: number): Promise { - return this.ruleRepository.findById(id); - } - - /** - * Get a rule with its criteria by group ID - * @param ruleGroupId The rule group ID - */ - async getRuleWithCriteria(ruleGroupId: string): Promise { - return this.ruleRepository.findRuleWithCriteria(ruleGroupId); - } - - /** - * Get rules by device EUI - * @param devEui The device EUI - */ - async getRulesByDevice(devEui: string): Promise { - return this.ruleRepository.findByDevice(devEui); - } - - /** - * Get rules with Criteria by device EUI - * @param devEui The device EUI - */ - getRulesAndCriteriaByDevice(devEui: string): Promise { - return this.ruleRepository.findRuleWithCriteria(devEui); - } - - /** - * Get rules by profile ID - * @param profileId The profile ID - */ - async getRulesByProfile(profileId: string): Promise { - return this.ruleRepository.findByProfile(profileId); - } - - /** - * Get rule criteria by rule group ID - * @param ruleGroupId The rule group ID - */ - async getRuleCriteriaByGroup(ruleGroupId: string): Promise { - return this.ruleRepository.findCriteriaByGroup(ruleGroupId); - } - - /** - * Create a new rule - * @param rule The rule to create - */ - async createRule(rule: RuleInsert): Promise { - return this.ruleRepository.create(rule); - } - - /** - * Create a new rule criteria - * @param criteria The rule criteria to create - */ - async createRuleCriteria(criteria: RuleCriteriaInsert): Promise { - return this.ruleRepository.createCriteria(criteria); - } - - /** - * Create a complete rule with criteria - * @param rule The rule to create - * @param criteria Array of criteria to create for the rule - */ - async createRuleWithCriteria(rule: RuleInsert, criteria: RuleCriteriaInsert[]): Promise { - // First create the rule - const createdRule = await this.createRule(rule); - - // Then create all the criteria - const createdCriteria: RuleCriteria[] = []; - - for (const criterion of criteria) { - // Ensure all criteria reference the same rule group ID - const criterionWithGroup = { - ...criterion, - ruleGroupId: createdRule.ruleGroupId - }; - - const created = await this.createRuleCriteria(criterionWithGroup); - createdCriteria.push(created); - } - - // Return the combined result - return { - ...createdRule, - criteria: createdCriteria - }; - } - - /** - * Update an existing rule - * @param id The rule ID - * @param rule The rule with updated values - */ - async updateRule(id: number, rule: RuleUpdate): Promise { - return this.ruleRepository.update(id, rule); - } - - /** - * Update an existing rule criteria - * @param id The criteria ID - * @param criteria The criteria with updated values - */ - async updateRuleCriteria(id: number, criteria: RuleCriteriaUpdate): Promise { - return this.ruleRepository.updateCriteria(id, criteria); - } - - /** - * Delete a rule and its associated criteria - * @param id The rule ID - */ - async deleteRule(id: number): Promise { - // First get the rule to find its group ID - const rule = await this.getRuleById(id); - - if (!rule) { - // Rule doesn't exist, so it's already effectively deleted - return true; - } - - // Delete all criteria associated with this rule - await this.ruleRepository.deleteCriteriaByGroup(rule.ruleGroupId); - - // Then delete the rule itself - return this.ruleRepository.delete(id); - } - - /** - * Delete a rule criteria - * @param id The criteria ID - */ - async deleteRuleCriteria(id: number): Promise { - return this.ruleRepository.deleteCriteria(id); - } -} \ No newline at end of file + /** + * Constructor with RuleRepository dependency + */ + constructor(private ruleRepository: RuleRepository) {} + + /** + * Get a rule by its ID + * @param id The rule ID + */ + async getRuleById(id: number): Promise { + return this.ruleRepository.findById(id); + } + + /** + * Get a rule with its criteria by group ID + * @param ruleGroupId The rule group ID + */ + async getRuleWithCriteria(ruleGroupId: string): Promise { + return this.ruleRepository.findRuleWithCriteria(ruleGroupId); + } + + /** + * Get rules by device EUI + * @param devEui The device EUI + */ + async getRulesByDevice(devEui: string): Promise { + return this.ruleRepository.findByDevice(devEui); + } + + /** + * Get rules with Criteria by device EUI + * @param devEui The device EUI + */ + getRulesAndCriteriaByDevice(devEui: string): Promise { + return this.ruleRepository.findRuleWithCriteria(devEui); + } + + /** + * Get rules by profile ID + * @param profileId The profile ID + */ + async getRulesByProfile(profileId: string): Promise { + return this.ruleRepository.findByProfile(profileId); + } + + /** + * Get rule criteria by rule group ID + * @param ruleGroupId The rule group ID + */ + async getRuleCriteriaByGroup(ruleGroupId: string): Promise { + return this.ruleRepository.findCriteriaByGroup(ruleGroupId); + } + + /** + * Create a new rule + * @param rule The rule to create + */ + async createRule(rule: RuleInsert): Promise { + return this.ruleRepository.create(rule); + } + + /** + * Create a new rule criteria + * @param criteria The rule criteria to create + */ + async createRuleCriteria(criteria: RuleCriteriaInsert): Promise { + return this.ruleRepository.createCriteria(criteria); + } + + /** + * Create a complete rule with criteria + * @param rule The rule to create + * @param criteria Array of criteria to create for the rule + */ + async createRuleWithCriteria( + rule: RuleInsert, + criteria: RuleCriteriaInsert[] + ): Promise { + // First create the rule + const createdRule = await this.createRule(rule); + + // Then create all the criteria + const createdCriteria: RuleCriteria[] = []; + + for (const criterion of criteria) { + // Ensure all criteria reference the same rule group ID + const criterionWithGroup = { + ...criterion, + ruleGroupId: createdRule.ruleGroupId + }; + + const created = await this.createRuleCriteria(criterionWithGroup); + createdCriteria.push(created); + } + + // Return the combined result + return { + ...createdRule, + criteria: createdCriteria + }; + } + + /** + * Update an existing rule + * @param id The rule ID + * @param rule The rule with updated values + */ + async updateRule(id: number, rule: RuleUpdate): Promise { + return this.ruleRepository.update(id, rule); + } + + /** + * Update an existing rule criteria + * @param id The criteria ID + * @param criteria The criteria with updated values + */ + async updateRuleCriteria(id: number, criteria: RuleCriteriaUpdate): Promise { + return this.ruleRepository.updateCriteria(id, criteria); + } + + /** + * Delete a rule and its associated criteria + * @param id The rule ID + */ + async deleteRule(id: number): Promise { + // First get the rule to find its group ID + const rule = await this.getRuleById(id); + + if (!rule) { + // Rule doesn't exist, so it's already effectively deleted + return true; + } + + // Delete all criteria associated with this rule + await this.ruleRepository.deleteCriteriaByGroup(rule.ruleGroupId); + + // Then delete the rule itself + return this.ruleRepository.delete(id); + } + + /** + * Delete a rule criteria + * @param id The criteria ID + */ + async deleteRuleCriteria(id: number): Promise { + return this.ruleRepository.deleteCriteria(id); + } +} diff --git a/src/routes/+page.server.ts b/src/routes/+page.server.ts index 21d6a024..7fad90b2 100644 --- a/src/routes/+page.server.ts +++ b/src/routes/+page.server.ts @@ -1,17 +1,17 @@ -import 'reflect-metadata'; -import { container } from '$lib/server/ioc.config'; -import { TYPES } from '$lib/server/ioc.types'; import type { PageServerLoad } from './$types'; -import type { IDeviceService } from '$lib/interfaces/IDeviceService'; -// export const load: PageServerLoad = async () => { -// // Get the device service from the IoC container -// const deviceService = container.get(TYPES.DeviceService); +export const load: PageServerLoad = async ({ locals: { supabase } }) => { + // Check if there are any devices in the database + const { data: devices, error } = await supabase.from('CW_DEVICES').select('id').limit(1); -// // Use the service to fetch devices -// const devices = await deviceService.getAllDevices(); + if (error) { + console.error('Error checking devices:', error); + return { + hasDevices: false + }; + } -// return { -// devices -// }; -// }; + return { + hasDevices: devices && devices.length > 0 + }; +}; diff --git a/src/routes/account/update-password/+page.server.ts b/src/routes/account/update-password/+page.server.ts index 6a243be7..b706e70a 100644 --- a/src/routes/account/update-password/+page.server.ts +++ b/src/routes/account/update-password/+page.server.ts @@ -1,61 +1,59 @@ import { fail, redirect } from '@sveltejs/kit'; import type { Actions, PageServerLoad } from './$types'; -import { container } from '$lib/server/ioc.config'; -import { TYPES } from '$lib/server/ioc.types'; -import type { ErrorHandlingService } from '$lib/errors/ErrorHandlingService'; +import { ErrorHandlingService } from '$lib/errors/ErrorHandlingService'; import { AuthService } from '$lib/services/AuthService'; // Make sure users are authenticated to access this page -export const load: PageServerLoad = async ({locals: { supabase }}) => { - // Get error handler from container - const errorHandler = container.get(TYPES.ErrorHandlingService); - // Create AuthService with the request's Supabase client - const authService = new AuthService(supabase, errorHandler); - const session = await authService.getSession(); - // If not logged in, redirect to login page - if (!session || (session && !session.user)) { - throw redirect(302, '/auth/login'); - } - - return {}; +export const load: PageServerLoad = async ({ locals: { supabase } }) => { + // Create services directly + const errorHandler = new ErrorHandlingService(); + // Create AuthService with the request's Supabase client + const authService = new AuthService(supabase, errorHandler); + const session = await authService.getSession(); + // If not logged in, redirect to login page + if (!session || (session && !session.user)) { + throw redirect(302, '/auth/login'); + } + + return {}; }; export const actions: Actions = { - default: async ({ request, locals: { supabase } }) => { - try { - // Check if user is authenticated - const errorHandler = container.get(TYPES.ErrorHandlingService); - // Create AuthService with the request's Supabase client - const authService = new AuthService(locals.supabase, errorHandler); - const session = await authService.getSession(); - if (!session) { - return fail(401, { error: 'You must be logged in to update your password' }); - } - - // Parse form data - const formData = await request.formData(); - const newPassword = formData.get('newPassword')?.toString() || ''; - const confirmPassword = formData.get('confirmPassword')?.toString() || ''; - - if (newPassword.length < 8) { - return fail(400, { error: 'New password must be at least 8 characters long' }); - } - - if (newPassword !== confirmPassword) { - return fail(400, { error: 'New passwords don\'t match' }); - } - - // Update the password - const result = await authService.updatePassword(newPassword); - - if (!result.success) { - return fail(400, { error: result.error || 'Failed to update password' }); - } - - return { success: true }; - } catch (error) { - console.error('Password update error:', error); - return fail(500, { error: 'An unexpected error occurred. Please try again later.' }); - } - } -}; \ No newline at end of file + default: async ({ request, locals: { supabase } }) => { + try { + // Check if user is authenticated + const errorHandler = new ErrorHandlingService(); + // Create AuthService with the request's Supabase client + const authService = new AuthService(supabase, errorHandler); + const session = await authService.getSession(); + if (!session) { + return fail(401, { error: 'You must be logged in to update your password' }); + } + + // Parse form data + const formData = await request.formData(); + const newPassword = formData.get('newPassword')?.toString() || ''; + const confirmPassword = formData.get('confirmPassword')?.toString() || ''; + + if (newPassword.length < 8) { + return fail(400, { error: 'New password must be at least 8 characters long' }); + } + + if (newPassword !== confirmPassword) { + return fail(400, { error: "New passwords don't match" }); + } + + // Update the password + const result = await authService.updatePassword(newPassword); + + if (!result.success) { + return fail(400, { error: result.error || 'Failed to update password' }); + } + + return { success: true }; + } catch (error) { + console.error('Password update error:', error); + return fail(500, { error: 'An unexpected error occurred. Please try again later.' }); + } + } +}; diff --git a/src/routes/api/auth/login/+server.ts b/src/routes/api/auth/login/+server.ts index 6d7fbf1a..0cc75808 100644 --- a/src/routes/api/auth/login/+server.ts +++ b/src/routes/api/auth/login/+server.ts @@ -1,9 +1,7 @@ import { json } from '@sveltejs/kit'; import type { RequestHandler } from './$types'; -import { TYPES } from '$lib/server/ioc.types'; import { AuthService } from '$lib/services/AuthService'; import { ErrorHandlingService } from '$lib/errors/ErrorHandlingService'; -import { container } from '$lib/server/ioc.config'; export const POST: RequestHandler = async ({ request, locals }) => { try { @@ -12,7 +10,7 @@ export const POST: RequestHandler = async ({ request, locals }) => { // Create a new AuthService instance with the per-request Supabase client // This ensures authentication state is isolated per user/request - const errorHandler = container.get(TYPES.ErrorHandlingService); + const errorHandler = new ErrorHandlingService(); const authService = new AuthService(locals.supabase, errorHandler); // Attempt to sign in with the per-request auth service diff --git a/src/routes/api/auth/logout/+server.ts b/src/routes/api/auth/logout/+server.ts index 049d1954..2d412bc6 100644 --- a/src/routes/api/auth/logout/+server.ts +++ b/src/routes/api/auth/logout/+server.ts @@ -1,7 +1,6 @@ import { json, redirect } from '@sveltejs/kit'; import type { RequestHandler } from './$types'; -import { container } from '$lib/server/ioc.config'; -import { TYPES } from '$lib/server/ioc.types'; + import type { IAuthService } from '$lib/interfaces/IAuthService'; import type { ISessionService } from '$lib/interfaces/ISessionService'; import { AuthService } from '$lib/services/AuthService'; @@ -13,7 +12,7 @@ export const POST: RequestHandler = async ({ locals, cookies }) => { // Create a new AuthService instance with the per-request Supabase client // This ensures authentication state is isolated per user/request - const errorHandler = container.get(TYPES.ErrorHandlingService); + const errorHandler = new ErrorHandlingService(); const authService = new AuthService(locals.supabase, errorHandler); // Sign out the user using the auth service diff --git a/src/routes/api/auth/register/+server.ts b/src/routes/api/auth/register/+server.ts index 4c7cdb10..f72690f3 100644 --- a/src/routes/api/auth/register/+server.ts +++ b/src/routes/api/auth/register/+server.ts @@ -1,47 +1,59 @@ import { json } from '@sveltejs/kit'; import type { RequestHandler } from './$types'; -import { container } from '$lib/server/ioc.config'; -import { TYPES } from '$lib/server/ioc.types'; import { AuthService } from '$lib/services/AuthService'; -import type { ErrorHandlingService } from '$lib/errors/ErrorHandlingService'; +import { ErrorHandlingService } from '$lib/errors/ErrorHandlingService'; export const POST: RequestHandler = async ({ request, locals }) => { try { // Parse the request body const userData = await request.json(); - + // Validate required fields - if (!userData.email || !userData.password || !userData.firstName || - !userData.lastName || !userData.company) { - return json({ - success: false, - error: 'All fields are required' - }, { status: 400 }); + if ( + !userData.email || + !userData.password || + !userData.firstName || + !userData.lastName || + !userData.company + ) { + return json( + { + success: false, + error: 'All fields are required' + }, + { status: 400 } + ); } - + // Validate email format const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; if (!emailRegex.test(userData.email)) { - return json({ - success: false, - error: 'Invalid email format' - }, { status: 400 }); + return json( + { + success: false, + error: 'Invalid email format' + }, + { status: 400 } + ); } - + // Validate password length if (userData.password.length < 8) { - return json({ - success: false, - error: 'Password must be at least 8 characters' - }, { status: 400 }); + return json( + { + success: false, + error: 'Password must be at least 8 characters' + }, + { status: 400 } + ); } - - // Get error handler from container - const errorHandler = container.get(TYPES.ErrorHandlingService); - + + // Get error handler + const errorHandler = new ErrorHandlingService(); + // Create AuthService with the request's Supabase client const authService = new AuthService(locals.supabase, errorHandler); - + // Register the user const result = await authService.register({ email: userData.email, @@ -50,25 +62,31 @@ export const POST: RequestHandler = async ({ request, locals }) => { lastName: userData.lastName, company: userData.company }); - + if (!result.success) { - return json({ - success: false, - error: result.error || 'Registration failed' - }, { status: 400 }); + return json( + { + success: false, + error: result.error || 'Registration failed' + }, + { status: 400 } + ); } - - return json({ + + return json({ success: true, message: 'Registration successful.', emailConfirmationRequired: result.emailConfirmationRequired || false }); } catch (err) { console.error('API Registration error:', err); - - return json({ - success: false, - error: 'An unexpected error occurred during registration' - }, { status: 500 }); + + return json( + { + success: false, + error: 'An unexpected error occurred during registration' + }, + { status: 500 } + ); } -}; \ No newline at end of file +}; diff --git a/src/routes/api/auth/status/+server.ts b/src/routes/api/auth/status/+server.ts index 2b2d7152..ddcce57e 100644 --- a/src/routes/api/auth/status/+server.ts +++ b/src/routes/api/auth/status/+server.ts @@ -1,42 +1,39 @@ import { json } from '@sveltejs/kit'; import type { RequestHandler } from './$types'; -import { container } from '$lib/server/ioc.config'; -import { TYPES } from '$lib/server/ioc.types'; import { AuthService } from '$lib/services/AuthService'; import { ErrorHandlingService } from '$lib/errors/ErrorHandlingService'; export const GET: RequestHandler = async ({ locals }) => { - try { - // Get error handler from container - const errorHandler = container.get(TYPES.ErrorHandlingService); - - // Create AuthService with the request's Supabase client - const authService = new AuthService(locals.supabase, errorHandler); - - // Get the current session - const sessionData = await authService.getSession(); - - if (!sessionData) { - return json({ authenticated: false }); - } - - const { user } = sessionData; - - // Return user data - return json({ - authenticated: true, - user: { - id: user.id, - email: user.email, - name: user.user_metadata?.name || user.email?.split('@')[0] || 'User' - } - }); - - } catch (error) { - console.error('Error checking authentication status:', error); - return json({ - authenticated: false, - reason: 'Error checking authentication status' - }); - } -}; \ No newline at end of file + try { + // Get error handler + const errorHandler = new ErrorHandlingService(); + + // Create AuthService with the request's Supabase client + const authService = new AuthService(locals.supabase, errorHandler); + + // Get the current session + const sessionData = await authService.getSession(); + + if (!sessionData) { + return json({ authenticated: false }); + } + + const { user } = sessionData; + + // Return user data + return json({ + authenticated: true, + user: { + id: user.id, + email: user.email, + name: user.user_metadata?.name || user.email?.split('@')[0] || 'User' + } + }); + } catch (error) { + console.error('Error checking authentication status:', error); + return json({ + authenticated: false, + reason: 'Error checking authentication status' + }); + } +}; diff --git a/src/routes/api/devices/[devEui]/+server.ts b/src/routes/api/devices/[devEui]/+server.ts index 26c5582c..2f54e8b8 100644 --- a/src/routes/api/devices/[devEui]/+server.ts +++ b/src/routes/api/devices/[devEui]/+server.ts @@ -1,89 +1,98 @@ -import type { ErrorHandlingService } from "$lib/errors/ErrorHandlingService"; -import type { IDeviceService } from "$lib/interfaces/IDeviceService"; -import { DeviceRepository } from "$lib/repositories/DeviceRepository"; -import { container } from "$lib/server/ioc.config"; -import { TYPES } from "$lib/server/ioc.types"; -import { DeviceService } from "$lib/services/DeviceService"; -import { error, json, type RequestHandler } from "@sveltejs/kit"; +import { ErrorHandlingService } from '$lib/errors/ErrorHandlingService'; +import { DeviceRepository } from '$lib/repositories/DeviceRepository'; +import { DeviceService } from '$lib/services/DeviceService'; +import { error, json, type RequestHandler } from '@sveltejs/kit'; -export const GET: RequestHandler = async ({ params, url, locals: { safeGetSession } }) => { - const { devEui } = params; - const { session, user } = await safeGetSession(); - if (!session) { - console.error('Session is null on device data API for device:', devEui); - } - if (!devEui) { - console.error('Device EUI is missing in the request'); - throw error(400, 'Bad Request - Device EUI is required'); - } +export const GET: RequestHandler = async ({ + params, + url, + locals: { safeGetSession, supabase } +}) => { + const { devEui } = params; + const { session, user } = await safeGetSession(); + if (!session) { + console.error('Session is null on device data API for device:', devEui); + } + if (!devEui) { + console.error('Device EUI is missing in the request'); + throw error(400, 'Bad Request - Device EUI is required'); + } - try { - // Get services from the container - const deviceService = container.get(TYPES.DeviceService); - const device = await deviceService.getDeviceWithTypeByEui(devEui); + try { + // Create services directly + const errorHandler = new ErrorHandlingService(); + const deviceRepo = new DeviceRepository(supabase, errorHandler); + const deviceService = new DeviceService(deviceRepo); - if (!device) { - throw error(404, 'Device not found'); - } + const device = await deviceService.getDeviceWithTypeByEui(devEui); - // Return a proper Response object using json helper - return json(device); - } catch (err) { - console.error('Error fetching device data:', err); + if (!device) { + throw error(404, 'Device not found'); + } - // If it's already a SvelteKit error, rethrow it - if (err && typeof err === 'object' && 'status' in err) { - throw err; - } + // Return a proper Response object using json helper + return json(device); + } catch (err) { + console.error('Error fetching device data:', err); - // Otherwise throw a generic 500 error - throw error(500, 'Internal Server Error'); - } -} + // If it's already a SvelteKit error, rethrow it + if (err && typeof err === 'object' && 'status' in err) { + throw err; + } -export const DELETE: RequestHandler = async ({ params, url, locals: { safeGetSession, supabase } }) => { - const { devEui } = params; - const { session, user } = await safeGetSession(); - if (!session) { - console.error('Session is null on device data API for device:', devEui); - } - if (!devEui) { - console.error('Device EUI is missing in the request'); - throw error(400, 'Bad Request - Device EUI is required'); - } - try { - // Get services from the container - const errorHandler = container.get(TYPES.ErrorHandlingService); - const deviceRepo = new DeviceRepository(supabase, errorHandler); - const deviceService = new DeviceService(deviceRepo); + // Otherwise throw a generic 500 error + throw error(500, 'Internal Server Error'); + } +}; - const device = await deviceService.getDeviceByEui(devEui); - if (!device) { - throw error(404, 'Device not found'); - } - // Check if the user is authorized to delete the device - if (device.user_id !== user.id) { - throw error(403, 'Forbidden - You do not have permission to delete this device'); - } +export const DELETE: RequestHandler = async ({ + params, + url, + locals: { safeGetSession, supabase } +}) => { + const { devEui } = params; + const { session, user } = await safeGetSession(); + if (!session || !user) { + console.error('Session is null on device data API for device:', devEui); + throw error(401, 'Unauthorized - Authentication required'); + } + if (!devEui) { + console.error('Device EUI is missing in the request'); + throw error(400, 'Bad Request - Device EUI is required'); + } + try { + // Create services directly + const errorHandler = new ErrorHandlingService(); + const deviceRepo = new DeviceRepository(supabase, errorHandler); + const deviceService = new DeviceService(deviceRepo); - // Delete the device - const deviceDeleted = await deviceService.deleteDevice(devEui); + const device = await deviceService.getDeviceByEui(devEui); + if (!device) { + throw error(404, 'Device not found'); + } + // Check if the user is authorized to delete the device + if (device.user_id !== user.id) { + throw error(403, 'Forbidden - You do not have permission to delete this device'); + } - if (!deviceDeleted) { - throw error(500, 'Internal Server Error - Device could not be deleted'); - } + // Delete the device + const deviceDeleted = await deviceService.deleteDevice(devEui); - // Return a proper Response object using json helper - return json({ message: 'Device deleted successfully' }); - } catch (err) { - console.error('Error deleting device data:', err); + if (!deviceDeleted) { + throw error(500, 'Internal Server Error - Device could not be deleted'); + } - // If it's already a SvelteKit error, rethrow it - if (err && typeof err === 'object' && 'status' in err) { - throw err; - } + // Return a proper Response object using json helper + return json({ message: 'Device deleted successfully' }); + } catch (err) { + console.error('Error deleting device data:', err); - // Otherwise throw a generic 500 error - throw error(500, 'Internal Server Error'); - } -} \ No newline at end of file + // If it's already a SvelteKit error, rethrow it + if (err && typeof err === 'object' && 'status' in err) { + throw err; + } + + // Otherwise throw a generic 500 error + throw error(500, 'Internal Server Error'); + } +}; diff --git a/src/routes/api/devices/[devEui]/downlink/+server.ts b/src/routes/api/devices/[devEui]/downlink/+server.ts index b187c78d..b8ba4769 100644 --- a/src/routes/api/devices/[devEui]/downlink/+server.ts +++ b/src/routes/api/devices/[devEui]/downlink/+server.ts @@ -1,84 +1,86 @@ import { json, error } from '@sveltejs/kit'; import type { RequestHandler } from './$types'; -import { container } from '$lib/server/ioc.config'; -import { TYPES } from '$lib/server/ioc.types'; import { DeviceRepository } from '$lib/repositories/DeviceRepository'; import { DeviceService } from '$lib/services/DeviceService'; import { ErrorHandlingService } from '$lib/errors/ErrorHandlingService'; import { DRAGINO_LT22222L_PAYLOADS } from '$lib/lorawan/dragino'; import { env } from '$env/dynamic/private'; -export const POST: RequestHandler = async ({ params, request, locals: { supabase, safeGetSession } }) => { - const { devEui } = params; - const { session, user } = await safeGetSession(); - if (!session || !user) { - throw error(401, 'Authentication required'); - } +export const POST: RequestHandler = async ({ + params, + request, + locals: { supabase, safeGetSession } +}) => { + const { devEui } = params; + const { session, user } = await safeGetSession(); + if (!session || !user) { + throw error(401, 'Authentication required'); + } - if (!devEui) { - throw error(400, 'Device EUI is required'); - } + if (!devEui) { + throw error(400, 'Device EUI is required'); + } - const errorHandler = container.get(TYPES.ErrorHandlingService); - const repo = new DeviceRepository(supabase, errorHandler); - const deviceService = new DeviceService(repo); + const errorHandler = new ErrorHandlingService(); + const repo = new DeviceRepository(supabase, errorHandler); + const deviceService = new DeviceService(repo); - const device = await deviceService.getDeviceWithTypeByEui(devEui); - if (!device) { - throw error(404, 'Device not found'); - } + const device = await deviceService.getDeviceWithTypeByEui(devEui); + if (!device) { + throw error(404, 'Device not found'); + } - const owner = await repo.findDeviceOwner(devEui, user.id); - if (!owner) { - throw error(403, 'Forbidden'); - } + const owner = await repo.findDeviceOwner(devEui, user.id); + if (!owner) { + throw error(403, 'Forbidden'); + } - const body = await request.json(); - const payloadName = body.payloadName as keyof typeof DRAGINO_LT22222L_PAYLOADS | undefined; - const frm_payload = body.frm_payload as string | undefined; + const body = await request.json(); + const payloadName = body.payloadName as keyof typeof DRAGINO_LT22222L_PAYLOADS | undefined; + const frm_payload = body.frm_payload as string | undefined; - const base64Payload = payloadName ? DRAGINO_LT22222L_PAYLOADS[payloadName] : frm_payload; - if (!base64Payload) { - throw error(400, 'No payload specified'); - } + const base64Payload = payloadName ? DRAGINO_LT22222L_PAYLOADS[payloadName] : frm_payload; + if (!base64Payload) { + throw error(400, 'No payload specified'); + } - const appId = device.cw_device_type?.TTI_application_id; - if (!appId) { - throw error(500, 'Device type missing TTI application id'); - } + const appId = device.cw_device_type?.TTI_application_id; + if (!appId) { + throw error(500, 'Device type missing TTI application id'); + } - const apiKey = env.TTI_API_KEY; - if (!apiKey) { - throw error(500, 'TTI_API_KEY not configured'); - } + const apiKey = env.TTI_API_KEY; + if (!apiKey) { + throw error(500, 'TTI_API_KEY not configured'); + } - const url = `https://cropwatch.au1.cloud.thethings.industries/api/v3/as/applications/${appId}/devices/${device.tti_name}/down/replace`; + const url = `https://cropwatch.au1.cloud.thethings.industries/api/v3/as/applications/${appId}/devices/${device.tti_name}/down/replace`; - const payload = { - downlinks: [ - { - frm_payload: base64Payload, - f_port: 2, - priority: 'HIGH', - confirmed: true - } - ] - }; + const payload = { + downlinks: [ + { + frm_payload: base64Payload, + f_port: 2, + priority: 'HIGH', + confirmed: true + } + ] + }; - const resp = await fetch(url, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - Authorization: `Bearer ${apiKey}` - }, - body: JSON.stringify(payload) - }); + const resp = await fetch(url, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${apiKey}` + }, + body: JSON.stringify(payload) + }); - if (!resp.ok) { - const text = await resp.text(); - console.error('TTI downlink error', text); - throw error(500, 'Failed to send downlink'); - } + if (!resp.ok) { + const text = await resp.text(); + console.error('TTI downlink error', text); + throw error(500, 'Failed to send downlink'); + } - return json({ success: true }); + return json({ success: true }); }; diff --git a/src/routes/api/devices/[devEui]/permissions/+server.ts b/src/routes/api/devices/[devEui]/permissions/+server.ts index 557692d9..eba12527 100644 --- a/src/routes/api/devices/[devEui]/permissions/+server.ts +++ b/src/routes/api/devices/[devEui]/permissions/+server.ts @@ -1,97 +1,50 @@ -import 'reflect-metadata'; -import { container } from '$lib/server/ioc.config'; -import { TYPES } from '$lib/server/ioc.types'; import { json } from '@sveltejs/kit'; import type { RequestHandler } from './$types'; -import type { IDeviceService } from '$lib/interfaces/IDeviceService'; -import type { IAuthService } from '$lib/interfaces/IAuthService'; +import { DeviceService } from '$lib/services/DeviceService'; +import { DeviceRepository } from '$lib/repositories/DeviceRepository'; +import { ErrorHandlingService } from '$lib/errors/ErrorHandlingService'; import { ForbiddenError, NotFoundError } from '$lib/errors/SpecificErrors'; // GET all permissions for a device -export const GET: RequestHandler = async ({ params }) => { - try { - const devEui = params.devEui; - - // Get services from IoC container - const deviceService = container.get(TYPES.DeviceService); - const authService = container.get(TYPES.AuthService); - - // Check if the user is authenticated - const sessionData = await authService.getSession(); - if (!sessionData) { - return json({ error: 'Authentication required' }, { status: 401 }); - } - - const { user } = sessionData; - - // Get device to check if it exists - const device = await deviceService.getDeviceByEui(devEui); - if (!device) { - return json({ error: 'Device not found' }, { status: 404 }); - } - - // Get device permissions - // This approach calls a method we'll add to DeviceService - const permissions = await deviceService.getDevicePermissions(devEui, user.id); - - return json(permissions); - } catch (error) { - console.error('Error fetching device permissions:', error); - - if (error instanceof ForbiddenError) { - return json({ error: error.message }, { status: 403 }); - } - - if (error instanceof NotFoundError) { - return json({ error: error.message }, { status: 404 }); - } - - return json({ error: 'Failed to fetch device permissions' }, { status: 500 }); - } +export const GET: RequestHandler = async ({ params, locals }) => { + try { + const devEui = params.devEui; + + // Create services with direct instantiation + const errorHandler = new ErrorHandlingService(); + const deviceRepo = new DeviceRepository(locals.supabase, errorHandler); + const deviceService = new DeviceService(deviceRepo); + + // Check if the user is authenticated + const session = locals.session; + if (!session) { + return json({ error: 'Authentication required' }, { status: 401 }); + } + + const { user } = session; + + // Get device to check if it exists + const device = await deviceService.getDeviceByEui(devEui); + if (!device) { + throw new NotFoundError('Device not found'); + } + + // For now, return basic permissions info + // TODO: Implement proper permission checking + const permissions: any[] = []; + + return json({ permissions }); + } catch (error) { + console.error('Error fetching device permissions:', error); + + if (error instanceof NotFoundError) { + return json({ error: error.message }, { status: 404 }); + } + + if (error instanceof ForbiddenError) { + return json({ error: error.message }, { status: 403 }); + } + + return json({ error: 'Internal server error' }, { status: 500 }); + } }; - -// POST add a new permission for a device -export const POST: RequestHandler = async ({ request, params }) => { - try { - const devEui = params.devEui; - const { user_id, permission_level } = await request.json(); - - if (!user_id || permission_level === undefined) { - return json({ error: 'User ID and permission level are required' }, { status: 400 }); - } - - // Get services from IoC container - const deviceService = container.get(TYPES.DeviceService); - const authService = container.get(TYPES.AuthService); - - // Check if the user is authenticated - const sessionData = await authService.getSession(); - if (!sessionData) { - return json({ error: 'Authentication required' }, { status: 401 }); - } - - const { user } = sessionData; - - // Add device permission - const permission = await deviceService.addDevicePermission( - devEui, - user_id, - permission_level, - user.id - ); - - return json(permission, { status: 201 }); - } catch (error) { - console.error('Error adding device permission:', error); - - if (error instanceof ForbiddenError) { - return json({ error: error.message }, { status: 403 }); - } - - if (error instanceof NotFoundError) { - return json({ error: error.message }, { status: 404 }); - } - - return json({ error: 'Failed to add device permission' }, { status: 500 }); - } -}; \ No newline at end of file diff --git a/src/routes/api/devices/[devEui]/permissions/[permissionId]/+server.ts b/src/routes/api/devices/[devEui]/permissions/[permissionId]/+server.ts index d987d81b..8d93b22c 100644 --- a/src/routes/api/devices/[devEui]/permissions/[permissionId]/+server.ts +++ b/src/routes/api/devices/[devEui]/permissions/[permissionId]/+server.ts @@ -1,101 +1,112 @@ -import 'reflect-metadata'; -import { container } from '$lib/server/ioc.config'; -import { TYPES } from '$lib/server/ioc.types'; import { json } from '@sveltejs/kit'; import type { RequestHandler } from './$types'; -import type { IDeviceService } from '$lib/interfaces/IDeviceService'; -import type { IAuthService } from '$lib/interfaces/IAuthService'; +import { DeviceService } from '$lib/services/DeviceService'; +import { DeviceRepository } from '$lib/repositories/DeviceRepository'; +import { DeviceOwnersRepository } from '$lib/repositories/DeviceOwnersRepository'; +import { AuthService } from '$lib/services/AuthService'; +import { ErrorHandlingService } from '$lib/errors/ErrorHandlingService'; import { ForbiddenError, NotFoundError } from '$lib/errors/SpecificErrors'; // PUT update a specific permission -export const PUT: RequestHandler = async ({ request, params, locals: { safeGetSession} }) => { - try { - const devEui = params.devEui; - const permissionId = parseInt(params.permissionId); - const session = await safeGetSession(); - - if (isNaN(permissionId)) { - return json({ error: 'Invalid permission ID' }, { status: 400 }); - } - - const { permission_level } = await request.json(); - - if (permission_level === undefined) { - return json({ error: 'Permission level is required' }, { status: 400 }); - } - - // Get services from IoC container - const deviceService = container.get(TYPES.DeviceService); - - // Check if the user is authenticated - const sessionData = session; - if (!sessionData) { - return json({ error: 'Authentication required' }, { status: 401 }); - } - - const { user } = sessionData; - - // Update the permission - const updatedPermission = await deviceService.updateDevicePermission( - devEui, - permissionId, - permission_level, - user.id - ); - - return json(updatedPermission); - } catch (error) { - console.error('Error updating device permission:', error); - - if (error instanceof ForbiddenError) { - return json({ error: error.message }, { status: 403 }); - } - - if (error instanceof NotFoundError) { - return json({ error: error.message }, { status: 404 }); - } - - return json({ error: 'Failed to update device permission' }, { status: 500 }); - } +export const PUT: RequestHandler = async ({ request, params, locals }) => { + try { + const devEui = params.devEui; + const permissionId = parseInt(params.permissionId); + + if (isNaN(permissionId)) { + return json({ error: 'Invalid permission ID' }, { status: 400 }); + } + + const { permission_level } = await request.json(); + + if (permission_level === undefined) { + return json({ error: 'Permission level is required' }, { status: 400 }); + } + + // Get services + const errorHandler = new ErrorHandlingService(); + const deviceRepo = new DeviceRepository(locals.supabase, errorHandler); + const deviceService = new DeviceService(deviceRepo); + const authService = new AuthService(locals.supabase, errorHandler); + + // Check if the user is authenticated + const sessionData = await authService.getSession(); + if (!sessionData) { + return json({ error: 'Authentication required' }, { status: 401 }); + } + + const { user } = sessionData; + + // Update the permission + const updatedPermission = await deviceRepo.updateDevicePermission( + permissionId, + permission_level + ); + + return json(updatedPermission); + } catch (error) { + console.error('Error updating device permission:', error); + + if (error instanceof ForbiddenError) { + return json({ error: error.message }, { status: 403 }); + } + + if (error instanceof NotFoundError) { + return json({ error: error.message }, { status: 404 }); + } + + return json({ error: 'Failed to update device permission' }, { status: 500 }); + } }; // DELETE remove a specific permission -export const DELETE: RequestHandler = async ({ params }) => { - try { - const devEui = params.devEui; - const permissionId = parseInt(params.permissionId); - - if (isNaN(permissionId)) { - return json({ error: 'Invalid permission ID' }, { status: 400 }); - } - - // Get services from IoC container - const deviceService = container.get(TYPES.DeviceService); - const authService = container.get(TYPES.AuthService); - - // Check if the user is authenticated - const sessionData = await authService.getSession(); - if (!sessionData) { - return json({ error: 'Authentication required' }, { status: 401 }); - } - - const { user } = sessionData; - - // Delete the permission - await deviceService.deleteDevicePermission(devEui, permissionId, user.id); - - return json({ success: true }); - } catch (error) { - console.error('Error deleting device permission:', error); - - if (error instanceof ForbiddenError) { - return json({ error: error.message }, { status: 403 }); - } - - if (error instanceof NotFoundError) { - return json({ error: error.message }, { status: 404 }); - } - - return json({ error: 'Failed to delete device permission' }, { status: 500 }); - } -}; \ No newline at end of file +export const DELETE: RequestHandler = async ({ params, locals }) => { + try { + const devEui = params.devEui; + const permissionId = parseInt(params.permissionId); + + if (isNaN(permissionId)) { + return json({ error: 'Invalid permission ID' }, { status: 400 }); + } + + // Get services + const errorHandler = new ErrorHandlingService(); + const deviceRepo = new DeviceRepository(locals.supabase, errorHandler); + const deviceOwnersRepo = new DeviceOwnersRepository(locals.supabase, errorHandler); + const deviceService = new DeviceService(deviceRepo); + const authService = new AuthService(locals.supabase, errorHandler); + + // Check if the user is authenticated + const sessionData = await authService.getSession(); + if (!sessionData) { + return json({ error: 'Authentication required' }, { status: 401 }); + } + + const { user } = sessionData; + + // Get the permission to find the user to remove + const permissions = await deviceOwnersRepo.findByDeviceEui(devEui); + const permission = permissions.find((p: any) => p.id === permissionId); + + if (!permission) { + return json({ error: 'Permission not found' }, { status: 404 }); + } + + // Remove the user from the device + await deviceRepo.removeUserFromDevice(devEui, permission.user_id); + + return json({ success: true }); + } catch (error) { + console.error('Error deleting device permission:', error); + + if (error instanceof ForbiddenError) { + return json({ error: error.message }, { status: 403 }); + } + + if (error instanceof NotFoundError) { + return json({ error: error.message }, { status: 404 }); + } + + return json({ error: 'Failed to delete device permission' }, { status: 500 }); + } +}; diff --git a/src/routes/api/devices/[devEui]/rules/+server.ts b/src/routes/api/devices/[devEui]/rules/+server.ts index 9e07cafb..fc74a08a 100644 --- a/src/routes/api/devices/[devEui]/rules/+server.ts +++ b/src/routes/api/devices/[devEui]/rules/+server.ts @@ -1,69 +1,73 @@ -import 'reflect-metadata'; -import { container } from '$lib/server/ioc.config'; -import { TYPES } from '$lib/server/ioc.types'; import { json } from '@sveltejs/kit'; import type { RequestHandler } from './$types'; -import type { IRuleService } from '$lib/interfaces/IRuleService'; +import { RuleService } from '$lib/services/RuleService'; +import { RuleRepository } from '$lib/repositories/RuleRepository'; +import { ErrorHandlingService } from '$lib/errors/ErrorHandlingService'; import { toRuleDtos, toRuleWithCriteriaDto } from '$lib/dtos/RuleDto'; import type { RuleInsert, RuleCriteriaInsert } from '$lib/models/Rule'; /** * GET handler to retrieve rules for a specific device */ -export const GET: RequestHandler = async ({ params }) => { - try { - const devEui = params.devEui; +export const GET: RequestHandler = async ({ params, locals }) => { + try { + const devEui = params.devEui; - if (!devEui) { - return json({ error: 'Device EUI is required' }, { status: 400 }); - } + if (!devEui) { + return json({ error: 'Device EUI is required' }, { status: 400 }); + } - // Get rule service from IoC container - const ruleService = container.get(TYPES.RuleService); - const rules = await ruleService.getRulesByDevice(devEui); + // Create services directly + const errorHandler = new ErrorHandlingService(); + const ruleRepo = new RuleRepository(locals.supabase, errorHandler); + const ruleService = new RuleService(ruleRepo); - return json({ rules: toRuleDtos(rules) }); - } catch (error) { - console.error('Error retrieving rules for device:', error); - return json({ error: 'Failed to retrieve rules' }, { status: 500 }); - } + const rules = await ruleService.getRulesByDevice(devEui); + + return json({ rules: toRuleDtos(rules) }); + } catch (error) { + console.error('Error retrieving rules for device:', error); + return json({ error: 'Failed to retrieve rules' }, { status: 500 }); + } }; /** * POST handler to create a new rule for a device */ -export const POST: RequestHandler = async ({ params, request }) => { - try { - const devEui = params.devEui; - const data = await request.json(); +export const POST: RequestHandler = async ({ params, request, locals }) => { + try { + const devEui = params.devEui; + const data = await request.json(); + + if (!devEui) { + return json({ error: 'Device EUI is required' }, { status: 400 }); + } - if (!devEui) { - return json({ error: 'Device EUI is required' }, { status: 400 }); - } + if (!data.rule || !data.criteria || !Array.isArray(data.criteria)) { + return json({ error: 'Rule and criteria array are required' }, { status: 400 }); + } - if (!data.rule || !data.criteria || !Array.isArray(data.criteria)) { - return json({ error: 'Rule and criteria array are required' }, { status: 400 }); - } + // Prepare rule data with the device EUI + const ruleData: RuleInsert = { + ...data.rule, + dev_eui: devEui, + ruleGroupId: `rule-${devEui}-${Date.now()}` // Generate a unique rule group ID + }; - // Prepare rule data with the device EUI - const ruleData: RuleInsert = { - ...data.rule, - dev_eui: devEui, - ruleGroupId: `rule-${devEui}-${Date.now()}` // Generate a unique rule group ID - }; + // Prepare criteria data + const criteriaData: RuleCriteriaInsert[] = data.criteria; - // Prepare criteria data - const criteriaData: RuleCriteriaInsert[] = data.criteria; + // Create services directly + const errorHandler = new ErrorHandlingService(); + const ruleRepo = new RuleRepository(locals.supabase, errorHandler); + const ruleService = new RuleService(ruleRepo); - // Get rule service from IoC container - const ruleService = container.get(TYPES.RuleService); - - // Create rule with its criteria - const createdRule = await ruleService.createRuleWithCriteria(ruleData, criteriaData); + // Create rule with its criteria + const createdRule = await ruleService.createRuleWithCriteria(ruleData, criteriaData); - return json({ rule: toRuleWithCriteriaDto(createdRule) }, { status: 201 }); - } catch (error) { - console.error('Error creating rule for device:', error); - return json({ error: 'Failed to create rule' }, { status: 500 }); - } -}; \ No newline at end of file + return json({ rule: toRuleWithCriteriaDto(createdRule) }, { status: 201 }); + } catch (error) { + console.error('Error creating rule for device:', error); + return json({ error: 'Failed to create rule' }, { status: 500 }); + } +}; diff --git a/src/routes/api/devices/[devEui]/rules/[ruleGroupId]/criteria/+server.ts b/src/routes/api/devices/[devEui]/rules/[ruleGroupId]/criteria/+server.ts index 625b7ef6..15cbe897 100644 --- a/src/routes/api/devices/[devEui]/rules/[ruleGroupId]/criteria/+server.ts +++ b/src/routes/api/devices/[devEui]/rules/[ruleGroupId]/criteria/+server.ts @@ -1,64 +1,68 @@ -import 'reflect-metadata'; -import { container } from '$lib/server/ioc.config'; -import { TYPES } from '$lib/server/ioc.types'; import { json } from '@sveltejs/kit'; import type { RequestHandler } from './$types'; -import type { IRuleService } from '$lib/interfaces/IRuleService'; import { toRuleCriteriaDto } from '$lib/dtos/RuleDto'; import type { RuleCriteriaInsert } from '$lib/models/Rule'; +import { RuleService } from '$lib/services/RuleService'; +import { RuleRepository } from '$lib/repositories/RuleRepository'; +import { ErrorHandlingService } from '$lib/errors/ErrorHandlingService'; /** * GET handler to retrieve all criteria for a rule group */ -export const GET: RequestHandler = async ({ params }) => { - try { - const ruleGroupId = params.ruleGroupId; +export const GET: RequestHandler = async ({ params, locals: { supabase } }) => { + try { + const ruleGroupId = params.ruleGroupId; - if (!ruleGroupId) { - return json({ error: 'Rule group ID is required' }, { status: 400 }); - } + if (!ruleGroupId) { + return json({ error: 'Rule group ID is required' }, { status: 400 }); + } - // Get rule service from IoC container - const ruleService = container.get(TYPES.RuleService); - const criteria = await ruleService.getRuleCriteriaByGroup(ruleGroupId); + // Create rule service with direct instantiation + const errorHandler = new ErrorHandlingService(); + const ruleRepo = new RuleRepository(supabase, errorHandler); + const ruleService = new RuleService(ruleRepo); - return json({ criteria: criteria.map(toRuleCriteriaDto) }); - } catch (error) { - console.error('Error retrieving rule criteria:', error); - return json({ error: 'Failed to retrieve rule criteria' }, { status: 500 }); - } + const criteria = await ruleService.getRuleCriteriaByGroup(ruleGroupId); + + return json({ criteria: criteria.map(toRuleCriteriaDto) }); + } catch (error) { + console.error('Error retrieving rule criteria:', error); + return json({ error: 'Failed to retrieve rule criteria' }, { status: 500 }); + } }; /** * POST handler to add new criteria to an existing rule */ -export const POST: RequestHandler = async ({ params, request }) => { - try { - const ruleGroupId = params.ruleGroupId; - const data = await request.json(); +export const POST: RequestHandler = async ({ params, request, locals: { supabase } }) => { + try { + const ruleGroupId = params.ruleGroupId; + const data = await request.json(); - if (!ruleGroupId) { - return json({ error: 'Rule group ID is required' }, { status: 400 }); - } + if (!ruleGroupId) { + return json({ error: 'Rule group ID is required' }, { status: 400 }); + } - if (!data.criteria) { - return json({ error: 'Criteria data is required' }, { status: 400 }); - } + if (!data.criteria) { + return json({ error: 'Criteria data is required' }, { status: 400 }); + } - // Get rule service from IoC container - const ruleService = container.get(TYPES.RuleService); + // Create rule service with direct instantiation + const errorHandler = new ErrorHandlingService(); + const ruleRepo = new RuleRepository(supabase, errorHandler); + const ruleService = new RuleService(ruleRepo); - // Create new criteria with the rule group ID - const criteriaData: RuleCriteriaInsert = { - ...data.criteria, - ruleGroupId - }; + // Create new criteria with the rule group ID + const criteriaData: RuleCriteriaInsert = { + ...data.criteria, + ruleGroupId + }; - const createdCriteria = await ruleService.createRuleCriteria(criteriaData); + const createdCriteria = await ruleService.createRuleCriteria(criteriaData); - return json({ criteria: toRuleCriteriaDto(createdCriteria) }, { status: 201 }); - } catch (error) { - console.error('Error creating rule criteria:', error); - return json({ error: 'Failed to create rule criteria' }, { status: 500 }); - } -}; \ No newline at end of file + return json({ criteria: toRuleCriteriaDto(createdCriteria) }, { status: 201 }); + } catch (error) { + console.error('Error creating rule criteria:', error); + return json({ error: 'Failed to create rule criteria' }, { status: 500 }); + } +}; diff --git a/src/routes/api/devices/[devEui]/rules/[ruleId]/+server.ts b/src/routes/api/devices/[devEui]/rules/[ruleId]/+server.ts index c903cd7e..6e298846 100644 --- a/src/routes/api/devices/[devEui]/rules/[ruleId]/+server.ts +++ b/src/routes/api/devices/[devEui]/rules/[ruleId]/+server.ts @@ -1,94 +1,101 @@ -import 'reflect-metadata'; -import { container } from '$lib/server/ioc.config'; -import { TYPES } from '$lib/server/ioc.types'; import { json } from '@sveltejs/kit'; import type { RequestHandler } from './$types'; -import type { IRuleService } from '$lib/interfaces/IRuleService'; +import { RuleService } from '$lib/services/RuleService'; +import { RuleRepository } from '$lib/repositories/RuleRepository'; +import { ErrorHandlingService } from '$lib/errors/ErrorHandlingService'; import { toRuleDto } from '$lib/dtos/RuleDto'; import type { RuleUpdate } from '$lib/models/Rule'; /** * GET handler to retrieve a specific rule */ -export const GET: RequestHandler = async ({ params }) => { - try { - const ruleId = parseInt(params.ruleId); - - if (isNaN(ruleId)) { - return json({ error: 'Invalid rule ID' }, { status: 400 }); - } - - // Get rule service from IoC container - const ruleService = container.get(TYPES.RuleService); - const rule = await ruleService.getRuleById(ruleId); - - if (!rule) { - return json({ error: 'Rule not found' }, { status: 404 }); - } - - return json({ rule: toRuleDto(rule) }); - } catch (error) { - console.error('Error retrieving rule:', error); - return json({ error: 'Failed to retrieve rule' }, { status: 500 }); - } +export const GET: RequestHandler = async ({ params, locals }) => { + try { + const ruleId = parseInt(params.ruleId); + + if (isNaN(ruleId)) { + return json({ error: 'Invalid rule ID' }, { status: 400 }); + } + + // Create services directly + const errorHandler = new ErrorHandlingService(); + const ruleRepo = new RuleRepository(locals.supabase, errorHandler); + const ruleService = new RuleService(ruleRepo); + + const rule = await ruleService.getRuleById(ruleId); + + if (!rule) { + return json({ error: 'Rule not found' }, { status: 404 }); + } + + return json({ rule: toRuleDto(rule) }); + } catch (error) { + console.error('Error retrieving rule:', error); + return json({ error: 'Failed to retrieve rule' }, { status: 500 }); + } }; /** * PUT handler to update a specific rule */ -export const PUT: RequestHandler = async ({ params, request }) => { - try { - const ruleId = parseInt(params.ruleId); - const data = await request.json(); - - if (isNaN(ruleId)) { - return json({ error: 'Invalid rule ID' }, { status: 400 }); - } - - if (!data.rule) { - return json({ error: 'Rule data is required' }, { status: 400 }); - } - - // Get rule service from IoC container - const ruleService = container.get(TYPES.RuleService); - - // Update rule - const ruleData: RuleUpdate = data.rule; - const updatedRule = await ruleService.updateRule(ruleId, ruleData); - - if (!updatedRule) { - return json({ error: 'Rule not found' }, { status: 404 }); - } - - return json({ rule: toRuleDto(updatedRule) }); - } catch (error) { - console.error('Error updating rule:', error); - return json({ error: 'Failed to update rule' }, { status: 500 }); - } +export const PUT: RequestHandler = async ({ params, request, locals }) => { + try { + const ruleId = parseInt(params.ruleId); + const data = await request.json(); + + if (isNaN(ruleId)) { + return json({ error: 'Invalid rule ID' }, { status: 400 }); + } + + if (!data.rule) { + return json({ error: 'Rule data is required' }, { status: 400 }); + } + + // Create services directly + const errorHandler = new ErrorHandlingService(); + const ruleRepo = new RuleRepository(locals.supabase, errorHandler); + const ruleService = new RuleService(ruleRepo); + + // Update rule + const ruleData: RuleUpdate = data.rule; + const updatedRule = await ruleService.updateRule(ruleId, ruleData); + + if (!updatedRule) { + return json({ error: 'Rule not found' }, { status: 404 }); + } + + return json({ rule: toRuleDto(updatedRule) }); + } catch (error) { + console.error('Error updating rule:', error); + return json({ error: 'Failed to update rule' }, { status: 500 }); + } }; /** * DELETE handler to delete a specific rule */ -export const DELETE: RequestHandler = async ({ params }) => { - try { - const ruleId = parseInt(params.ruleId); - - if (isNaN(ruleId)) { - return json({ error: 'Invalid rule ID' }, { status: 400 }); - } - - // Get rule service from IoC container - const ruleService = container.get(TYPES.RuleService); - const deleted = await ruleService.deleteRule(ruleId); - - if (!deleted) { - return json({ error: 'Failed to delete rule' }, { status: 500 }); - } - - return json({ success: true }); - } catch (error) { - console.error('Error deleting rule:', error); - return json({ error: 'Failed to delete rule' }, { status: 500 }); - } -}; \ No newline at end of file +export const DELETE: RequestHandler = async ({ params, locals }) => { + try { + const ruleId = parseInt(params.ruleId); + + if (isNaN(ruleId)) { + return json({ error: 'Invalid rule ID' }, { status: 400 }); + } + + // Create services directly + const errorHandler = new ErrorHandlingService(); + const ruleRepo = new RuleRepository(locals.supabase, errorHandler); + const ruleService = new RuleService(ruleRepo); + + const deleted = await ruleService.deleteRule(ruleId); + + if (!deleted) { + return json({ error: 'Failed to delete rule' }, { status: 500 }); + } + + return json({ success: true }); + } catch (error) { + console.error('Error deleting rule:', error); + return json({ error: 'Failed to delete rule' }, { status: 500 }); + } +}; diff --git a/src/routes/api/devices/[devEui]/rules/criteria/[criteriaId]/+server.ts b/src/routes/api/devices/[devEui]/rules/criteria/[criteriaId]/+server.ts index 51965764..e515e42b 100644 --- a/src/routes/api/devices/[devEui]/rules/criteria/[criteriaId]/+server.ts +++ b/src/routes/api/devices/[devEui]/rules/criteria/[criteriaId]/+server.ts @@ -1,68 +1,72 @@ -import 'reflect-metadata'; -import { container } from '$lib/server/ioc.config'; -import { TYPES } from '$lib/server/ioc.types'; import { json } from '@sveltejs/kit'; import type { RequestHandler } from './$types'; -import type { IRuleService } from '$lib/interfaces/IRuleService'; +import { RuleService } from '$lib/services/RuleService'; +import { RuleRepository } from '$lib/repositories/RuleRepository'; +import { ErrorHandlingService } from '$lib/errors/ErrorHandlingService'; import { toRuleCriteriaDto } from '$lib/dtos/RuleDto'; import type { RuleCriteriaUpdate } from '$lib/models/Rule'; /** * PUT handler to update a specific rule criteria */ -export const PUT: RequestHandler = async ({ params, request }) => { - try { - const criteriaId = parseInt(params.criteriaId); - const data = await request.json(); +export const PUT: RequestHandler = async ({ params, request, locals }) => { + try { + const criteriaId = parseInt(params.criteriaId); + const data = await request.json(); - if (isNaN(criteriaId)) { - return json({ error: 'Invalid criteria ID' }, { status: 400 }); - } + if (isNaN(criteriaId)) { + return json({ error: 'Invalid criteria ID' }, { status: 400 }); + } - if (!data.criteria) { - return json({ error: 'Criteria data is required' }, { status: 400 }); - } + if (!data.criteria) { + return json({ error: 'Criteria data is required' }, { status: 400 }); + } - // Get rule service from IoC container - const ruleService = container.get(TYPES.RuleService); + // Create services directly + const errorHandler = new ErrorHandlingService(); + const ruleRepo = new RuleRepository(locals.supabase, errorHandler); + const ruleService = new RuleService(ruleRepo); - // Update criteria - const criteriaData: RuleCriteriaUpdate = data.criteria; - const updatedCriteria = await ruleService.updateRuleCriteria(criteriaId, criteriaData); + // Update criteria + const criteriaData: RuleCriteriaUpdate = data.criteria; + const updatedCriteria = await ruleService.updateRuleCriteria(criteriaId, criteriaData); - if (!updatedCriteria) { - return json({ error: 'Criteria not found' }, { status: 404 }); - } + if (!updatedCriteria) { + return json({ error: 'Criteria not found' }, { status: 404 }); + } - return json({ criteria: toRuleCriteriaDto(updatedCriteria) }); - } catch (error) { - console.error('Error updating rule criteria:', error); - return json({ error: 'Failed to update rule criteria' }, { status: 500 }); - } + return json({ criteria: toRuleCriteriaDto(updatedCriteria) }); + } catch (error) { + console.error('Error updating rule criteria:', error); + return json({ error: 'Failed to update rule criteria' }, { status: 500 }); + } }; /** * DELETE handler to delete a specific rule criteria */ -export const DELETE: RequestHandler = async ({ params }) => { - try { - const criteriaId = parseInt(params.criteriaId); +export const DELETE: RequestHandler = async ({ params, locals }) => { + try { + const criteriaId = parseInt(params.criteriaId); - if (isNaN(criteriaId)) { - return json({ error: 'Invalid criteria ID' }, { status: 400 }); - } + if (isNaN(criteriaId)) { + return json({ error: 'Invalid criteria ID' }, { status: 400 }); + } - // Get rule service from IoC container - const ruleService = container.get(TYPES.RuleService); - const deleted = await ruleService.deleteRuleCriteria(criteriaId); + // Create services directly + const errorHandler = new ErrorHandlingService(); + const ruleRepo = new RuleRepository(locals.supabase, errorHandler); + const ruleService = new RuleService(ruleRepo); - if (!deleted) { - return json({ error: 'Failed to delete criteria' }, { status: 500 }); - } + const deleted = await ruleService.deleteRuleCriteria(criteriaId); - return json({ success: true }); - } catch (error) { - console.error('Error deleting rule criteria:', error); - return json({ error: 'Failed to delete rule criteria' }, { status: 500 }); - } -}; \ No newline at end of file + if (!deleted) { + return json({ error: 'Failed to delete criteria' }, { status: 500 }); + } + + return json({ success: true }); + } catch (error) { + console.error('Error deleting rule criteria:', error); + return json({ error: 'Failed to delete rule criteria' }, { status: 500 }); + } +}; diff --git a/src/routes/api/locations/+server.ts b/src/routes/api/locations/+server.ts index a50f6fc1..965a4cec 100644 --- a/src/routes/api/locations/+server.ts +++ b/src/routes/api/locations/+server.ts @@ -1,52 +1,50 @@ import { json } from '@sveltejs/kit'; import type { RequestHandler } from './$types'; -import { container } from '$lib/server/ioc.config'; -import { TYPES } from '$lib/server/ioc.types'; import { LocationRepository } from '$lib/repositories/LocationRepository'; import { DeviceRepository } from '$lib/repositories/DeviceRepository'; import { LocationService } from '$lib/services/LocationService'; import { ErrorHandlingService } from '$lib/errors/ErrorHandlingService'; export const GET: RequestHandler = async ({ locals }) => { - try { - // Get error handler from container - const errorHandler = container.get(TYPES.ErrorHandlingService); - - // Create repositories with the per-request Supabase client - const locationRepo = new LocationRepository(locals.supabase, errorHandler); - const deviceRepo = new DeviceRepository(locals.supabase, errorHandler); - - // Create the location service with the repositories - const locationService = new LocationService(locationRepo, deviceRepo); - - // Get all basic location data without devices - const locations = await locationService.getAllLocations(); - - // Add a deviceCount property to each location - const enhancedLocations = await Promise.all( - locations.map(async (location) => { - try { - // Instead of fetching all device data, just get the count - const deviceCount = await locationService.getDeviceCountForLocation(location.location_id); - - return { - ...location, - deviceCount - }; - } catch (error) { - console.error(`Error getting device count for location ${location.location_id}:`, error); - return { - ...location, - deviceCount: 0, - error: 'Failed to get device count' - }; - } - }) - ); - - return json(enhancedLocations); - } catch (error) { - console.error('Error fetching locations:', error); - return json({ error: 'Failed to fetch location data' }, { status: 500 }); - } -}; \ No newline at end of file + try { + // Create error handler + const errorHandler = new ErrorHandlingService(); + + // Create repositories with the per-request Supabase client + const locationRepo = new LocationRepository(locals.supabase, errorHandler); + const deviceRepo = new DeviceRepository(locals.supabase, errorHandler); + + // Create the location service with the repositories + const locationService = new LocationService(locationRepo, deviceRepo, errorHandler); + + // Get all basic location data without devices + const locations = await locationService.getAllLocations(); + + // Add a deviceCount property to each location + const enhancedLocations = await Promise.all( + locations.map(async (location) => { + try { + // Instead of fetching all device data, just get the count + const deviceCount = await locationService.getDeviceCountForLocation(location.location_id); + + return { + ...location, + deviceCount + }; + } catch (error) { + console.error(`Error getting device count for location ${location.location_id}:`, error); + return { + ...location, + deviceCount: 0, + error: 'Failed to get device count' + }; + } + }) + ); + + return json(enhancedLocations); + } catch (error) { + console.error('Error fetching locations:', error); + return json({ error: 'Failed to fetch location data' }, { status: 500 }); + } +}; diff --git a/src/routes/api/locations/[locationId]/devices/+server.ts b/src/routes/api/locations/[locationId]/devices/+server.ts index a9067e64..d2d44052 100644 --- a/src/routes/api/locations/[locationId]/devices/+server.ts +++ b/src/routes/api/locations/[locationId]/devices/+server.ts @@ -1,9 +1,6 @@ -import 'reflect-metadata'; import { json } from '@sveltejs/kit'; import type { RequestHandler } from './$types'; import type { DeviceType } from '$lib/models/Device'; -import { container } from '$lib/server/ioc.config'; -import { TYPES } from '$lib/server/ioc.types'; import { ErrorHandlingService } from '$lib/errors/ErrorHandlingService'; import { DeviceRepository } from '$lib/repositories/DeviceRepository'; import { AirDataRepository } from '$lib/repositories/AirDataRepository'; @@ -12,79 +9,79 @@ import { AirDataService } from '$lib/services/AirDataService'; import { DeviceDataService } from '$lib/services/DeviceDataService'; export const GET: RequestHandler = async ({ params, locals }) => { - try { - const locationId = parseInt(params.locationId); + try { + const locationId = parseInt(params.locationId); - if (isNaN(locationId)) { - return json({ error: 'Invalid location ID' }, { status: 400 }); - } + if (isNaN(locationId)) { + return json({ error: 'Invalid location ID' }, { status: 400 }); + } - // Get error handler from container - const errorHandler = container.get(TYPES.ErrorHandlingService); - - // Create repositories with per-request Supabase client - const deviceRepo = new DeviceRepository(locals.supabase, errorHandler); - const airDataRepo = new AirDataRepository(locals.supabase, errorHandler); - - // Create services with repositories - const deviceService = new DeviceService(deviceRepo); - const airDataService = new AirDataService(airDataRepo); - const deviceDataService = new DeviceDataService(locals.supabase); + // Get error handler + const errorHandler = new ErrorHandlingService(); - // Get devices for this location - now includes device type info directly - const devices = await deviceService.getDevicesByLocation(locationId); + // Create repositories with per-request Supabase client + const deviceRepo = new DeviceRepository(locals.supabase, errorHandler); + const airDataRepo = new AirDataRepository(locals.supabase, errorHandler); - // Process devices in parallel with Promise.all for better performance - const devicesWithData = await Promise.all( - devices.map(async (device) => { - try { - // Cast device to access the nested device type data from the joined query - const deviceWithJoins = device as any; - // Extract device type from the joined data - const deviceType = deviceWithJoins.cw_device_type as DeviceType; - - let latestData = null; - - // Dynamically get latest data based on device type's data_table_v2 value - if (deviceType && deviceType.data_table_v2) { - try { - latestData = await deviceDataService.getLatestDeviceData(device.dev_eui, deviceType); - } catch (dataError) { - console.error(`Error fetching dynamic data for device ${device.dev_eui}:`, dataError); - // Fall back to specific services if dynamic approach fails - latestData = null; - } - } + // Create services with repositories + const deviceService = new DeviceService(deviceRepo); + const airDataService = new AirDataService(airDataRepo); + const deviceDataService = new DeviceDataService(locals.supabase); - // Fallback to specific services if dynamic approach fails or returns no data - // This maintains backward compatibility - if (!latestData) { - // Get latest air data for this device, if available - const latestAirData = await airDataService.getLatestAirDataByDevice(device.dev_eui); - - // Set latestData to whichever data is available - latestData = latestAirData || null; - } - - return { - ...device, - deviceType: deviceType, - latestData - }; - } catch (error) { - console.error(`Error processing device ${device.dev_eui}:`, error); - return { - ...device, - error: error instanceof Error ? error.message : 'Failed to fetch device data', - latestData: null - }; - } - }) - ); + // Get devices for this location - now includes device type info directly + const devices = await deviceService.getDevicesByLocation(locationId); - return json(devicesWithData); - } catch (error) { - console.error(`Error fetching devices for location ${params.locationId}:`, error); - return json({ error: 'Failed to fetch devices' }, { status: 500 }); - } -}; \ No newline at end of file + // Process devices in parallel with Promise.all for better performance + const devicesWithData = await Promise.all( + devices.map(async (device) => { + try { + // Cast device to access the nested device type data from the joined query + const deviceWithJoins = device as any; + // Extract device type from the joined data + const deviceType = deviceWithJoins.cw_device_type as DeviceType; + + let latestData = null; + + // Dynamically get latest data based on device type's data_table_v2 value + if (deviceType && deviceType.data_table_v2) { + try { + latestData = await deviceDataService.getLatestDeviceData(device.dev_eui, deviceType); + } catch (dataError) { + console.error(`Error fetching dynamic data for device ${device.dev_eui}:`, dataError); + // Fall back to specific services if dynamic approach fails + latestData = null; + } + } + + // Fallback to specific services if dynamic approach fails or returns no data + // This maintains backward compatibility + if (!latestData) { + // Get latest air data for this device, if available + const latestAirData = await airDataService.getLatestAirDataByDevice(device.dev_eui); + + // Set latestData to whichever data is available + latestData = latestAirData || null; + } + + return { + ...device, + deviceType: deviceType, + latestData + }; + } catch (error) { + console.error(`Error processing device ${device.dev_eui}:`, error); + return { + ...device, + error: error instanceof Error ? error.message : 'Failed to fetch device data', + latestData: null + }; + } + }) + ); + + return json(devicesWithData); + } catch (error) { + console.error(`Error fetching devices for location ${params.locationId}:`, error); + return json({ error: 'Failed to fetch devices' }, { status: 500 }); + } +}; diff --git a/src/routes/api/locations/[locationId]/permissions/+server.ts b/src/routes/api/locations/[locationId]/permissions/+server.ts index 49a6f0e9..e69de29b 100644 --- a/src/routes/api/locations/[locationId]/permissions/+server.ts +++ b/src/routes/api/locations/[locationId]/permissions/+server.ts @@ -1,124 +0,0 @@ -import 'reflect-metadata'; -import { container } from '$lib/server/ioc.config'; -import { TYPES } from '$lib/server/ioc.types'; -import { json } from '@sveltejs/kit'; -import type { RequestHandler } from './$types'; -import type { ILocationService } from '$lib/interfaces/ILocationService'; - -export const GET: RequestHandler = async ({ params }) => { - try { - const locationId = parseInt(params.locationId); - - if (isNaN(locationId)) { - return json({ error: 'Invalid location ID' }, { status: 400 }); - } - - const locationService = container.get(TYPES.LocationService); - const locationUsers = await locationService.getLocationUsers(locationId); - - return json({ users: locationUsers }, { status: 200 }); - } catch (error) { - console.error(`Error fetching users for location ${params.locationId}:`, error); - return json({ error: 'Failed to fetch users' }, { status: 500 }); - } -}; - -export const POST: RequestHandler = async ({ params, request }) => { - try { - const locationId = parseInt(params.locationId); - - if (isNaN(locationId)) { - return json({ error: 'Invalid location ID' }, { status: 400 }); - } - - const data = await request.json(); - const { email, permissionLevel, applyToDevices } = data; - - if (!email || permissionLevel === undefined) { - return json({ error: 'Missing required fields' }, { status: 400 }); - } - - const locationService = container.get(TYPES.LocationService); - const result = await locationService.addUserToLocation( - locationId, - email, - permissionLevel, - applyToDevices || false - ); - - return json(result, { status: 200 }); - } catch (error) { - console.error(`Error adding user to location ${params.locationId}:`, error); - return json({ - success: false, - error: error instanceof Error ? error.message : 'Failed to add user to location' - }, { status: 500 }); - } -}; - -export const PUT: RequestHandler = async ({ params, request }) => { - try { - const locationId = parseInt(params.locationId); - - if (isNaN(locationId)) { - return json({ error: 'Invalid location ID' }, { status: 400 }); - } - - const data = await request.json(); - const { userId, permissionLevel, applyToDevices, locationOwnerId } = data; - - if (!userId || permissionLevel === undefined || !locationOwnerId) { - return json({ error: 'Missing required fields' }, { status: 400 }); - } - - const locationService = container.get(TYPES.LocationService); - const result = await locationService.updateUserPermission( - locationId, - userId, - locationOwnerId, - permissionLevel, - applyToDevices || false - ); - - return json(result, { status: 200 }); - } catch (error) { - console.error(`Error updating permission for location ${params.locationId}:`, error); - return json({ - success: false, - error: error instanceof Error ? error.message : 'Failed to update permission' - }, { status: 500 }); - } -}; - -export const DELETE: RequestHandler = async ({ params, request }) => { - try { - const locationId = parseInt(params.locationId); - - if (isNaN(locationId)) { - return json({ error: 'Invalid location ID' }, { status: 400 }); - } - - const url = new URL(request.url); - const userId = url.searchParams.get('userId'); - const locationOwnerId = url.searchParams.get('locationOwnerId'); - - if (!userId || !locationOwnerId) { - return json({ error: 'Missing required parameters' }, { status: 400 }); - } - - const locationService = container.get(TYPES.LocationService); - const result = await locationService.removeUserFromLocation( - locationId, - userId, - parseInt(locationOwnerId) - ); - - return json(result, { status: 200 }); - } catch (error) { - console.error(`Error removing user from location ${params.locationId}:`, error); - return json({ - success: false, - error: error instanceof Error ? error.message : 'Failed to remove user from location' - }, { status: 500 }); - } -}; \ No newline at end of file diff --git a/src/routes/api/permissions/levels/+server.ts b/src/routes/api/permissions/levels/+server.ts index 3164a907..995cf25b 100644 --- a/src/routes/api/permissions/levels/+server.ts +++ b/src/routes/api/permissions/levels/+server.ts @@ -1,37 +1,30 @@ -import 'reflect-metadata'; -import { container } from '$lib/server/ioc.config'; -import { TYPES } from '$lib/server/ioc.types'; import { json } from '@sveltejs/kit'; import type { RequestHandler } from './$types'; -import type { SupabaseClient } from '@supabase/supabase-js'; -import type { IAuthService } from '$lib/interfaces/IAuthService'; -export const GET: RequestHandler = async () => { - try { - // Get services from IoC container - const supabase = container.get(TYPES.SupabaseClient); - const authService = container.get(TYPES.AuthService); +export const GET: RequestHandler = async ({ locals }) => { + try { + // Check if the user is authenticated + const { + data: { user } + } = await locals.supabase.auth.getUser(); + if (!user) { + return json({ error: 'Authentication required' }, { status: 401 }); + } - // Check if the user is authenticated - const sessionData = await authService.getSession(); - if (!sessionData) { - return json({ error: 'Authentication required' }, { status: 401 }); - } + // Fetch permission levels from the database + const { data, error } = await locals.supabase + .from('cw_permission_level_types') + .select('*') + .order('permission_level_id', { ascending: true }); - // Fetch permission levels from the database - const { data, error } = await supabase - .from('cw_permission_level_types') - .select('*') - .order('permission_level_id', { ascending: true }); + if (error) { + console.error('Error fetching permission levels:', error); + return json({ error: 'Failed to fetch permission levels' }, { status: 500 }); + } - if (error) { - console.error('Error fetching permission levels:', error); - return json({ error: 'Failed to fetch permission levels' }, { status: 500 }); - } - - return json(data); - } catch (error) { - console.error('Error fetching permission levels:', error); - return json({ error: 'Failed to fetch permission levels' }, { status: 500 }); - } -}; \ No newline at end of file + return json(data); + } catch (error) { + console.error('Error fetching permission levels:', error); + return json({ error: 'Failed to fetch permission levels' }, { status: 500 }); + } +}; diff --git a/src/routes/api/users/+server.ts b/src/routes/api/users/+server.ts index 2ee09268..8fcdfc1f 100644 --- a/src/routes/api/users/+server.ts +++ b/src/routes/api/users/+server.ts @@ -1,31 +1,29 @@ -import 'reflect-metadata'; -import { container } from '$lib/server/ioc.config'; -import { TYPES } from '$lib/server/ioc.types'; import { json } from '@sveltejs/kit'; import type { RequestHandler } from './$types'; -import type { IAuthService } from '$lib/interfaces/IAuthService'; import { UserRepository } from '$lib/repositories/UserRepository'; import { ErrorHandlingService } from '$lib/errors/ErrorHandlingService'; -export const GET: RequestHandler = async () => { - try { - // Get services from IoC container - const authService = container.get(TYPES.AuthService); - const userRepo = container.get(TYPES.UserRepository); +export const GET: RequestHandler = async ({ locals }) => { + try { + // Create error handler and repository + const errorHandler = new ErrorHandlingService(); + const userRepo = new UserRepository(locals.supabase, errorHandler); - // Check if the user is authenticated - const sessionData = await authService.getSession(); - if (!sessionData) { - return json({ error: 'Authentication required' }, { status: 401 }); - } + // Check if the user is authenticated + const { + data: { user } + } = await locals.supabase.auth.getUser(); + if (!user) { + return json({ error: 'Authentication required' }, { status: 401 }); + } - // Fetch user profiles using the repository - const users = await userRepo.findAll(); + // Fetch user profiles using the repository + const users = await userRepo.findAll(); - return json(users); - } catch (error) { - const errorHandler = container.get(TYPES.ErrorHandlingService); - errorHandler.logError(error as Error); - return json({ error: 'Failed to fetch users' }, { status: 500 }); - } -}; \ No newline at end of file + return json(users); + } catch (error) { + const errorHandler = new ErrorHandlingService(); + errorHandler.logError(error as Error); + return json({ error: 'Failed to fetch users' }, { status: 500 }); + } +}; diff --git a/src/routes/app/dashboard/location/[location_id]/+layout.server.ts b/src/routes/app/dashboard/location/[location_id]/+layout.server.ts index 08776567..a779fc05 100644 --- a/src/routes/app/dashboard/location/[location_id]/+layout.server.ts +++ b/src/routes/app/dashboard/location/[location_id]/+layout.server.ts @@ -1,8 +1,6 @@ import { ErrorHandlingService } from '$lib/errors/ErrorHandlingService'; import { DeviceRepository } from '$lib/repositories/DeviceRepository'; import { LocationRepository } from '$lib/repositories/LocationRepository'; -import { container } from '$lib/server/ioc.config'; -import { TYPES } from '$lib/server/ioc.types'; import { LocationService } from '$lib/services/LocationService'; import { SessionService } from '$lib/services/SessionService'; import { error, redirect } from '@sveltejs/kit'; @@ -27,8 +25,8 @@ export const load = (async ({ params, locals: { supabase } }) => { } try { - // Get the error handler from the container - const errorHandler = container.get(TYPES.ErrorHandlingService); + // Create services directly + const errorHandler = new ErrorHandlingService(); const deviceRepo = new DeviceRepository(supabase, errorHandler); const locationRepo = new LocationRepository(supabase, errorHandler); const locationService = new LocationService(locationRepo, deviceRepo); diff --git a/src/routes/app/dashboard/location/[location_id]/+page.server.ts b/src/routes/app/dashboard/location/[location_id]/+page.server.ts index 6c562970..e17bee7e 100644 --- a/src/routes/app/dashboard/location/[location_id]/+page.server.ts +++ b/src/routes/app/dashboard/location/[location_id]/+page.server.ts @@ -1,6 +1,4 @@ import { error } from '@sveltejs/kit'; -import { container } from '$lib/server/ioc.config'; -import { TYPES } from '$lib/server/ioc.types'; import type { PageServerLoad } from './$types'; import { ErrorHandlingService } from '$lib/errors/ErrorHandlingService'; import { DeviceRepository } from '$lib/repositories/DeviceRepository'; @@ -14,8 +12,8 @@ export const load: PageServerLoad = async ({ params, locals }) => { const locationId = parseInt(params.location_id, 10); try { - // Get the error handler from the container - const errorHandler = container.get(TYPES.ErrorHandlingService); + // Create error handler and dependencies + const errorHandler = new ErrorHandlingService(); const deviceRepo = new DeviceRepository(locals.supabase, errorHandler); const deviceService = new DeviceService(deviceRepo); diff --git a/src/routes/app/dashboard/location/[location_id]/devices/+page.server.ts b/src/routes/app/dashboard/location/[location_id]/devices/+page.server.ts index 6c562970..cc0ab33f 100644 --- a/src/routes/app/dashboard/location/[location_id]/devices/+page.server.ts +++ b/src/routes/app/dashboard/location/[location_id]/devices/+page.server.ts @@ -1,6 +1,4 @@ import { error } from '@sveltejs/kit'; -import { container } from '$lib/server/ioc.config'; -import { TYPES } from '$lib/server/ioc.types'; import type { PageServerLoad } from './$types'; import { ErrorHandlingService } from '$lib/errors/ErrorHandlingService'; import { DeviceRepository } from '$lib/repositories/DeviceRepository'; @@ -14,8 +12,8 @@ export const load: PageServerLoad = async ({ params, locals }) => { const locationId = parseInt(params.location_id, 10); try { - // Get the error handler from the container - const errorHandler = container.get(TYPES.ErrorHandlingService); + // Get the error handler + const errorHandler = new ErrorHandlingService(); const deviceRepo = new DeviceRepository(locals.supabase, errorHandler); const deviceService = new DeviceService(deviceRepo); diff --git a/src/routes/app/dashboard/location/[location_id]/devices/[devEui]/settings/+page.server.ts b/src/routes/app/dashboard/location/[location_id]/devices/[devEui]/settings/+page.server.ts index 89cdc560..582e3dad 100644 --- a/src/routes/app/dashboard/location/[location_id]/devices/[devEui]/settings/+page.server.ts +++ b/src/routes/app/dashboard/location/[location_id]/devices/[devEui]/settings/+page.server.ts @@ -1,4 +1,3 @@ -import 'reflect-metadata'; import { error, type Actions } from '@sveltejs/kit'; import type { PageServerLoad } from './$types'; import { SessionService } from '$lib/services/SessionService'; diff --git a/src/routes/app/dashboard/location/[location_id]/devices/[devEui]/settings/permissions/+page.server.ts b/src/routes/app/dashboard/location/[location_id]/devices/[devEui]/settings/permissions/+page.server.ts index 88d34fcd..8938d62c 100644 --- a/src/routes/app/dashboard/location/[location_id]/devices/[devEui]/settings/permissions/+page.server.ts +++ b/src/routes/app/dashboard/location/[location_id]/devices/[devEui]/settings/permissions/+page.server.ts @@ -1,4 +1,3 @@ -import 'reflect-metadata'; import { error, type Actions } from '@sveltejs/kit'; import type { PageServerLoad } from './$types'; import { SessionService } from '$lib/services/SessionService'; diff --git a/src/routes/app/dashboard/location/[location_id]/devices/[devEui]/settings/rules/+page.server.ts b/src/routes/app/dashboard/location/[location_id]/devices/[devEui]/settings/rules/+page.server.ts index 4144b294..77f0cfcc 100644 --- a/src/routes/app/dashboard/location/[location_id]/devices/[devEui]/settings/rules/+page.server.ts +++ b/src/routes/app/dashboard/location/[location_id]/devices/[devEui]/settings/rules/+page.server.ts @@ -1,6 +1,3 @@ -import 'reflect-metadata'; -import { container } from '$lib/server/ioc.config'; -import { TYPES } from '$lib/server/ioc.types'; import { error } from '@sveltejs/kit'; import type { PageServerLoad } from './$types'; import type { Actions } from './$types'; @@ -28,8 +25,8 @@ export const load: PageServerLoad = async ({ params, locals }) => { const sessionService = new SessionService(locals.supabase); const sessionResult = await sessionService.getSafeSession(); - // Get error handler from container - const errorHandler = container.get(TYPES.ErrorHandlingService); + // Create error handler and repositories + const errorHandler = new ErrorHandlingService(); // Create repositories with per-request Supabase client const ruleRepo = new RuleRepository(locals.supabase, errorHandler); @@ -136,8 +133,8 @@ export const actions: Actions = { ruleGroupId })); - // Get error handler from container - const errorHandler = container.get(TYPES.ErrorHandlingService); + // Create error handler and dependencies + const errorHandler = new ErrorHandlingService(); // Create repository with per-request Supabase client const ruleRepo = new RuleRepository(locals.supabase, errorHandler); @@ -184,8 +181,8 @@ export const actions: Actions = { notifier_type }; - // Get error handler from container - const errorHandler = container.get(TYPES.ErrorHandlingService); + // Create error handler and dependencies + const errorHandler = new ErrorHandlingService(); // Create repository with per-request Supabase client const ruleRepo = new RuleRepository(locals.supabase, errorHandler); @@ -299,8 +296,8 @@ export const actions: Actions = { return { success: false, error: 'Invalid rule ID' }; } - // Get error handler from container - const errorHandler = container.get(TYPES.ErrorHandlingService); + // Create error handler and dependencies + const errorHandler = new ErrorHandlingService(); // Create repository with per-request Supabase client const ruleRepo = new RuleRepository(locals.supabase, errorHandler); diff --git a/src/routes/app/dashboard/location/[location_id]/settings/+page.server.ts b/src/routes/app/dashboard/location/[location_id]/settings/+page.server.ts index 8541b753..f909c83b 100644 --- a/src/routes/app/dashboard/location/[location_id]/settings/+page.server.ts +++ b/src/routes/app/dashboard/location/[location_id]/settings/+page.server.ts @@ -1,10 +1,7 @@ import { error, redirect } from '@sveltejs/kit'; import type { PageServerLoad, Actions } from './$types'; -import { container } from '$lib/server/ioc.config'; -import { TYPES } from '$lib/server/ioc.types'; -import type { ILocationService } from '$lib/interfaces/ILocationService'; import { PermissionLevel } from '$lib/models/LocationUser'; -import type { ErrorHandlingService } from '$lib/errors/ErrorHandlingService'; +import { ErrorHandlingService } from '$lib/errors/ErrorHandlingService'; import { AuthService } from '$lib/services/AuthService'; import { LocationService } from '$lib/services/LocationService'; import { LocationRepository } from '$lib/repositories/LocationRepository'; @@ -17,7 +14,7 @@ export const load: PageServerLoad = async ({ params, locals: { supabase } }) => throw error(400, 'Invalid location ID'); } - const errorHandler = container.get(TYPES.ErrorHandlingService); + const errorHandler = new ErrorHandlingService(); const authService = new AuthService(supabase, errorHandler); const session = await authService.getSession(); @@ -109,7 +106,7 @@ export const actions: Actions = { } try { - const errorHandler = container.get(TYPES.ErrorHandlingService); + const errorHandler = new ErrorHandlingService(); const locationRepo = new LocationRepository(supabase, errorHandler); const deviceRepo = new DeviceRepository(supabase, errorHandler); const locationService = new LocationService(locationRepo, deviceRepo); @@ -147,7 +144,7 @@ export const actions: Actions = { return { success: false, error: 'Invalid input data' }; } - const errorHandler = container.get(TYPES.ErrorHandlingService); + const errorHandler = new ErrorHandlingService(); const locationRepo = new LocationRepository(supabase, errorHandler); const deviceRepo = new DeviceRepository(supabase, errorHandler); const locationService = new LocationService(locationRepo, deviceRepo); @@ -179,7 +176,7 @@ export const actions: Actions = { return { success: false, error: 'Invalid input data' }; } - const errorHandler = container.get(TYPES.ErrorHandlingService); + const errorHandler = new ErrorHandlingService(); const locationRepo = new LocationRepository(supabase, errorHandler); const deviceRepo = new DeviceRepository(supabase, errorHandler); const locationService = new LocationService(locationRepo, deviceRepo); @@ -210,7 +207,7 @@ export const actions: Actions = { return { success: false, error: 'Invalid input data' }; } - const errorHandler = container.get(TYPES.ErrorHandlingService); + const errorHandler = new ErrorHandlingService(); const locationRepo = new LocationRepository(supabase, errorHandler); const deviceRepo = new DeviceRepository(supabase, errorHandler); const locationService = new LocationService(locationRepo, deviceRepo); @@ -237,7 +234,7 @@ export const actions: Actions = { return { success: false, error: 'Authentication required' }; } - const errorHandler = container.get(TYPES.ErrorHandlingService); + const errorHandler = new ErrorHandlingService(); const locationRepo = new LocationRepository(supabase, errorHandler); const deviceRepo = new DeviceRepository(supabase, errorHandler); const locationService = new LocationService(locationRepo, deviceRepo); diff --git a/src/routes/auth/forgot-password/+page.server.ts b/src/routes/auth/forgot-password/+page.server.ts index fc45d7b9..47308735 100644 --- a/src/routes/auth/forgot-password/+page.server.ts +++ b/src/routes/auth/forgot-password/+page.server.ts @@ -1,72 +1,69 @@ import { fail, redirect } from '@sveltejs/kit'; import type { Actions, PageServerLoad } from './$types'; -import { container } from '$lib/server/ioc.config'; -import { TYPES } from '$lib/server/ioc.types'; -import type { IAuthService } from '$lib/interfaces/IAuthService'; -import type { ErrorHandlingService } from '$lib/errors/ErrorHandlingService'; import { AuthService } from '$lib/services/AuthService'; +import { ErrorHandlingService } from '$lib/errors/ErrorHandlingService'; // Check if user is already logged in, redirect them to dashboard if they are export const load: PageServerLoad = async ({ locals }) => { - const errorHandler = container.get(TYPES.ErrorHandlingService); - const authService = new AuthService(locals.supabase, errorHandler); - const user = await authService.getSession(); - if (user) { - throw redirect(303, '/dashboard'); - } - return {}; + const errorHandler = new ErrorHandlingService(); + const authService = new AuthService(locals.supabase, errorHandler); + const user = await authService.getSession(); + if (user) { + throw redirect(303, '/dashboard'); + } + return {}; }; export const actions: Actions = { - default: async ({ request, locals }) => { - try { - // Parse form data - const formData = await request.formData(); - const email = formData.get('email')?.toString().trim() || ''; + default: async ({ request, locals }) => { + try { + // Parse form data + const formData = await request.formData(); + const email = formData.get('email')?.toString().trim() || ''; - // Validate email - if (!email) { - return fail(400, { error: 'Email is required', email: '' }); - } + // Validate email + if (!email) { + return fail(400, { error: 'Email is required', email: '' }); + } - // Validate email format - const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; - if (!emailRegex.test(email)) { - return fail(400, { error: 'Invalid email format', email }); - } + // Validate email format + const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; + if (!emailRegex.test(email)) { + return fail(400, { error: 'Invalid email format', email }); + } - // Get services from IoC container - const errorHandler = container.get(TYPES.ErrorHandlingService); - const authService = new AuthService(locals.supabase, errorHandler); + // Get services + const errorHandler = new ErrorHandlingService(); + const authService = new AuthService(locals.supabase, errorHandler); - // Request password reset - const result = await authService.resetPassword(email); + // Request password reset + const result = await authService.resetPassword(email); - if (!result.success) { - console.error('Password reset error:', result.error); + if (!result.success) { + console.error('Password reset error:', result.error); - // Don't expose specific errors to prevent email enumeration - // Always show success to avoid revealing which emails are registered - return { - success: result.success, - error: result.error, - email - }; - } + // Don't expose specific errors to prevent email enumeration + // Always show success to avoid revealing which emails are registered + return { + success: result.success, + error: result.error, + email + }; + } - // Return success without exposing if email exists in system - return { - success: true, - email - }; - } catch (error) { - console.error('Password reset request error:', error); + // Return success without exposing if email exists in system + return { + success: true, + email + }; + } catch (error) { + console.error('Password reset request error:', error); - // Generic error message - return fail(500, { - error: 'An error occurred. Please try again later.', - email: '' - }); - } - } -}; \ No newline at end of file + // Generic error message + return fail(500, { + error: 'An error occurred. Please try again later.', + email: '' + }); + } + } +}; diff --git a/src/routes/auth/register/+page.server.ts b/src/routes/auth/register/+page.server.ts index 1a294f80..28483476 100644 --- a/src/routes/auth/register/+page.server.ts +++ b/src/routes/auth/register/+page.server.ts @@ -1,14 +1,12 @@ import { fail, redirect } from '@sveltejs/kit'; import type { Actions } from './$types'; -import { container } from '$lib/server/ioc.config'; -import { TYPES } from '$lib/server/ioc.types'; import { AuthService } from '$lib/services/AuthService'; -import type { ErrorHandlingService } from '$lib/errors/ErrorHandlingService'; +import { ErrorHandlingService } from '$lib/errors/ErrorHandlingService'; export const actions: Actions = { register: async ({ request, locals }) => { const data = await request.formData(); - + // Get form values const firstName = data.get('firstName')?.toString() || ''; const lastName = data.get('lastName')?.toString() || ''; @@ -73,12 +71,12 @@ export const actions: Actions = { } try { - // Get error handler from container - const errorHandler = container.get(TYPES.ErrorHandlingService); - + // Get error handler + const errorHandler = new ErrorHandlingService(); + // Create AuthService with the request's Supabase client const authService = new AuthService(locals.supabase, errorHandler); - + // Register the user const result = await authService.register({ email, @@ -103,7 +101,7 @@ export const actions: Actions = { }); } - // Check if email verification is needed + // Check if email verification is needed if (!result.emailConfirmationRequired) { // Redirect to check-email page with email parameter throw redirect(303, `/auth/check-email?email=${encodeURIComponent(email)}`); @@ -113,14 +111,14 @@ export const actions: Actions = { } } catch (err) { console.error('Registration error:', err); - + // If this is a redirect, let it pass through if (err instanceof Response && err.status === 303) { throw err; } - - return fail(500, { - message: 'An unexpected error occurred during registration', + + return fail(500, { + message: 'An unexpected error occurred during registration', errors: {}, firstName, lastName, @@ -132,4 +130,4 @@ export const actions: Actions = { }); } } -}; \ No newline at end of file +}; diff --git a/static/build-info.json b/static/build-info.json index c1c0e21e..14065ee5 100644 --- a/static/build-info.json +++ b/static/build-info.json @@ -1,9 +1,9 @@ { - "commit": "53e6cd1", - "branch": "develop", + "commit": "fa55f10", + "branch": "remove-iocconfig", "author": "Kevin Cantrell", - "date": "2025-07-06T05:33:21.338Z", + "date": "2025-07-06T10:21:49.669Z", "builder": "kevin@kevin-desktop", "ipAddress": "192.168.1.100", - "timestamp": 1751780001338 + "timestamp": 1751797309669 } From a1322d434092601c698751582af78b33437b7cc5 Mon Sep 17 00:00:00 2001 From: Kevin Cantrell Date: Sun, 6 Jul 2025 22:06:59 +0900 Subject: [PATCH 2/5] reports work --- src/lib/components/GlobalSidebar.svelte | 2 +- src/lib/components/Reports/NumberLine.svelte | 35 +- src/lib/interfaces/IReportService.ts | 141 ++++ src/lib/models/Report.ts | 86 +++ .../ReportAlertPointRepository.ts | 115 ++++ .../repositories/ReportRecipientRepository.ts | 166 +++++ src/lib/repositories/ReportRepository.ts | 187 ++++++ .../ReportUserScheduleRepository.ts | 196 ++++++ src/lib/services/ReportService.ts | 217 +++++++ .../[devEui]/settings/reports/+page.server.ts | 154 +++++ .../[devEui]/settings/reports/+page.svelte | 300 ++++++++- .../reports/[reportId]/edit/+page.server.ts | 183 ++++++ .../reports/[reportId]/edit/+page.svelte | 494 ++++++++++++++ .../settings/reports/create/+page.server.ts | 148 +++++ .../settings/reports/create/+page.svelte | 605 ++++++++++++++++-- static/build-info.json | 6 +- 16 files changed, 2961 insertions(+), 74 deletions(-) create mode 100644 src/lib/interfaces/IReportService.ts create mode 100644 src/lib/models/Report.ts create mode 100644 src/lib/repositories/ReportAlertPointRepository.ts create mode 100644 src/lib/repositories/ReportRecipientRepository.ts create mode 100644 src/lib/repositories/ReportRepository.ts create mode 100644 src/lib/repositories/ReportUserScheduleRepository.ts create mode 100644 src/lib/services/ReportService.ts create mode 100644 src/routes/app/dashboard/location/[location_id]/devices/[devEui]/settings/reports/+page.server.ts create mode 100644 src/routes/app/dashboard/location/[location_id]/devices/[devEui]/settings/reports/[reportId]/edit/+page.server.ts create mode 100644 src/routes/app/dashboard/location/[location_id]/devices/[devEui]/settings/reports/[reportId]/edit/+page.svelte create mode 100644 src/routes/app/dashboard/location/[location_id]/devices/[devEui]/settings/reports/create/+page.server.ts diff --git a/src/lib/components/GlobalSidebar.svelte b/src/lib/components/GlobalSidebar.svelte index fcfdd2b1..d830243b 100644 --- a/src/lib/components/GlobalSidebar.svelte +++ b/src/lib/components/GlobalSidebar.svelte @@ -115,7 +115,7 @@