diff --git a/libs/server/common/src/lib/file.ts b/libs/server/common/src/lib/file.ts index d67086c7..27785e25 100644 --- a/libs/server/common/src/lib/file.ts +++ b/libs/server/common/src/lib/file.ts @@ -3,7 +3,7 @@ import { ValibotDto } from "./valibot.dto"; export const FileSchema = object({ fieldname: string(), - originalName: string(), + originalname: string(), mimetype: string(), size: number(), buffer: instance(Buffer), diff --git a/libs/server/common/src/lib/valibot-to-openapi.ts b/libs/server/common/src/lib/valibot-to-openapi.ts index 9a672822..5187d229 100644 --- a/libs/server/common/src/lib/valibot-to-openapi.ts +++ b/libs/server/common/src/lib/valibot-to-openapi.ts @@ -1,5 +1,8 @@ import { Type } from "@nestjs/common"; -import { ObjectSchema } from "valibot"; +import { ObjectSchema, OptionalSchema } from "valibot"; + +// biome-ignore lint/suspicious/noExplicitAny: valibot needs any here +type Object = ObjectSchema>; const getType = (schema: { schema: string; @@ -44,9 +47,12 @@ const getType = (schema: { throw new Error(`Unknown Type: "${schema.schema}"`); } }; - -// biome-ignore lint/suspicious/noExplicitAny: valibot needs any here -export const schemaToOpenAPI = (schema: ObjectSchema>) => { +export const schemaToOpenAPI = ( + schema: Object | OptionalSchema, +): Record => { + if (schema.schema === "optional") { + return { required: false, ...schemaToOpenAPI(schema.wrapped) }; + } const valibotSchema = schema.object; const schemaObj: Record = {}; const schemaKeys = Object.keys(valibotSchema); diff --git a/libs/server/common/src/lib/valibot.dto.ts b/libs/server/common/src/lib/valibot.dto.ts index aaa962e9..1b7508dc 100644 --- a/libs/server/common/src/lib/valibot.dto.ts +++ b/libs/server/common/src/lib/valibot.dto.ts @@ -1,9 +1,11 @@ import { TypeschemaDto } from "@nest-lab/typeschema"; -import { ObjectSchema } from "valibot"; +import { ObjectSchema, OptionalSchema, OptionalSchemaAsync } from "valibot"; import { schemaToOpenAPI } from "./valibot-to-openapi"; -// biome-ignore lint/suspicious/noExplicitAny: valibot requires any here -export const ValibotDto: >>( +// biome-ignore lint/suspicious/noExplicitAny: Valibot required any here +type Object = ObjectSchema>; + +export const ValibotDto: ,>( schema: T, ) => ReturnType> = (schema) => { class DtoSchema extends TypeschemaDto(schema) { diff --git a/libs/server/location/src/lib/location.repository.ts b/libs/server/location/src/lib/location.repository.ts index b9ba528f..b515b0c1 100644 --- a/libs/server/location/src/lib/location.repository.ts +++ b/libs/server/location/src/lib/location.repository.ts @@ -1,4 +1,5 @@ import { Injectable } from "@nestjs/common"; +import { File } from "@unteris/server/common"; import { Database, InjectKysely } from "@unteris/server/kysely"; import { Location, @@ -29,10 +30,23 @@ export class LocationRepository { async createLocation( location: Insertable, + file?: string, ): Promise { + let fileId; + if (file) { + const result = await this.db + .insertInto("image") + .values({ + originalUrl: file, + type: "location_image", + }) + .returning(["id"]) + .execute(); + fileId = result[0].id; + } return this.db .insertInto("location") - .values(location) + .values({ ...location, imageId: fileId }) .returningAll() .executeTakeFirstOrThrow(); } diff --git a/libs/server/location/src/lib/models/image-file.dto.ts b/libs/server/location/src/lib/models/image-file.dto.ts new file mode 100644 index 00000000..0dfadebc --- /dev/null +++ b/libs/server/location/src/lib/models/image-file.dto.ts @@ -0,0 +1,4 @@ +import { FileSchema, ValibotDto } from "@unteris/server/common"; +import { optional } from "valibot"; + +export class ImageFile extends ValibotDto(optional(FileSchema)) {} diff --git a/libs/server/location/src/lib/models/index.ts b/libs/server/location/src/lib/models/index.ts index e23eb65f..b612eed4 100644 --- a/libs/server/location/src/lib/models/index.ts +++ b/libs/server/location/src/lib/models/index.ts @@ -1 +1,4 @@ export * from "./by-type-query.dto"; +export * from "./image-file.dto"; +export * from "./location-creation.dto"; +export * from "./location-update.dto"; diff --git a/libs/server/location/src/lib/serer-location.controller.ts b/libs/server/location/src/lib/serer-location.controller.ts index 9ac618a3..4a215374 100644 --- a/libs/server/location/src/lib/serer-location.controller.ts +++ b/libs/server/location/src/lib/serer-location.controller.ts @@ -24,12 +24,15 @@ import { getSchemaPath, } from "@nestjs/swagger"; import { Action, Castle, CastleGuard, Subject } from "@unteris/server/castle"; -import { FileDto, IdParamDto, OverviewObjectDto } from "@unteris/server/common"; +import { IdParamDto, OverviewObjectDto } from "@unteris/server/common"; import { SkipSessionCheck } from "@unteris/server/session"; import { locationRoute } from "@unteris/shared/types"; -import { ByTypeQueryDto } from "./models"; -import { LocationCreationDto } from "./models/location-creation.dto"; -import { LocationUpdateDto } from "./models/location-update.dto"; +import { + ByTypeQueryDto, + ImageFile, + LocationCreationDto, + LocationUpdateDto, +} from "./models"; import { ServerLocationService } from "./server-location.service"; @ApiTags("Location") @@ -77,7 +80,7 @@ export class ServerLocationController { @UseGuards(CastleGuard) @Castle([Action.Create, Subject.Location]) @Post("new") - create(@Body() body: LocationCreationDto, @UploadedFile() file?: FileDto) { + create(@Body() body: LocationCreationDto, @UploadedFile() file?: ImageFile) { return this.service.createLocation(body.data, file?.data ?? undefined); } diff --git a/libs/server/location/src/lib/server-location.module.ts b/libs/server/location/src/lib/server-location.module.ts index 9de60c99..45c4db58 100644 --- a/libs/server/location/src/lib/server-location.module.ts +++ b/libs/server/location/src/lib/server-location.module.ts @@ -1,12 +1,19 @@ import { Module } from "@nestjs/common"; import { ServerCastleModule } from "@unteris/server/castle"; +import { ServerFileStorageModule } from "@unteris/server/file-storage"; +import { ServerImageClientModule } from "@unteris/server/image-client"; import { KyselyModule } from "@unteris/server/kysely"; import { LocationRepository } from "./location.repository"; import { ServerLocationController } from "./serer-location.controller"; import { ServerLocationService } from "./server-location.service"; @Module({ - imports: [KyselyModule, ServerCastleModule], + imports: [ + KyselyModule, + ServerCastleModule, + ServerImageClientModule, + ServerFileStorageModule, + ], controllers: [ServerLocationController], providers: [ServerLocationService, LocationRepository], exports: [ServerLocationService], diff --git a/libs/server/location/src/lib/server-location.service.ts b/libs/server/location/src/lib/server-location.service.ts index 5c4bb2f1..dbfec754 100644 --- a/libs/server/location/src/lib/server-location.service.ts +++ b/libs/server/location/src/lib/server-location.service.ts @@ -1,5 +1,7 @@ import { Injectable, NotFoundException } from "@nestjs/common"; import { File } from "@unteris/server/common"; +import { ServerFileStorageService } from "@unteris/server/file-storage"; +import { ServerImageClientService } from "@unteris/server/image-client"; import { Database } from "@unteris/server/kysely"; import { Location, @@ -11,7 +13,11 @@ import { LocationRepository } from "./location.repository"; @Injectable() export class ServerLocationService { - constructor(private readonly locationRepo: LocationRepository) {} + constructor( + private readonly locationRepo: LocationRepository, + private readonly imageService: ServerImageClientService, + private readonly fileService: ServerFileStorageService, + ) {} async getByType(type: Location["type"]): Promise { return this.locationRepo.getByType(type); @@ -29,7 +35,15 @@ export class ServerLocationService { location: Insertable, file?: File, ): Promise { - const result = this.locationRepo.createLocation(location); + let filePath; + if (file) { + filePath = file.originalname; + await this.fileService.writeFileToStore(filePath, file.buffer); + } + const result = await this.locationRepo.createLocation(location, filePath); + if (result.imageId) { + this.imageService.sendImageIdForProcessing(result.imageId); + } return result; } diff --git a/libs/shared/types/src/lib/image.ts b/libs/shared/types/src/lib/image.ts index 05f718d7..fda383f2 100644 --- a/libs/shared/types/src/lib/image.ts +++ b/libs/shared/types/src/lib/image.ts @@ -2,7 +2,7 @@ import { Output, enumType, nullable, object, string, ulid } from "valibot"; export const ImageSchema = object({ id: string([ulid()]), - type: enumType(["deity_avatar", "user_avatar"]), + type: enumType(["deity_avatar", "user_avatar", "location_image"]), originalUrl: string(), smallUrl: nullable(string()), mediumUrl: nullable(string()),