diff --git a/apps/server/src/app.module.ts b/apps/server/src/app.module.ts index ffeeecad..157c6f0b 100644 --- a/apps/server/src/app.module.ts +++ b/apps/server/src/app.module.ts @@ -9,12 +9,16 @@ import { PrivateArchitectureModule } from 'src/private-architecture/private-arch import { PrismaService } from 'src/prisma/prisma.service'; import { MyModule } from './my/my.module'; import { ScheduleModule } from '@nestjs/schedule'; -import { NcloudResourcesService } from './ncloud-resources/ncloud-resources.service.js'; -import { CloudsModule } from './clouds/clouds.module'; -import { resourceUsage } from 'process'; +import { NcloudResourcesService } from './ncloud-resource/ncloud-resource.service.js'; +import { CloudModule } from './cloud/cloud.module'; +import { RedisModule } from '@nestjs-modules/ioredis'; @Module({ imports: [ + RedisModule.forRoot({ + type: 'single', + url: `redis://${process.env.REDIS_HOST}:${process.env.REDIS_PORT}`, + }), ConfigModule.forRoot(), AuthModule, UserModule, @@ -22,7 +26,7 @@ import { resourceUsage } from 'process'; PrivateArchitectureModule, MyModule, ScheduleModule.forRoot(), - CloudsModule, + CloudModule, ], controllers: [AppController], providers: [AppService, PrismaService, NcloudResourcesService], diff --git a/apps/server/src/clouds/clouds.controller.spec.ts b/apps/server/src/cloud/cloud.controller.spec.ts similarity index 53% rename from apps/server/src/clouds/clouds.controller.spec.ts rename to apps/server/src/cloud/cloud.controller.spec.ts index e9d76d7b..2bd10063 100644 --- a/apps/server/src/clouds/clouds.controller.spec.ts +++ b/apps/server/src/cloud/cloud.controller.spec.ts @@ -1,15 +1,15 @@ import { Test, TestingModule } from '@nestjs/testing'; -import { CloudsController } from './clouds.controller'; +import { CloudController } from './cloud.controller'; -describe('CloudsController', () => { - let controller: CloudsController; +describe('CloudController', () => { + let controller: CloudController; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ - controllers: [CloudsController], + controllers: [CloudController], }).compile(); - controller = module.get(CloudsController); + controller = module.get(CloudController); }); it('should be defined', () => { diff --git a/apps/server/src/cloud/cloud.controller.ts b/apps/server/src/cloud/cloud.controller.ts new file mode 100644 index 00000000..94239ddc --- /dev/null +++ b/apps/server/src/cloud/cloud.controller.ts @@ -0,0 +1,17 @@ +import { Controller, Get } from '@nestjs/common'; +import { CloudService } from './cloud.service'; + +@Controller('cloud') +export class CloudController { + constructor(private readonly service: CloudService) {} + + @Get('/prices') + getCloudResorucePrices() { + return this.service.findCloudResourcePrices(); + } + + // @Get('/test') + // getTestPrices() { + // return this.service.calculatePrice(); + // } +} diff --git a/apps/server/src/cloud/cloud.module.ts b/apps/server/src/cloud/cloud.module.ts new file mode 100644 index 00000000..883c9a03 --- /dev/null +++ b/apps/server/src/cloud/cloud.module.ts @@ -0,0 +1,12 @@ +import { Module } from '@nestjs/common'; +import { CloudController } from './cloud.controller'; +import { CloudService } from './cloud.service'; +import { PrismaModule } from 'src/prisma/prisma.module'; + +@Module({ + imports: [PrismaModule], + controllers: [CloudController], + providers: [CloudService], + exports: [CloudService], +}) +export class CloudModule {} diff --git a/apps/server/src/clouds/clouds.service.spec.ts b/apps/server/src/cloud/cloud.service.spec.ts similarity index 56% rename from apps/server/src/clouds/clouds.service.spec.ts rename to apps/server/src/cloud/cloud.service.spec.ts index e9aceb2c..2d1fe80a 100644 --- a/apps/server/src/clouds/clouds.service.spec.ts +++ b/apps/server/src/cloud/cloud.service.spec.ts @@ -1,15 +1,15 @@ import { Test, TestingModule } from '@nestjs/testing'; -import { CloudsService } from './clouds.service'; +import { CloudService } from './cloud.service'; -describe('CloudsService', () => { - let service: CloudsService; +describe('CloudService', () => { + let service: CloudService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ - providers: [CloudsService], + providers: [CloudService], }).compile(); - service = module.get(CloudsService); + service = module.get(CloudService); }); it('should be defined', () => { diff --git a/apps/server/src/cloud/cloud.service.ts b/apps/server/src/cloud/cloud.service.ts new file mode 100644 index 00000000..bfea8df2 --- /dev/null +++ b/apps/server/src/cloud/cloud.service.ts @@ -0,0 +1,65 @@ +import { InjectRedis } from '@nestjs-modules/ioredis'; +import { Injectable, NotFoundException } from '@nestjs/common'; +import Redis from 'ioredis'; +import { PrismaService } from 'src/prisma/prisma.service'; + +@Injectable() +export class CloudService { + constructor( + private readonly prisma: PrismaService, + @InjectRedis() private readonly redis: Redis, + ) {} + + async findCloudResourcePrices() { + return Object.fromEntries(await this.getCloudResourcesMap()); + } + + async calculatePrice(nodes: Record) { + if (nodes && !Object.keys(nodes).length) throw new NotFoundException(); + const cloudResourcesPriceMap = await this.getCloudResourcesMap(); + const nodeValues = Object.values(nodes); + const totalMonthPrice = nodeValues.reduce( + (price, nodeValue): number => { + if (nodeValue.properties['server_spec_code']) { + if ( + !cloudResourcesPriceMap.has( + nodeValue.properties['server_spec_code'], + ) + ) + throw new NotFoundException(); + price += cloudResourcesPriceMap.get( + nodeValue.properties['server_spec_code'], + ).monthCost as number; + return price; + } + return price; + }, + 0, + ); + return { totalMonthPrice }; + } + + private async getCloudResourcesMap() { + const cachePrices = JSON.parse(await this.redis.get('cloudResource')); + const prices = + cachePrices || + (await this.prisma.ncloudServerResource.findMany({ + select: { + serverSpecCode: true, + productName: true, + hourCost: true, + monthCost: true, + }, + })); + const priceMap = new Map>(); + prices.map((price) => { + const { productName, hourCost, monthCost } = price; + priceMap.set(price.serverSpecCode, { + productName, + hourCost, + monthCost, + }); + }); + return priceMap; + } +} diff --git a/apps/server/src/clouds/clouds.controller.ts b/apps/server/src/clouds/clouds.controller.ts deleted file mode 100644 index 688c2fc2..00000000 --- a/apps/server/src/clouds/clouds.controller.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { Controller } from '@nestjs/common'; - -@Controller('clouds') -export class CloudsController {} diff --git a/apps/server/src/clouds/clouds.module.ts b/apps/server/src/clouds/clouds.module.ts deleted file mode 100644 index 868d44bb..00000000 --- a/apps/server/src/clouds/clouds.module.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Module } from '@nestjs/common'; -import { CloudsController } from './clouds.controller'; -import { CloudsService } from './clouds.service'; - -@Module({ - controllers: [CloudsController], - providers: [CloudsService], -}) -export class CloudsModule {} diff --git a/apps/server/src/clouds/clouds.service.ts b/apps/server/src/clouds/clouds.service.ts deleted file mode 100644 index 28e059e3..00000000 --- a/apps/server/src/clouds/clouds.service.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { Injectable } from '@nestjs/common'; - -@Injectable() -export class CloudsService {} diff --git a/apps/server/src/ncloud-resources/ncloud-resources.service.spec.ts b/apps/server/src/ncloud-resource/ncloud-resource.service.spec.ts similarity index 87% rename from apps/server/src/ncloud-resources/ncloud-resources.service.spec.ts rename to apps/server/src/ncloud-resource/ncloud-resource.service.spec.ts index ff55a8ca..a45bd279 100644 --- a/apps/server/src/ncloud-resources/ncloud-resources.service.spec.ts +++ b/apps/server/src/ncloud-resource/ncloud-resource.service.spec.ts @@ -1,5 +1,5 @@ import { Test, TestingModule } from '@nestjs/testing'; -import { NcloudResourcesService } from './ncloud-resources.service'; +import { NcloudResourcesService } from './ncloud-resource.service'; describe('NcloudResourcesService', () => { let service: NcloudResourcesService; diff --git a/apps/server/src/ncloud-resources/ncloud-resources.service.ts b/apps/server/src/ncloud-resource/ncloud-resource.service.ts similarity index 92% rename from apps/server/src/ncloud-resources/ncloud-resources.service.ts rename to apps/server/src/ncloud-resource/ncloud-resource.service.ts index 453444ee..d580bc05 100644 --- a/apps/server/src/ncloud-resources/ncloud-resources.service.ts +++ b/apps/server/src/ncloud-resource/ncloud-resource.service.ts @@ -1,12 +1,16 @@ import { Ncloud, PriceApi, ApiKeyCredentials } from '@cloud-canvas/ncloud-sdk'; +import { InjectRedis } from '@nestjs-modules/ioredis'; import { Injectable } from '@nestjs/common'; import { Cron, CronExpression } from '@nestjs/schedule'; +import Redis from 'ioredis'; import { PrismaService } from 'src/prisma/prisma.service'; -import { writeFile } from 'fs/promises'; @Injectable() export class NcloudResourcesService { - constructor(private readonly prisma: PrismaService) {} + constructor( + private readonly prisma: PrismaService, + @InjectRedis() private readonly redis: Redis, + ) {} @Cron(CronExpression.EVERY_DAY_AT_3AM, { name: 'Insert Ncloud Resource Cron Job', @@ -93,6 +97,10 @@ export class NcloudResourcesService { await tx.ncloudServerResource.createMany({ data: flattenedResources, }); + await this.redis.set( + 'cloudResource', + JSON.stringify(flattenedResources), + ); }); } diff --git a/apps/server/src/private-architecture/private-architecture.module.ts b/apps/server/src/private-architecture/private-architecture.module.ts index 31b13f88..18dc9e47 100644 --- a/apps/server/src/private-architecture/private-architecture.module.ts +++ b/apps/server/src/private-architecture/private-architecture.module.ts @@ -2,9 +2,10 @@ import { Module } from '@nestjs/common'; import { PrivateArchitectureService } from './private-architecture.service'; import { PrivateArchitectureController } from './private-architecture.controller'; import { PrismaModule } from 'src/prisma/prisma.module'; +import { CloudModule } from 'src/cloud/cloud.module'; @Module({ - imports: [PrismaModule], + imports: [PrismaModule, CloudModule], controllers: [PrivateArchitectureController], providers: [PrivateArchitectureService], }) diff --git a/apps/server/src/private-architecture/private-architecture.service.ts b/apps/server/src/private-architecture/private-architecture.service.ts index b9b44cb7..84ec5249 100644 --- a/apps/server/src/private-architecture/private-architecture.service.ts +++ b/apps/server/src/private-architecture/private-architecture.service.ts @@ -8,23 +8,27 @@ import { RemoveArchitectureDto } from './dto/remove-architecture.dto'; import { RemoveVersionDto } from './dto/remove-version.dto'; import { SaveArchitectureDto } from './dto/save-architecture.dto'; import { SaveVersionDto } from './dto/save-version.dto'; +import { CloudService } from 'src/cloud/cloud.service'; @Injectable() export class PrivateArchitectureService { - constructor(private readonly prisma: PrismaService) {} + constructor( + private readonly prisma: PrismaService, + private readonly cloud: CloudService, + ) {} - saveArchitecture({ + async saveArchitecture({ title, userId: authorId, architecture, - cost, }: SaveArchitectureDto) { + const cost = await this.cloud.calculatePrice(architecture?.nodes); return this.prisma.privateArchitecture.create({ data: { title, authorId, architecture, - cost, + cost: cost.totalMonthPrice, }, }); } @@ -53,9 +57,9 @@ export class PrivateArchitectureService { userId: authorId, title, architecture, - cost, }: ModifyArchitectureDto) { const isAuthor = await this.isArchitectureAuthor(id, authorId); + const cost = await this.cloud.calculatePrice(architecture?.nodes); if (!isAuthor) throw new ForbiddenException(); return this.prisma.privateArchitecture.update({ where: { @@ -65,7 +69,7 @@ export class PrivateArchitectureService { data: { title, architecture, - cost, + cost: cost.totalMonthPrice, }, }); } @@ -108,16 +112,16 @@ export class PrivateArchitectureService { userId: authorId, title, architecture, - cost, }: SaveVersionDto) { const isAuthor = await this.isArchitectureAuthor(id, authorId); + const cost = await this.cloud.calculatePrice(architecture?.nodes); if (!isAuthor) throw new ForbiddenException(); return this.prisma.version.create({ data: { privateArchitectureId: id, title, architecture, - cost, + cost: cost.totalMonthPrice, }, }); } diff --git a/apps/server/src/public-architecture/public-architecture.module.ts b/apps/server/src/public-architecture/public-architecture.module.ts index 1cf85520..8ff6220a 100644 --- a/apps/server/src/public-architecture/public-architecture.module.ts +++ b/apps/server/src/public-architecture/public-architecture.module.ts @@ -2,9 +2,10 @@ import { Module } from '@nestjs/common'; import { PublicArchitectureService } from './public-architecture.service'; import { PublicArchitectureController } from './public-architecture.controller'; import { PrismaModule } from 'src/prisma/prisma.module'; +import { CloudModule } from 'src/cloud/cloud.module'; @Module({ - imports: [PrismaModule], + imports: [PrismaModule, CloudModule], controllers: [PublicArchitectureController], providers: [PublicArchitectureService], }) diff --git a/apps/server/src/public-architecture/public-architecture.service.ts b/apps/server/src/public-architecture/public-architecture.service.ts index 74d7a44b..02ed2b70 100644 --- a/apps/server/src/public-architecture/public-architecture.service.ts +++ b/apps/server/src/public-architecture/public-architecture.service.ts @@ -13,10 +13,14 @@ import { RemoveArchitectureDto } from './dto/remove-architecture.dto'; import { UnstarDto } from './dto/unstar.dto'; import { StarDto } from './dto/star.dto'; import { ImportDto } from './dto/import.dto'; +import { CloudService } from 'src/cloud/cloud.service'; @Injectable() export class PublicArchitectureService { - constructor(private readonly prisma: PrismaService) {} + constructor( + private readonly prisma: PrismaService, + private readonly cloud: CloudService, + ) {} async findArchitectures(queryParams: FindArchitecturesDto) { const [data, total] = await this.prisma.$transaction([ @@ -55,18 +59,18 @@ export class PublicArchitectureService { return { data, total }; } - saveArchitecture({ + async saveArchitecture({ title, architecture, - cost, tags, userId: authorId, }: SaveArchitectureDto) { + const cost = await this.cloud.calculatePrice(architecture?.nodes); return this.prisma.publicArchitecture.create({ data: { title, architecture, - cost, + cost: cost.totalMonthPrice, authorId, tags: { create: tags.map((name) => ({ diff --git a/apps/server/tsconfig.json b/apps/server/tsconfig.json index dd264f7e..918effdd 100644 --- a/apps/server/tsconfig.json +++ b/apps/server/tsconfig.json @@ -16,6 +16,7 @@ "noImplicitAny": false, "strictBindCallApply": false, "forceConsistentCasingInFileNames": false, - "noFallthroughCasesInSwitch": false + "noFallthroughCasesInSwitch": false, + "resolveJsonModule": true } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a5147a6d..e0049d76 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -9497,8 +9497,8 @@ snapshots: '@typescript-eslint/parser': 6.21.0(eslint@8.57.1)(typescript@5.6.3) eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1))(eslint@8.57.1) - eslint-plugin-import: 2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1) + eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@8.57.1) + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) eslint-plugin-jsx-a11y: 6.10.2(eslint@8.57.1) eslint-plugin-react: 7.37.2(eslint@8.57.1) eslint-plugin-react-hooks: 5.0.0(eslint@8.57.1) @@ -9521,37 +9521,37 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1))(eslint@8.57.1): + eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@8.57.1): dependencies: '@nolyfill/is-core-module': 1.0.39 debug: 4.3.7 enhanced-resolve: 5.17.1 eslint: 8.57.1 - eslint-module-utils: 2.12.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1) + eslint-module-utils: 2.12.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) fast-glob: 3.3.2 get-tsconfig: 4.8.1 is-bun-module: 1.2.1 is-glob: 4.0.3 optionalDependencies: - eslint-plugin-import: 2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1) + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) transitivePeerDependencies: - '@typescript-eslint/parser' - eslint-import-resolver-node - eslint-import-resolver-webpack - supports-color - eslint-module-utils@2.12.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1): + eslint-module-utils@2.12.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1): dependencies: debug: 3.2.7 optionalDependencies: '@typescript-eslint/parser': 6.21.0(eslint@8.57.1)(typescript@5.6.3) eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1))(eslint@8.57.1) + eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@8.57.1) transitivePeerDependencies: - supports-color - eslint-plugin-import@2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1): + eslint-plugin-import@2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.8 @@ -9562,7 +9562,7 @@ snapshots: doctrine: 2.1.0 eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1) + eslint-module-utils: 2.12.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) hasown: 2.0.2 is-core-module: 2.15.1 is-glob: 4.0.3