diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index c1f808e..ec8a9a4 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -6,6 +6,7 @@ on: jobs: build: + if: github.repository == 'SOS-RS/backend' runs-on: self-hosted steps: diff --git a/.github/workflows/dev.yml b/.github/workflows/dev.yml index 37b800a..01f24ec 100644 --- a/.github/workflows/dev.yml +++ b/.github/workflows/dev.yml @@ -6,6 +6,7 @@ on: jobs: build: + if: github.repository == 'SOS-RS/backend' runs-on: dev steps: diff --git a/.github/workflows/staging.yml b/.github/workflows/staging.yml index 818565f..a21bbf9 100644 --- a/.github/workflows/staging.yml +++ b/.github/workflows/staging.yml @@ -6,6 +6,7 @@ on: jobs: build: + if: github.repository == 'SOS-RS/backend' runs-on: stg steps: diff --git a/package-lock.json b/package-lock.json index afb97b4..65c1373 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,6 +15,7 @@ "@nestjs/jwt": "^10.2.0", "@nestjs/passport": "^10.0.3", "@nestjs/platform-fastify": "^10.3.8", + "@nestjs/schedule": "^4.0.2", "@nestjs/swagger": "^7.3.1", "@prisma/client": "^5.13.0", "bcrypt": "^5.1.1", @@ -2571,6 +2572,11 @@ "@types/node": "*" } }, + "node_modules/@types/luxon": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/@types/luxon/-/luxon-3.4.2.tgz", + "integrity": "sha512-TifLZlFudklWlMBfhubvgqTXRzLDI5pCbGa4P8a3wPyUQSW+1xQ5eDsreP9DWHX3tjq1ke96uYG/nwundroWcA==" + }, "node_modules/@types/methods": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/@types/methods/-/methods-1.1.4.tgz", @@ -4140,6 +4146,15 @@ "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", "dev": true }, + "node_modules/cron": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/cron/-/cron-3.1.7.tgz", + "integrity": "sha512-tlBg7ARsAMQLzgwqVxy8AZl/qlTc5nibqYwtNGoCrd+cV+ugI+tvZC1oT/8dFH8W455YrywGykx/KMmAqOr7Jw==", + "dependencies": { + "@types/luxon": "~3.4.0", + "luxon": "~3.4.0" + } + }, "node_modules/cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -7078,6 +7093,14 @@ "yallist": "^3.0.2" } }, + "node_modules/luxon": { + "version": "3.4.4", + "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.4.4.tgz", + "integrity": "sha512-zobTr7akeGHnv7eBOXcRgMeCP6+uyYsczwmeRCauvpvaAltgNyTbLH/+VaEAPUeWBT+1GuNmz4wC/6jtQzbbVA==", + "engines": { + "node": ">=12" + } + }, "node_modules/magic-string": { "version": "0.30.5", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.5.tgz", @@ -9764,6 +9787,18 @@ "node": ">= 0.4.0" } }, + "node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/v8-compile-cache-lib": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", diff --git a/package.json b/package.json index b56b950..a7caf97 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,7 @@ "@nestjs/jwt": "^10.2.0", "@nestjs/passport": "^10.0.3", "@nestjs/platform-fastify": "^10.3.8", + "@nestjs/schedule": "^4.0.2", "@nestjs/swagger": "^7.3.1", "@prisma/client": "^5.13.0", "bcrypt": "^5.1.1", diff --git a/prisma/dev_dump.sql b/prisma/dev_dump.sql index 359fb6e..211625a 100644 --- a/prisma/dev_dump.sql +++ b/prisma/dev_dump.sql @@ -97,7 +97,7 @@ INSERT INTO public.users VALUES ('e0306bc0-8c29-429a-bbd2-384f48d4f993', 'Dinho' INSERT INTO public.users VALUES ('e82f476a-1574-4dd2-a4c4-1c1f0117db12', 'Rhuam', '51992047974', '$2b$10$V4hFTbT7MrskROc4TI2lNe6gAd0g7U1niziAPycFueLhPJRFIfoGm', '51992047974', '2024-05-05T22:06:11.390Z', '2024-05-16T18:52:36.650Z', 'Admin', 'Estevam'); --- +--- -- Data for Name: shelters; Type: TABLE DATA; Schema: public; Owner: doadmin -- diff --git a/src/app.module.ts b/src/app.module.ts index 942260d..3f81421 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -12,6 +12,7 @@ import { SupplyCategoriesModule } from './supply-categories/supply-categories.mo import { ShelterManagersModule } from './shelter-managers/shelter-managers.module'; import { ShelterSupplyModule } from './shelter-supply/shelter-supply.module'; import { PartnersModule } from './partners/partners.module'; +import { DashboardModule } from './modules/dashboard/dashboard.module'; import { SupportersModule } from './supporters/supporters.module'; import { SuppliesHistoryModule } from './supplies-history/supplies-history.module'; @@ -26,6 +27,7 @@ import { SuppliesHistoryModule } from './supplies-history/supplies-history.modul ShelterManagersModule, ShelterSupplyModule, PartnersModule, + DashboardModule, SupportersModule, SuppliesHistoryModule, ], diff --git a/src/interceptors/interceptors/shelter-supply-history/shelter-supply-history.interceptor.ts b/src/interceptors/interceptors/shelter-supply-history/shelter-supply-history.interceptor.ts index 358afc1..83baed4 100644 --- a/src/interceptors/interceptors/shelter-supply-history/shelter-supply-history.interceptor.ts +++ b/src/interceptors/interceptors/shelter-supply-history/shelter-supply-history.interceptor.ts @@ -8,7 +8,7 @@ import { Observable } from 'rxjs'; import { ShelterSupplyHistoryAction } from './types'; import { handler } from './utils'; -import { prisma } from '../../../prisma/prisma.service'; +import { PrismaService } from '../../../prisma/prisma.service'; @Injectable() export class ShelterSupplyHistoryInterceptor implements NestInterceptor { @@ -16,7 +16,7 @@ export class ShelterSupplyHistoryInterceptor implements NestInterceptor { intercept(context: ExecutionContext, next: CallHandler): Observable { const request = context.switchToHttp().getRequest(); - handler(prisma, this.action, request); + handler(PrismaService.getInstance(), this.action, request); return next.handle(); } } diff --git a/src/interceptors/interceptors/shelter-supply-history/utils.ts b/src/interceptors/interceptors/shelter-supply-history/utils.ts index 61debca..4611973 100644 --- a/src/interceptors/interceptors/shelter-supply-history/utils.ts +++ b/src/interceptors/interceptors/shelter-supply-history/utils.ts @@ -14,15 +14,14 @@ import { ShelterSupplyHistoryAction, UserIdentity } from './types'; import { getSessionData } from '@/utils/utils'; function registerSupplyLog( - prismaService: PrismaService, body: z.infer, - user: UserIdentity, + user: UserIdentity = {}, ) { const fn = async () => { const { shelterId, supplyId, ...rest } = CreateSupplyHistorySchema.parse(body); - const prev = await prismaService.supplyHistory.findFirst({ + const prev = await PrismaService.getInstance().supplyHistory.findFirst({ where: { shelterId, supplyId, @@ -32,7 +31,7 @@ function registerSupplyLog( }, }); - await prismaService.supplyHistory.create({ + await PrismaService.getInstance().supplyHistory.create({ data: { shelterId, supplyId, @@ -57,23 +56,20 @@ function registerSupplyLog( } function registerCreateSupplyLog( - prismaService: PrismaService, body: z.infer, user: UserIdentity, ) { const payload = CreateShelterSupplySchema.parse(body); - registerSupplyLog(prismaService, payload, user); + registerSupplyLog(payload, user); } function registerUpdateSupplyLog( - prismaService: PrismaService, body: z.infer, user: UserIdentity, ) { const payload = UpdateShelterSupplySchema.parse(body); registerSupplyLog( - prismaService, { shelterId: payload.where.shelterId, supplyId: payload.where.supplyId, @@ -85,7 +81,6 @@ function registerUpdateSupplyLog( } function registerUpdateManySupplyLog( - prismaService: PrismaService, body: z.infer, user: UserIdentity, ) { @@ -93,7 +88,6 @@ function registerUpdateManySupplyLog( ids.forEach((id) => registerSupplyLog( - prismaService, { shelterId, supplyId: id, @@ -124,11 +118,10 @@ function handler( switch (action) { case ShelterSupplyHistoryAction.Create: - registerCreateSupplyLog(prismaService, request.body as any, user); + registerCreateSupplyLog(request.body as any, user); break; case ShelterSupplyHistoryAction.Update: registerUpdateSupplyLog( - prismaService, { data: request.body as any, where: request.params as any, @@ -138,7 +131,6 @@ function handler( break; case ShelterSupplyHistoryAction.UpdateMany: registerUpdateManySupplyLog( - prismaService, { shelterId: (request.params as any).shelterId, ids: (request.body as any).ids, @@ -149,4 +141,4 @@ function handler( } } -export { handler }; +export { handler, registerSupplyLog }; diff --git a/src/modules/dashboard/dashboard.controller.spec.ts b/src/modules/dashboard/dashboard.controller.spec.ts new file mode 100644 index 0000000..9ae8da7 --- /dev/null +++ b/src/modules/dashboard/dashboard.controller.spec.ts @@ -0,0 +1,20 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { DashboardController } from './dashboard.controller'; +import { DashboardService } from './dashboard.service'; + +describe('DashboardController', () => { + let controller: DashboardController; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + controllers: [DashboardController], + providers: [DashboardService], + }).compile(); + + controller = module.get(DashboardController); + }); + + it('should be defined', () => { + expect(controller).toBeDefined(); + }); +}); diff --git a/src/modules/dashboard/dashboard.controller.ts b/src/modules/dashboard/dashboard.controller.ts new file mode 100644 index 0000000..db2c43a --- /dev/null +++ b/src/modules/dashboard/dashboard.controller.ts @@ -0,0 +1,22 @@ +import { Controller, Get, HttpException, Logger, Query } from '@nestjs/common'; +import { DashboardService } from './dashboard.service'; +import { ServerResponse } from '@/utils/utils'; +import { ApiTags } from '@nestjs/swagger'; + +@ApiTags('Dashboard') +@Controller('dashboard') +export class DashboardController { + private logger = new Logger(); + constructor(private readonly dashboardService: DashboardService) {} + + @Get('') + async index(@Query() query) { + try { + const data = await this.dashboardService.index(query); + return new ServerResponse(200, 'Successfully get dashboard', data); + } catch (err: any) { + this.logger.error(`Failed to get shelters: ${err}`); + throw new HttpException(err?.code ?? err?.name ?? `${err}`, 400); + } + } +} diff --git a/src/modules/dashboard/dashboard.module.ts b/src/modules/dashboard/dashboard.module.ts new file mode 100644 index 0000000..9a988b8 --- /dev/null +++ b/src/modules/dashboard/dashboard.module.ts @@ -0,0 +1,11 @@ +import { Module } from '@nestjs/common'; +import { DashboardService } from './dashboard.service'; +import { DashboardController } from './dashboard.controller'; +import { PrismaModule } from 'src/prisma/prisma.module'; + +@Module({ + imports: [PrismaModule], + controllers: [DashboardController], + providers: [DashboardService], +}) +export class DashboardModule {} diff --git a/src/modules/dashboard/dashboard.service.spec.ts b/src/modules/dashboard/dashboard.service.spec.ts new file mode 100644 index 0000000..dc3993a --- /dev/null +++ b/src/modules/dashboard/dashboard.service.spec.ts @@ -0,0 +1,18 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { DashboardService } from './dashboard.service'; + +describe('DashboardService', () => { + let service: DashboardService; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [DashboardService], + }).compile(); + + service = module.get(DashboardService); + }); + + it('should be defined', () => { + expect(service).toBeDefined(); + }); +}); diff --git a/src/modules/dashboard/dashboard.service.ts b/src/modules/dashboard/dashboard.service.ts new file mode 100644 index 0000000..22952e2 --- /dev/null +++ b/src/modules/dashboard/dashboard.service.ts @@ -0,0 +1,160 @@ +import * as qs from 'qs'; +import { Injectable } from '@nestjs/common'; +import { PrismaService } from 'src/prisma/prisma.service'; +import { ShelterSearchPropsSchema } from 'src/shelter/types/search.types'; +import { SearchSchema } from 'src/types'; +import { ShelterSearch } from 'src/shelter/ShelterSearch'; +import { DefaultArgs } from '@prisma/client/runtime/library'; +import { Prisma } from '@prisma/client'; + +@Injectable() +export class DashboardService { + constructor(private readonly prismaService: PrismaService) {} + + async index(query: any) { + const { + order, + orderBy, + page, + perPage, + search: searchQuery, + } = SearchSchema.parse(query); + const queryData = ShelterSearchPropsSchema.parse(qs.parse(searchQuery)); + const { getQuery } = new ShelterSearch(this.prismaService, queryData); + const where = await getQuery(); + + const take = perPage; + const skip = perPage * (page - 1); + + const whereData: Prisma.ShelterFindManyArgs = { + take, + skip, + orderBy: { [orderBy]: order }, + where, + }; + + const allShelters = await this.prismaService.shelter.findMany({ + ...whereData, + select: { + id: true, + name: true, + shelteredPeople: true, + actived: true, + capacity: true, + shelterSupplies: { + select: { + priority: true, + supply: { + select: { + supplyCategory: { + select: { + name: true, + }, + }, + }, + }, + }, + }, + }, + }); + + const categoriesWithPriorities = await this.prismaService.supplyCategory.findMany({ + select: { + id: true, + name: true, + supplies: { + select: { + shelterSupplies: { + select: { + priority: true, + shelterId: true + } + } + } + } + } + }); + + const result = categoriesWithPriorities.map(category => { + const priorityCounts = { + priority100: 0, + priority10: 0, + priority1: 0, + }; + + const countedShelters = new Set(); + + category.supplies.forEach(supply => { + supply.shelterSupplies.forEach(shelterSupply => { + if (!countedShelters.has(shelterSupply.shelterId)) { + switch (shelterSupply.priority) { + case 100: + priorityCounts.priority100++; + break; + case 10: + priorityCounts.priority10++; + break; + case 1: + priorityCounts.priority1++; + break; + default: + break; + } + countedShelters.add(shelterSupply.shelterId); + } + }); + }); + + return { + categoryId: category.id, + categoryName: category.name, + ...priorityCounts, + }; + }); + + const allPeopleSheltered = allShelters.reduce((accumulator, current) => { + if ( + current.actived && + current.capacity !== null && + current.capacity > 0 + ) { + return accumulator + (current.shelteredPeople ?? 0); + } else { + return accumulator; + } + }, 0); + + const numSheltersAvailable = allShelters.filter(shelter => { + if (shelter.actived && shelter.capacity !== null && shelter.capacity > 0) { + return (shelter.shelteredPeople ?? 0) < shelter.capacity; + } + return false; + }).length; + + const numSheltersFull = allShelters.reduce((count, shelter) => { + if (shelter.actived && shelter.capacity !== null && shelter.capacity > 0) { + if ((shelter.shelteredPeople ?? 0) >= shelter.capacity) { + return count + 1; + } + } + return count; + }, 0); + + const shelterWithoutInformation = allShelters.reduce((count, shelter) => { + if (shelter.shelteredPeople === null || shelter.shelteredPeople === undefined) { + return count + 1; + } + return count; + }, 0); + + + return { + allShelters: allShelters.length, + allPeopleSheltered: allPeopleSheltered, + shelterAvaliable: numSheltersAvailable, + shelterFull: numSheltersFull, + shelterWithoutInformation: shelterWithoutInformation, + categoriesWithPriorities: result, + }; + } +} diff --git a/src/partners/partners.controller.ts b/src/partners/partners.controller.ts index 9b33a6f..2e1c3f4 100644 --- a/src/partners/partners.controller.ts +++ b/src/partners/partners.controller.ts @@ -9,8 +9,10 @@ import { } from '@nestjs/common'; import { PartnersService } from './partners.service'; import { ServerResponse } from '../utils'; +import { ApiTags } from '@nestjs/swagger'; import { AdminGuard } from '@/guards/admin.guard'; +@ApiTags('Parceiros') @Controller('partners') export class PartnersController { private logger = new Logger(PartnersController.name); diff --git a/src/prisma/prisma.service.ts b/src/prisma/prisma.service.ts index 12806a8..7237939 100644 --- a/src/prisma/prisma.service.ts +++ b/src/prisma/prisma.service.ts @@ -39,5 +39,3 @@ export class PrismaService }); } } - -export const prisma = PrismaService.getInstance(); diff --git a/src/shelter/ShelterSearch.ts b/src/shelter/ShelterSearch.ts index 1b9c76c..d921dbf 100644 --- a/src/shelter/ShelterSearch.ts +++ b/src/shelter/ShelterSearch.ts @@ -31,12 +31,14 @@ class ShelterSearch { } priority(supplyIds: string[] = []): Prisma.ShelterWhereInput { - if (!this.formProps.priority) return {}; + if (!this.formProps.priorities?.length) return {}; return { shelterSupplies: { some: { - priority: +this.formProps.priority, + priority: { + in: this.formProps.priorities, + }, supplyId: supplyIds.length > 0 ? { @@ -71,13 +73,30 @@ class ShelterSearch { } supplyCategoryIds( - priority?: SupplyPriority | null, + priority?: SupplyPriority[] | null, ): Prisma.ShelterWhereInput { if (!this.formProps.supplyCategoryIds) return {}; + + if (!priority || !priority.length) { + return { + shelterSupplies: { + some: { + supply: { + supplyCategoryId: { + in: this.formProps.supplyCategoryIds, + }, + }, + }, + }, + }; + } + return { shelterSupplies: { some: { - priority: priority ? +priority : undefined, + priority: { + in: priority, + }, supply: { supplyCategoryId: { in: this.formProps.supplyCategoryIds, @@ -159,7 +178,7 @@ class ShelterSearch { search, { OR: this.shelterStatus }, this.priority(this.formProps.supplyIds), - this.supplyCategoryIds(this.formProps.priority), + this.supplyCategoryIds(this.formProps.priorities), ], }; diff --git a/src/shelter/shelter.service.ts b/src/shelter/shelter.service.ts index f79ac36..058db35 100644 --- a/src/shelter/shelter.service.ts +++ b/src/shelter/shelter.service.ts @@ -1,7 +1,7 @@ -import { Injectable, OnModuleInit } from '@nestjs/common'; -import { Prisma } from '@prisma/client'; +import { Injectable, Logger, OnModuleInit } from '@nestjs/common'; +import { Prisma, ShelterSupply } from '@prisma/client'; import { DefaultArgs } from '@prisma/client/runtime/library'; -import { subDays } from 'date-fns'; +import { millisecondsToHours, subDays } from 'date-fns'; import * as qs from 'qs'; import { z } from 'zod'; @@ -13,11 +13,14 @@ import { ShelterSearchPropsSchema } from './types/search.types'; import { CreateShelterSchema, FullUpdateShelterSchema, + IShelterSupplyDecay, UpdateShelterSchema, } from './types/types'; +import { registerSupplyLog } from '@/interceptors/interceptors/shelter-supply-history/utils'; @Injectable() export class ShelterService implements OnModuleInit { + private logger = new Logger(ShelterService.name); private voluntaryIds: string[] = []; constructor(private readonly prismaService: PrismaService) {} @@ -93,6 +96,10 @@ export class ShelterService implements OnModuleInit { select: { priority: true, quantity: true, + supplyId: true, + shelterId: true, + createdAt: true, + updatedAt: true, supply: { select: { id: true, @@ -115,6 +122,8 @@ export class ShelterService implements OnModuleInit { }, }); + if (data) this.decayShelterSupply(data.shelterSupplies); + return data; } @@ -181,6 +190,8 @@ export class ShelterService implements OnModuleInit { }, }); + this.decayShelterSupply(results.flatMap((r) => r.shelterSupplies)); + const parsed = parseTagResponse(queryData, results, this.voluntaryIds); return { @@ -228,4 +239,100 @@ export class ShelterService implements OnModuleInit { this.voluntaryIds.push(...resp.map((s) => s.id)); }); } + + private parseShelterSupply( + shelterSupply: ShelterSupply, + ): IShelterSupplyDecay { + return { + shelterId: shelterSupply.shelterId, + supplyId: shelterSupply.supplyId, + priority: shelterSupply.priority, + createdAt: new Date(shelterSupply.createdAt).getTime(), + updatedAt: shelterSupply.updatedAt + ? new Date(shelterSupply.updatedAt).getTime() + : 0, + }; + } + + private canDecayShelterSupply( + shelterSupply: IShelterSupplyDecay, + priorities: SupplyPriority[], + timeInHoursToDecay: number, + ): boolean { + return ( + priorities.includes(shelterSupply.priority) && + millisecondsToHours( + new Date().getTime() - + Math.max(shelterSupply.createdAt, shelterSupply.updatedAt), + ) > timeInHoursToDecay + ); + } + + private async handleDecayShelterSupply( + shelterSupplies: IShelterSupplyDecay[], + newPriority: SupplyPriority, + ) { + const shelterIds: Set = new Set(); + shelterSupplies.forEach((s) => shelterIds.add(s.shelterId)); + + await this.prismaService.$transaction([ + this.prismaService.shelter.updateMany({ + where: { + id: { + in: Array.from(shelterIds), + }, + }, + data: { + updatedAt: new Date().toISOString(), + }, + }), + ...shelterSupplies.map((s) => + this.prismaService.shelterSupply.update({ + where: { + shelterId_supplyId: { + shelterId: s.shelterId, + supplyId: s.supplyId, + }, + }, + data: { + priority: newPriority, + updatedAt: new Date().toISOString(), + }, + }), + ), + ]); + + shelterSupplies.forEach((s) => { + registerSupplyLog({ + shelterId: s.shelterId, + supplyId: s.supplyId, + priority: newPriority, + }); + }); + } + + private async decayShelterSupply(shelterSupplies: ShelterSupply[]) { + this.handleDecayShelterSupply( + shelterSupplies + .map(this.parseShelterSupply) + .filter((f) => + this.canDecayShelterSupply(f, [SupplyPriority.Urgent], 12), + ), + + SupplyPriority.Needing, + ); + + this.handleDecayShelterSupply( + shelterSupplies + .map(this.parseShelterSupply) + .filter((f) => + this.canDecayShelterSupply( + f, + [SupplyPriority.Needing, SupplyPriority.Remaining], + 48, + ), + ), + SupplyPriority.UnderControl, + ); + } } diff --git a/src/shelter/types/search.types.ts b/src/shelter/types/search.types.ts index b87e653..ab5e6a1 100644 --- a/src/shelter/types/search.types.ts +++ b/src/shelter/types/search.types.ts @@ -31,10 +31,13 @@ export type GeolocationFilter = z.infer; export const ShelterSearchPropsSchema = z.object({ search: z.string().optional(), - priority: z.preprocess( - (value) => Number(value) || undefined, - z.nativeEnum(SupplyPriority).optional(), - ), + priorities: z + .array(z.string()) + .optional() + .transform((values) => + values ? values.map(parseInt).filter((v) => !isNaN(v)) : [], + ) + .pipe(z.array(z.nativeEnum(SupplyPriority))), supplyCategoryIds: z.array(z.string()).optional(), supplyIds: z.array(z.string()).optional(), shelterStatus: z.array(ShelterStatusSchema).optional(), diff --git a/src/shelter/types/types.ts b/src/shelter/types/types.ts index 0bdee09..c236178 100644 --- a/src/shelter/types/types.ts +++ b/src/shelter/types/types.ts @@ -49,6 +49,14 @@ const FullUpdateShelterSchema = ShelterSchema.omit({ .partial() .transform((args) => removeEmptyStrings(args)); +export interface IShelterSupplyDecay { + shelterId: string; + supplyId: string; + priority: number; + createdAt: number; + updatedAt: number; +} + export { ShelterSchema, CreateShelterSchema,