diff --git a/app/backend/src/aid/aid.controller.ts b/app/backend/src/aid/aid.controller.ts index f8f07df..a5838ac 100644 --- a/app/backend/src/aid/aid.controller.ts +++ b/app/backend/src/aid/aid.controller.ts @@ -1,9 +1,4 @@ -import { - Controller, - Body, - Param, - Post, -} from '@nestjs/common'; +import { Controller, Body, Param, Post } from '@nestjs/common'; import { AidService } from './aid.service'; import { AiTaskWebhookDto } from './dto/ai-task-webhook.dto'; import { diff --git a/app/backend/src/aid/aid.service.ts b/app/backend/src/aid/aid.service.ts index af28830..42efb9e 100644 --- a/app/backend/src/aid/aid.service.ts +++ b/app/backend/src/aid/aid.service.ts @@ -52,8 +52,10 @@ export class AidService { async handleTaskWebhook(payload: AiTaskWebhookDto) { // Log the task notification - console.log(`[AI Webhook] Task ${payload.taskId} completed with status: ${payload.status}`); - + console.log( + `[AI Webhook] Task ${payload.taskId} completed with status: ${payload.status}`, + ); + // Record audit log for the task completion await this.auditService.record({ actorId: 'ai-service', @@ -72,20 +74,27 @@ export class AidService { switch (payload.status) { case TaskStatus.COMPLETED: // Task completed successfully - trigger any follow-up actions - console.log(`[AI Webhook] Task ${payload.taskId} completed successfully`); + console.log( + `[AI Webhook] Task ${payload.taskId} completed successfully`, + ); if (payload.result) { console.log(`[AI Webhook] Result:`, payload.result); } break; case TaskStatus.FAILED: // Task failed - log error and potentially trigger alerts - console.error(`[AI Webhook] Task ${payload.taskId} failed:`, payload.error); + console.error( + `[AI Webhook] Task ${payload.taskId} failed:`, + payload.error, + ); break; case TaskStatus.PROCESSING: console.log(`[AI Webhook] Task ${payload.taskId} is still processing`); break; default: - console.log(`[AI Webhook] Task ${payload.taskId} status: ${payload.status}`); + console.log( + `[AI Webhook] Task ${payload.taskId} status: ${payload.status}`, + ); } return { diff --git a/app/backend/src/aid/dto/ai-task-webhook.dto.ts b/app/backend/src/aid/dto/ai-task-webhook.dto.ts index d79f830..588a62a 100644 --- a/app/backend/src/aid/dto/ai-task-webhook.dto.ts +++ b/app/backend/src/aid/dto/ai-task-webhook.dto.ts @@ -55,4 +55,4 @@ export class AiTaskWebhookDto { @IsOptional() @IsString() completedAt?: string; -} \ No newline at end of file +} diff --git a/app/backend/src/analytics/analytics.controller.ts b/app/backend/src/analytics/analytics.controller.ts index d87d8ce..a02cad0 100644 --- a/app/backend/src/analytics/analytics.controller.ts +++ b/app/backend/src/analytics/analytics.controller.ts @@ -61,6 +61,7 @@ import { GlobalStatsQuery, MapDataDto, MapDataQuery, + GeoJsonFeatureCollection, } from './dto'; @Controller('analytics') @@ -69,7 +70,6 @@ export class AnalyticsController { constructor(private readonly analyticsService: AnalyticsService) {} - @Get('global-stats') @HttpCode(HttpStatus.OK) async getGlobalStats( @@ -83,7 +83,6 @@ export class AnalyticsController { return this.analyticsService.getGlobalStats(query); } - @Get('map-data') @HttpCode(HttpStatus.OK) async getMapData( @@ -95,4 +94,16 @@ export class AnalyticsController { this.logger.log(`GET /analytics/map-data ${JSON.stringify(query)}`); return this.analyticsService.getMapData(query); } -} \ No newline at end of file + + @Get('map-anonymized') + @HttpCode(HttpStatus.OK) + async getMapAnonymizedData( + @Query('region') region?: string, + @Query('token') token?: string, + @Query('status') status?: string, + ): Promise { + const query: MapDataQuery = { region, token, status }; + this.logger.log(`GET /analytics/map-anonymized ${JSON.stringify(query)}`); + return this.analyticsService.getMapAnonymizedData(query); + } +} diff --git a/app/backend/src/analytics/analytics.module.ts b/app/backend/src/analytics/analytics.module.ts index 8416d80..38f2cdd 100644 --- a/app/backend/src/analytics/analytics.module.ts +++ b/app/backend/src/analytics/analytics.module.ts @@ -3,12 +3,14 @@ import { AnalyticsController } from './analytics.controller'; import { AnalyticsService } from './analytics.service'; import { PrismaModule } from '../prisma/prisma.module'; import { RedisService } from '../../cache/redis.service'; +import { PrivacyService } from './privacy.service'; @Module({ imports: [PrismaModule], controllers: [AnalyticsController], providers: [ AnalyticsService, + PrivacyService, /** * RedisService manages its own ioredis client and is provided here as a * plain class — no CacheModule or external adapter needed. @@ -18,4 +20,4 @@ import { RedisService } from '../../cache/redis.service'; ], exports: [AnalyticsService], }) -export class AnalyticsModule {} \ No newline at end of file +export class AnalyticsModule {} diff --git a/app/backend/src/analytics/analytics.service.ts b/app/backend/src/analytics/analytics.service.ts index a7e950f..f1c1bec 100644 --- a/app/backend/src/analytics/analytics.service.ts +++ b/app/backend/src/analytics/analytics.service.ts @@ -10,8 +10,10 @@ import { MapDataQuery, BreakdownEntry, TimeframeBucket, + GeoJsonFeatureCollection, } from './dto'; import { RedisService } from '../../cache/redis.service'; +import { PrivacyService } from './privacy.service'; // export type MapDataPoint = { // id: string; @@ -71,8 +73,6 @@ import { RedisService } from '../../cache/redis.service'; // } - - const CACHE_TTL_SECONDS = 300; // 5 minutes const DEFAULT_LOOKBACK_DAYS = 30; @@ -83,7 +83,6 @@ const FALLBACK_TOKEN = 'UNKNOWN'; const FALLBACK_LAT = 0; const FALLBACK_LNG = 0; - interface CampaignMetadata { region?: string; token?: string; @@ -98,9 +97,9 @@ export class AnalyticsService { constructor( private readonly prisma: PrismaService, private readonly redis: RedisService, + private readonly privacyService: PrivacyService, ) {} - /** * Return aggregated totals for the global dashboard. * @@ -112,7 +111,10 @@ export class AnalyticsService { * GET /analytics/global-stats?from=2024-01-01&to=2024-03-31&token=USDC */ async getGlobalStats(query: GlobalStatsQuery = {}): Promise { - const cacheKey = this.buildCacheKey('global-stats', query as Record); + const cacheKey = this.buildCacheKey( + 'global-stats', + query as Record, + ); const cached = await this.redis.get(cacheKey); if (cached) { @@ -138,7 +140,10 @@ export class AnalyticsService { * GET /analytics/map-data?region=West+Africa&token=USDC */ async getMapData(query: MapDataQuery = {}): Promise { - const cacheKey = this.buildCacheKey('map-data', query as Record); + const cacheKey = this.buildCacheKey( + 'map-data', + query as Record, + ); const cached = await this.redis.get(cacheKey); if (cached) { @@ -153,6 +158,33 @@ export class AnalyticsService { return result; } + /** + * Return anonymized geo-coordinates formatted as GeoJSON. + */ + async getMapAnonymizedData( + query: MapDataQuery = {}, + ): Promise { + const rawData = await this.getMapData(query); + + const features = rawData.points.map(p => { + const { lat, lng } = this.privacyService.fuzzCoordinates(p.lat, p.lng); + const { lat: _lat, lng: _lng, ...properties } = p; + return { + type: 'Feature' as const, + geometry: { + type: 'Point' as const, + coordinates: [lng, lat] as [number, number], + }, + properties, + }; + }); + + return { + type: 'FeatureCollection', + features, + computedAt: rawData.computedAt, + }; + } private async computeGlobalStats( query: GlobalStatsQuery, @@ -167,9 +199,7 @@ export class AnalyticsService { status: ClaimStatus.disbursed, createdAt: { gte: startDate, lte: endDate }, campaign: { - ...(region || token - ? this.buildMetadataFilter(region, token) - : {}), + ...(region || token ? this.buildMetadataFilter(region, token) : {}), }, }, select: { @@ -192,7 +222,7 @@ export class AnalyticsService { }, }); - // Aggregate in JS (avoids complex Prisma JSON path queries) + // Aggregate in JS (avoids complex Prisma JSON path queries) let totalAidDisbursed = 0; const uniqueRecipients = new Set(); @@ -265,7 +295,7 @@ export class AnalyticsService { }; } - // Private — map data computation + // Private — map data computation private async computeMapData(query: MapDataQuery): Promise { const { region, token, status } = query; @@ -280,9 +310,7 @@ export class AnalyticsService { where: { status: claimStatus, campaign: { - ...(region || token - ? this.buildMetadataFilter(region, token) - : {}), + ...(region || token ? this.buildMetadataFilter(region, token) : {}), }, }, select: { @@ -295,7 +323,7 @@ export class AnalyticsService { }, }); - const points: MapDataPoint[] = claims.map((claim) => { + const points: MapDataPoint[] = claims.map(claim => { const meta = (claim.campaign.metadata ?? {}) as CampaignMetadata; return { @@ -314,7 +342,6 @@ export class AnalyticsService { return { points, computedAt: new Date().toISOString() }; } - private buildMetadataFilter( region?: string, token?: string, @@ -342,7 +369,6 @@ export class AnalyticsService { return conditions.length === 1 ? conditions[0] : { AND: conditions }; } - private resolveDateRange( from?: string, to?: string, @@ -363,7 +389,10 @@ export class AnalyticsService { * * Example: "analytics:global-stats:from=2024-01-01:token=USDC" */ - private buildCacheKey(endpoint: string, query: Record): string { + private buildCacheKey( + endpoint: string, + query: Record, + ): string { const sorted = Object.entries(query) .filter(([, v]) => v !== undefined && v !== null && v !== '') .sort(([a], [b]) => a.localeCompare(b)) @@ -373,7 +402,6 @@ export class AnalyticsService { return `analytics:${endpoint}${sorted ? ':' + sorted : ''}`; } - private anonymiseId(id: string): string { return createHash('sha256').update(id).digest('hex').slice(0, 12); } @@ -381,4 +409,4 @@ export class AnalyticsService { private truncate2dp(n: number): number { return Math.trunc(n * 100) / 100; } -} \ No newline at end of file +} diff --git a/app/backend/src/analytics/dto/index.ts b/app/backend/src/analytics/dto/index.ts index 20a6e04..e1427e6 100644 --- a/app/backend/src/analytics/dto/index.ts +++ b/app/backend/src/analytics/dto/index.ts @@ -1,4 +1,3 @@ - export interface BreakdownEntry { label: string; totalAmount: number; @@ -21,7 +20,6 @@ export interface GlobalStatsDto { computedAt: string; } - export interface MapDataPoint { id: string; lat: number; @@ -37,6 +35,21 @@ export interface MapDataDto { computedAt: string; } +export interface GeoJsonFeature { + type: 'Feature'; + geometry: { + type: 'Point'; + coordinates: [number, number]; // [lng, lat] + }; + properties: Omit; +} + +export interface GeoJsonFeatureCollection { + type: 'FeatureCollection'; + features: GeoJsonFeature[]; + computedAt: string; +} + export interface GlobalStatsQuery { from?: string; to?: string; @@ -48,4 +61,4 @@ export interface MapDataQuery { region?: string; token?: string; status?: string; -} \ No newline at end of file +} diff --git a/app/backend/src/analytics/privacy.service.ts b/app/backend/src/analytics/privacy.service.ts new file mode 100644 index 0000000..4baa075 --- /dev/null +++ b/app/backend/src/analytics/privacy.service.ts @@ -0,0 +1,61 @@ +import { Injectable } from '@nestjs/common'; +import { createHash } from 'crypto'; + +@Injectable() +export class PrivacyService { + /** + * Fuzzes geo-coordinates to protect the exact location. + * Adds a deterministic pseudo-random offset within ~500m to 1km based on the original coordinates. + */ + public fuzzCoordinates( + lat: number, + lng: number, + ): { lat: number; lng: number } { + if (!lat && !lng) return { lat, lng }; + + // Generate a deterministic hash based on coordinates + const hash = createHash('sha256') + .update(`${lat.toFixed(6)},${lng.toFixed(6)}`) + .digest('hex'); + + // Use the first few bytes of the hash to generate deterministic pseudo-random numbers + const rand1 = parseInt(hash.substring(0, 8), 16) / 0xffffffff; // 0 to 1 + const rand2 = parseInt(hash.substring(8, 16), 16) / 0xffffffff; // 0 to 1 + + // Target offset: 500m to 1000m + // 1 degree of latitude is ~111km. + const minOffsetDeg = 500 / 111000; // ~0.0045 + const maxOffsetDeg = 1000 / 111000; // ~0.0090 + + const latOffsetMagnitude = + minOffsetDeg + rand1 * (maxOffsetDeg - minOffsetDeg); + const lngOffsetMagnitude = + minOffsetDeg + rand2 * (maxOffsetDeg - minOffsetDeg); + + // Determine sign based on hash + const latSign = parseInt(hash.substring(16, 17), 16) % 2 === 0 ? 1 : -1; + const lngSign = parseInt(hash.substring(17, 18), 16) % 2 === 0 ? 1 : -1; + + // Adjust longitude offset magnitude based on latitude so actual distance is consistent + // cos(lat) in radians + const latRad = (lat * Math.PI) / 180; + const adjustedLngOffsetMagnitude = lngOffsetMagnitude / Math.cos(latRad); + + let fuzzedLat = lat + latSign * latOffsetMagnitude; + let fuzzedLng = lng + lngSign * adjustedLngOffsetMagnitude; + + // Clamp values if they go out of bounds + fuzzedLat = Math.max(-90, Math.min(90, fuzzedLat)); + fuzzedLng = + fuzzedLng > 180 + ? fuzzedLng - 360 + : fuzzedLng < -180 + ? fuzzedLng + 360 + : fuzzedLng; + + return { + lat: Number(fuzzedLat.toFixed(5)), + lng: Number(fuzzedLng.toFixed(5)), + }; + } +} diff --git a/app/backend/src/audit/audit.controller.spec.ts b/app/backend/src/audit/audit.controller.spec.ts index 88531a9..cc54640 100644 --- a/app/backend/src/audit/audit.controller.spec.ts +++ b/app/backend/src/audit/audit.controller.spec.ts @@ -66,7 +66,10 @@ describe('AuditController', () => { it('should return the result object for JSON format', async () => { const res = makeRes(); - const returned = await controller.exportLogs({ page: 1, limit: 10 }, res as any); + const returned = await controller.exportLogs( + { page: 1, limit: 10 }, + res as any, + ); // eslint-disable-next-line @typescript-eslint/unbound-method expect(service.exportLogs).toHaveBeenCalledWith({ page: 1, limit: 10 }); expect(returned).toBe(mockExportResult); @@ -74,7 +77,10 @@ describe('AuditController', () => { it('should return CSV string and set headers when format=csv', async () => { const res = makeRes(); - const returned = await controller.exportLogs({ format: 'csv' } as any, res as any); + const returned = await controller.exportLogs( + { format: 'csv' } as any, + res as any, + ); // eslint-disable-next-line @typescript-eslint/unbound-method expect(service.buildCsv).toHaveBeenCalledWith(mockExportResult.data); expect(res.setHeader).toHaveBeenCalledWith('Content-Type', 'text/csv'); diff --git a/app/backend/src/audit/audit.controller.ts b/app/backend/src/audit/audit.controller.ts index a11b2d3..23ed1f0 100644 --- a/app/backend/src/audit/audit.controller.ts +++ b/app/backend/src/audit/audit.controller.ts @@ -45,12 +45,37 @@ export class AuditController { @ApiUnauthorizedResponse({ description: 'Missing or invalid authentication credentials.', }) - @ApiQuery({ name: 'format', required: false, enum: ['json', 'csv'], description: 'Export format (default: json)' }) - @ApiQuery({ name: 'from', required: false, description: 'Start date (ISO string)' }) - @ApiQuery({ name: 'to', required: false, description: 'End date (ISO string)' }) - @ApiQuery({ name: 'entity', required: false, description: 'Filter by entity type' }) - @ApiQuery({ name: 'page', required: false, description: 'Page number (default: 1)' }) - @ApiQuery({ name: 'limit', required: false, description: 'Items per page (default: 50, max: 200)' }) + @ApiQuery({ + name: 'format', + required: false, + enum: ['json', 'csv'], + description: 'Export format (default: json)', + }) + @ApiQuery({ + name: 'from', + required: false, + description: 'Start date (ISO string)', + }) + @ApiQuery({ + name: 'to', + required: false, + description: 'End date (ISO string)', + }) + @ApiQuery({ + name: 'entity', + required: false, + description: 'Filter by entity type', + }) + @ApiQuery({ + name: 'page', + required: false, + description: 'Page number (default: 1)', + }) + @ApiQuery({ + name: 'limit', + required: false, + description: 'Items per page (default: 50, max: 200)', + }) async exportLogs( @Query() query: ExportAuditQuery & { format?: string }, @Res({ passthrough: true }) res: Response, diff --git a/app/backend/src/audit/audit.service.spec.ts b/app/backend/src/audit/audit.service.spec.ts index 0e864f5..6268e48 100644 --- a/app/backend/src/audit/audit.service.spec.ts +++ b/app/backend/src/audit/audit.service.spec.ts @@ -128,22 +128,24 @@ describe('AuditService', () => { }); it('should throw BadRequestException for invalid from date', async () => { - await expect( - service.exportLogs({ from: 'not-a-date' }), - ).rejects.toThrow(BadRequestException); + await expect(service.exportLogs({ from: 'not-a-date' })).rejects.toThrow( + BadRequestException, + ); }); it('should throw BadRequestException for invalid to date', async () => { - await expect( - service.exportLogs({ to: 'not-a-date' }), - ).rejects.toThrow(BadRequestException); + await expect(service.exportLogs({ to: 'not-a-date' })).rejects.toThrow( + BadRequestException, + ); }); }); describe('buildCsv', () => { it('should include header row', () => { const csv = service.buildCsv([]); - expect(csv).toBe('id,actorHash,entity,entityHash,action,timestamp,metadata'); + expect(csv).toBe( + 'id,actorHash,entity,entityHash,action,timestamp,metadata', + ); }); it('should use CRLF line endings (RFC 4180)', () => { diff --git a/app/backend/src/audit/audit.service.ts b/app/backend/src/audit/audit.service.ts index 6e96683..11fe428 100644 --- a/app/backend/src/audit/audit.service.ts +++ b/app/backend/src/audit/audit.service.ts @@ -130,7 +130,7 @@ export class AuditService { this.prisma.auditLog.count({ where }), ]); - const data: AnonymizedAuditLog[] = rows.map((row) => ({ + const data: AnonymizedAuditLog[] = rows.map(row => ({ id: row.id, actorHash: this.anonymize(row.actorId), entity: row.entity, @@ -149,9 +149,8 @@ export class AuditService { return `"${str}"`; }; - const header = - 'id,actorHash,entity,entityHash,action,timestamp,metadata'; - const lines = rows.map((r) => { + const header = 'id,actorHash,entity,entityHash,action,timestamp,metadata'; + const lines = rows.map(r => { const metadata = escape(JSON.stringify(r.metadata ?? '')); return [ escape(r.id), diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index af31ff4..94bd8aa 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -10,7 +10,7 @@ importers: dependencies: '@nestjs/swagger': specifier: ^11.2.6 - version: 11.2.6(@nestjs/common@11.1.17(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.17(@nestjs/common@11.1.17(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.17)(reflect-metadata@0.2.2)(rxjs@7.8.2))(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2) + version: 11.2.6(@nestjs/common@11.1.17(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.17)(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2) express: specifier: ^5.2.1 version: 5.2.1 @@ -32,16 +32,16 @@ importers: dependencies: '@liaoliaots/nestjs-redis': specifier: ^10.0.0 - version: 10.0.0(@nestjs/common@11.1.17(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.17(@nestjs/common@11.1.17(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.17)(reflect-metadata@0.2.2)(rxjs@7.8.2))(ioredis@5.10.1) + version: 10.0.0(@nestjs/common@11.1.17(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.17)(ioredis@5.10.1) '@nestjs/axios': specifier: ^4.0.1 version: 4.0.1(@nestjs/common@11.1.17(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(axios@1.13.6)(rxjs@7.8.2) '@nestjs/bull': specifier: ^11.0.4 - version: 11.0.4(@nestjs/common@11.1.17(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.17(@nestjs/common@11.1.17(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.17)(reflect-metadata@0.2.2)(rxjs@7.8.2))(bull@4.16.5) + version: 11.0.4(@nestjs/common@11.1.17(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.17)(bull@4.16.5) '@nestjs/bullmq': specifier: ^11.0.4 - version: 11.0.4(@nestjs/common@11.1.17(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.17(@nestjs/common@11.1.17(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.17)(reflect-metadata@0.2.2)(rxjs@7.8.2))(bullmq@5.71.0) + version: 11.0.4(@nestjs/common@11.1.17(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.17)(bullmq@5.71.0) '@nestjs/common': specifier: ^11.0.1 version: 11.1.17(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2) @@ -56,13 +56,13 @@ importers: version: 11.1.17(@nestjs/common@11.1.17(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.17) '@nestjs/swagger': specifier: ^11.2.5 - version: 11.2.6(@nestjs/common@11.1.17(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.17(@nestjs/common@11.1.17(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.17)(reflect-metadata@0.2.2)(rxjs@7.8.2))(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2) + version: 11.2.6(@nestjs/common@11.1.17(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.17)(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2) '@nestjs/terminus': specifier: ^11.0.0 - version: 11.1.1(@nestjs/axios@4.0.1(@nestjs/common@11.1.17(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(axios@1.13.6)(rxjs@7.8.2))(@nestjs/common@11.1.17(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.17(@nestjs/common@11.1.17(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.17)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@prisma/client@6.19.2(prisma@6.19.2(typescript@5.9.3))(typescript@5.9.3))(reflect-metadata@0.2.2)(rxjs@7.8.2) + version: 11.1.1(@nestjs/axios@4.0.1(@nestjs/common@11.1.17(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(axios@1.13.6)(rxjs@7.8.2))(@nestjs/common@11.1.17(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.17)(@prisma/client@6.19.2(prisma@6.19.2(typescript@5.9.3))(typescript@5.9.3))(reflect-metadata@0.2.2)(rxjs@7.8.2) '@nestjs/throttler': specifier: ^6.5.0 - version: 6.5.0(@nestjs/common@11.1.17(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.17(@nestjs/common@11.1.17(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.17)(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2) + version: 6.5.0(@nestjs/common@11.1.17(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.17)(reflect-metadata@0.2.2) '@prisma/client': specifier: ^6.19.2 version: 6.19.2(prisma@6.19.2(typescript@5.9.3))(typescript@5.9.3) @@ -126,7 +126,7 @@ importers: version: 11.0.9(chokidar@4.0.3)(typescript@5.9.3) '@nestjs/testing': specifier: ^11.0.1 - version: 11.1.17(@nestjs/common@11.1.17(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.17(@nestjs/common@11.1.17(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.17)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.17(@nestjs/common@11.1.17(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.17)) + version: 11.1.17(@nestjs/common@11.1.17(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.17)(@nestjs/platform-express@11.1.17) '@types/express': specifier: ^5.0.0 version: 5.0.6 @@ -180,7 +180,7 @@ importers: version: 7.2.2 ts-jest: specifier: ^29.2.5 - version: 29.4.6(@babel/core@7.29.0)(@jest/transform@30.3.0)(@jest/types@30.3.0)(babel-jest@29.7.0(@babel/core@7.29.0))(jest-util@30.3.0)(jest@30.3.0(@types/node@22.19.15)(ts-node@10.9.2(@types/node@22.19.15)(typescript@5.9.3)))(typescript@5.9.3) + version: 29.4.6(@babel/core@7.29.0)(@jest/transform@30.3.0)(@jest/types@30.3.0)(babel-jest@30.3.0(@babel/core@7.29.0))(jest-util@30.3.0)(jest@30.3.0(@types/node@22.19.15)(ts-node@10.9.2(@types/node@22.19.15)(typescript@5.9.3)))(typescript@5.9.3) ts-loader: specifier: ^9.5.2 version: 9.5.4(typescript@5.9.3)(webpack@5.104.1) @@ -199,6 +199,9 @@ importers: app/frontend: dependencies: + '@heroicons/react': + specifier: ^2.2.0 + version: 2.2.0(react@19.2.3) '@radix-ui/react-avatar': specifier: ^1.1.11 version: 1.1.11(@types/react-dom@19.2.3(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) @@ -232,6 +235,9 @@ importers: next: specifier: ^16.2.1 version: 16.2.1(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + next-themes: + specifier: ^0.4.6 + version: 0.4.6(react-dom@19.2.3(react@19.2.3))(react@19.2.3) react: specifier: 19.2.3 version: 19.2.3 @@ -264,20 +270,20 @@ importers: specifier: ^19 version: 19.2.3(@types/react@19.1.17) eslint: - specifier: ^9 + specifier: ^9.39.4 version: 9.39.4(jiti@2.6.1) eslint-config-next: - specifier: 16.1.3 - version: 16.1.3(@typescript-eslint/parser@8.57.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) + specifier: ^0.2.4 + version: 0.2.4 jest: - specifier: ^30.2.0 + specifier: ^30.3.0 version: 30.3.0(@types/node@20.19.37)(ts-node@10.9.2(@types/node@20.19.37)(typescript@5.9.3)) tailwindcss: specifier: ^4 version: 4.2.2 ts-jest: - specifier: ^29.2.5 - version: 29.4.6(@babel/core@7.29.0)(@jest/transform@30.3.0)(@jest/types@30.3.0)(babel-jest@29.7.0(@babel/core@7.29.0))(jest-util@30.3.0)(jest@30.3.0(@types/node@20.19.37)(ts-node@10.9.2(@types/node@20.19.37)(typescript@5.9.3)))(typescript@5.9.3) + specifier: ^29.4.6 + version: 29.4.6(@babel/core@7.29.0)(@jest/transform@30.3.0)(@jest/types@30.3.0)(babel-jest@30.3.0(@babel/core@7.29.0))(jest-util@30.3.0)(jest@30.3.0(@types/node@20.19.37)(ts-node@10.9.2(@types/node@20.19.37)(typescript@5.9.3)))(typescript@5.9.3) ts-node: specifier: ^10.9.2 version: 10.9.2(@types/node@20.19.37)(typescript@5.9.3) @@ -1155,6 +1161,11 @@ packages: '@floating-ui/utils@0.2.11': resolution: {integrity: sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==} + '@heroicons/react@2.2.0': + resolution: {integrity: sha512-LMcepvRaS9LYHJGsF0zzmgKCUim/X3N/DQKc4jepAXJ7l8QxJ1PmxJzqplF2Z3FE4PqBAIGyJAQ/w4B5dsqbtQ==} + peerDependencies: + react: '>= 16 || ^19.0.0-rc' + '@hono/node-server@1.19.9': resolution: {integrity: sha512-vHL6w3ecZsky+8P5MD+eFfaGTyCeOHUIFYMGpQGbrBTSmNNoxv0if69rEZ5giu36weC5saFuznL411gRX7bJDw==} engines: {node: '>=18.14.1'} @@ -1905,9 +1916,6 @@ packages: '@next/env@16.2.1': resolution: {integrity: sha512-n8P/HCkIWW+gVal2Z8XqXJ6aB3J0tuM29OcHpCsobWlChH/SITBs1DFBk/HajgrwDkqqBXPbuUuzgDvUekREPg==} - '@next/eslint-plugin-next@16.1.3': - resolution: {integrity: sha512-MqBh3ltFAy0AZCRFVdjVjjeV7nEszJDaVIpDAnkQcn8U9ib6OEwkSnuK6xdYxMGPhV/Y4IlY6RbDipPOpLfBqQ==} - '@next/swc-darwin-arm64@16.2.1': resolution: {integrity: sha512-BwZ8w8YTaSEr2HIuXLMLxIdElNMPvY9fLqb20LX9A9OMGtJilhHLbCL3ggyd0TwjmMcTxi0XXt+ur1vWUoxj2Q==} engines: {node: '>= 10'} @@ -1980,18 +1988,6 @@ packages: resolution: {integrity: sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==} engines: {node: ^14.21.3 || >=16} - '@nodelib/fs.scandir@2.1.5': - resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} - engines: {node: '>= 8'} - - '@nodelib/fs.stat@2.0.5': - resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} - engines: {node: '>= 8'} - - '@nodelib/fs.walk@1.2.8': - resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} - engines: {node: '>= 8'} - '@nolyfill/is-core-module@1.0.39': resolution: {integrity: sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==} engines: {node: '>=12.4.0'} @@ -3220,6 +3216,7 @@ packages: '@xmldom/xmldom@0.8.11': resolution: {integrity: sha512-cQzWCtO6C8TQiYl1ruKNn2U6Ao4o4WBBcbL61yJl84x+j5sOWWFU9X7DpND8XZG3daDppSsigMdfAIl2upQBRw==} engines: {node: '>=10.0.0'} + deprecated: this version has critical issues, please update to the latest version '@xtuc/ieee754@1.2.0': resolution: {integrity: sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==} @@ -3396,10 +3393,6 @@ packages: resolution: {integrity: sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==} engines: {node: '>=10'} - aria-query@5.3.2: - resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==} - engines: {node: '>= 0.4'} - array-buffer-byte-length@1.0.2: resolution: {integrity: sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==} engines: {node: '>= 0.4'} @@ -3438,9 +3431,6 @@ packages: asap@2.0.6: resolution: {integrity: sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==} - ast-types-flow@0.0.8: - resolution: {integrity: sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==} - async-function@1.0.0: resolution: {integrity: sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==} engines: {node: '>= 0.4'} @@ -3463,17 +3453,9 @@ packages: resolution: {integrity: sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g==} engines: {node: '>= 6.0.0'} - axe-core@4.11.1: - resolution: {integrity: sha512-BASOg+YwO2C+346x3LZOeoovTIoTrRqEsqMa6fmfAV0P+U9mFr9NsyOEpiYvFjbc64NMrSswhV50WdXzdb/Z5A==} - engines: {node: '>=4'} - axios@1.13.6: resolution: {integrity: sha512-ChTCHMouEe2kn713WHbQGcuYrr6fXTBiu460OTwWrWob16g1bXn4vtz07Ope7ewMozJAnEquLk5lWQWtBig9DQ==} - axobject-query@4.1.0: - resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==} - engines: {node: '>= 0.4'} - babel-jest@29.7.0: resolution: {integrity: sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -3991,9 +3973,6 @@ packages: csstype@3.2.3: resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} - damerau-levenshtein@1.0.8: - resolution: {integrity: sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==} - data-urls@3.0.2: resolution: {integrity: sha512-Jy/tj3ldjZJo63sVAvg6LHt2mHvl4V6AgRAmNDtLdm7faqtsx+aJG42rsyCo9JCoRVKwPFzKlIPx3DIibwSIaQ==} engines: {node: '>=12'} @@ -4289,14 +4268,8 @@ packages: peerDependencies: eslint: '>=8.10' - eslint-config-next@16.1.3: - resolution: {integrity: sha512-q2Z87VSsoJcv+vgR+Dm8NPRf+rErXcRktuBR5y3umo/j5zLjIWH7rqBCh3X804gUGKbOrqbgsLUkqDE35C93Gw==} - peerDependencies: - eslint: '>=9.0.0' - typescript: '>=3.3.1' - peerDependenciesMeta: - typescript: - optional: true + eslint-config-next@0.2.4: + resolution: {integrity: sha512-ggVKB4iVijT3qzCmRxqKsTGi/jzQAEr2CJclnNWJlIqyXH4MdAtr4oDQKpC857LiKrRhmBaLR4CHaclyGP8hHQ==} eslint-config-prettier@10.1.8: resolution: {integrity: sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==} @@ -4357,12 +4330,6 @@ packages: '@typescript-eslint/parser': optional: true - eslint-plugin-jsx-a11y@6.10.2: - resolution: {integrity: sha512-scB3nz4WmG75pV8+3eRUQOHZlNSUhFNq37xnpgRkCCELU3XMvXAxLk1eqWWyE22Ki4Q01Fnsw9BA3cJHDPgn2Q==} - engines: {node: '>=4.0'} - peerDependencies: - eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9 - eslint-plugin-prettier@5.5.5: resolution: {integrity: sha512-hscXkbqUZ2sPithAuLm5MXL+Wph+U7wHngPBv9OMWwlP8iaflyxpjTYZkmdgB4/vPIhemRlBEoLrH7UC1n7aUw==} engines: {node: ^14.18.0 || >=16.0.0} @@ -4383,12 +4350,6 @@ packages: peerDependencies: eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 - eslint-plugin-react-hooks@7.0.1: - resolution: {integrity: sha512-O0d0m04evaNzEPoSW+59Mezf8Qt0InfgGIBJnpC0h3NH/WjUAR7BIKUfysC6todmtiZ/A0oUVS8Gce0WhBrHsA==} - engines: {node: '>=18'} - peerDependencies: - eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 - eslint-plugin-react@7.37.5: resolution: {integrity: sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==} engines: {node: '>=4'} @@ -4609,10 +4570,6 @@ packages: fast-diff@1.3.0: resolution: {integrity: sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==} - fast-glob@3.3.1: - resolution: {integrity: sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==} - engines: {node: '>=8.6.0'} - fast-json-stable-stringify@2.1.0: resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} @@ -4628,9 +4585,6 @@ packages: fast-uri@3.1.0: resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==} - fastq@1.20.1: - resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==} - fb-watchman@2.0.2: resolution: {integrity: sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==} @@ -4835,10 +4789,6 @@ packages: resolution: {integrity: sha512-L5bGsVkxJbJgdnwyuheIunkGatUF/zssUoxxjACCseZYAVbaqdh9Tsmmlkl8vYan09H7sbvKt4pS8GqKLBrEzA==} hasBin: true - glob-parent@5.1.2: - resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} - engines: {node: '>= 6'} - glob-parent@6.0.2: resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} engines: {node: '>=10.13.0'} @@ -4867,10 +4817,6 @@ packages: resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} engines: {node: '>=18'} - globals@16.4.0: - resolution: {integrity: sha512-ob/2LcVVaVGCYN+r14cnwnoDPUufjiYgSqRhiFD0Q1iI4Odora5RE8Iv1D24hAz5oMophRGkGz+yuvQmmUMnMw==} - engines: {node: '>=18'} - globals@16.5.0: resolution: {integrity: sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==} engines: {node: '>=18'} @@ -4938,9 +4884,6 @@ packages: help-me@5.0.0: resolution: {integrity: sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==} - hermes-estree@0.25.1: - resolution: {integrity: sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==} - hermes-estree@0.29.1: resolution: {integrity: sha512-jl+x31n4/w+wEqm0I2r4CMimukLbLQEYpisys5oCre611CI5fc9TxhqkBBCJ1edDG4Kza0f7CgNz8xVMLZQOmQ==} @@ -4950,9 +4893,6 @@ packages: hermes-estree@0.33.3: resolution: {integrity: sha512-6kzYZHCk8Fy1Uc+t3HGYyJn3OL4aeqKLTyina4UFtWl8I0kSL7OmKThaiX+Uh2f8nGw3mo4Ifxg0M5Zk3/Oeqg==} - hermes-parser@0.25.1: - resolution: {integrity: sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==} - hermes-parser@0.29.1: resolution: {integrity: sha512-xBHWmUtRC5e/UL0tI7Ivt2riA/YBq9+SiYFU7C1oBa/j2jYGlIF9043oak1F47ihuDIxQ5nbsKueYJDRY02UgA==} @@ -5668,13 +5608,6 @@ packages: resolution: {integrity: sha512-mnIlAEMu4OyEvUNdzco9xpuB9YVcPkQec+QsgycBCtPZvEqWPCDPfbAE4OJMdBBWpZWtpCn1xw9jJYlwjWI5zQ==} hasBin: true - language-subtag-registry@0.3.23: - resolution: {integrity: sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ==} - - language-tags@1.0.9: - resolution: {integrity: sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==} - engines: {node: '>=0.10'} - leaflet@1.9.4: resolution: {integrity: sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA==} @@ -5899,10 +5832,6 @@ packages: merge-stream@2.0.0: resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} - merge2@1.4.1: - resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} - engines: {node: '>= 8'} - methods@1.1.2: resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==} engines: {node: '>= 0.6'} @@ -6161,6 +6090,12 @@ packages: nested-error-stacks@2.0.1: resolution: {integrity: sha512-SrQrok4CATudVzBS7coSz26QRSmlK9TzzoFbeKfcPBUFPjcQM9Rqvr/DlJkOrwI/0KcgvMub1n1g5Jt9EgRn4A==} + next-themes@0.4.6: + resolution: {integrity: sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA==} + peerDependencies: + react: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc + react-dom: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc + next@16.2.1: resolution: {integrity: sha512-VaChzNL7o9rbfdt60HUj8tev4m6d7iC1igAy157526+cJlXOQu5LzsBXNT+xaJnTP/k+utSX5vMv7m0G+zKH+Q==} engines: {node: '>=20.9.0'} @@ -6704,9 +6639,6 @@ packages: querystringify@2.2.0: resolution: {integrity: sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==} - queue-microtask@1.2.3: - resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} - queue@6.0.2: resolution: {integrity: sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA==} @@ -6993,10 +6925,6 @@ packages: resolution: {integrity: sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==} engines: {node: '>= 4'} - reusify@1.1.0: - resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} - engines: {iojs: '>=1.0.0', node: '>=0.10.0'} - rimraf@3.0.2: resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} deprecated: Rimraf versions prior to v4 are no longer supported @@ -7006,9 +6934,6 @@ packages: resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==} engines: {node: '>= 18'} - run-parallel@1.2.0: - resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} - rxjs@7.8.1: resolution: {integrity: sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==} @@ -7312,10 +7237,6 @@ packages: resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} engines: {node: '>=12'} - string.prototype.includes@2.0.1: - resolution: {integrity: sha512-o7+c9bW6zpAdJHTtujeePODAhkuicdAryFsfVKwA+wGw89wJ4GTY484WTucM9hLtDEOpOvI+aHnzqnC5lHp4Rg==} - engines: {node: '>= 0.4'} - string.prototype.matchall@4.0.12: resolution: {integrity: sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==} engines: {node: '>= 0.4'} @@ -8149,12 +8070,6 @@ packages: zeptomatch@2.1.0: resolution: {integrity: sha512-KiGErG2J0G82LSpniV0CtIzjlJ10E04j02VOudJsPyPwNZgGnRKQy7I1R7GMyg/QswnE4l7ohSGrQbQbjXPPDA==} - zod-validation-error@4.0.2: - resolution: {integrity: sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==} - engines: {node: '>=18.0.0'} - peerDependencies: - zod: ^3.25.0 || ^4.0.0 - zod@4.3.6: resolution: {integrity: sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==} @@ -9240,6 +9155,10 @@ snapshots: '@floating-ui/utils@0.2.11': {} + '@heroicons/react@2.2.0(react@19.2.3)': + dependencies: + react: 19.2.3 + '@hono/node-server@1.19.9(hono@4.11.4)': dependencies: hono: 4.11.4 @@ -9930,7 +9849,7 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.5 - '@liaoliaots/nestjs-redis@10.0.0(@nestjs/common@11.1.17(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.17(@nestjs/common@11.1.17(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.17)(reflect-metadata@0.2.2)(rxjs@7.8.2))(ioredis@5.10.1)': + '@liaoliaots/nestjs-redis@10.0.0(@nestjs/common@11.1.17(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.17)(ioredis@5.10.1)': dependencies: '@nestjs/common': 11.1.17(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2) '@nestjs/core': 11.1.17(@nestjs/common@11.1.17(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.17)(reflect-metadata@0.2.2)(rxjs@7.8.2) @@ -9979,23 +9898,23 @@ snapshots: axios: 1.13.6 rxjs: 7.8.2 - '@nestjs/bull-shared@11.0.4(@nestjs/common@11.1.17(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.17(@nestjs/common@11.1.17(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.17)(reflect-metadata@0.2.2)(rxjs@7.8.2))': + '@nestjs/bull-shared@11.0.4(@nestjs/common@11.1.17(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.17)': dependencies: '@nestjs/common': 11.1.17(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2) '@nestjs/core': 11.1.17(@nestjs/common@11.1.17(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.17)(reflect-metadata@0.2.2)(rxjs@7.8.2) tslib: 2.8.1 - '@nestjs/bull@11.0.4(@nestjs/common@11.1.17(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.17(@nestjs/common@11.1.17(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.17)(reflect-metadata@0.2.2)(rxjs@7.8.2))(bull@4.16.5)': + '@nestjs/bull@11.0.4(@nestjs/common@11.1.17(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.17)(bull@4.16.5)': dependencies: - '@nestjs/bull-shared': 11.0.4(@nestjs/common@11.1.17(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.17(@nestjs/common@11.1.17(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.17)(reflect-metadata@0.2.2)(rxjs@7.8.2)) + '@nestjs/bull-shared': 11.0.4(@nestjs/common@11.1.17(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.17) '@nestjs/common': 11.1.17(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2) '@nestjs/core': 11.1.17(@nestjs/common@11.1.17(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.17)(reflect-metadata@0.2.2)(rxjs@7.8.2) bull: 4.16.5 tslib: 2.8.1 - '@nestjs/bullmq@11.0.4(@nestjs/common@11.1.17(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.17(@nestjs/common@11.1.17(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.17)(reflect-metadata@0.2.2)(rxjs@7.8.2))(bullmq@5.71.0)': + '@nestjs/bullmq@11.0.4(@nestjs/common@11.1.17(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.17)(bullmq@5.71.0)': dependencies: - '@nestjs/bull-shared': 11.0.4(@nestjs/common@11.1.17(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.17(@nestjs/common@11.1.17(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.17)(reflect-metadata@0.2.2)(rxjs@7.8.2)) + '@nestjs/bull-shared': 11.0.4(@nestjs/common@11.1.17(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.17) '@nestjs/common': 11.1.17(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2) '@nestjs/core': 11.1.17(@nestjs/common@11.1.17(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.17)(reflect-metadata@0.2.2)(rxjs@7.8.2) bullmq: 5.71.0 @@ -10095,7 +10014,7 @@ snapshots: transitivePeerDependencies: - chokidar - '@nestjs/swagger@11.2.6(@nestjs/common@11.1.17(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.17(@nestjs/common@11.1.17(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.17)(reflect-metadata@0.2.2)(rxjs@7.8.2))(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)': + '@nestjs/swagger@11.2.6(@nestjs/common@11.1.17(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.17)(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)': dependencies: '@microsoft/tsdoc': 0.16.0 '@nestjs/common': 11.1.17(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2) @@ -10110,7 +10029,7 @@ snapshots: class-transformer: 0.5.1 class-validator: 0.14.4 - '@nestjs/terminus@11.1.1(@nestjs/axios@4.0.1(@nestjs/common@11.1.17(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(axios@1.13.6)(rxjs@7.8.2))(@nestjs/common@11.1.17(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.17(@nestjs/common@11.1.17(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.17)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@prisma/client@6.19.2(prisma@6.19.2(typescript@5.9.3))(typescript@5.9.3))(reflect-metadata@0.2.2)(rxjs@7.8.2)': + '@nestjs/terminus@11.1.1(@nestjs/axios@4.0.1(@nestjs/common@11.1.17(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(axios@1.13.6)(rxjs@7.8.2))(@nestjs/common@11.1.17(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.17)(@prisma/client@6.19.2(prisma@6.19.2(typescript@5.9.3))(typescript@5.9.3))(reflect-metadata@0.2.2)(rxjs@7.8.2)': dependencies: '@nestjs/common': 11.1.17(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2) '@nestjs/core': 11.1.17(@nestjs/common@11.1.17(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.17)(reflect-metadata@0.2.2)(rxjs@7.8.2) @@ -10122,7 +10041,7 @@ snapshots: '@nestjs/axios': 4.0.1(@nestjs/common@11.1.17(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(axios@1.13.6)(rxjs@7.8.2) '@prisma/client': 6.19.2(prisma@6.19.2(typescript@5.9.3))(typescript@5.9.3) - '@nestjs/testing@11.1.17(@nestjs/common@11.1.17(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.17(@nestjs/common@11.1.17(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.17)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.17(@nestjs/common@11.1.17(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.17))': + '@nestjs/testing@11.1.17(@nestjs/common@11.1.17(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.17)(@nestjs/platform-express@11.1.17)': dependencies: '@nestjs/common': 11.1.17(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2) '@nestjs/core': 11.1.17(@nestjs/common@11.1.17(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.17)(reflect-metadata@0.2.2)(rxjs@7.8.2) @@ -10130,7 +10049,7 @@ snapshots: optionalDependencies: '@nestjs/platform-express': 11.1.17(@nestjs/common@11.1.17(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.17) - '@nestjs/throttler@6.5.0(@nestjs/common@11.1.17(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.17(@nestjs/common@11.1.17(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.17)(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)': + '@nestjs/throttler@6.5.0(@nestjs/common@11.1.17(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.17)(reflect-metadata@0.2.2)': dependencies: '@nestjs/common': 11.1.17(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2) '@nestjs/core': 11.1.17(@nestjs/common@11.1.17(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.17)(reflect-metadata@0.2.2)(rxjs@7.8.2) @@ -10138,10 +10057,6 @@ snapshots: '@next/env@16.2.1': {} - '@next/eslint-plugin-next@16.1.3': - dependencies: - fast-glob: 3.3.1 - '@next/swc-darwin-arm64@16.2.1': optional: true @@ -10184,18 +10099,6 @@ snapshots: '@noble/hashes@1.8.0': {} - '@nodelib/fs.scandir@2.1.5': - dependencies: - '@nodelib/fs.stat': 2.0.5 - run-parallel: 1.2.0 - - '@nodelib/fs.stat@2.0.5': {} - - '@nodelib/fs.walk@1.2.8': - dependencies: - '@nodelib/fs.scandir': 2.1.5 - fastq: 1.20.1 - '@nolyfill/is-core-module@1.0.39': {} '@nuxt/opencollective@0.4.1': @@ -11890,8 +11793,6 @@ snapshots: dependencies: tslib: 2.8.1 - aria-query@5.3.2: {} - array-buffer-byte-length@1.0.2: dependencies: call-bound: 1.0.4 @@ -11963,8 +11864,6 @@ snapshots: asap@2.0.6: {} - ast-types-flow@0.0.8: {} - async-function@1.0.0: {} async-limiter@1.0.1: {} @@ -11979,8 +11878,6 @@ snapshots: aws-ssl-profiles@1.1.2: {} - axe-core@4.11.1: {} - axios@1.13.6: dependencies: follow-redirects: 1.15.11 @@ -11989,8 +11886,6 @@ snapshots: transitivePeerDependencies: - debug - axobject-query@4.1.0: {} - babel-jest@29.7.0(@babel/core@7.29.0): dependencies: '@babel/core': 7.29.0 @@ -12625,8 +12520,6 @@ snapshots: csstype@3.2.3: {} - damerau-levenshtein@1.0.8: {} - data-urls@3.0.2: dependencies: abab: 2.0.6 @@ -12941,25 +12834,7 @@ snapshots: - supports-color - typescript - eslint-config-next@16.1.3(@typescript-eslint/parser@8.57.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3): - dependencies: - '@next/eslint-plugin-next': 16.1.3 - eslint: 9.39.4(jiti@2.6.1) - eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.4(jiti@2.6.1)) - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.57.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.4(jiti@2.6.1)) - eslint-plugin-jsx-a11y: 6.10.2(eslint@9.39.4(jiti@2.6.1)) - eslint-plugin-react: 7.37.5(eslint@9.39.4(jiti@2.6.1)) - eslint-plugin-react-hooks: 7.0.1(eslint@9.39.4(jiti@2.6.1)) - globals: 16.4.0 - typescript-eslint: 8.57.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) - optionalDependencies: - typescript: 5.9.3 - transitivePeerDependencies: - - '@typescript-eslint/parser' - - eslint-import-resolver-webpack - - eslint-plugin-import-x - - supports-color + eslint-config-next@0.2.4: {} eslint-config-prettier@10.1.8(eslint@9.39.4(jiti@2.6.1)): dependencies: @@ -12988,7 +12863,7 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.1(@typescript-eslint/parser@8.57.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.4(jiti@2.6.1)))(eslint@9.39.4(jiti@2.6.1)): + eslint-module-utils@2.12.1(@typescript-eslint/parser@8.57.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.4(jiti@2.6.1)): dependencies: debug: 3.2.7 optionalDependencies: @@ -13019,7 +12894,7 @@ snapshots: doctrine: 2.1.0 eslint: 9.39.4(jiti@2.6.1) eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.57.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.4(jiti@2.6.1)))(eslint@9.39.4(jiti@2.6.1)) + eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.57.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.4(jiti@2.6.1)) hasown: 2.0.2 is-core-module: 2.16.1 is-glob: 4.0.3 @@ -13037,25 +12912,6 @@ snapshots: - eslint-import-resolver-webpack - supports-color - eslint-plugin-jsx-a11y@6.10.2(eslint@9.39.4(jiti@2.6.1)): - dependencies: - aria-query: 5.3.2 - array-includes: 3.1.9 - array.prototype.flatmap: 1.3.3 - ast-types-flow: 0.0.8 - axe-core: 4.11.1 - axobject-query: 4.1.0 - damerau-levenshtein: 1.0.8 - emoji-regex: 9.2.2 - eslint: 9.39.4(jiti@2.6.1) - hasown: 2.0.2 - jsx-ast-utils: 3.3.5 - language-tags: 1.0.9 - minimatch: 3.1.5 - object.fromentries: 2.0.8 - safe-regex-test: 1.1.0 - string.prototype.includes: 2.0.1 - eslint-plugin-prettier@5.5.5(@types/eslint@9.6.1)(eslint-config-prettier@10.1.8(eslint@9.39.4(jiti@2.6.1)))(eslint@9.39.4(jiti@2.6.1))(prettier@3.8.1): dependencies: eslint: 9.39.4(jiti@2.6.1) @@ -13070,17 +12926,6 @@ snapshots: dependencies: eslint: 9.39.4(jiti@2.6.1) - eslint-plugin-react-hooks@7.0.1(eslint@9.39.4(jiti@2.6.1)): - dependencies: - '@babel/core': 7.29.0 - '@babel/parser': 7.29.2 - eslint: 9.39.4(jiti@2.6.1) - hermes-parser: 0.25.1 - zod: 4.3.6 - zod-validation-error: 4.0.2(zod@4.3.6) - transitivePeerDependencies: - - supports-color - eslint-plugin-react@7.37.5(eslint@9.39.4(jiti@2.6.1)): dependencies: array-includes: 3.1.9 @@ -13389,14 +13234,6 @@ snapshots: fast-diff@1.3.0: {} - fast-glob@3.3.1: - dependencies: - '@nodelib/fs.stat': 2.0.5 - '@nodelib/fs.walk': 1.2.8 - glob-parent: 5.1.2 - merge2: 1.4.1 - micromatch: 4.0.8 - fast-json-stable-stringify@2.1.0: {} fast-levenshtein@2.0.6: {} @@ -13407,10 +13244,6 @@ snapshots: fast-uri@3.1.0: {} - fastq@1.20.1: - dependencies: - reusify: 1.1.0 - fb-watchman@2.0.2: dependencies: bser: 2.1.1 @@ -13645,10 +13478,6 @@ snapshots: nypm: 0.6.5 pathe: 2.0.3 - glob-parent@5.1.2: - dependencies: - is-glob: 4.0.3 - glob-parent@6.0.2: dependencies: is-glob: 4.0.3 @@ -13687,8 +13516,6 @@ snapshots: globals@14.0.0: {} - globals@16.4.0: {} - globals@16.5.0: {} globalthis@1.0.4: @@ -13753,18 +13580,12 @@ snapshots: help-me@5.0.0: {} - hermes-estree@0.25.1: {} - hermes-estree@0.29.1: {} hermes-estree@0.32.0: {} hermes-estree@0.33.3: {} - hermes-parser@0.25.1: - dependencies: - hermes-estree: 0.25.1 - hermes-parser@0.29.1: dependencies: hermes-estree: 0.29.1 @@ -15021,12 +14842,6 @@ snapshots: lan-network@0.1.7: {} - language-subtag-registry@0.3.23: {} - - language-tags@1.0.9: - dependencies: - language-subtag-registry: 0.3.23 - leaflet@1.9.4: {} leven@3.1.0: {} @@ -15199,8 +15014,6 @@ snapshots: merge-stream@2.0.0: {} - merge2@1.4.1: {} - methods@1.1.2: {} metro-babel-transformer@0.83.3: @@ -15674,6 +15487,11 @@ snapshots: nested-error-stacks@2.0.1: {} + next-themes@0.4.6(react-dom@19.2.3(react@19.2.3))(react@19.2.3): + dependencies: + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) + next@16.2.1(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3): dependencies: '@next/env': 16.2.1 @@ -16205,8 +16023,6 @@ snapshots: querystringify@2.2.0: {} - queue-microtask@1.2.3: {} - queue@6.0.2: dependencies: inherits: 2.0.4 @@ -16535,8 +16351,6 @@ snapshots: retry@0.12.0: {} - reusify@1.1.0: {} - rimraf@3.0.2: dependencies: glob: 7.2.3 @@ -16551,10 +16365,6 @@ snapshots: transitivePeerDependencies: - supports-color - run-parallel@1.2.0: - dependencies: - queue-microtask: 1.2.3 - rxjs@7.8.1: dependencies: tslib: 2.8.1 @@ -16907,12 +16717,6 @@ snapshots: emoji-regex: 9.2.2 strip-ansi: 7.2.0 - string.prototype.includes@2.0.1: - dependencies: - call-bind: 1.0.8 - define-properties: 1.2.1 - es-abstract: 1.24.1 - string.prototype.matchall@4.0.12: dependencies: call-bind: 1.0.8 @@ -17201,7 +17005,7 @@ snapshots: babel-jest: 29.7.0(@babel/core@7.29.0) jest-util: 30.3.0 - ts-jest@29.4.6(@babel/core@7.29.0)(@jest/transform@30.3.0)(@jest/types@30.3.0)(babel-jest@29.7.0(@babel/core@7.29.0))(jest-util@30.3.0)(jest@30.3.0(@types/node@20.19.37)(ts-node@10.9.2(@types/node@20.19.37)(typescript@5.9.3)))(typescript@5.9.3): + ts-jest@29.4.6(@babel/core@7.29.0)(@jest/transform@30.3.0)(@jest/types@30.3.0)(babel-jest@30.3.0(@babel/core@7.29.0))(jest-util@30.3.0)(jest@30.3.0(@types/node@20.19.37)(ts-node@10.9.2(@types/node@20.19.37)(typescript@5.9.3)))(typescript@5.9.3): dependencies: bs-logger: 0.2.6 fast-json-stable-stringify: 2.1.0 @@ -17218,10 +17022,10 @@ snapshots: '@babel/core': 7.29.0 '@jest/transform': 30.3.0 '@jest/types': 30.3.0 - babel-jest: 29.7.0(@babel/core@7.29.0) + babel-jest: 30.3.0(@babel/core@7.29.0) jest-util: 30.3.0 - ts-jest@29.4.6(@babel/core@7.29.0)(@jest/transform@30.3.0)(@jest/types@30.3.0)(babel-jest@29.7.0(@babel/core@7.29.0))(jest-util@30.3.0)(jest@30.3.0(@types/node@22.19.15)(ts-node@10.9.2(@types/node@22.19.15)(typescript@5.9.3)))(typescript@5.9.3): + ts-jest@29.4.6(@babel/core@7.29.0)(@jest/transform@30.3.0)(@jest/types@30.3.0)(babel-jest@30.3.0(@babel/core@7.29.0))(jest-util@30.3.0)(jest@30.3.0(@types/node@22.19.15)(ts-node@10.9.2(@types/node@22.19.15)(typescript@5.9.3)))(typescript@5.9.3): dependencies: bs-logger: 0.2.6 fast-json-stable-stringify: 2.1.0 @@ -17238,7 +17042,7 @@ snapshots: '@babel/core': 7.29.0 '@jest/transform': 30.3.0 '@jest/types': 30.3.0 - babel-jest: 29.7.0(@babel/core@7.29.0) + babel-jest: 30.3.0(@babel/core@7.29.0) jest-util: 30.3.0 ts-loader@9.5.4(typescript@5.9.3)(webpack@5.104.1): @@ -17772,11 +17576,8 @@ snapshots: grammex: 3.1.12 graphmatch: 1.1.1 - zod-validation-error@4.0.2(zod@4.3.6): - dependencies: - zod: 4.3.6 - - zod@4.3.6: {} + zod@4.3.6: + optional: true zustand@5.0.12(@types/react@19.1.17)(react@19.2.3)(use-sync-external-store@1.6.0(react@19.2.3)): optionalDependencies: diff --git a/pr_body.md b/pr_body.md new file mode 100644 index 0000000..9e5db18 --- /dev/null +++ b/pr_body.md @@ -0,0 +1,22 @@ +# Title: Location Anonymization Service for Map Privacy + +Resolves #222 + +## Tasks and Fixes Made +1. **Created `PrivacyService`**: + - Implemented an algorithm to fuzz geo-coordinates and protect the exact locations. + - Using a deterministic hash based on coordinates to add an offset within a 500m to 1km radius limit. + - Added logic to keep fuzzing consistent for the same location to prevent jumping. + +2. **Backend Module Updating**: + - Implemented `getMapAnonymizedData` inside `AnalyticsService` formatting to standardized `GeoJsonFeatureCollection`. + - Exposed `GET /analytics/map-anonymized` inside `AnalyticsController`. + +3. **Data Transfer Objects (DTOs)**: + - Built out `GeoJsonFeature` and `GeoJsonFeatureCollection` definition for the endpoint types. + +4. **CI Checks Verified**: + - Nest.js backend tests passed smoothly without regressions. + - Project dependencies correctly build. + +Please review and merge.