diff --git a/apps/image-server/.eslintrc.json b/apps/image-server/.eslintrc.json new file mode 100644 index 00000000..9d9c0db5 --- /dev/null +++ b/apps/image-server/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/apps/image-server/Dockerfile b/apps/image-server/Dockerfile new file mode 100644 index 00000000..782b07ad --- /dev/null +++ b/apps/image-server/Dockerfile @@ -0,0 +1,37 @@ +FROM node:20.2-alpine3.18 as unteris-node + + +RUN npm i -g pnpm@8.6.10 && \ + apk add --no-cache \ + dumb-init=1.2.5-r2 + +FROM unteris-node AS unteris-common + +WORKDIR /src +RUN apk add --no-cache \ + python3 \ + make \ + gcc \ + g++ +COPY package.json \ + tsconfig* \ + nx.json \ + pnpm-lock.yaml \ + ./ +ENV CYPRESS_INSTALL_BINARY=0 +RUN pnpm i + +FROM unteris-common AS image-server-build +COPY apps/image-server ./apps/image-server/ +COPY libs/server ./libs/server/ +COPY libs/shared ./libs/shared/ +RUN pnpm nx run image-server:build:production + +FROM unteris-node AS image-server-prod +LABEL description="The image processing serve code for the Unteris website. It runs a NestJS server and connects to a redis and postgres database" +USER node +WORKDIR /src +COPY --from=image-server-build --chown=node:node /src/dist/apps/image-server ./ +ENV NODE_ENV=production +RUN pnpm i +CMD ["dumb-init", "node", "main.js"] diff --git a/apps/image-server/project.json b/apps/image-server/project.json new file mode 100644 index 00000000..2dbe62ea --- /dev/null +++ b/apps/image-server/project.json @@ -0,0 +1,72 @@ +{ + "name": "image-server", + "$schema": "../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "apps/image-server/src", + "projectType": "application", + "targets": { + "build": { + "executor": "@nx/webpack:webpack", + "outputs": ["{options.outputPath}"], + "defaultConfiguration": "production", + "options": { + "target": "node", + "compiler": "tsc", + "outputPath": "dist/apps/image-server", + "main": "apps/image-server/src/main.ts", + "tsConfig": "apps/image-server/tsconfig.app.json", + "assets": ["apps/image-server/src/assets"], + "isolatedConfig": true, + "webpackConfig": "apps/image-server/webpack.config.js" + }, + "configurations": { + "production": { + "optimization": true, + "extractLicenses": true, + "inspect": false, + "generatePackageJson": true, + "buildLibsFromSrc": true, + "fileReplacements": [ + { + "replace": "apps/image-server/src/environments/environment.ts", + "with": "apps/image-server/src/environments/environment.prod.ts" + } + ] + } + } + }, + "serve": { + "executor": "@nx/js:node", + "defaultConfiguration": "development", + "options": { + "inspect": false, + "buildTarget": "image-server:build" + }, + "configurations": { + "development": { + "buildTarget": "image-server:build:development" + }, + "production": { + "buildTarget": "image-server:build:production" + } + } + }, + "lint": { + "executor": "@nx/linter:eslint", + "outputs": ["{options.outputFile}"], + "options": { + "lintFilePatterns": ["apps/image-server/**/*.ts"] + } + }, + "package": { + "executor": "@unteris/plugin/docker:build", + "outputs": ["{workspaceRoot}/docker/cache/server"], + "options": {}, + "configurations": { + "ci": { + "publish": true + } + } + } + }, + "tags": [] +} diff --git a/apps/image-server/src/assets/.gitkeep b/apps/image-server/src/assets/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/apps/image-server/src/main.ts b/apps/image-server/src/main.ts new file mode 100644 index 00000000..eeae20d8 --- /dev/null +++ b/apps/image-server/src/main.ts @@ -0,0 +1,27 @@ +import { Logger } from '@nestjs/common'; +import { NestFactory } from '@nestjs/core'; +import { Transport } from '@nestjs/microservices'; +import { OgmaService } from '@ogma/nestjs-module'; + +import { ImageRootModule } from '@unteris/server/image-root'; + +async function bootstrap() { + const app = await NestFactory.createMicroservice(ImageRootModule, { + bufferLogs: true, + transport: Transport.RMQ, + options: { + urls: [ + `amqp://${process.env.RABBIT_USER}:${process.env.RABBIT_PASSWORD}@${process.env.RABBIT_HOST}:${process.env.RABBIT_PORT}`, + ], + queue: '', + queueOptions: { + durable: true, + }, + }, + }); + app.useLogger(app.get(OgmaService)); + await app.listen(); + Logger.log(`🚀 Image Processing Server up and running!`); +} + +bootstrap(); diff --git a/apps/image-server/tsconfig.app.json b/apps/image-server/tsconfig.app.json new file mode 100644 index 00000000..0661ef01 --- /dev/null +++ b/apps/image-server/tsconfig.app.json @@ -0,0 +1,18 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "module": "commonjs", + "types": ["node"], + "emitDecoratorMetadata": true, + "target": "es2021", + "strictNullChecks": true, + "noImplicitAny": true, + "strictBindCallApply": true, + "forceConsistentCasingInFileNames": true, + "noFallthroughCasesInSwitch": true, + "esModuleInterop": true + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/apps/image-server/tsconfig.json b/apps/image-server/tsconfig.json new file mode 100644 index 00000000..3685ac7f --- /dev/null +++ b/apps/image-server/tsconfig.json @@ -0,0 +1,13 @@ +{ + "extends": "../../tsconfig.base.json", + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.app.json" + } + ], + "compilerOptions": { + "esModuleInterop": true + } +} diff --git a/apps/image-server/webpack.config.js b/apps/image-server/webpack.config.js new file mode 100644 index 00000000..81db92b9 --- /dev/null +++ b/apps/image-server/webpack.config.js @@ -0,0 +1,8 @@ +const { composePlugins, withNx } = require('@nx/webpack'); + +// Nx plugins for webpack. +module.exports = composePlugins(withNx(), (config) => { + // Update the webpack config as needed here. + // e.g. `config.plugins.push(new MyPlugin())` + return config; +}); diff --git a/apps/kysely-cli/src/app/seed.command.ts b/apps/kysely-cli/src/app/seed.command.ts index c5edd895..556ce224 100644 --- a/apps/kysely-cli/src/app/seed.command.ts +++ b/apps/kysely-cli/src/app/seed.command.ts @@ -99,7 +99,7 @@ export class SeedCommand extends CommandRunner { } await this.db .insertInto('deity') - .values([{ ...data, category: category.id }]) + .values([{ ...data, categoryId: category.id }]) .executeTakeFirstOrThrow(); } diff --git a/apps/server/project.json b/apps/server/project.json index 4791a110..a4b229e9 100644 --- a/apps/server/project.json +++ b/apps/server/project.json @@ -33,8 +33,18 @@ }, "serve": { "executor": "@nx/js:node", + "defaultConfiguration": "development", "options": { + "inspect": false, "buildTarget": "server:build" + }, + "configurations": { + "development": { + "buildTarget": "server:build:development" + }, + "production": { + "buildTarget": "server:build:production" + } } }, "lint": { diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index 24b9850c..00f2a5d1 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -23,6 +23,22 @@ services: - '3333:3333' depends_on: - postgres + - redis + - rabbit + labels: + - 'com.centurylinklabs.watchtower.enable=true' + image-server: + image: jmcdo29/unteris-image-server + logging: + driver: local + env_file: .env + environment: + DATABASE_HOST: postgres + volumes: + - './images:/src/images' + depends_on: + - postgres + - rabbit labels: - 'com.centurylinklabs.watchtower.enable=true' migrations: @@ -57,3 +73,9 @@ services: volumes: - './redis.conf:/usr/local/etc/redis/redis.conf' command: redis-server /usr/local/etc/redis/redis.conf + rabbitmq: + image: rabbitmq + ports: + - '5672:5672' + volumes: + - './rabbitmq.conf:/etc/rabbitmq/rabbitmq.conf' diff --git a/docker-compose.yml b/docker-compose.yml index 1f140dd1..a71d0ce5 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -14,3 +14,10 @@ services: image: redis ports: - '6379:6379' + rabbitmq: + image: rabbitmq + ports: + - '5672:5672' + environment: + RABBITMQ_DEFAULT_USER: rabbit + RABBITMQ_DEFAULT_PASS: rabbit diff --git a/libs/db/migrations/README.md b/libs/db/migrations/README.md index 96b6838c..0437a00b 100644 --- a/libs/db/migrations/README.md +++ b/libs/db/migrations/README.md @@ -18,6 +18,8 @@ erDiagram User_Account ||--|{ User_Permission : "should have" Verification_Token ||--|| User_Account : "relates to" Role ||--|{ User_Permission : "relates_to" + User_Account ||--|| : "has an avatar" + Deity ||--|| Image : "has a portrait" DeityCategory { string name ulid id @@ -26,9 +28,9 @@ erDiagram string name ulid id string description - string image_url - string category - string location + string image_id + string category_id + string location_id } Domain { string name @@ -67,7 +69,7 @@ erDiagram string name string email boolean isVerified - string photo_url + string image_id } User_Permission { ulid id @@ -96,5 +98,13 @@ erDiagram ulid user_id string type } + Image { + ulid id + string type + string original_url + string small_url + string medium_url + string large_url + } ``` diff --git a/libs/db/migrations/diagram.mmd b/libs/db/migrations/diagram.mmd index 548eab99..99a80609 100644 --- a/libs/db/migrations/diagram.mmd +++ b/libs/db/migrations/diagram.mmd @@ -9,6 +9,8 @@ erDiagram User_Account ||--|{ User_Permission : "should have" Verification_Token ||--|| User_Account : "relates to" Role ||--|{ User_Permission : "relates_to" + User_Account ||--|| : "has an avatar" + Deity ||--|| Image : "has a portrait" DeityCategory { string name ulid id @@ -17,9 +19,9 @@ erDiagram string name ulid id string description - string image_url - string category - string location + string image_id + string category_id + string location_id } Domain { string name @@ -58,7 +60,7 @@ erDiagram string name string email boolean isVerified - string photo_url + string image_id } User_Permission { ulid id @@ -87,3 +89,11 @@ erDiagram ulid user_id string type } + Image { + ulid id + string type + string original_url + string small_url + string medium_url + string large_url + } diff --git a/libs/db/migrations/src/01H5TV87JJ3CE73WBGZZWQYBP4-convert-to-new-ulid-type.ts b/libs/db/migrations/src/01H5TV87JJ3CE73WBGZZWQYBP4-convert-to-new-ulid-type.ts index 05ddf52f..9bb6f0d4 100644 --- a/libs/db/migrations/src/01H5TV87JJ3CE73WBGZZWQYBP4-convert-to-new-ulid-type.ts +++ b/libs/db/migrations/src/01H5TV87JJ3CE73WBGZZWQYBP4-convert-to-new-ulid-type.ts @@ -4,20 +4,21 @@ import { Kysely, sql, } from 'kysely'; +import { kyselyUlid } from './ulid.sql'; const convertToUlid = ( column: string ): [string, AlterColumnBuilderCallback] => [ column, (col: AlterColumnBuilder) => - col.setDataType(sql`ulid USING (${column}::ulid)`), + col.setDataType(sql`ulid USING (${sql.ref(column)}::ulid)`), ]; const setUlidDefault = ( column: string ): [string, AlterColumnBuilderCallback] => [ column, - (col: AlterColumnBuilder) => col.setDefault(sql`gen_ulid()`), + (col: AlterColumnBuilder) => col.setDefault(kyselyUlid()), ]; const migrateTableColumnToUlid = async ( @@ -28,6 +29,7 @@ const migrateTableColumnToUlid = async ( ): Promise => { let command = db.schema .alterTable(table) + .alterColumn(column, (col) => col.dropDefault()) .alterColumn(...convertToUlid(column)); if (setDefault) { command = command.alterColumn(...setUlidDefault(column)); diff --git a/libs/db/migrations/src/01H7BA221268FVXQ1BDAVE4JZE-image-field-updates-and-image-table.ts b/libs/db/migrations/src/01H7BA221268FVXQ1BDAVE4JZE-image-field-updates-and-image-table.ts new file mode 100644 index 00000000..1bc7201f --- /dev/null +++ b/libs/db/migrations/src/01H7BA221268FVXQ1BDAVE4JZE-image-field-updates-and-image-table.ts @@ -0,0 +1,81 @@ +import { ExpressionBuilder, Kysely, sql } from 'kysely'; +import { kyselyUlid } from './ulid.sql'; + +export const up = async (db: Kysely) => { + await db.schema + .createTable('image') + .addColumn('id', sql`ulid`, (col) => col.defaultTo(kyselyUlid())) + .addColumn('type', 'text', (col) => col.notNull()) + .addColumn('original_url', 'text') + .addColumn('small_url', 'text') + .addColumn('medium_url', 'text') + .addColumn('large_url', 'text') + .execute(); + await db + .insertInto('image') + .columns(['type', 'original_url']) + .expression((eb) => + eb + .selectFrom('deity') + .select([eb.val('deity_avatar').as('type'), 'image_url']) + ) + .execute(); + await db + .insertInto('image') + .columns(['type', 'original_url']) + .expression((eb) => + eb + .selectFrom('user_account') + .select([eb.val('user_avatar').as('type'), 'photo_url']) + ) + .execute(); + await db.schema + .alterTable('deity') + .renameColumn('image_url', 'image_id') + .renameColumn('category', 'category_id') + .renameColumn('location', 'location_id') + .execute(); + await db.schema + .alterTable('user_account') + .renameColumn('photo_url', 'image_id') + .execute(); + await db + .updateTable('deity') + .set((eb: ExpressionBuilder) => ({ + image_id: eb + .selectFrom('image') + .select('id') + .where('original_url', '=', 'deity.image_id') + .where('type', '=', 'deity_avatar'), + })) + .execute(); + await db + .updateTable('user_account') + .set((eb: ExpressionBuilder) => ({ + image_id: eb + .selectFrom('image') + .select('id') + .where('original_url', '=', 'user_account.image_id') + .where('type', '=', 'user_avatar'), + })) + .execute(); + await db.schema + .alterTable('deity') + .addForeignKeyConstraint('deity_image_id_fkey', ['image_id'], 'image', [ + 'id', + ]) + .execute(); + await db.schema + .alterTable('user_account') + .addForeignKeyConstraint( + 'user_account_image_id_fkey', + ['image_id'], + 'image', + ['id'] + ) + .execute(); +}; + +export const down = async (_db: Kysely) => { + /* intentioanlly no op */ +}; diff --git a/libs/db/migrations/src/ulid.sql.ts b/libs/db/migrations/src/ulid.sql.ts new file mode 100644 index 00000000..419e37c0 --- /dev/null +++ b/libs/db/migrations/src/ulid.sql.ts @@ -0,0 +1,5 @@ +import { RawBuilder, sql } from 'kysely'; + +export const kyselyUlid = (): RawBuilder => { + return sql`gen_ulid()`; +}; diff --git a/libs/server/config/src/lib/config.schema.ts b/libs/server/config/src/lib/config.schema.ts index 9ed9f430..fbe4e3a9 100644 --- a/libs/server/config/src/lib/config.schema.ts +++ b/libs/server/config/src/lib/config.schema.ts @@ -1,3 +1,4 @@ +import { join } from 'path'; import { z } from 'zod'; const hourInSeconds = 60 * 60; @@ -8,6 +9,7 @@ const prodConfig = z.object({ NOREPLY_EMAIL: z.string().email(), SMTP_PASS: z.string(), SMTP_HOST: z.string(), + FILE_PATH: z.string().optional().default(join(process.cwd(), 'images')), }); const devConfig = z.object({ @@ -15,14 +17,28 @@ const devConfig = z.object({ NOREPLY_EMAIL: z.string().email().optional(), SMTP_PASS: z.string().optional(), SMTP_HOST: z.string().optional(), + FILE_PATH: z + .string() + .optional() + .default(join(process.cwd(), 'apps', 'site', 'public', 'images')), }); -const commonConfig = z.object({ +const dbConfig = z.object({ DATABASE_USER: z.string(), DATABASE_PASSWORD: z.string(), DATABASE_PORT: z.string().transform((val) => Number.parseInt(val)), DATABASE_HOST: z.string(), DATABASE_NAME: z.string(), +}); + +const rabbitConfig = z.object({ + RABBIT_USER: z.string(), + RABBIT_PASSWORD: z.string(), + RABBIT_HOST: z.string(), + RABBIT_PORT: z.string(), +}); + +const commonConfig = z.object({ PORT: z .optional(z.string().transform((val) => Number.parseInt(val, 10))) .default('3333'), @@ -36,6 +52,6 @@ const commonConfig = z.object({ .default(7 * dayInSeconds), }); export const Config = z.intersection( - commonConfig, + commonConfig.merge(dbConfig).merge(rabbitConfig), z.discriminatedUnion('NODE_ENV', [prodConfig, devConfig]) ); diff --git a/libs/server/deities/src/lib/server-deities.module.ts b/libs/server/deities/src/lib/server-deities.module.ts index 68d5499e..a16d13c8 100644 --- a/libs/server/deities/src/lib/server-deities.module.ts +++ b/libs/server/deities/src/lib/server-deities.module.ts @@ -1,10 +1,11 @@ import { Module } from '@nestjs/common'; import { KyselyModule } from '@unteris/server/kysely'; +import { ServerImageClientModule } from '@unteris/server/image-client'; import { ServerDeitiesController } from './server-deities.controller'; import { ServerDeitiesService } from './server-deities.service'; @Module({ - imports: [KyselyModule], + imports: [KyselyModule, ServerImageClientModule], controllers: [ServerDeitiesController], providers: [ServerDeitiesService], exports: [ServerDeitiesService], diff --git a/libs/server/deities/src/lib/server-deities.service.ts b/libs/server/deities/src/lib/server-deities.service.ts index db55a41e..9f43e3f3 100644 --- a/libs/server/deities/src/lib/server-deities.service.ts +++ b/libs/server/deities/src/lib/server-deities.service.ts @@ -3,6 +3,8 @@ import { Database, InjectKysely } from '@unteris/server/kysely'; import { Deity } from '@unteris/shared/types'; import { Kysely } from 'kysely'; +type DeityReturn = Omit & { imageUrl: string | null }; + @Injectable() export class ServerDeitiesService { constructor(@InjectKysely() private readonly db: Kysely) {} @@ -14,30 +16,31 @@ export class ServerDeitiesService { .selectFrom('deity') .select(['id', 'name']) .orderBy('id', 'asc') - .where('category', '=', category) + .where('categoryId', '=', category) .execute(); } - async getDeityById(id: string): Promise { + async getDeityById(id: string): Promise { const deityRecords = await this.db .selectFrom('deity') .leftJoin('deityDomain', 'deity.id', 'deityDomain.deityId') .leftJoin('domain', 'deityDomain.domainId', 'domain.id') + .leftJoin('image', 'imageId', 'image.id') .select([ 'deity.id as id', 'deity.name as name', - 'imageUrl', 'description', 'domain.name as domainName', 'domain.type as domainType', 'domain.id as domainId', + 'image.originalUrl as imageUrl', ]) .where('deity.id', '=', id) .execute(); if (deityRecords.length === 0) { throw new BadRequestException(`No deity found with Id ${id}`); } - const deity: Deity = { + const deity: DeityReturn = { name: deityRecords[0].name, id, description: deityRecords[0].description, @@ -68,7 +71,7 @@ export class ServerDeitiesService { .selectFrom('deity') .select(['id', 'name']) .orderBy('id', 'asc') - .where('location', '=', location) + .where('locationId', '=', location) .execute(); } } diff --git a/libs/server/file-storage/.eslintrc.json b/libs/server/file-storage/.eslintrc.json new file mode 100644 index 00000000..3456be9b --- /dev/null +++ b/libs/server/file-storage/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/libs/server/file-storage/README.md b/libs/server/file-storage/README.md new file mode 100644 index 00000000..118523fc --- /dev/null +++ b/libs/server/file-storage/README.md @@ -0,0 +1,3 @@ +# server-file-storage + +This library was generated with [Nx](https://nx.dev). diff --git a/libs/server/file-storage/project.json b/libs/server/file-storage/project.json new file mode 100644 index 00000000..bb1d520b --- /dev/null +++ b/libs/server/file-storage/project.json @@ -0,0 +1,16 @@ +{ + "name": "server-file-storage", + "$schema": "../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "libs/server/file-storage/src", + "projectType": "library", + "targets": { + "lint": { + "executor": "@nx/linter:eslint", + "outputs": ["{options.outputFile}"], + "options": { + "lintFilePatterns": ["libs/server/file-storage/**/*.ts"] + } + } + }, + "tags": ["rmq", "storage", "server", "utility"] +} diff --git a/libs/server/file-storage/src/index.ts b/libs/server/file-storage/src/index.ts new file mode 100644 index 00000000..b6419431 --- /dev/null +++ b/libs/server/file-storage/src/index.ts @@ -0,0 +1,2 @@ +export * from './lib/file-storage.service'; +export * from './lib/file-storage.module'; diff --git a/libs/server/file-storage/src/lib/file-manager.interface.ts b/libs/server/file-storage/src/lib/file-manager.interface.ts new file mode 100644 index 00000000..12e6ab6f --- /dev/null +++ b/libs/server/file-storage/src/lib/file-manager.interface.ts @@ -0,0 +1,9 @@ +export interface FileManager { + write(path: string, data: string | Buffer): void | Promise; + + read(path: string): Buffer | Promise; +} + +export interface LocalStoreConfig { + path: string; +} diff --git a/libs/server/file-storage/src/lib/file-storage.constants.ts b/libs/server/file-storage/src/lib/file-storage.constants.ts new file mode 100644 index 00000000..5cc15ae9 --- /dev/null +++ b/libs/server/file-storage/src/lib/file-storage.constants.ts @@ -0,0 +1,5 @@ +export const FILE_STORE_TOKEN = Symbol('METADATA:FILE-STORE'); + +export const FILE_LOCAL_CONFIG_TOKEN = Symbol( + 'METADATA:LOCAL_FILE_STORE:CONFIG' +); diff --git a/libs/server/file-storage/src/lib/file-storage.module.ts b/libs/server/file-storage/src/lib/file-storage.module.ts new file mode 100644 index 00000000..ae11e036 --- /dev/null +++ b/libs/server/file-storage/src/lib/file-storage.module.ts @@ -0,0 +1,34 @@ +import { Module } from '@nestjs/common'; +import { + ServerConfigModule, + ServerConfigService, +} from '@unteris/server/config'; +import { ServerFileStorageService } from './file-storage.service'; +import { + FILE_LOCAL_CONFIG_TOKEN, + FILE_STORE_TOKEN, +} from './file-storage.constants'; +import { LocalStoreConfig } from './file-manager.interface'; +import { LocalStore } from './local.store'; + +@Module({ + imports: [ServerConfigModule], + providers: [ + ServerFileStorageService, + { + provide: FILE_LOCAL_CONFIG_TOKEN, + inject: [ServerConfigService], + useFactory: (config: ServerConfigService): LocalStoreConfig => { + return { + path: config.get('FILE_PATH'), + }; + }, + }, + { + provide: FILE_STORE_TOKEN, + useClass: LocalStore, + }, + ], + exports: [ServerFileStorageService], +}) +export class ServerFileStorageModule {} diff --git a/libs/server/file-storage/src/lib/file-storage.service.ts b/libs/server/file-storage/src/lib/file-storage.service.ts new file mode 100644 index 00000000..2be336c9 --- /dev/null +++ b/libs/server/file-storage/src/lib/file-storage.service.ts @@ -0,0 +1,16 @@ +import { Inject, Injectable } from '@nestjs/common'; +import { FILE_STORE_TOKEN } from './file-storage.constants'; +import { FileManager } from './file-manager.interface'; + +@Injectable() +export class ServerFileStorageService { + constructor(@Inject(FILE_STORE_TOKEN) private readonly store: FileManager) {} + + async readFileFromStore(path: string): Promise { + return this.store.read(path); + } + + async writeFileToStore(path: string, data: string | Buffer): Promise { + return this.store.write(path, data); + } +} diff --git a/libs/server/file-storage/src/lib/local.store.ts b/libs/server/file-storage/src/lib/local.store.ts new file mode 100644 index 00000000..f3202e7b --- /dev/null +++ b/libs/server/file-storage/src/lib/local.store.ts @@ -0,0 +1,20 @@ +import { Inject, Injectable } from '@nestjs/common'; +import { readFile, writeFile } from 'fs/promises'; +import { join } from 'path'; +import { FileManager, LocalStoreConfig } from './file-manager.interface'; +import { FILE_LOCAL_CONFIG_TOKEN } from './file-storage.constants'; + +@Injectable() +export class LocalStore implements FileManager { + constructor( + @Inject(FILE_LOCAL_CONFIG_TOKEN) private readonly config: LocalStoreConfig + ) {} + + read(path: string) { + return readFile(join(this.config.path, path)); + } + + write(path: string, data: string | Buffer) { + return writeFile(join(this.config.path, path), data); + } +} diff --git a/libs/server/file-storage/src/lib/server-file-storage.ts b/libs/server/file-storage/src/lib/server-file-storage.ts new file mode 100644 index 00000000..fa501cc4 --- /dev/null +++ b/libs/server/file-storage/src/lib/server-file-storage.ts @@ -0,0 +1,3 @@ +export function serverFileStorage(): string { + return 'server-file-storage'; +} diff --git a/libs/server/file-storage/tsconfig.json b/libs/server/file-storage/tsconfig.json new file mode 100644 index 00000000..f2400abe --- /dev/null +++ b/libs/server/file-storage/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/libs/server/file-storage/tsconfig.lib.json b/libs/server/file-storage/tsconfig.lib.json new file mode 100644 index 00000000..dbf54fd7 --- /dev/null +++ b/libs/server/file-storage/tsconfig.lib.json @@ -0,0 +1,16 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../../dist/out-tsc", + "declaration": true, + "types": ["node"], + "target": "es2021", + "strictNullChecks": true, + "noImplicitAny": true, + "strictBindCallApply": true, + "forceConsistentCasingInFileNames": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src/**/*.ts"], + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"] +} diff --git a/libs/server/image-client/.eslintrc.json b/libs/server/image-client/.eslintrc.json new file mode 100644 index 00000000..3456be9b --- /dev/null +++ b/libs/server/image-client/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/libs/server/image-client/README.md b/libs/server/image-client/README.md new file mode 100644 index 00000000..f4827c9b --- /dev/null +++ b/libs/server/image-client/README.md @@ -0,0 +1,3 @@ +# server-image-client + +This library was generated with [Nx](https://nx.dev). diff --git a/libs/server/image-client/project.json b/libs/server/image-client/project.json new file mode 100644 index 00000000..716844d1 --- /dev/null +++ b/libs/server/image-client/project.json @@ -0,0 +1,16 @@ +{ + "name": "server-image-client", + "$schema": "../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "libs/server/image-client/src", + "projectType": "library", + "targets": { + "lint": { + "executor": "@nx/linter:eslint", + "outputs": ["{options.outputFile}"], + "options": { + "lintFilePatterns": ["libs/server/image-client/**/*.ts"] + } + } + }, + "tags": ["server", "utility"] +} diff --git a/libs/server/image-client/src/index.ts b/libs/server/image-client/src/index.ts new file mode 100644 index 00000000..1ce70647 --- /dev/null +++ b/libs/server/image-client/src/index.ts @@ -0,0 +1,2 @@ +export * from './lib/image-client.service'; +export * from './lib/image-client.module'; diff --git a/libs/server/image-client/src/lib/image-client.module.ts b/libs/server/image-client/src/lib/image-client.module.ts new file mode 100644 index 00000000..5202a670 --- /dev/null +++ b/libs/server/image-client/src/lib/image-client.module.ts @@ -0,0 +1,36 @@ +import { Module } from '@nestjs/common'; +import { ClientProxyFactory, Transport } from '@nestjs/microservices'; +import { + ServerConfigModule, + ServerConfigService, +} from '@unteris/server/config'; +import { ServerImageClientService } from './image-client.service'; + +@Module({ + imports: [ServerConfigModule], + providers: [ + ServerImageClientService, + { + provide: 'IMAGE_SERVER_CLIENT', + inject: [ServerConfigService], + useFactory: (config: ServerConfigService) => { + return ClientProxyFactory.create({ + transport: Transport.RMQ, + options: { + urls: [ + `amqp://${config.get('RABBIT_USER')}:${config.get( + 'RABBIT_PASSWORD' + )}@${config.get('RABBIT_HOST')}:${config.get('RABBIT_PORT')}`, + ], + queue: '', + queueOptions: { + durable: true, + }, + }, + }); + }, + }, + ], + exports: [ServerImageClientService], +}) +export class ServerImageClientModule {} diff --git a/libs/server/image-client/src/lib/image-client.service.ts b/libs/server/image-client/src/lib/image-client.service.ts new file mode 100644 index 00000000..ac37d57c --- /dev/null +++ b/libs/server/image-client/src/lib/image-client.service.ts @@ -0,0 +1,14 @@ +import { Inject, Injectable } from '@nestjs/common'; +import { ClientRMQ } from '@nestjs/microservices'; +import { PROCESS_IMAGE_EVENT } from '@unteris/shared/types'; + +@Injectable() +export class ServerImageClientService { + constructor( + @Inject('IMAGE_SERVER_CLIENT') private readonly imageProxy: ClientRMQ + ) {} + + sendImageIdForProcessing(id: string): void { + this.imageProxy.emit(PROCESS_IMAGE_EVENT, { id }); + } +} diff --git a/libs/server/image-client/src/lib/server-image-client.ts b/libs/server/image-client/src/lib/server-image-client.ts new file mode 100644 index 00000000..9a799fe1 --- /dev/null +++ b/libs/server/image-client/src/lib/server-image-client.ts @@ -0,0 +1,3 @@ +export function serverImageClient(): string { + return 'server-image-client'; +} diff --git a/libs/server/image-client/tsconfig.json b/libs/server/image-client/tsconfig.json new file mode 100644 index 00000000..f2400abe --- /dev/null +++ b/libs/server/image-client/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/libs/server/image-client/tsconfig.lib.json b/libs/server/image-client/tsconfig.lib.json new file mode 100644 index 00000000..dbf54fd7 --- /dev/null +++ b/libs/server/image-client/tsconfig.lib.json @@ -0,0 +1,16 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../../dist/out-tsc", + "declaration": true, + "types": ["node"], + "target": "es2021", + "strictNullChecks": true, + "noImplicitAny": true, + "strictBindCallApply": true, + "forceConsistentCasingInFileNames": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src/**/*.ts"], + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"] +} diff --git a/libs/server/image-processing/.eslintrc.json b/libs/server/image-processing/.eslintrc.json new file mode 100644 index 00000000..3456be9b --- /dev/null +++ b/libs/server/image-processing/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/libs/server/image-processing/README.md b/libs/server/image-processing/README.md new file mode 100644 index 00000000..41a4b0f5 --- /dev/null +++ b/libs/server/image-processing/README.md @@ -0,0 +1,3 @@ +# server-image-processing + +This library was generated with [Nx](https://nx.dev). diff --git a/libs/server/image-processing/project.json b/libs/server/image-processing/project.json new file mode 100644 index 00000000..a0c4dcf2 --- /dev/null +++ b/libs/server/image-processing/project.json @@ -0,0 +1,16 @@ +{ + "name": "server-image-processing", + "$schema": "../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "libs/server/image-processing/src", + "projectType": "library", + "targets": { + "lint": { + "executor": "@nx/linter:eslint", + "outputs": ["{options.outputFile}"], + "options": { + "lintFilePatterns": ["libs/server/image-processing/**/*.ts"] + } + } + }, + "tags": ["rmq", "image", "utility"] +} diff --git a/libs/server/image-processing/src/index.ts b/libs/server/image-processing/src/index.ts new file mode 100644 index 00000000..4eb910b2 --- /dev/null +++ b/libs/server/image-processing/src/index.ts @@ -0,0 +1,2 @@ +export * from './lib/image-processing.service'; +export * from './lib/image-processing.module'; diff --git a/libs/server/image-processing/src/lib/image-processing.controller.ts b/libs/server/image-processing/src/lib/image-processing.controller.ts new file mode 100644 index 00000000..4ded11aa --- /dev/null +++ b/libs/server/image-processing/src/lib/image-processing.controller.ts @@ -0,0 +1,16 @@ +import { Controller } from '@nestjs/common'; +import { EventPattern, Payload } from '@nestjs/microservices'; +import { ServerImageProcessingService } from './image-processing.service'; +import { PROCESS_IMAGE_EVENT } from '@unteris/shared/types'; + +@Controller() +export class ServerImageProcessingController { + constructor( + private readonly processingService: ServerImageProcessingService + ) {} + + @EventPattern(PROCESS_IMAGE_EVENT) + async processImage(@Payload() imageId: string) { + await this.processingService.processImage(imageId); + } +} diff --git a/libs/server/image-processing/src/lib/image-processing.module.ts b/libs/server/image-processing/src/lib/image-processing.module.ts new file mode 100644 index 00000000..118ff6d6 --- /dev/null +++ b/libs/server/image-processing/src/lib/image-processing.module.ts @@ -0,0 +1,14 @@ +import { Module } from '@nestjs/common'; +import { ServerFileStorageModule } from '@unteris/server/file-storage'; +import { KyselyModule } from '@unteris/server/kysely'; +import { ServerImageProcessingController } from './image-processing.controller'; +import { ServerImageProcessingService } from './image-processing.service'; +import { ImageRepo } from './image.repository'; + +@Module({ + imports: [ServerFileStorageModule, KyselyModule], + controllers: [ServerImageProcessingController], + providers: [ServerImageProcessingService, ImageRepo], + exports: [ServerImageProcessingService], +}) +export class ServerImageProcessingModule {} diff --git a/libs/server/image-processing/src/lib/image-processing.service.ts b/libs/server/image-processing/src/lib/image-processing.service.ts new file mode 100644 index 00000000..9be25ea9 --- /dev/null +++ b/libs/server/image-processing/src/lib/image-processing.service.ts @@ -0,0 +1,39 @@ +import { Injectable } from '@nestjs/common'; +import { ServerFileStorageService } from '@unteris/server/file-storage'; +import sharp from 'sharp'; +import { ImageRepo } from './image.repository'; + +@Injectable() +export class ServerImageProcessingService { + constructor( + private readonly fileStorage: ServerFileStorageService, + private readonly imageRepo: ImageRepo + ) {} + + async processImage(imageId: string): Promise { + const { originalUrl } = (await this.imageRepo.getImageById(imageId)) ?? {}; + if (!originalUrl) { + return; + } + const file = await this.fileStorage.readFileFromStore(originalUrl); + const [name, ...path] = originalUrl.split('/').reverse(); + const [small, medium, large] = await Promise.all([ + sharp(file).resize(256, 384).toFormat('webp').toBuffer(), + sharp(file).resize(512, 768).toFormat('webp').toBuffer(), + sharp(file).resize(1536, 2304).toFormat('webp').toBuffer(), + ]); + const fileNames = ['sm', 'md', 'lg'].map( + (size) => `${path.reverse().join('/')}/${name}_${size}.webp` + ); + await Promise.all([ + this.fileStorage.writeFileToStore(fileNames[0], small), + this.fileStorage.writeFileToStore(fileNames[1], medium), + this.fileStorage.writeFileToStore(fileNames[2], large), + ]); + await this.imageRepo.updateImageUrls(imageId, { + small: fileNames[0], + medium: fileNames[1], + large: fileNames[2], + }); + } +} diff --git a/libs/server/image-processing/src/lib/image.repository.ts b/libs/server/image-processing/src/lib/image.repository.ts new file mode 100644 index 00000000..1efa9f46 --- /dev/null +++ b/libs/server/image-processing/src/lib/image.repository.ts @@ -0,0 +1,32 @@ +import { Injectable } from '@nestjs/common'; +import { Database, InjectKysely } from '@unteris/server/kysely'; +import { Image } from '@unteris/shared/types'; +import { Kysely } from 'kysely'; + +@Injectable() +export class ImageRepo { + constructor(@InjectKysely() private readonly db: Kysely) {} + + async getImageById(id: string): Promise { + return this.db + .selectFrom('image') + .selectAll() + .where('id', '=', id) + .executeTakeFirst(); + } + + async updateImageUrls( + imageId: string, + paths: { small: string; medium: string; large: string } + ): Promise { + await this.db + .updateTable('image') + .set({ + smallUrl: paths.small, + mediumUrl: paths.medium, + largeUrl: paths.large, + }) + .where('id', '=', imageId) + .execute(); + } +} diff --git a/libs/server/image-processing/src/lib/server-image-processing.ts b/libs/server/image-processing/src/lib/server-image-processing.ts new file mode 100644 index 00000000..d348017b --- /dev/null +++ b/libs/server/image-processing/src/lib/server-image-processing.ts @@ -0,0 +1,3 @@ +export function serverImageProcessing(): string { + return 'server-image-processing'; +} diff --git a/libs/server/image-processing/tsconfig.json b/libs/server/image-processing/tsconfig.json new file mode 100644 index 00000000..f2400abe --- /dev/null +++ b/libs/server/image-processing/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/libs/server/image-processing/tsconfig.lib.json b/libs/server/image-processing/tsconfig.lib.json new file mode 100644 index 00000000..dbf54fd7 --- /dev/null +++ b/libs/server/image-processing/tsconfig.lib.json @@ -0,0 +1,16 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../../dist/out-tsc", + "declaration": true, + "types": ["node"], + "target": "es2021", + "strictNullChecks": true, + "noImplicitAny": true, + "strictBindCallApply": true, + "forceConsistentCasingInFileNames": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src/**/*.ts"], + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"] +} diff --git a/libs/server/image-root/.eslintrc.json b/libs/server/image-root/.eslintrc.json new file mode 100644 index 00000000..3456be9b --- /dev/null +++ b/libs/server/image-root/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/libs/server/image-root/README.md b/libs/server/image-root/README.md new file mode 100644 index 00000000..b3cce27b --- /dev/null +++ b/libs/server/image-root/README.md @@ -0,0 +1,3 @@ +# server-image-root + +This library was generated with [Nx](https://nx.dev). diff --git a/libs/server/image-root/project.json b/libs/server/image-root/project.json new file mode 100644 index 00000000..9d1f6d63 --- /dev/null +++ b/libs/server/image-root/project.json @@ -0,0 +1,16 @@ +{ + "name": "server-image-root", + "$schema": "../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "libs/server/image-root/src", + "projectType": "library", + "targets": { + "lint": { + "executor": "@nx/linter:eslint", + "outputs": ["{options.outputFile}"], + "options": { + "lintFilePatterns": ["libs/server/image-root/**/*.ts"] + } + } + }, + "tags": ["image", "module:root"] +} diff --git a/libs/server/image-root/src/index.ts b/libs/server/image-root/src/index.ts new file mode 100644 index 00000000..ed5378e0 --- /dev/null +++ b/libs/server/image-root/src/index.ts @@ -0,0 +1 @@ +export * from './lib/image-root.module'; diff --git a/libs/server/image-root/src/lib/app.controller.spec.ts b/libs/server/image-root/src/lib/app.controller.spec.ts new file mode 100644 index 00000000..de8007e1 --- /dev/null +++ b/libs/server/image-root/src/lib/app.controller.spec.ts @@ -0,0 +1,22 @@ +import { Test, TestingModule } from '@nestjs/testing'; + +import { AppController } from './app.controller'; +import { AppService } from './app.service'; + +describe('AppController', () => { + let app: TestingModule; + + beforeAll(async () => { + app = await Test.createTestingModule({ + controllers: [AppController], + providers: [AppService], + }).compile(); + }); + + describe('getData', () => { + it('should return "Hello API"', () => { + const appController = app.get(AppController); + expect(appController.getData()).toEqual({ message: 'Hello API' }); + }); + }); +}); diff --git a/libs/server/image-root/src/lib/app.controller.ts b/libs/server/image-root/src/lib/app.controller.ts new file mode 100644 index 00000000..dff210a8 --- /dev/null +++ b/libs/server/image-root/src/lib/app.controller.ts @@ -0,0 +1,13 @@ +import { Controller, Get } from '@nestjs/common'; + +import { AppService } from './app.service'; + +@Controller() +export class AppController { + constructor(private readonly appService: AppService) {} + + @Get() + getData() { + return this.appService.getData(); + } +} diff --git a/libs/server/image-root/src/lib/app.module.ts b/libs/server/image-root/src/lib/app.module.ts new file mode 100644 index 00000000..6a9bc166 --- /dev/null +++ b/libs/server/image-root/src/lib/app.module.ts @@ -0,0 +1,11 @@ +import { Module } from '@nestjs/common'; + +import { AppController } from './app.controller'; +import { AppService } from './app.service'; + +@Module({ + imports: [], + controllers: [AppController], + providers: [AppService], +}) +export class AppModule {} diff --git a/libs/server/image-root/src/lib/app.service.spec.ts b/libs/server/image-root/src/lib/app.service.spec.ts new file mode 100644 index 00000000..42cf0a25 --- /dev/null +++ b/libs/server/image-root/src/lib/app.service.spec.ts @@ -0,0 +1,21 @@ +import { Test } from '@nestjs/testing'; + +import { AppService } from './app.service'; + +describe('AppService', () => { + let service: AppService; + + beforeAll(async () => { + const app = await Test.createTestingModule({ + providers: [AppService], + }).compile(); + + service = app.get(AppService); + }); + + describe('getData', () => { + it('should return "Hello API"', () => { + expect(service.getData()).toEqual({ message: 'Hello API' }); + }); + }); +}); diff --git a/libs/server/image-root/src/lib/app.service.ts b/libs/server/image-root/src/lib/app.service.ts new file mode 100644 index 00000000..cd8cedef --- /dev/null +++ b/libs/server/image-root/src/lib/app.service.ts @@ -0,0 +1,8 @@ +import { Injectable } from '@nestjs/common'; + +@Injectable() +export class AppService { + getData(): { message: string } { + return { message: 'Hello API' }; + } +} diff --git a/libs/server/image-root/src/lib/image-root.module.ts b/libs/server/image-root/src/lib/image-root.module.ts new file mode 100644 index 00000000..e756f97d --- /dev/null +++ b/libs/server/image-root/src/lib/image-root.module.ts @@ -0,0 +1,13 @@ +import { Module } from '@nestjs/common'; +import { ServerImageProcessingModule } from '@unteris/server/image-processing'; +import { KyselyModule } from '@unteris/server/kysely'; +import { ServerLoggingModule } from '@unteris/server/logging'; + +@Module({ + imports: [ + ServerLoggingModule.forApplication('Unteris Image', 'DEBUG'), + KyselyModule, + ServerImageProcessingModule, + ], +}) +export class ImageRootModule {} diff --git a/libs/server/image-root/src/lib/server-image-root.ts b/libs/server/image-root/src/lib/server-image-root.ts new file mode 100644 index 00000000..f14fd154 --- /dev/null +++ b/libs/server/image-root/src/lib/server-image-root.ts @@ -0,0 +1,3 @@ +export function serverImageRoot(): string { + return 'server-image-root'; +} diff --git a/libs/server/image-root/tsconfig.json b/libs/server/image-root/tsconfig.json new file mode 100644 index 00000000..f2400abe --- /dev/null +++ b/libs/server/image-root/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/libs/server/image-root/tsconfig.lib.json b/libs/server/image-root/tsconfig.lib.json new file mode 100644 index 00000000..dbf54fd7 --- /dev/null +++ b/libs/server/image-root/tsconfig.lib.json @@ -0,0 +1,16 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../../dist/out-tsc", + "declaration": true, + "types": ["node"], + "target": "es2021", + "strictNullChecks": true, + "noImplicitAny": true, + "strictBindCallApply": true, + "forceConsistentCasingInFileNames": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src/**/*.ts"], + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"] +} diff --git a/libs/server/kysely/src/lib/database.interface.ts b/libs/server/kysely/src/lib/database.interface.ts index de42208c..2c639725 100644 --- a/libs/server/kysely/src/lib/database.interface.ts +++ b/libs/server/kysely/src/lib/database.interface.ts @@ -4,6 +4,7 @@ import { DeityCategory, Domain, DeityDomain, + Image, Race, RacialAbility, UserPermission, @@ -33,4 +34,5 @@ export interface Database { verificationToken: Omit, 'expiresAt'> & { expiresAt: Generated; }; + image: GeneratedId; } diff --git a/libs/shared/sdk/src/lib/routes-with-types.ts b/libs/shared/sdk/src/lib/routes-with-types.ts index 00658be1..88aca14e 100644 --- a/libs/shared/sdk/src/lib/routes-with-types.ts +++ b/libs/shared/sdk/src/lib/routes-with-types.ts @@ -32,7 +32,9 @@ export type RouteToType = { 'session/refresh': [{ success: boolean }]; [key: `deities/category/${string}`]: [Array>]; [key: `deities/location/${string}`]: [Array>]; - [key: `deities/id/${string}`]: [Deity]; + [key: `deities/id/${string}`]: [ + Omit & { imageUrl: 'string' } + ]; locations: [Array>]; }; post: { diff --git a/libs/shared/types/src/index.ts b/libs/shared/types/src/index.ts index 1bf7c4ba..85cbdd0f 100644 --- a/libs/shared/types/src/index.ts +++ b/libs/shared/types/src/index.ts @@ -15,3 +15,4 @@ export * from './lib/auth'; export * from './lib/deities'; export * from './lib/verification-token'; export * from './lib/constants'; +export * from './lib/image'; diff --git a/libs/shared/types/src/lib/constants.ts b/libs/shared/types/src/lib/constants.ts index 48350d42..085633b6 100644 --- a/libs/shared/types/src/lib/constants.ts +++ b/libs/shared/types/src/lib/constants.ts @@ -1 +1,2 @@ export const csrfHeader = 'x-unteris-csrf-protection'; +export const PROCESS_IMAGE_EVENT = 'event:process_image'; diff --git a/libs/shared/types/src/lib/deity.ts b/libs/shared/types/src/lib/deity.ts index 1692221b..614cfe59 100644 --- a/libs/shared/types/src/lib/deity.ts +++ b/libs/shared/types/src/lib/deity.ts @@ -5,9 +5,9 @@ export const DeitySchema = z.object({ id: z.string().ulid(), name: z.string(), description: z.string(), - imageUrl: z.string(), - category: z.string().ulid().optional(), - location: z.string().ulid().optional(), + imageId: z.string(), + categoryId: z.string().ulid().optional(), + locationId: z.string().ulid().optional(), domain: z.array(DomainSchema).optional(), }); diff --git a/libs/shared/types/src/lib/image.ts b/libs/shared/types/src/lib/image.ts new file mode 100644 index 00000000..a91aad7d --- /dev/null +++ b/libs/shared/types/src/lib/image.ts @@ -0,0 +1,12 @@ +import { z } from 'zod'; + +export const ImageSchema = z.object({ + id: z.string().ulid(), + type: z.enum(['deity_avatar', 'user_avatar']), + originalUrl: z.string(), + smallUrl: z.string().optional(), + mediumUrl: z.string().optional(), + largeUrl: z.string().optional(), +}); + +export type Image = z.infer; diff --git a/libs/shared/types/src/lib/user-account.ts b/libs/shared/types/src/lib/user-account.ts index 2d53b3bc..162ccd9e 100644 --- a/libs/shared/types/src/lib/user-account.ts +++ b/libs/shared/types/src/lib/user-account.ts @@ -5,7 +5,7 @@ export const UserAccountSchema = z.object({ name: z.string(), email: z.string().email(), isVerified: z.boolean(), - photoUrl: z.string().url().optional(), + imageId: z.string().url().optional(), }); export type UserAccount = z.infer; diff --git a/libs/ui/deities/src/lib/atoms.ts b/libs/ui/deities/src/lib/atoms.ts index 68fcd8f7..7d6a9893 100644 --- a/libs/ui/deities/src/lib/atoms.ts +++ b/libs/ui/deities/src/lib/atoms.ts @@ -59,7 +59,9 @@ export const deityIdAtom = atom>(async (get) => { return deities[deityIndex].id; }); -export const deityAtom = atom>(async (get) => { +export const deityAtom = atom< + Promise<(Omit & { imageUrl: string }) | undefined> +>(async (get) => { const deityId = await get(deityIdAtom); if (!deityId) { return; diff --git a/libs/ui/deities/src/lib/deity-domains.tsx b/libs/ui/deities/src/lib/deity-domains.tsx index 18141e9c..f083f04b 100644 --- a/libs/ui/deities/src/lib/deity-domains.tsx +++ b/libs/ui/deities/src/lib/deity-domains.tsx @@ -6,7 +6,7 @@ import { Deity } from '@unteris/shared/types'; type DomainType = Exclude[number]; const filterDomains = ( - deity: Deity, + deity: Omit, domainType: DomainType['type'] ): Omit[] => { return (deity.domain ?? []) @@ -41,7 +41,11 @@ const Domain = ({ ); }; -export const DeityDomains = ({ deity }: { deity: Deity }): JSX.Element => { +export const DeityDomains = ({ + deity, +}: { + deity: Omit; +}): JSX.Element => { const druidDomains = filterDomains(deity, 'druid'); const warlockDomains = filterDomains(deity, 'warlock'); const clericDomains = filterDomains(deity, 'cleric'); diff --git a/libs/ui/deities/src/lib/deity-editor.tsx b/libs/ui/deities/src/lib/deity-editor.tsx index 3536605d..a037c9ec 100644 --- a/libs/ui/deities/src/lib/deity-editor.tsx +++ b/libs/ui/deities/src/lib/deity-editor.tsx @@ -8,9 +8,11 @@ import { useTheme } from '@mui/material'; import { useSetAtom } from 'jotai'; import { editingAtom } from './atoms'; +type DeityReturn = Omit & { imageUrl: string }; + interface DeityEditorProps { - deity: Deity; - setDeity: (deity: Deity) => void; + deity: DeityReturn; + setDeity: (deity: DeityReturn) => void; } export const DeityEditor = (props: DeityEditorProps): JSX.Element => { @@ -28,7 +30,7 @@ export const DeityEditor = (props: DeityEditorProps): JSX.Element => { targetField: e.target.dataset.fieldid, value: e.target.value, }); - deityCopy[targetField as keyof Deity] = + deityCopy[targetField as keyof DeityReturn] = targetField === 'domains' ? e.target.value.split(',') : (e.target.value as any); diff --git a/libs/ui/deities/src/lib/deity-viewer.tsx b/libs/ui/deities/src/lib/deity-viewer.tsx index 7f568e56..e7b87eeb 100644 --- a/libs/ui/deities/src/lib/deity-viewer.tsx +++ b/libs/ui/deities/src/lib/deity-viewer.tsx @@ -8,8 +8,10 @@ import { Image } from '@unteris/ui/components'; import { useTheme } from '@mui/material'; import { DeityDomains } from './deity-domains'; +type DeityReturn = Omit & { imageUrl: string }; + interface DeityViewerProps { - deity: Deity; + deity: DeityReturn; } export const DeityViewer = ({ diff --git a/libs/ui/deities/src/lib/deity.tsx b/libs/ui/deities/src/lib/deity.tsx index a7da608b..6127cb89 100644 --- a/libs/ui/deities/src/lib/deity.tsx +++ b/libs/ui/deities/src/lib/deity.tsx @@ -13,7 +13,9 @@ export const Deity = (): JSX.Element => { const deity = useAtomValue(deityAtom); const isEditing = useAtomValue(editingAtom); - const updateDeity = (_deity: IDeity) => { + const updateDeity = ( + _deity: Omit & { imageUrl: string } + ) => { /* this is where I'll call back to the server to save */ }; diff --git a/package.json b/package.json index 198a9cc3..e25c2ae7 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "@commitlint/config-conventional": "^17.1.0", "@nestjs/common": "10.1.3", "@nestjs/core": "10.1.3", + "@nestjs/microservices": "^10.1.3", "@nestjs/platform-express": "10.1.3", "@nestjs/platform-fastify": "^10.0.0", "@ogma/logger": "^3.1.2", @@ -30,6 +31,8 @@ "@ogma/platform-express": "^5.0.1", "@ogma/styler": "^1.0.1", "@swc/helpers": "0.5.1", + "amqp-connection-manager": "^4.1.14", + "amqplib": "^0.10.3", "argon2": "^0.30.3", "axios": "^1.0.0", "jotai": "^2.2.2", @@ -43,6 +46,7 @@ "redis": "^4.6.6", "reflect-metadata": "^0.1.13", "rxjs": "~7.8.0", + "sharp": "^0.32.4", "tslib": "^2.3.0", "zod": "^3.21.4" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index bf4e5c5e..73862c60 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -13,7 +13,10 @@ dependencies: version: 10.1.3(reflect-metadata@0.1.13)(rxjs@7.8.0) '@nestjs/core': specifier: 10.1.3 - version: 10.1.3(@nestjs/common@10.1.3)(@nestjs/platform-express@10.1.3)(reflect-metadata@0.1.13)(rxjs@7.8.0) + version: 10.1.3(@nestjs/common@10.1.3)(@nestjs/microservices@10.1.3)(@nestjs/platform-express@10.1.3)(reflect-metadata@0.1.13)(rxjs@7.8.0) + '@nestjs/microservices': + specifier: ^10.1.3 + version: 10.1.3(@nestjs/common@10.1.3)(@nestjs/core@10.1.3)(amqp-connection-manager@4.1.14)(amqplib@0.10.3)(reflect-metadata@0.1.13)(rxjs@7.8.0) '@nestjs/platform-express': specifier: 10.1.3 version: 10.1.3(@nestjs/common@10.1.3)(@nestjs/core@10.1.3) @@ -35,6 +38,12 @@ dependencies: '@swc/helpers': specifier: 0.5.1 version: 0.5.1 + amqp-connection-manager: + specifier: ^4.1.14 + version: 4.1.14(amqplib@0.10.3) + amqplib: + specifier: ^0.10.3 + version: 0.10.3 argon2: specifier: ^0.30.3 version: 0.30.3 @@ -74,6 +83,9 @@ dependencies: rxjs: specifier: ~7.8.0 version: 7.8.0 + sharp: + specifier: ^0.32.4 + version: 0.32.4 tslib: specifier: ^2.3.0 version: 2.3.0 @@ -114,7 +126,7 @@ devDependencies: version: 10.0.1(typescript@5.1.6) '@nestjs/testing': specifier: 10.1.3 - version: 10.1.3(@nestjs/common@10.1.3)(@nestjs/core@10.1.3)(@nestjs/platform-express@10.1.3) + version: 10.1.3(@nestjs/common@10.1.3)(@nestjs/core@10.1.3)(@nestjs/microservices@10.1.3)(@nestjs/platform-express@10.1.3) '@nx/cypress': specifier: 16.6.0 version: 16.6.0(@swc-node/register@1.4.2)(@swc/core@1.3.41)(@types/node@18.14.2)(cypress@12.17.3)(eslint@8.25.0)(nx@16.6.0)(typescript@5.1.6) @@ -339,6 +351,16 @@ packages: engines: {node: '>=0.10.0'} dev: true + /@acuminous/bitsyntax@0.1.2: + resolution: {integrity: sha512-29lUK80d1muEQqiUsSo+3A0yP6CdspgC95EnKBMi22Xlwt79i/En4Vr67+cXhU+cZjbti3TgGGC5wy1stIywVQ==} + engines: {node: '>=0.8'} + dependencies: + buffer-more-ints: 1.0.0 + debug: 4.3.4(supports-color@8.1.1) + safe-buffer: 5.1.2 + transitivePeerDependencies: + - supports-color + /@adobe/css-tools@4.2.0: resolution: {integrity: sha512-E09FiIft46CmH5Qnjb0wsW54/YQd69LsxeKUOWawmws1XWvyFGURnAChH0mlr7YPFR1ofwvUQfcL0J3lMxXqPA==} dev: true @@ -471,7 +493,7 @@ packages: debug: 4.3.4(supports-color@8.1.1) gensync: 1.0.0-beta.2 json5: 2.2.3 - semver: 6.3.0 + semver: 6.3.1 transitivePeerDependencies: - supports-color dev: true @@ -544,7 +566,7 @@ packages: '@babel/helper-validator-option': 7.22.5 browserslist: 4.21.9 lru-cache: 5.1.1 - semver: 6.3.0 + semver: 6.3.1 dev: true /@babel/helper-compilation-targets@7.22.5(@babel/core@7.22.5): @@ -558,7 +580,7 @@ packages: '@babel/helper-validator-option': 7.22.5 browserslist: 4.21.9 lru-cache: 5.1.1 - semver: 6.3.0 + semver: 6.3.1 dev: true /@babel/helper-compilation-targets@7.22.9(@babel/core@7.22.5): @@ -604,7 +626,7 @@ packages: '@babel/helper-replace-supers': 7.22.5 '@babel/helper-skip-transparent-expression-wrappers': 7.22.5 '@babel/helper-split-export-declaration': 7.22.5 - semver: 6.3.0 + semver: 6.3.1 transitivePeerDependencies: - supports-color dev: true @@ -624,7 +646,7 @@ packages: '@babel/helper-replace-supers': 7.22.5 '@babel/helper-skip-transparent-expression-wrappers': 7.22.5 '@babel/helper-split-export-declaration': 7.22.5 - semver: 6.3.0 + semver: 6.3.1 transitivePeerDependencies: - supports-color dev: true @@ -682,7 +704,7 @@ packages: debug: 4.3.4(supports-color@8.1.1) lodash.debounce: 4.0.8 resolve: 1.22.2 - semver: 6.3.0 + semver: 6.3.1 transitivePeerDependencies: - supports-color dev: true @@ -2831,7 +2853,7 @@ packages: babel-plugin-polyfill-corejs3: 0.8.1(@babel/core@7.22.5) babel-plugin-polyfill-regenerator: 0.5.0(@babel/core@7.22.5) core-js-compat: 3.31.0 - semver: 6.3.0 + semver: 6.3.1 transitivePeerDependencies: - supports-color dev: true @@ -4662,7 +4684,7 @@ packages: tslib: 2.6.1 uid: 2.0.2 - /@nestjs/core@10.1.3(@nestjs/common@10.1.3)(@nestjs/platform-express@10.1.3)(reflect-metadata@0.1.13)(rxjs@7.8.0): + /@nestjs/core@10.1.3(@nestjs/common@10.1.3)(@nestjs/microservices@10.1.3)(@nestjs/platform-express@10.1.3)(reflect-metadata@0.1.13)(rxjs@7.8.0): resolution: {integrity: sha512-VzK54TuacC3Vmq3b5xTyMVTlDNJeKbjpKfV9fNqm4TbIBm8ZPo3FC0osJAbAK4XwbVvv2Flq1yA3CutasupVjw==} requiresBuild: true peerDependencies: @@ -4681,6 +4703,7 @@ packages: optional: true dependencies: '@nestjs/common': 10.1.3(reflect-metadata@0.1.13)(rxjs@7.8.0) + '@nestjs/microservices': 10.1.3(@nestjs/common@10.1.3)(@nestjs/core@10.1.3)(amqp-connection-manager@4.1.14)(amqplib@0.10.3)(reflect-metadata@0.1.13)(rxjs@7.8.0) '@nestjs/platform-express': 10.1.3(@nestjs/common@10.1.3)(@nestjs/core@10.1.3) '@nuxtjs/opencollective': 0.3.2 fast-safe-stringify: 2.1.1 @@ -4693,6 +4716,51 @@ packages: transitivePeerDependencies: - encoding + /@nestjs/microservices@10.1.3(@nestjs/common@10.1.3)(@nestjs/core@10.1.3)(amqp-connection-manager@4.1.14)(amqplib@0.10.3)(reflect-metadata@0.1.13)(rxjs@7.8.0): + resolution: {integrity: sha512-IBKefw+DR6v2SaXjPJ8tRT+gQTJUSGN83gxuaA32uCQNW2rK+CyVapgX3fDeM/zJsLfBkdveSMX+R74w5wuk+Q==} + peerDependencies: + '@grpc/grpc-js': '*' + '@nestjs/common': ^10.0.0 + '@nestjs/core': ^10.0.0 + '@nestjs/websockets': ^10.0.0 + amqp-connection-manager: '*' + amqplib: '*' + cache-manager: '*' + ioredis: '*' + kafkajs: '*' + mqtt: '*' + nats: '*' + reflect-metadata: ^0.1.12 + rxjs: ^7.1.0 + peerDependenciesMeta: + '@grpc/grpc-js': + optional: true + '@nestjs/websockets': + optional: true + amqp-connection-manager: + optional: true + amqplib: + optional: true + cache-manager: + optional: true + ioredis: + optional: true + kafkajs: + optional: true + mqtt: + optional: true + nats: + optional: true + dependencies: + '@nestjs/common': 10.1.3(reflect-metadata@0.1.13)(rxjs@7.8.0) + '@nestjs/core': 10.1.3(@nestjs/common@10.1.3)(@nestjs/microservices@10.1.3)(@nestjs/platform-express@10.1.3)(reflect-metadata@0.1.13)(rxjs@7.8.0) + amqp-connection-manager: 4.1.14(amqplib@0.10.3) + amqplib: 0.10.3 + iterare: 1.2.1 + reflect-metadata: 0.1.13 + rxjs: 7.8.0 + tslib: 2.6.1 + /@nestjs/platform-express@10.1.3(@nestjs/common@10.1.3)(@nestjs/core@10.1.3): resolution: {integrity: sha512-RSf7ooCrxiWJlWl3CLfpaYmAf3U0tRsN7pJakujWdvzVJU2EzVZTLcy1MtnSg/HBm9/Rvg98VI5QI6oOhOpt+A==} peerDependencies: @@ -4700,7 +4768,7 @@ packages: '@nestjs/core': ^10.0.0 dependencies: '@nestjs/common': 10.1.3(reflect-metadata@0.1.13)(rxjs@7.8.0) - '@nestjs/core': 10.1.3(@nestjs/common@10.1.3)(@nestjs/platform-express@10.1.3)(reflect-metadata@0.1.13)(rxjs@7.8.0) + '@nestjs/core': 10.1.3(@nestjs/common@10.1.3)(@nestjs/microservices@10.1.3)(@nestjs/platform-express@10.1.3)(reflect-metadata@0.1.13)(rxjs@7.8.0) body-parser: 1.20.2 cors: 2.8.5 express: 4.18.2 @@ -4726,7 +4794,7 @@ packages: '@fastify/formbody': 7.4.0 '@fastify/middie': 8.3.0 '@nestjs/common': 10.1.3(reflect-metadata@0.1.13)(rxjs@7.8.0) - '@nestjs/core': 10.1.3(@nestjs/common@10.1.3)(@nestjs/platform-express@10.1.3)(reflect-metadata@0.1.13)(rxjs@7.8.0) + '@nestjs/core': 10.1.3(@nestjs/common@10.1.3)(@nestjs/microservices@10.1.3)(@nestjs/platform-express@10.1.3)(reflect-metadata@0.1.13)(rxjs@7.8.0) fastify: 4.18.0 light-my-request: 5.10.0 path-to-regexp: 3.2.0 @@ -4764,7 +4832,7 @@ packages: - chokidar dev: true - /@nestjs/testing@10.1.3(@nestjs/common@10.1.3)(@nestjs/core@10.1.3)(@nestjs/platform-express@10.1.3): + /@nestjs/testing@10.1.3(@nestjs/common@10.1.3)(@nestjs/core@10.1.3)(@nestjs/microservices@10.1.3)(@nestjs/platform-express@10.1.3): resolution: {integrity: sha512-zMrO9xLPYnKtC6q1diWubuMshIp0v2aGHa58jcIfZaAlJlU/6RKsgCOiFQ42aFzxUEBRWF0LBF0aiwt04LKMyQ==} peerDependencies: '@nestjs/common': ^10.0.0 @@ -4778,7 +4846,8 @@ packages: optional: true dependencies: '@nestjs/common': 10.1.3(reflect-metadata@0.1.13)(rxjs@7.8.0) - '@nestjs/core': 10.1.3(@nestjs/common@10.1.3)(@nestjs/platform-express@10.1.3)(reflect-metadata@0.1.13)(rxjs@7.8.0) + '@nestjs/core': 10.1.3(@nestjs/common@10.1.3)(@nestjs/microservices@10.1.3)(@nestjs/platform-express@10.1.3)(reflect-metadata@0.1.13)(rxjs@7.8.0) + '@nestjs/microservices': 10.1.3(@nestjs/common@10.1.3)(@nestjs/core@10.1.3)(amqp-connection-manager@4.1.14)(amqplib@0.10.3)(reflect-metadata@0.1.13)(rxjs@7.8.0) '@nestjs/platform-express': 10.1.3(@nestjs/common@10.1.3)(@nestjs/core@10.1.3) tslib: 2.6.1 dev: true @@ -4998,7 +5067,7 @@ packages: hasBin: true dependencies: nx: 16.6.0(@swc-node/register@1.4.2)(@swc/core@1.3.41) - tslib: 2.5.3 + tslib: 2.6.1 transitivePeerDependencies: - '@swc-node/register' - '@swc/core' @@ -5524,7 +5593,7 @@ packages: detect-port: 1.5.1 http-server: 14.1.1 ignore: 5.2.4 - tslib: 2.5.3 + tslib: 2.6.1 transitivePeerDependencies: - '@babel/traverse' - '@swc-node/register' @@ -5649,7 +5718,7 @@ packages: rxjs: ^7.0.0 dependencies: '@nestjs/common': 10.1.3(reflect-metadata@0.1.13)(rxjs@7.8.0) - '@nestjs/core': 10.1.3(@nestjs/common@10.1.3)(@nestjs/platform-express@10.1.3)(reflect-metadata@0.1.13)(rxjs@7.8.0) + '@nestjs/core': 10.1.3(@nestjs/common@10.1.3)(@nestjs/microservices@10.1.3)(@nestjs/platform-express@10.1.3)(reflect-metadata@0.1.13)(rxjs@7.8.0) '@ogma/logger': 3.1.2 '@ogma/styler': 1.0.1 reflect-metadata: 0.1.13 @@ -6951,7 +7020,7 @@ packages: engines: {node: '>=14.15.0'} dependencies: js-yaml: 3.14.1 - tslib: 2.5.3 + tslib: 2.6.1 dev: true /@zkochan/js-yaml@0.0.6: @@ -7094,6 +7163,26 @@ packages: require-from-string: 2.0.2 uri-js: 4.4.1 + /amqp-connection-manager@4.1.14(amqplib@0.10.3): + resolution: {integrity: sha512-1km47dIvEr0HhMUazqovSvNwIlSvDX2APdUpULaINtHpiki1O+cLRaTeXb/jav4OLtH+k6GBXx5gsKOT9kcGKQ==} + engines: {node: '>=10.0.0', npm: '>5.0.0'} + peerDependencies: + amqplib: '*' + dependencies: + amqplib: 0.10.3 + promise-breaker: 6.0.0 + + /amqplib@0.10.3: + resolution: {integrity: sha512-UHmuSa7n8vVW/a5HGh2nFPqAEr8+cD4dEZ6u9GjP91nHfr1a54RyAKyra7Sb5NH7NBKOUlyQSMXIp0qAixKexw==} + engines: {node: '>=10'} + dependencies: + '@acuminous/bitsyntax': 0.1.2 + buffer-more-ints: 1.0.0 + readable-stream: 1.1.14 + url-parse: 1.5.10 + transitivePeerDependencies: + - supports-color + /ansi-colors@4.1.3: resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==} engines: {node: '>=6'} @@ -7395,6 +7484,10 @@ packages: dequal: 2.0.3 dev: true + /b4a@1.6.4: + resolution: {integrity: sha512-fpWrvyVHEKyeEvbKZTVOeZF3VSKKWtJxFIxX/jaVPf+cLbGUSitjb49pHLqPV2BUNNZ0LcoeEGfE/YCpyDYHIw==} + dev: false + /babel-core@7.0.0-bridge.0(@babel/core@7.19.3): resolution: {integrity: sha512-poPX9mZH/5CSanm50Q+1toVci6pv5KSRv/5TWCwtzQS5XEwn40BcCrgIeMFWP9CKKIniKXNxoIOnOq4VVlGXhg==} peerDependencies: @@ -7495,7 +7588,7 @@ packages: '@babel/compat-data': 7.22.5 '@babel/core': 7.22.5 '@babel/helper-define-polyfill-provider': 0.4.0(@babel/core@7.22.5) - semver: 6.3.0 + semver: 6.3.1 transitivePeerDependencies: - supports-color dev: true @@ -7793,6 +7886,9 @@ packages: /buffer-from@1.1.2: resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} + /buffer-more-ints@1.0.0: + resolution: {integrity: sha512-EMetuGFz5SLsT0QTnXzINh4Ksr+oo4i+UGTXEshiGCQWnsgSs7ZhJ8fzlwQ+OzEMs0MpDAMr1hxnblp5a4vcHg==} + /buffer-writer@2.0.0: resolution: {integrity: sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw==} engines: {node: '>=4'} @@ -7991,6 +8087,10 @@ packages: fsevents: 2.3.2 dev: true + /chownr@1.1.4: + resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==} + dev: false + /chownr@2.0.0: resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==} engines: {node: '>=10'} @@ -8121,11 +8221,26 @@ packages: /color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + /color-string@1.9.1: + resolution: {integrity: sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==} + dependencies: + color-name: 1.1.4 + simple-swizzle: 0.2.2 + dev: false + /color-support@1.1.3: resolution: {integrity: sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==} hasBin: true dev: false + /color@4.2.3: + resolution: {integrity: sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==} + engines: {node: '>=12.5.0'} + dependencies: + color-convert: 2.0.1 + color-string: 1.9.1 + dev: false + /colord@2.9.3: resolution: {integrity: sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==} dev: true @@ -8795,7 +8910,6 @@ packages: engines: {node: '>=10'} dependencies: mimic-response: 3.1.0 - dev: true /dedent@0.7.0: resolution: {integrity: sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==} @@ -8831,6 +8945,11 @@ packages: which-typed-array: 1.1.9 dev: true + /deep-extend@0.6.0: + resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} + engines: {node: '>=4.0.0'} + dev: false + /deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} dev: true @@ -8910,6 +9029,11 @@ packages: engines: {node: '>=8'} dev: false + /detect-libc@2.0.2: + resolution: {integrity: sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==} + engines: {node: '>=8'} + dev: false + /detect-newline@3.1.0: resolution: {integrity: sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==} engines: {node: '>=8'} @@ -9025,7 +9149,7 @@ packages: resolution: {integrity: sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==} dependencies: no-case: 3.0.4 - tslib: 2.5.3 + tslib: 2.6.1 dev: true /dot-prop@5.3.0: @@ -9105,7 +9229,6 @@ packages: resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==} dependencies: once: 1.4.0 - dev: true /enhanced-resolve@5.15.0: resolution: {integrity: sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg==} @@ -9677,6 +9800,11 @@ packages: engines: {node: '>= 0.8.0'} dev: true + /expand-template@2.0.3: + resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==} + engines: {node: '>=6'} + dev: false + /expect@29.5.0: resolution: {integrity: sha512-yM7xqUrCO2JdpFo4XpM82t+PJBFybdqoQuJLDGeDX2ij8NZzqRHyu3Hp188/JX7SWqud+7t4MUdvcgGBICMHZg==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -9791,6 +9919,10 @@ packages: resolution: {integrity: sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==} dev: true + /fast-fifo@1.3.0: + resolution: {integrity: sha512-IgfweLvEpwyA4WgiQe9Nx6VV2QkML2NkvZnk1oKnIzXgXdWxuhF7zw4DvLTPZJn6PIUneiAXPF24QmoEqHTjyw==} + dev: false + /fast-glob@3.2.7: resolution: {integrity: sha512-rYGMRwip6lUMvYD3BTScMwT1HtAs2d71SMv66Vrxs0IekGZEjhM0pcMfjQPnknBt2zeCwQMEupiN02ZP4DiT1Q==} engines: {node: '>=8'} @@ -10141,7 +10273,6 @@ packages: /fs-constants@1.0.0: resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==} - dev: true /fs-extra@10.1.0: resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==} @@ -10323,6 +10454,10 @@ packages: through2: 4.0.2 dev: true + /github-from-package@0.0.0: + resolution: {integrity: sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==} + dev: false + /gitignore-parser@0.0.2: resolution: {integrity: sha512-X6mpqUv59uWLGD4n3hZ8Cu8KbF2PMWPSFYmxZjdkpm3yOU7hSUYnzTkZI1mcWqchphvqyuz3/BhgBR4E/JtkCg==} engines: {node: '>=0.10.0'} @@ -10813,7 +10948,6 @@ packages: /ini@1.3.8: resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} - dev: true /ini@2.0.0: resolution: {integrity: sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==} @@ -10877,6 +11011,10 @@ packages: /is-arrayish@0.2.1: resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} + /is-arrayish@0.3.2: + resolution: {integrity: sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==} + dev: false + /is-bigint@1.0.4: resolution: {integrity: sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==} dependencies: @@ -11114,6 +11252,9 @@ packages: is-docker: 2.2.1 dev: true + /isarray@0.0.1: + resolution: {integrity: sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==} + /isarray@1.0.0: resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} @@ -11142,7 +11283,7 @@ packages: '@babel/parser': 7.22.5 '@istanbuljs/schema': 0.1.3 istanbul-lib-coverage: 3.2.0 - semver: 6.3.0 + semver: 6.3.1 transitivePeerDependencies: - supports-color dev: true @@ -11944,7 +12085,7 @@ packages: dependencies: copy-anything: 2.0.6 parse-node-version: 1.0.1 - tslib: 2.5.3 + tslib: 2.6.1 optionalDependencies: errno: 0.1.8 graceful-fs: 4.2.11 @@ -12163,7 +12304,7 @@ packages: /lower-case@2.0.2: resolution: {integrity: sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==} dependencies: - tslib: 2.5.3 + tslib: 2.6.1 dev: true /lowercase-keys@2.0.0: @@ -12216,7 +12357,7 @@ packages: resolution: {integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==} engines: {node: '>=8'} dependencies: - semver: 6.3.0 + semver: 6.3.1 /make-error@1.3.6: resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} @@ -12360,7 +12501,6 @@ packages: /mimic-response@3.1.0: resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==} engines: {node: '>=10'} - dev: true /min-indent@1.0.1: resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} @@ -12434,6 +12574,10 @@ packages: engines: {node: '>= 8.0.0'} dev: true + /mkdirp-classic@0.5.3: + resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==} + dev: false + /mkdirp@0.5.6: resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==} hasBin: true @@ -12508,6 +12652,10 @@ packages: hasBin: true dev: true + /napi-build-utils@1.0.2: + resolution: {integrity: sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==} + dev: false + /natural-compare-lite@1.4.0: resolution: {integrity: sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==} dev: true @@ -12547,8 +12695,8 @@ packages: nest-commander: ^2.5.0 || ^3.0.0 dependencies: '@nestjs/common': 10.1.3(reflect-metadata@0.1.13)(rxjs@7.8.0) - '@nestjs/core': 10.1.3(@nestjs/common@10.1.3)(@nestjs/platform-express@10.1.3)(reflect-metadata@0.1.13)(rxjs@7.8.0) - '@nestjs/testing': 10.1.3(@nestjs/common@10.1.3)(@nestjs/core@10.1.3)(@nestjs/platform-express@10.1.3) + '@nestjs/core': 10.1.3(@nestjs/common@10.1.3)(@nestjs/microservices@10.1.3)(@nestjs/platform-express@10.1.3)(reflect-metadata@0.1.13)(rxjs@7.8.0) + '@nestjs/testing': 10.1.3(@nestjs/common@10.1.3)(@nestjs/core@10.1.3)(@nestjs/microservices@10.1.3)(@nestjs/platform-express@10.1.3) nest-commander: 3.9.0(@nestjs/common@10.1.3)(@nestjs/core@10.1.3)(@types/inquirer@8.1.0) dev: true @@ -12561,7 +12709,7 @@ packages: dependencies: '@golevelup/nestjs-discovery': 3.0.0 '@nestjs/common': 10.1.3(reflect-metadata@0.1.13)(rxjs@7.8.0) - '@nestjs/core': 10.1.3(@nestjs/common@10.1.3)(@nestjs/platform-express@10.1.3)(reflect-metadata@0.1.13)(rxjs@7.8.0) + '@nestjs/core': 10.1.3(@nestjs/common@10.1.3)(@nestjs/microservices@10.1.3)(@nestjs/platform-express@10.1.3)(reflect-metadata@0.1.13)(rxjs@7.8.0) '@types/inquirer': 8.1.0 commander: 11.0.0 cosmiconfig: 8.2.0 @@ -12579,9 +12727,16 @@ packages: resolution: {integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==} dependencies: lower-case: 2.0.2 - tslib: 2.5.3 + tslib: 2.6.1 dev: true + /node-abi@3.45.0: + resolution: {integrity: sha512-iwXuFrMAcFVi/ZoZiqq8BzAdsLw9kxDfTC0HMyjXfSL/6CSDAGD5UmR7azrAgWV1zKYq7dUUMj4owusBWKLsiQ==} + engines: {node: '>=10'} + dependencies: + semver: 7.5.4 + dev: false + /node-abort-controller@3.1.1: resolution: {integrity: sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==} dev: true @@ -12594,6 +12749,10 @@ packages: resolution: {integrity: sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==} dev: false + /node-addon-api@6.1.0: + resolution: {integrity: sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA==} + dev: false + /node-fetch@2.6.12: resolution: {integrity: sha512-C/fGU2E8ToujUivIO0H+tpQ6HWo4eEmchoPIoXtxCrVghxdKq+QOHqEZW7tuP3KlV3bC8FRMO5nMCC7Zm1VP6g==} engines: {node: 4.x || >=6.0.0} @@ -13698,6 +13857,25 @@ packages: dependencies: xtend: 4.0.2 + /prebuild-install@7.1.1: + resolution: {integrity: sha512-jAXscXWMcCK8GgCoHOfIr0ODh5ai8mj63L2nWrjuAgXE6tDyYGnx4/8o/rCgU+B4JSyZBKbeZqzhtwtC3ovxjw==} + engines: {node: '>=10'} + hasBin: true + dependencies: + detect-libc: 2.0.2 + expand-template: 2.0.3 + github-from-package: 0.0.0 + minimist: 1.2.8 + mkdirp-classic: 0.5.3 + napi-build-utils: 1.0.2 + node-abi: 3.45.0 + pump: 3.0.0 + rc: 1.2.8 + simple-get: 4.0.1 + tar-fs: 2.1.1 + tunnel-agent: 0.6.0 + dev: false + /preferred-pm@3.0.3: resolution: {integrity: sha512-+wZgbxNES/KlJs9q40F/1sfOd/j7f1O9JaHcW5Dsn3aUUOZg3L2bjpVUcKV2jvtElYfoTuQiNeMfQJ4kwUAhCQ==} engines: {node: '>=10'} @@ -13754,6 +13932,9 @@ packages: engines: {node: '>= 0.6.0'} dev: false + /promise-breaker@6.0.0: + resolution: {integrity: sha512-BthzO9yTPswGf7etOBiHCVuugs2N01/Q/94dIPls48z2zCmrnDptUUZzfIb+41xq0MnYZ/BzmOd6ikDR4ibNZA==} + /prompts@2.4.2: resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} engines: {node: '>= 6'} @@ -13803,7 +13984,6 @@ packages: dependencies: end-of-stream: 1.4.4 once: 1.4.0 - dev: true /punycode@2.3.0: resolution: {integrity: sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==} @@ -13839,12 +14019,15 @@ packages: /querystringify@2.2.0: resolution: {integrity: sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==} - dev: true /queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} dev: true + /queue-tick@1.0.1: + resolution: {integrity: sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==} + dev: false + /quick-format-unescaped@4.0.4: resolution: {integrity: sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==} dev: false @@ -13887,6 +14070,16 @@ packages: iconv-lite: 0.4.24 unpipe: 1.0.0 + /rc@1.2.8: + resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} + hasBin: true + dependencies: + deep-extend: 0.6.0 + ini: 1.3.8 + minimist: 1.2.8 + strip-json-comments: 2.0.1 + dev: false + /react-dom@18.2.0(react@18.2.0): resolution: {integrity: sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==} peerDependencies: @@ -13991,6 +14184,14 @@ packages: strip-bom: 3.0.0 dev: true + /readable-stream@1.1.14: + resolution: {integrity: sha512-+MeVjFf4L44XUkhM1eYbD8fyEsxcV81pqMSR5gblfcLCHfZvbrqy4/qYHE+/R5HoBUT11WV5O08Cr1n3YXkWVQ==} + dependencies: + core-util-is: 1.0.3 + inherits: 2.0.4 + isarray: 0.0.1 + string_decoder: 0.10.31 + /readable-stream@2.3.8: resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==} dependencies: @@ -14141,7 +14342,6 @@ packages: /requires-port@1.0.0: resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==} - dev: true /resolve-alpn@1.2.1: resolution: {integrity: sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==} @@ -14272,7 +14472,7 @@ packages: /rxjs@7.8.1: resolution: {integrity: sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==} dependencies: - tslib: 2.5.3 + tslib: 2.6.1 dev: true /sade@1.8.1: @@ -14419,11 +14619,11 @@ packages: /semver@6.3.0: resolution: {integrity: sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==} hasBin: true + dev: true /semver@6.3.1: resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} hasBin: true - dev: true /semver@7.5.2: resolution: {integrity: sha512-SoftuTROv/cRjCze/scjGyiDtcUyxw1rgYQSZY7XTmtR5hX+dm76iDbTH8TkLPHCQmlbQVSSbNZCPM2hb0knnQ==} @@ -14440,6 +14640,14 @@ packages: dependencies: lru-cache: 6.0.0 + /semver@7.5.4: + resolution: {integrity: sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==} + engines: {node: '>=10'} + hasBin: true + dependencies: + lru-cache: 6.0.0 + dev: false + /send@0.18.0: resolution: {integrity: sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==} engines: {node: '>= 0.8.0'} @@ -14506,6 +14714,21 @@ packages: /setprototypeof@1.2.0: resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} + /sharp@0.32.4: + resolution: {integrity: sha512-exUnZewqVZC6UXqXuQ8fyJJv0M968feBi04jb9GcUHrWtkRoAKnbJt8IfwT4NJs7FskArbJ14JAFGVuooszoGg==} + engines: {node: '>=14.15.0'} + requiresBuild: true + dependencies: + color: 4.2.3 + detect-libc: 2.0.2 + node-addon-api: 6.1.0 + prebuild-install: 7.1.1 + semver: 7.5.4 + simple-get: 4.0.1 + tar-fs: 3.0.4 + tunnel-agent: 0.6.0 + dev: false + /shebang-command@1.2.0: resolution: {integrity: sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==} engines: {node: '>=0.10.0'} @@ -14548,6 +14771,24 @@ packages: /signal-exit@3.0.7: resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} + /simple-concat@1.0.1: + resolution: {integrity: sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==} + dev: false + + /simple-get@4.0.1: + resolution: {integrity: sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==} + dependencies: + decompress-response: 6.0.0 + once: 1.4.0 + simple-concat: 1.0.1 + dev: false + + /simple-swizzle@0.2.2: + resolution: {integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==} + dependencies: + is-arrayish: 0.3.2 + dev: false + /sirv@2.0.3: resolution: {integrity: sha512-O9jm9BsID1P+0HOi81VpXPoDxYP374pkOLzACAoyUQ/3OUVndNpsz6wMnY2z+yOxzbllCKZrM+9QrWsv4THnyA==} engines: {node: '>= 10'} @@ -14606,7 +14847,7 @@ packages: resolution: {integrity: sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==} dependencies: dot-case: 3.0.4 - tslib: 2.5.3 + tslib: 2.6.1 dev: true /sockjs@0.3.24: @@ -14817,6 +15058,13 @@ packages: resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==} engines: {node: '>=10.0.0'} + /streamx@2.15.1: + resolution: {integrity: sha512-fQMzy2O/Q47rgwErk/eGeLu/roaFWV0jVsogDmrszM9uIw8L5OA+t+V93MgYlufNptfjmYR1tOMWhei/Eh7TQA==} + dependencies: + fast-fifo: 1.3.0 + queue-tick: 1.0.1 + dev: false + /string-length@4.0.2: resolution: {integrity: sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==} engines: {node: '>=10'} @@ -14871,6 +15119,9 @@ packages: es-abstract: 1.21.2 dev: true + /string_decoder@0.10.31: + resolution: {integrity: sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==} + /string_decoder@1.1.1: resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} dependencies: @@ -14914,6 +15165,11 @@ packages: min-indent: 1.0.1 dev: true + /strip-json-comments@2.0.1: + resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==} + engines: {node: '>=0.10.0'} + dev: false + /strip-json-comments@3.1.1: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} @@ -15058,6 +15314,23 @@ packages: engines: {node: '>=6'} dev: true + /tar-fs@2.1.1: + resolution: {integrity: sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==} + dependencies: + chownr: 1.1.4 + mkdirp-classic: 0.5.3 + pump: 3.0.0 + tar-stream: 2.2.0 + dev: false + + /tar-fs@3.0.4: + resolution: {integrity: sha512-5AFQU8b9qLfZCX9zp2duONhPmZv0hGYiBPJsyUdqMjzq/mqVpy/rEUSeHk1+YitmxugaptgBh5oDGU3VsAJq4w==} + dependencies: + mkdirp-classic: 0.5.3 + pump: 3.0.0 + tar-stream: 3.1.6 + dev: false + /tar-stream@2.2.0: resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==} engines: {node: '>=6'} @@ -15067,7 +15340,14 @@ packages: fs-constants: 1.0.0 inherits: 2.0.4 readable-stream: 3.6.2 - dev: true + + /tar-stream@3.1.6: + resolution: {integrity: sha512-B/UyjYwPpMBv+PaFSWAmtYjwdrlEaZQEhMIBFNC5oEG8lpiW8XjcSdmEaClj28ArfKScKHs2nshz3k2le6crsg==} + dependencies: + b4a: 1.6.4 + fast-fifo: 1.3.0 + streamx: 2.15.1 + dev: false /tar@6.1.11: resolution: {integrity: sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA==} @@ -15463,7 +15743,6 @@ packages: resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==} dependencies: safe-buffer: 5.2.1 - dev: true /tweetnacl@0.14.5: resolution: {integrity: sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==} @@ -15686,7 +15965,6 @@ packages: dependencies: querystringify: 2.2.0 requires-port: 1.0.0 - dev: true /util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} diff --git a/tsconfig.base.json b/tsconfig.base.json index af656dbc..1831a6c2 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -16,6 +16,7 @@ "strict": true, "baseUrl": ".", "strictPropertyInitialization": false, + "esModuleInterop": true, "paths": { "@unteris/db/migrations": ["libs/db/migrations/src/index.ts"], "@unteris/plugin/docker": ["libs/docker/src/index.ts"], @@ -24,7 +25,13 @@ "@unteris/server/csrf": ["libs/server/csrf/src/index.ts"], "@unteris/server/deities": ["libs/server/deities/src/index.ts"], "@unteris/server/email": ["libs/server/email/src/index.ts"], + "@unteris/server/file-storage": ["libs/server/file-storage/src/index.ts"], "@unteris/server/hash": ["libs/server/hash/src/index.ts"], + "@unteris/server/image-client": ["libs/server/image-client/src/index.ts"], + "@unteris/server/image-processing": [ + "libs/server/image-processing/src/index.ts" + ], + "@unteris/server/image-root": ["libs/server/image-root/src/index.ts"], "@unteris/server/kysely": ["libs/server/kysely/src/index.ts"], "@unteris/server/location": ["libs/server/location/src/index.ts"], "@unteris/server/logging": ["libs/server/logging/src/index.ts"],