From e25aa67ae0d1b3c4ad7960a820c929c0aa252653 Mon Sep 17 00:00:00 2001 From: asjasj3964 <84120715+asjasj3964@users.noreply.github.com> Date: Sun, 9 Feb 2025 17:36:29 +0900 Subject: [PATCH 1/4] =?UTF-8?q?[SWEP-85]=20=EB=A9=94=EB=AA=A8=EC=9E=A5=20A?= =?UTF-8?q?PI=20tsoa=20=EC=A0=81=EC=9A=A9=20(#168)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app.ts | 5 +- src/controllers/memo-folder.controller.ts | 211 ++- src/controllers/memo-image.controller.ts | 91 +- .../tsoa.memo-folder.controller.ts | 606 +++++++ src/controllers/tsoa.memo-image.controller.ts | 278 +++ src/dtos/memo-folder.dto.ts | 144 +- src/dtos/memo-folder.dto.tsoa.ts | 108 ++ src/dtos/memo-image.dto.tsoa.ts | 32 + src/errors.ts | 9 +- src/models/memo-folder.model.ts | 207 +-- src/models/memo-folder.model.tsoa.ts | 171 ++ src/models/memo-image.model.tsoa.ts | 34 + .../memo-folder.repository.tsoa.ts | 351 ++++ .../memo-image.repository.tsoa.ts | 148 ++ src/routers/memo.router.ts | 47 +- src/routers/tsoaRoutes.ts | 509 +++++- src/s3/image.mover.ts | 65 +- src/services/memo-folder.service.tsoa.ts | 144 ++ src/services/memo-image.service.tsoa.ts | 124 ++ swagger/openapi.json | 837 ++++++++- swagger/swagger.json | 1514 ++++++++++++++++- 21 files changed, 5258 insertions(+), 377 deletions(-) create mode 100644 src/controllers/tsoa.memo-folder.controller.ts create mode 100644 src/controllers/tsoa.memo-image.controller.ts create mode 100644 src/dtos/memo-folder.dto.tsoa.ts create mode 100644 src/dtos/memo-image.dto.tsoa.ts create mode 100644 src/models/memo-folder.model.tsoa.ts create mode 100644 src/models/memo-image.model.tsoa.ts create mode 100644 src/repositories/memo-folder.repository.tsoa.ts create mode 100644 src/repositories/memo-image.repository.tsoa.ts create mode 100644 src/services/memo-folder.service.tsoa.ts create mode 100644 src/services/memo-image.service.tsoa.ts diff --git a/src/app.ts b/src/app.ts index 2c7658e..d4329c7 100644 --- a/src/app.ts +++ b/src/app.ts @@ -28,6 +28,7 @@ import {authRouter} from './routers/auth.routers.js'; import {userRouter} from './routers/user.router.js'; import {tagRouter} from './routers/tag.router.js'; import {myPageRouter} from './routers/mypage.routers.js'; +import {imageUploader} from './s3/image.uploader.js'; dotenv.config(); @@ -113,11 +114,11 @@ app.use(sessionAuthMiddleware); app.use('/onboarding', userRouter); app.use('/memo', memoFolderRouter); app.use('/challenge', challengeRouter); -app.use('/user/mypage',myPageRouter); +app.use('/user/mypage', myPageRouter); app.use('/tag', tagRouter); app.post('/image/ai', labelDetectionController); -RegisterRoutes(app); +RegisterRoutes(app, {multer: imageUploader}); app.get('/', (req: Request, res: Response) => { res.send('Sweepic'); diff --git a/src/controllers/memo-folder.controller.ts b/src/controllers/memo-folder.controller.ts index 543cbce..82bcacb 100644 --- a/src/controllers/memo-folder.controller.ts +++ b/src/controllers/memo-folder.controller.ts @@ -1,10 +1,21 @@ -import { Response, Request, NextFunction } from 'express'; -import { StatusCodes } from 'http-status-codes'; -import { bodyToMemoFolder, bodyToMemoTextToUpdate } from '../dtos/memo-folder.dto.js'; -import { listMemoFolder, listMemoTextImage, memoFolderCreate, memoFolderImageCreate, memoFolderUpdate, memoSearch, memoTextUpdate } from '../services/memo-folder.service.js'; -import { memoImageDelete } from '../services/memo-image.service.js'; -import { bodyToMemoImagesToDelete } from '../dtos/memo-image.dto.js'; -import { DataValidationError, PhotoValidationError } from '../errors.js'; +import {Response, Request, NextFunction, Express} from 'express'; +import {StatusCodes} from 'http-status-codes'; +import { + bodyToMemoFolder, + bodyToMemoTextToUpdate, +} from '../dtos/memo-folder.dto.js'; +import { + listMemoFolder, + listMemoTextImage, + memoFolderCreate, + memoFolderImageCreate, + memoFolderUpdate, + memoSearch, + memoTextUpdate, +} from '../services/memo-folder.service.js'; +import {memoImageDelete} from '../services/memo-image.service.js'; +import {bodyToMemoImagesToDelete} from '../dtos/memo-image.dto.js'; +import {DataValidationError, PhotoValidationError} from '../errors.js'; export const handlerMemoFolderImageCreate = async ( req: Request, @@ -171,19 +182,23 @@ export const handlerMemoFolderImageCreate = async ( } }; */ - try{ - const userId = BigInt(req.user!.id); - if (!req.file) { - throw new PhotoValidationError({reason: '저장할 사진이 없습니다.'}); - } - const imageUrl = (req.file as Express.MulterS3File).key; - const folderId = req.uploadDirectory; - const memoFolderImage = await memoFolderImageCreate(userId, folderId, imageUrl, req.body); - res.status(StatusCodes.OK).success(memoFolderImage); - } - catch(error){ - next(error); + try { + const userId = BigInt(req.user!.id); + if (!req.file) { + throw new PhotoValidationError({reason: '저장할 사진이 없습니다.'}); } + const imageUrl = (req.file as Express.MulterS3File).key; + const folderId = req.uploadDirectory; + const memoFolderImage = await memoFolderImageCreate( + userId, + folderId, + imageUrl, + req.body, + ); + res.status(StatusCodes.OK).success(memoFolderImage); + } catch (error) { + next(error); + } }; export const handlerMemoFolderAdd = async ( req: Request, @@ -285,14 +300,16 @@ export const handlerMemoFolderAdd = async ( } }; */ - try{ - const userId = BigInt(req.user!.id); - const memoFolder = await memoFolderCreate(userId, bodyToMemoFolder(req.body)); - res.status(StatusCodes.OK).success(memoFolder); - } - catch(error) { - next (error); - } + try { + const userId = BigInt(req.user!.id); + const memoFolder = await memoFolderCreate( + userId, + bodyToMemoFolder(req.body), + ); + res.status(StatusCodes.OK).success(memoFolder); + } catch (error) { + next(error); + } }; export const handlerMemoFolderList = async ( @@ -339,14 +356,13 @@ export const handlerMemoFolderList = async ( } }; */ - try{ - const userId = BigInt(req.user!.id); - const memoList = await listMemoFolder(userId); - res.status(StatusCodes.OK).success(memoList); - } - catch(error) { - next (error); - } + try { + const userId = BigInt(req.user!.id); + const memoList = await listMemoFolder(userId); + res.status(StatusCodes.OK).success(memoList); + } catch (error) { + next(error); + } }; export const handlerMemoSearch = async ( @@ -428,22 +444,25 @@ export const handlerMemoSearch = async ( } }; */ - try{ - const userId = BigInt(req.user!.id); - const searchKeyword = req.query.keyword?.toString(); - if (searchKeyword === null || searchKeyword === undefined) { - throw new DataValidationError({reason: '검색어를 1자 이상 입력하세요.'}); - } - const searchMemoList = await memoSearch(userId, searchKeyword); - res.status(StatusCodes.OK).success(searchMemoList); - } - catch(error) { - next (error); + try { + const userId = BigInt(req.user!.id); + const searchKeyword = req.query.keyword?.toString(); + if (searchKeyword === null || searchKeyword === undefined) { + throw new DataValidationError({reason: '검색어를 1자 이상 입력하세요.'}); } + const searchMemoList = await memoSearch(userId, searchKeyword); + res.status(StatusCodes.OK).success(searchMemoList); + } catch (error) { + next(error); + } }; -export const handlerMemoImageDelete = async (req: Request, res: Response, next: NextFunction): Promise => { - /* +export const handlerMemoImageDelete = async ( + req: Request, + res: Response, + next: NextFunction, +): Promise => { + /* #swagger.tags = ['memo-image-controller'] #swagger.summary = '사진 삭제 API'; #swagger.description = '특정 폴더의 사진을 삭제하는 API입니다.' @@ -557,15 +576,18 @@ export const handlerMemoImageDelete = async (req: Request, res: Response, next: } }; */ - try{ - const userId = BigInt(req.user!.id); - const folderId = BigInt(req.params.folderId); - const memoImagesToMove = await memoImageDelete(userId, folderId, bodyToMemoImagesToDelete(req.body)); - res.status(StatusCodes.OK).success(memoImagesToMove); - } - catch(error) { - next (error); - } + try { + const userId = BigInt(req.user!.id); + const folderId = BigInt(req.params.folderId); + const memoImagesToMove = await memoImageDelete( + userId, + folderId, + bodyToMemoImagesToDelete(req.body), + ); + res.status(StatusCodes.OK).success(memoImagesToMove); + } catch (error) { + next(error); + } }; export const handlerMemoTextImageList = async ( req: Request, @@ -645,19 +667,22 @@ export const handlerMemoTextImageList = async ( } }; */ - try{ - const userId = BigInt(req.user!.id); - const folderId = BigInt(req.params.folderId); - const memoTextImageList = await listMemoTextImage(userId, folderId); - res.status(StatusCodes.OK).success(memoTextImageList); - } - catch(error) { - next (error); - } + try { + const userId = BigInt(req.user!.id); + const folderId = BigInt(req.params.folderId); + const memoTextImageList = await listMemoTextImage(userId, folderId); + res.status(StatusCodes.OK).success(memoTextImageList); + } catch (error) { + next(error); + } }; -export const handlerMemoFolderUpdate = async (req: Request, res: Response, next: NextFunction) :Promise => { - /* +export const handlerMemoFolderUpdate = async ( + req: Request, + res: Response, + next: NextFunction, +): Promise => { + /* #swagger.tags = ['memo-folder-controller'] #swagger.summary = '메모 폴더 이름 수정 API'; #swagger.description = '특정 폴더의 이름을 수정하는 API입니다.' @@ -825,19 +850,26 @@ export const handlerMemoFolderUpdate = async (req: Request, res: Response, next: } }; */ - try{ - const userId = BigInt(req.user!.id); - const folderId = BigInt(req.params.folderId); - const updatedMemoFolder = await memoFolderUpdate(userId, folderId, bodyToMemoFolder(req.body)); - res.status(StatusCodes.OK).success(updatedMemoFolder); - } - catch(error) { - next (error); - } + try { + const userId = BigInt(req.user!.id); + const folderId = BigInt(req.params.folderId); + const updatedMemoFolder = await memoFolderUpdate( + userId, + folderId, + bodyToMemoFolder(req.body), + ); + res.status(StatusCodes.OK).success(updatedMemoFolder); + } catch (error) { + next(error); + } }; -export const handlerMemoTextUpdate = async (req: Request, res: Response, next: NextFunction): Promise =>{ - /* +export const handlerMemoTextUpdate = async ( + req: Request, + res: Response, + next: NextFunction, +): Promise => { + /* #swagger.tags = ['memo-folder-controller'] #swagger.summary = '특정 폴더의 메모 텍스트 수정 API'; #swagger.description = '특정 폴더의 메모 텍스트를 수정하는 API입니다.' @@ -924,13 +956,16 @@ export const handlerMemoTextUpdate = async (req: Request, res: Response, next: N } }; */ - try{ - const userId = BigInt(req.user!.id); - const folderId = BigInt(req.params.folderId); - const memoTextImageList = await memoTextUpdate(userId, folderId, bodyToMemoTextToUpdate(req.body)); - res.status(StatusCodes.OK).success(memoTextImageList); - } - catch(error) { - next (error); - } + try { + const userId = BigInt(req.user!.id); + const folderId = BigInt(req.params.folderId); + const memoTextImageList = await memoTextUpdate( + userId, + folderId, + bodyToMemoTextToUpdate(req.body), + ); + res.status(StatusCodes.OK).success(memoTextImageList); + } catch (error) { + next(error); + } }; diff --git a/src/controllers/memo-image.controller.ts b/src/controllers/memo-image.controller.ts index 9b874bf..bc7ca65 100644 --- a/src/controllers/memo-image.controller.ts +++ b/src/controllers/memo-image.controller.ts @@ -1,8 +1,12 @@ -import { Response, Request, NextFunction } from 'express'; -import { StatusCodes } from 'http-status-codes'; -import { memoFolderDelete, memoImageAdd, memoImagesMove } from '../services/memo-image.service.js'; -import { bodyToMemoImagesToMove } from '../dtos/memo-image.dto.js'; -import { PhotoDataNotFoundError } from '../errors.js'; +import {Response, Request, NextFunction, Express} from 'express'; +import {StatusCodes} from 'http-status-codes'; +import { + memoFolderDelete, + memoImageAdd, + memoImagesMove, +} from '../services/memo-image.service.js'; +import {bodyToMemoImagesToMove} from '../dtos/memo-image.dto.js'; +import {PhotoDataNotFoundError} from '../errors.js'; export const handlerMemoImageAdd = async ( req: Request, @@ -172,22 +176,25 @@ export const handlerMemoImageAdd = async ( } }; */ - try{ - const folderId = BigInt(req.params.folderId); - if (!req.file) { - throw new PhotoDataNotFoundError({reason: '저장할 사진이 없습니다.'}); - } - const imageUrl = (req.file as Express.MulterS3File).key; - const memoImage = await memoImageAdd(folderId, imageUrl); - res.status(StatusCodes.OK).success(memoImage); - } - catch(error) { - next(error); + try { + const folderId = BigInt(req.params.folderId); + if (!req.file) { + throw new PhotoDataNotFoundError({reason: '저장할 사진이 없습니다.'}); } + const imageUrl = (req.file as Express.MulterS3File).key; + const memoImage = await memoImageAdd(folderId, imageUrl); + res.status(StatusCodes.OK).success(memoImage); + } catch (error) { + next(error); + } }; -export const handlerMemoImageMove = async (req: Request, res: Response, next: NextFunction): Promise => { - /* +export const handlerMemoImageMove = async ( + req: Request, + res: Response, + next: NextFunction, +): Promise => { + /* #swagger.tags = ['memo-image-controller'] #swagger.summary = '사진 이동 API'; #swagger.description = '특정 폴더의 사진을 이동하는 API입니다.' @@ -327,19 +334,26 @@ export const handlerMemoImageMove = async (req: Request, res: Response, next: Ne } }; */ - try{ - const userId = BigInt(req.user!.id); - const folderId = BigInt(req.params.folderId); - const memoImagesToMove = await memoImagesMove(userId, folderId, bodyToMemoImagesToMove(req.body)); - res.status(StatusCodes.OK).success(memoImagesToMove); - } - catch(error) { - next(error); - } + try { + const userId = BigInt(req.user!.id); + const folderId = BigInt(req.params.folderId); + const memoImagesToMove = await memoImagesMove( + userId, + folderId, + bodyToMemoImagesToMove(req.body), + ); + res.status(StatusCodes.OK).success(memoImagesToMove); + } catch (error) { + next(error); + } }; -export const handlerMemoFolderDelete = async (req: Request, res: Response, next: NextFunction) :Promise => { - /* +export const handlerMemoFolderDelete = async ( + req: Request, + res: Response, + next: NextFunction, +): Promise => { + /* #swagger.tags = ['memo-folder-controller'] #swagger.summary = '폴더 삭제 API'; #swagger.description = '특정 폴더를 삭제하는 API입니다.' @@ -400,13 +414,12 @@ export const handlerMemoFolderDelete = async (req: Request, res: Response, next: } }; */ - try{ - const userId = BigInt(req.user!.id); - const folderId = BigInt(req.params.folderId); - const memoImagesToDelete = await memoFolderDelete(userId, folderId); - res.status(StatusCodes.OK).success(memoImagesToDelete); - } - catch(error) { - next(error); - } -}; \ No newline at end of file + try { + const userId = BigInt(req.user!.id); + const folderId = BigInt(req.params.folderId); + const memoImagesToDelete = await memoFolderDelete(userId, folderId); + res.status(StatusCodes.OK).success(memoImagesToDelete); + } catch (error) { + next(error); + } +}; diff --git a/src/controllers/tsoa.memo-folder.controller.ts b/src/controllers/tsoa.memo-folder.controller.ts new file mode 100644 index 0000000..605472e --- /dev/null +++ b/src/controllers/tsoa.memo-folder.controller.ts @@ -0,0 +1,606 @@ +import {StatusCodes} from 'http-status-codes'; +import { + bodyToMemoFolder, + bodyToMemoTextToUpdate, +} from '../dtos/memo-folder.dto.tsoa.js'; +import { + listMemoFolder, + listMemoTextImage, + memoFolderCreate, + memoFolderImageCreate, + memoFolderUpdate, + memoSearch, + memoTextUpdate, +} from '../services/memo-folder.service.tsoa.js'; +import {memoImageDelete} from '../services/memo-image.service.tsoa.js'; +import {bodyToMemoImagesToDelete} from '../dtos/memo-image.dto.tsoa.js'; +import {DataValidationError, PhotoValidationError} from '../errors.js'; +import { + Response, + Body, + Controller, + Post, + Route, + SuccessResponse, + Tags, + Request, + Get, + Query, + Path, + Patch, + Example, + FormField, + UploadedFile, +} from 'tsoa'; +import { + BodyToMemoFolder, + BodyToMemoTextToUpdate, + MemoFolderListResponseDto, + MemoFolderResponseDto, + MemoTextImageListResponseDto, + MemoFolderImageResponseDto, +} from '../models/memo-folder.model.tsoa.js'; +import { + ITsoaErrorResponse, + ITsoaSuccessResponse, + TsoaSuccessResponse, +} from '../models/tsoaResponse.js'; +import {BodyToMemoImagesToDelete} from '../models/memo-image.model.tsoa.js'; +import {Request as ExpressRequest, Express} from 'express'; + +@Route('memo') +export class MemoFolderController extends Controller { + /** + * 폴더 생성과 동시에 파일을 저장하는 API입니다. + * + * @summary 폴더 생성 및 사진 저장 API + * @param req + * @param folderName 생성할 폴더 이름 + * @param image 파일 업로드 + * @returns 성공 시 폴더 생성 및 사진 저장 결과를 반환합니다. + * + */ + @Post('/image-format/folders') + @Tags('memo-folder-controller') + @Response( + StatusCodes.BAD_REQUEST, + '유효하지 않은 데이터 에러', + { + resultType: 'FAIL', + success: null, + error: { + errorCode: 'FOL-400', + reason: '폴더 생성 중 오류가 발생했습니다.', + data: {userId: '1', folderName: 'string'}, + }, + }, + ) + @Response( + StatusCodes.BAD_REQUEST, + '유효하지 않은 데이터 에러', + { + resultType: 'FAIL', + error: { + errorCode: 'PHO-400', + reason: '사진 데이터가 유효하지 않습니다.', + data: { + reason: '저장할 사진이 없습니다.', + }, + }, + success: null, + }, + ) + @Response( + StatusCodes.BAD_REQUEST, + '유효하지 않은 데이터 에러', + { + resultType: 'FAIL', + error: { + errorCode: 'MEM-400', + reason: '메모 사진 추가 중 오류가 발생했습니다.', + data: { + folderId: '1', + imageUrl: 'string', + }, + }, + success: null, + }, + ) + @Response( + StatusCodes.BAD_REQUEST, + '유효하지 않은 데이터 에러', + { + resultType: 'FAIL', + error: { + errorCode: 'PHO-400', + reason: '사진 데이터가 유효하지 않습니다.', + data: { + extension: 'string', + }, + }, + success: null, + }, + ) + @Response(StatusCodes.CONFLICT, '중복 데이터 에러', { + resultType: 'FAIL', + success: null, + error: { + errorCode: 'FOL-409', + reason: '이미 존재하는 폴더 이름입니다.', + data: {folderName: 'string'}, + }, + }) + @SuccessResponse(StatusCodes.OK, '폴더 생성 및 사진 저장 성공 응답') + @Example({ + resultType: 'SUCCESS', + error: null, + success: { + folderId: '1', + folderName: 'string', + imageId: '1', + imageUrl: 'string', + }, + }) + public async handlerMemoFolderImageAdd( + @Request() req: ExpressRequest, + @FormField() folderName: string, + @UploadedFile() image: Express.MulterS3File, + ): Promise> { + try { + const userId = BigInt(req.user!.id); + if (!image) { + throw new PhotoValidationError({reason: '저장할 사진이 없습니다.'}); + } + const imageUrl = image.key; + const folderId = req.uploadDirectory; + const memoFolderImage = await memoFolderImageCreate( + userId, + folderId, + imageUrl, + folderName, + ); + return new TsoaSuccessResponse(memoFolderImage); + } catch (error) { + throw error; + } + } + + /** + * 폴더를 생성하는 API입니다. + * + * @summary 폴더 생성 API + * @param req + * @param body 생성할 폴더 이름 + * @returns 성공 시 폴더 생성 결과를 반환합니다. + */ + @Post('/folders') + @Tags('memo-folder-controller') + @Response( + StatusCodes.BAD_REQUEST, + '유효하지 않은 데이터 에러', + { + resultType: 'FAIL', + success: null, + error: { + errorCode: 'FOL-400', + reason: '폴더 생성 중 오류가 발생했습니다.', + data: {userId: '1', folderName: 'string'}, + }, + }, + ) + @Response(StatusCodes.CONFLICT, '중복 데이터 에러', { + resultType: 'FAIL', + success: null, + error: { + errorCode: 'FOL-409', + reason: '이미 존재하는 폴더 이름입니다.', + data: {folderName: 'string'}, + }, + }) + @SuccessResponse(StatusCodes.OK, '폴더 생성 성공 응답') + @Example({ + resultType: 'SUCCESS', + error: null, + success: { + id: '1', + folderName: 'string', + }, + }) + public async handlerMemoFolderAdd( + @Request() req: ExpressRequest, + @Body() body: BodyToMemoFolder, + ): Promise> { + try { + const userId = BigInt(req.user!.id); + const memoFolder = await memoFolderCreate(userId, bodyToMemoFolder(body)); + return new TsoaSuccessResponse(memoFolder); + } catch (error) { + throw error; + } + } + + /** + * 모든 메모를 조회하는 API입니다. + * + * @summary 모든 메모 조회 API + * @param req + * @returns 성공 시 메모 조회 결과를 반환합니다. + */ + @Get('/list') + @Tags('memo-folder-controller') + @SuccessResponse(StatusCodes.OK, '메모 조회 성공 응답') + @Example({ + resultType: 'SUCCESS', + error: null, + success: { + data: [ + { + folderId: '1', + folderName: 'string', + imageCount: 0, + imageText: 'string', + firstImageId: '1', + firstImageUrl: 'string', + createdAt: '2025-01-17T03:50:25.923Z', + }, + ], + }, + }) + public async handlerMemoFolderList( + @Request() req: ExpressRequest, + ): Promise> { + try { + const userId = BigInt(req.user!.id); + const memoList = await listMemoFolder(userId); + return new TsoaSuccessResponse(memoList); + } catch (error) { + throw error; + } + } + + /** + * 메모를 검색 및 조회하는 API입니다. + * + * @summary 메모 검색 API + * @param req + * @param keyword 검색 키워드 + * @returns 성공 시 메모 검색 결과를 반환합니다. + */ + @Get('/search') + @Tags('memo-folder-controller') + @Response( + StatusCodes.BAD_REQUEST, + '유효하지 않은 검색 키워드 에러', + { + resultType: 'FAIL', + success: null, + error: { + errorCode: 'SRH-400', + reason: '입력 데이터가 유효하지 않습니다.', + data: {reason: '검색어를 1자 이상 입력하세요.'}, + }, + }, + ) + @SuccessResponse(StatusCodes.OK, '메모 검색 성공 응답') + @Example({ + resultType: 'SUCCESS', + error: null, + success: { + data: [ + { + folderId: '1', + folderName: 'string', + imageCount: 0, + imageText: 'string', + firstImageId: '1', + firstImageUrl: 'string', + createdAt: '2025-01-17T03:50:25.923Z', + }, + ], + }, + }) + public async handlerMemoSearch( + @Request() req: ExpressRequest, + @Query() keyword: string, + ): Promise> { + try { + const userId = BigInt(req.user!.id); + const searchKeyword = keyword?.toString(); + if (searchKeyword === null) { + throw new DataValidationError({ + reason: '검색어를 1자 이상 입력하세요.', + }); + } + const searchMemoList = await memoSearch(userId, searchKeyword); + return new TsoaSuccessResponse(searchMemoList); + } catch (error) { + throw error; + } + } + + /** + * 특정 폴더의 사진을 삭제하는 API입니다. + * + * @summary 사진 삭제 API + * @param req + * @param folderIdParam 폴더 ID + * @param body 이동할 사진 ID 배열 + * @returns 성공 시 사진 삭제 결과를 반환합니다. + */ + @Post('/folders/:folderId/images/delete') + @Tags('memo-image-controller') + @Response( + StatusCodes.NOT_FOUND, + '존재하지 않은 데이터 조회 에러', + { + resultType: 'FAIL', + error: { + errorCode: 'FOL-404', + reason: '해당 폴더를 찾을 수 없습니다.', + data: { + folderId: '1', + }, + }, + success: null, + }, + ) + @Response( + StatusCodes.NOT_FOUND, + '존재하지 않은 데이터 조회 에러', + { + resultType: 'FAIL', + error: { + errorCode: 'PHO-404', + reason: '해당 사진 데이터가 없습니다.', + data: { + imageId: '1', + }, + }, + success: null, + }, + ) + @SuccessResponse(StatusCodes.OK, '사진 삭제 성공 응답') + @Example({ + resultType: 'SUCCESS', + error: null, + success: { + folderId: '1', + folderName: 'string', + imageText: 'string', + images: [ + { + imageId: '1', + imageUrl: 'string', + }, + ], + }, + }) + public async handlerMemoImageDelete( + @Request() req: ExpressRequest, + @Path('folderId') folderIdParam: string, + @Body() body: BodyToMemoImagesToDelete, + ): Promise> { + try { + const userId = BigInt(req.user!.id); + const folderId = BigInt(folderIdParam); + const memoImagesToMove = await memoImageDelete( + userId, + folderId, + bodyToMemoImagesToDelete(body), + ); + return new TsoaSuccessResponse(memoImagesToMove); + } catch (error) { + throw error; + } + } + + /** + * 특정 폴더의 모든 메모(텍스트 및 사진)을 조회하는 API입니다. + * + * @summary 특정 폴더의 메모 조회 API + * @param req + * @param folderIdParam 폴더 ID + * @returns 성공 시 특정 메모의 조회 결과를 반환합니다. + */ + @Get('/folders/:folderId') + @Tags('memo-folder-controller') + @Response( + StatusCodes.NOT_FOUND, + '존재하지 않은 데이터 조회 에러', + { + resultType: 'FAIL', + error: { + errorCode: 'FOL-404', + reason: '해당 폴더를 찾을 수 없습니다.', + data: { + folderId: '1', + }, + }, + success: null, + }, + ) + @SuccessResponse(StatusCodes.OK, '메모 조회 성공 응답') + @Example({ + resultType: 'SUCCESS', + error: null, + success: { + folderId: '1', + folderName: 'string', + imageText: 'string', + images: [ + { + imageId: '1', + imageUrl: 'string', + }, + ], + }, + }) + public async handlerMemoTextImageList( + @Request() req: ExpressRequest, + @Path('folderId') folderIdParam: string, + ): Promise> { + try { + const userId = BigInt(req.user!.id); + const folderId = BigInt(folderIdParam); + const memoTextImageList = await listMemoTextImage(userId, folderId); + return new TsoaSuccessResponse(memoTextImageList); + } catch (error) { + throw error; + } + } + + /** + * 특정 폴더의 이름을 수정하는 API입니다. + * + * @summary 메모 폴더 이름 수정 API + * @param req + * @param folderIdParam 폴더 ID + * @param body 폴더 이름 수정 내용 + * @returns 성공 시 메모 폴더 이름 수정 결과를 반환합니다. + */ + @Patch('/folders/:folderId') + @Tags('memo-folder-controller') + @Response( + StatusCodes.BAD_REQUEST, + '유효하지 않은 데이터 에러', + { + resultType: 'FAIL', + success: null, + error: { + errorCode: 'FOL-400', + reason: '폴더 업데이트 중 오류가 발생했습니다.', + data: {folderId: '1'}, + }, + }, + ) + @Response( + StatusCodes.BAD_REQUEST, + '유효하지 않은 데이터 에러', + { + resultType: 'FAIL', + success: null, + error: { + errorCode: 'FOL-400', + reason: '변경 전의 폴더 이름과 같습니다.', + data: {folderName: 'string'}, + }, + }, + ) + @Response( + StatusCodes.NOT_FOUND, + '존재하지 않은 데이터 조회 에러', + { + resultType: 'FAIL', + error: { + errorCode: 'FOL-404', + reason: '해당 폴더를 찾을 수 없습니다.', + data: { + folderId: '1', + }, + }, + success: null, + }, + ) + @Response(StatusCodes.CONFLICT, '중복 데이터 에러', { + resultType: 'FAIL', + success: null, + error: { + errorCode: 'FOL-409', + reason: '이미 존재하는 폴더 이름입니다.', + data: {folderName: 'string'}, + }, + }) + @SuccessResponse(StatusCodes.OK, '폴더 이름 수정 성공 응답') + @Example({ + resultType: 'SUCCESS', + error: null, + success: { + folderId: '1', + folderName: 'string', + imageText: 'string', + images: [ + { + imageId: '1', + imageUrl: 'string', + }, + ], + }, + }) + public async handlerMemoFolderUpdate( + @Request() req: ExpressRequest, + @Path('folderId') folderIdParam: string, + @Body() body: BodyToMemoFolder, + ): Promise> { + try { + const userId = BigInt(req.user!.id); + const folderId = BigInt(folderIdParam); + const updatedMemoFolder = await memoFolderUpdate( + userId, + folderId, + bodyToMemoFolder(body), + ); + return new TsoaSuccessResponse(updatedMemoFolder); + } catch (error) { + throw error; + } + } + + /** + * 특정 폴더의 메모 텍스트를 수정하는 API입니다. + * + * @summary 특정 폴더의 메모 텍스트 수정 API + * @param req + * @param folderIdParam 폴더 ID + * @param body 메모 텍스트 수정 내용 + * @returns 성공 시 메모 텍스트 수정 결과를 반환합니다. + */ + @Patch('/folders/:folderId/text') + @Tags('memo-folder-controller') + @Response( + StatusCodes.NOT_FOUND, + '존재하지 않은 데이터 조회 에러', + { + resultType: 'FAIL', + success: null, + error: { + errorCode: 'FOL-404', + reason: '해당 폴더를 찾을 수 없습니다.', + data: {folderId: '1'}, + }, + }, + ) + @SuccessResponse(StatusCodes.OK, '메모 텍스트 수정 성공 응답') + @Example({ + resultType: 'SUCCESS', + error: null, + success: { + folderId: '1', + folderName: 'string', + imageText: 'string', + images: [ + { + imageId: '1', + imageUrl: 'string', + }, + ], + }, + }) + public async handlerMemoTextUpdate( + @Request() req: ExpressRequest, + @Path('folderId') folderIdParam: string, + @Body() body: BodyToMemoTextToUpdate, + ): Promise> { + try { + const userId = BigInt(req.user!.id); + const folderId = BigInt(folderIdParam); + const memoTextImageList = await memoTextUpdate( + userId, + folderId, + bodyToMemoTextToUpdate(body), + ); + return new TsoaSuccessResponse(memoTextImageList); + } catch (error) { + throw error; + } + } +} diff --git a/src/controllers/tsoa.memo-image.controller.ts b/src/controllers/tsoa.memo-image.controller.ts new file mode 100644 index 0000000..5cf1375 --- /dev/null +++ b/src/controllers/tsoa.memo-image.controller.ts @@ -0,0 +1,278 @@ +import {Request as ExpressRequest, Express} from 'express'; +import { + memoFolderDelete, + memoImageAdd, + memoImagesMove, +} from '../services/memo-image.service.tsoa.js'; +import {bodyToMemoImagesToMove} from '../dtos/memo-image.dto.tsoa.js'; +import { + Body, + Controller, + Delete, + Patch, + Path, + Request, + Route, + SuccessResponse, + Response, + Tags, + Example, + Post, + UploadedFile, +} from 'tsoa'; +import { + ITsoaErrorResponse, + ITsoaSuccessResponse, + TsoaSuccessResponse, +} from '../models/tsoaResponse.js'; +import { + MemoFolderImageResponseDto, + MemoTextImageListResponseDto, + ResponseMessage, +} from '../models/memo-folder.model.tsoa.js'; +import {StatusCodes} from 'http-status-codes'; +import {BodyToMemoImagesToMove} from '../models/memo-image.model.tsoa.js'; +import {PhotoDataNotFoundError} from '../errors.js'; + +@Route('memo') +export class MemoImageController extends Controller { + /** + * 특정 폴더에 사진을 저장하는 API입니다. + * + * @summary 사진 저장 API + * @param req + * @param targetFolderId 폴더 ID + * @returns 성공 시 사진 저장 결과를 반환합니다. + */ + @Post('/image-format/folders/:folderId') + @Tags('memo-image-controller') + @Response( + StatusCodes.BAD_REQUEST, + '유효하지 않은 데이터 조회 에러', + { + resultType: 'FAIL', + error: { + errorCode: 'MEM-400', + reason: '메모 사진 추가 중 오류가 발생했습니다.', + data: { + folderId: '1', + imageUrl: 'string', + }, + }, + success: null, + }, + ) + @Response( + StatusCodes.BAD_REQUEST, + '유효하지 않은 확장자 에러', + { + resultType: 'FAIL', + error: { + errorCode: 'PHO-400', + reason: '사진 데이터가 유효하지 않습니다.', + data: { + extension: 'string', + }, + }, + success: null, + }, + ) + @Response( + StatusCodes.NOT_FOUND, + '존재하지 않은 데이터 조회 에러', + { + resultType: 'FAIL', + error: { + errorCode: 'FOL-404', + reason: '해당 폴더를 찾을 수 없습니다.', + data: { + folderId: '1', + }, + }, + success: null, + }, + ) + @Response( + StatusCodes.NOT_FOUND, + '존재하지 않은 데이터 조회 에러', + { + resultType: 'FAIL', + error: { + errorCode: 'PHO-404', + reason: '해당 사진 데이터가 없습니다.', + data: { + imageId: '1', + }, + }, + success: null, + }, + ) + @SuccessResponse(StatusCodes.OK, '사진 저장 성공 응답') + @Example({ + resultType: 'SUCCESS', + error: null, + success: { + folderId: '1', + folderName: 'string', + imageId: '1', + imageUrl: 'string', + }, + }) + public async handlerMemoImageAdd( + @Request() req: ExpressRequest, + @Path('folderId') targetFolderId: string, + @UploadedFile() image: Express.MulterS3File, + ): Promise> { + try { + const userId = BigInt(req.user!.id); + const folderId = BigInt(targetFolderId); + if (!image) { + throw new PhotoDataNotFoundError({reason: '저장할 사진이 없습니다.'}); + } + const imageUrl = image.key; + const memoImage = await memoImageAdd(folderId, imageUrl, userId); + return new TsoaSuccessResponse(memoImage); + } catch (error) { + throw error; + } + } + + /** + * 특정 폴더의 사진을 이동하는 API입니다. + * + * @summary 사진 이동 API + * @param req + * @param currentFolderId 현재 폴더 ID + * @param body 이동할 폴더 ID, 이동할 사진 배열 + * @returns 성공 시 사진 이동 결과를 반환합니다. + */ + @Patch('/folders/:folderId/images') + @Tags('memo-image-controller') + @Response( + StatusCodes.BAD_REQUEST, + '유효하지 않은 데이터 조회 에러', + { + resultType: 'FAIL', + error: { + errorCode: 'MEM-400', + reason: '메모 사진 이동 중 오류가 발생했습니다.', + data: { + folderId: '1', + imageId: ['1'], + }, + }, + success: null, + }, + ) + @Response( + StatusCodes.NOT_FOUND, + '존재하지 않은 데이터 조회 에러', + { + resultType: 'FAIL', + error: { + errorCode: 'FOL-404', + reason: '해당 폴더를 찾을 수 없습니다.', + data: { + folderId: '1', + }, + }, + success: null, + }, + ) + @Response( + StatusCodes.NOT_FOUND, + '존재하지 않은 데이터 조회 에러', + { + resultType: 'FAIL', + error: { + errorCode: 'PHO-404', + reason: '해당 사진 데이터가 없습니다.', + data: { + imageId: '1', + }, + }, + success: null, + }, + ) + @SuccessResponse(StatusCodes.OK, '사진 이동 성공 응답') + @Example({ + resultType: 'SUCCESS', + error: null, + success: { + folderId: '1', + folderName: 'string', + imageText: 'string', + images: [ + { + imageId: '1', + imageUrl: 'string', + }, + ], + }, + }) + public async handlerMemoImageMove( + @Request() req: ExpressRequest, + @Path('folderId') currentFolderId: string, + @Body() body: BodyToMemoImagesToMove, + ): Promise> { + try { + const userId = BigInt(req.user!.id); + const folderId = BigInt(currentFolderId); + const memoImagesToMove = await memoImagesMove( + userId, + folderId, + bodyToMemoImagesToMove(body), + ); + return new TsoaSuccessResponse(memoImagesToMove); + } catch (error) { + throw error; + } + } + + /** + * 특정 폴더의 사진을 삭제하는 API입니다. + * + * @summary 폴더 삭제 API + * @param req + * @param targetFolderId 폴더 ID + * @returns 성공 시 폴더 삭제 결과를 반환합니다. + */ + @Delete('/folders/:folderId') + @Tags('memo-folder-controller') + @Response( + StatusCodes.NOT_FOUND, + '존재하지 않은 데이터 조회 에러', + { + resultType: 'FAIL', + error: { + errorCode: 'FOL-404', + reason: '해당 폴더를 찾을 수 없습니다.', + data: { + folderId: '1', + }, + }, + success: null, + }, + ) + @SuccessResponse(StatusCodes.OK, '폴더 삭제 성공 응답') + @Example({ + resultType: 'SUCCESS', + error: null, + success: { + message: 'string', + }, + }) + public async handlerMemoFolderDelete( + @Request() req: ExpressRequest, + @Path('folderId') targetFolderId: string, + ): Promise> { + try { + const userId = BigInt(req.user!.id); + const folderId = BigInt(targetFolderId); + const memoImagesToDelete = await memoFolderDelete(userId, folderId); + return new TsoaSuccessResponse(memoImagesToDelete); + } catch (error) { + throw error; + } + } +} diff --git a/src/dtos/memo-folder.dto.ts b/src/dtos/memo-folder.dto.ts index 006053c..f87caf4 100644 --- a/src/dtos/memo-folder.dto.ts +++ b/src/dtos/memo-folder.dto.ts @@ -1,64 +1,108 @@ -import { BodyToMemoFolder, ResponseMessage, MemoFolderImageResponseDto, MemoFolderListResponseDto, MemoFolderRequestDto, MemoFolderResponseDto, MemoTextImageListResponseDto, ResponseFromMemo, ResponseFromMemoFolder, ResponseFromMemoList, BodyToMemoTextToUpdate } from '../models/memo-folder.model.js'; -import { ResponseFromMemoImage } from '../models/memo-image.model.js'; +import { + BodyToMemoFolder, + ResponseMessage, + MemoFolderImageResponseDto, + MemoFolderListResponseDto, + MemoFolderRequestDto, + MemoFolderResponseDto, + MemoTextImageListResponseDto, + ResponseFromMemo, + ResponseFromMemoFolder, + ResponseFromMemoList, + BodyToMemoTextToUpdate, +} from '../models/memo-folder.model.js'; +import {ResponseFromMemoImage} from '../models/memo-image.model.js'; -export const bodyToMemoFolder = ({ folderName }: BodyToMemoFolder): MemoFolderRequestDto => { - return { - folderName - }; +export const bodyToMemoFolder = ({ + folderName, +}: BodyToMemoFolder): MemoFolderRequestDto => { + return { + folderName, + }; }; -export const responseFromMemoFolder = ({ id, name }: ResponseFromMemoFolder): MemoFolderResponseDto => { - return { - id, - folderName: name - }; +export const responseFromMemoFolder = ({ + id, + name, +}: ResponseFromMemoFolder): MemoFolderResponseDto => { + return { + id, + folderName: name, + }; }; -export const responseFromMemoFolderImage = ({ memoFolder: { id: folderId, name: folderName }, memoImage: { id: imageId, url: imageUrl }}: { - memoFolder: ResponseFromMemoFolder - memoImage: ResponseFromMemoImage +export const responseFromMemoFolderImage = ({ + memoFolder: {id: folderId, name: folderName}, + memoImage: {id: imageId, url: imageUrl}, +}: { + memoFolder: ResponseFromMemoFolder; + memoImage: ResponseFromMemoImage; }): MemoFolderImageResponseDto => { - return { - folderId, - folderName, - imageId, - imageUrl - }; + return { + folderId, + folderName, + imageId, + imageUrl, + }; }; -export const responseFromMemoFolderList = (searchMemoList: ResponseFromMemoList[]):{ data: MemoFolderListResponseDto[] }=> { - return { - data: searchMemoList.map(({ id: folderId, name: folderName, imageCount, imageText, memoImages }) => { - const [firstImage] = memoImages; // memoImages의 첫 번째 항목 구조 분해 - return { - folderId, - folderName, - imageText, - imageCount, - firstImageId: firstImage?.id || null, - firstImageUrl: firstImage?.url || null, - }; - }), - }; +export const responseFromMemoFolderList = ( + searchMemoList: ResponseFromMemoList[], +): {data: MemoFolderListResponseDto[]} => { + return { + data: searchMemoList.map( + ({ + id: folderId, + name: folderName, + imageCount, + imageText, + memoImages, + createdAt, + }) => { + const [firstImage] = memoImages; // memoImages의 첫 번째 항목 구조 분해 + return { + folderId, + folderName, + imageText, + imageCount, + firstImageId: firstImage?.id || null, + firstImageUrl: firstImage?.url || null, + createdAt, + }; + }, + ), + }; }; -export const responseFromMemoTextImageList = ({id, name, imageText, memoImages}: ResponseFromMemo) : MemoTextImageListResponseDto => { - return { - folderId: id, - folderName: name, - imageText, - images: memoImages.length > 0 ? memoImages.map((mi) => ({ imageId: mi.id, imageUrl: mi.url })) : null, - }; +export const responseFromMemoTextImageList = ({ + id, + name, + imageText, + memoImages, +}: ResponseFromMemo): MemoTextImageListResponseDto => { + return { + folderId: id, + folderName: name, + imageText, + images: + memoImages.length > 0 + ? memoImages.map(mi => ({imageId: mi.id, imageUrl: mi.url})) + : null, + }; }; -export const responseFromMessage = ({message} : ResponseMessage) : ResponseMessage => { - return { - message - }; +export const responseFromMessage = ({ + message, +}: ResponseMessage): ResponseMessage => { + return { + message, + }; }; -export const bodyToMemoTextToUpdate = ({memoText} : BodyToMemoTextToUpdate) : BodyToMemoTextToUpdate => { - return { - memoText - }; -}; \ No newline at end of file +export const bodyToMemoTextToUpdate = ({ + memoText, +}: BodyToMemoTextToUpdate): BodyToMemoTextToUpdate => { + return { + memoText, + }; +}; diff --git a/src/dtos/memo-folder.dto.tsoa.ts b/src/dtos/memo-folder.dto.tsoa.ts new file mode 100644 index 0000000..97ae4ea --- /dev/null +++ b/src/dtos/memo-folder.dto.tsoa.ts @@ -0,0 +1,108 @@ +import { + BodyToMemoFolder, + ResponseMessage, + MemoFolderImageResponseDto, + MemoFolderListResponseDto, + MemoFolderRequestDto, + MemoFolderResponseDto, + MemoTextImageListResponseDto, + ResponseFromMemo, + ResponseFromMemoFolder, + ResponseFromMemoList, + BodyToMemoTextToUpdate, +} from '../models/memo-folder.model.tsoa.js'; +import {ResponseFromMemoImage} from '../models/memo-image.model.tsoa.js'; + +export const bodyToMemoFolder = ({ + folderName, +}: BodyToMemoFolder): MemoFolderRequestDto => { + return { + folderName, + }; +}; + +export const responseFromMemoFolder = ({ + id, + name, +}: ResponseFromMemoFolder): MemoFolderResponseDto => { + return { + id, + folderName: name, + }; +}; + +export const responseFromMemoFolderImage = ({ + memoFolder: {id: folderId, name: folderName}, + memoImage: {id: imageId, url: imageUrl}, +}: { + memoFolder: ResponseFromMemoFolder; + memoImage: ResponseFromMemoImage; +}): MemoFolderImageResponseDto => { + return { + folderId, + folderName, + imageId, + imageUrl, + }; +}; + +export const responseFromMemoFolderList = ( + searchMemoList: ResponseFromMemoList[], +): {data: MemoFolderListResponseDto[]} => { + return { + data: searchMemoList.map( + ({ + id: folderId, + name: folderName, + imageCount, + imageText, + memoImages, + createdAt, + }) => { + const [firstImage] = memoImages; // memoImages의 첫 번째 항목 구조 분해 + return { + folderId, + folderName, + imageText, + imageCount, + firstImageId: firstImage?.id || null, + firstImageUrl: firstImage?.url || null, + createdAt, + }; + }, + ), + }; +}; + +export const responseFromMemoTextImageList = ({ + id, + name, + imageText, + memoImages, +}: ResponseFromMemo): MemoTextImageListResponseDto => { + return { + folderId: id, + folderName: name, + imageText, + images: + memoImages.length > 0 + ? memoImages.map(mi => ({imageId: mi.id, imageUrl: mi.url})) + : null, + }; +}; + +export const responseFromMessage = ({ + message, +}: ResponseMessage): ResponseMessage => { + return { + message, + }; +}; + +export const bodyToMemoTextToUpdate = ({ + memoText, +}: BodyToMemoTextToUpdate): BodyToMemoTextToUpdate => { + return { + memoText, + }; +}; diff --git a/src/dtos/memo-image.dto.tsoa.ts b/src/dtos/memo-image.dto.tsoa.ts new file mode 100644 index 0000000..03c0890 --- /dev/null +++ b/src/dtos/memo-image.dto.tsoa.ts @@ -0,0 +1,32 @@ +import { + BodyToMemoImage, + BodyToMemoImagesToDelete, + BodyToMemoImagesToMove, + MemoImageRequestDto, +} from '../models/memo-image.model.tsoa.js'; + +export const bodyToMemoImage = ({ + url, +}: BodyToMemoImage): MemoImageRequestDto => { + return { + url, + }; +}; + +export const bodyToMemoImagesToMove = ({ + targetFolderId, + imageId, +}: BodyToMemoImagesToMove): BodyToMemoImagesToMove => { + return { + targetFolderId, + imageId, + }; +}; + +export const bodyToMemoImagesToDelete = ({ + imageId, +}: BodyToMemoImagesToDelete): BodyToMemoImagesToDelete => { + return { + imageId, + }; +}; diff --git a/src/errors.ts b/src/errors.ts index 54c8be3..0ca6f0a 100644 --- a/src/errors.ts +++ b/src/errors.ts @@ -9,6 +9,7 @@ export type ErrorDetails = | {searchKeyword?: string} | FieldErrors | {extension?: string} + | {folderId?: string; imageId?: string[]} | null; // 기본 에러 클래스 @@ -215,7 +216,11 @@ export class ChallengeAcceptError extends BaseError { // 챌린지 완료 관련 에러 (CHL) export class ChallengeCompleteError extends BaseError { constructor(details: {challengeId: bigint; reason: string}) { - super(400, 'CHL-400', '챌린지 완료 실패', details); + const errorDetails = { + ...details, + challengeId: details.challengeId.toString(), + }; + super(400, 'CHL-400', '챌린지 완료 실패', errorDetails); } } @@ -263,7 +268,7 @@ export class DateChallengeNotFoundError extends BaseError { // 네이버 API 관련 에러 export class NaverGeoCodeError extends BaseError { - constructor(details: {reason: string}){ + constructor(details: {reason: string}) { super(500, 'CHL-500', '네이버 API 호출에 문제가 있습니다.', details); } } diff --git a/src/models/memo-folder.model.ts b/src/models/memo-folder.model.ts index f7d32b5..8520dba 100644 --- a/src/models/memo-folder.model.ts +++ b/src/models/memo-folder.model.ts @@ -1,168 +1,171 @@ export interface BodyToMemoFolder { - folderName: string; + folderName: string; } export interface ResponseFromMemoFolder { - id: string; - userId: bigint; - name: string; - imageText: string; - createdAt: Date; - updatedAt: Date | null; - status: number; + id: string; + userId: bigint; + name: string; + imageText: string; + createdAt: Date; + updatedAt: Date | null; + status: number; } export interface MemoFolderResponseDto { - id: string; - folderName: string; + id: string; + folderName: string; } export interface MemoFolderImageResponseDto { - folderId: string; - folderName: string; - imageId: string; - imageUrl: string; + folderId: string; + folderName: string; + imageId: string; + imageUrl: string; } export interface MemoFolderRequestDto { - folderName: string; + folderName: string; } export interface MemoFolderListResponseDto { - folderId: string ; - folderName: string; - imageText: string; - imageCount: number; - firstImageId: string | null; - firstImageUrl: string | null; + folderId: string; + folderName: string; + imageText: string; + imageCount: number; + firstImageId: string | null; + firstImageUrl: string | null; + createdAt: Date; } export interface MemoTextImageListResponseDto { - folderId: string; - folderName: string; - imageText: string; - images: { + folderId: string; + folderName: string; + imageText: string; + images: + | { imageId: string; imageUrl: string; - }[] | null; + }[] + | null; } export interface ResponseFromMemoFolderImage { - id: string; - userId: bigint; - name: string; - imageText: string; - createdAt: Date; - updatedAt: Date | null; - status: number; + id: string; + userId: bigint; + name: string; + imageText: string; + createdAt: Date; + updatedAt: Date | null; + status: number; } export interface ResponseFromMemoList { + id: string; + userId: bigint; + name: string; + imageText: string; + createdAt: Date; + updatedAt: Date | null; + status: number; + imageCount: number; + memoImages: { id: string; - userId: bigint; - name: string; - imageText: string; createdAt: Date; updatedAt: Date | null; status: number; - imageCount: number; - memoImages: { - id: string; - createdAt: Date; - updatedAt: Date | null; - status: number; - folderId: string; - url: string; - }[]; -}; + folderId: string; + url: string; + }[]; +} export interface ResponseFromMemo { + id: string; + userId: bigint; + name: string; + imageText: string; + createdAt: Date; + updatedAt: Date | null; + status: number; + memoImages: { id: string; - userId: bigint; - name: string; - imageText: string; createdAt: Date; updatedAt: Date | null; status: number; - memoImages: { - id: string; - createdAt: Date; - updatedAt: Date | null; - status: number; - folderId: string; - url: string; - }[]; + folderId: string; + url: string; + }[]; } export interface createdMemoFolderId { - id: bigint; + id: bigint; } export interface MemoFoler { - id: string; - name: string; - imageText: string; - createdAt: Date; - updatedAt: Date | null; - status: number; - userId: bigint; + id: string; + name: string; + imageText: string; + createdAt: Date; + updatedAt: Date | null; + status: number; + userId: bigint; } export interface MemoFolderList { + id: string; + imageCount: number; + memoImages: { id: string; - imageCount: number; - memoImages: { - id: string; - folderId: string; - url: string; - createdAt: Date; - updatedAt: Date | null; - status: number; - }[]; - name: string; - imageText: string; + folderId: string; + url: string; createdAt: Date; updatedAt: Date | null; status: number; - userId: bigint; - _count: { - memoImages: number; - }; + }[]; + name: string; + imageText: string; + createdAt: Date; + updatedAt: Date | null; + status: number; + userId: bigint; + _count: { + memoImages: number; + }; } export interface MemoTextImageList { + id: string; + memoImages: { id: string; - memoImages: { - id: string; - folderId: string; - url: string; - createdAt: Date; - updatedAt: Date | null; - status: number; - }[]; - name: string; - imageText: string; + folderId: string; + url: string; createdAt: Date; updatedAt: Date | null; status: number; - userId: bigint; -}; + }[]; + name: string; + imageText: string; + createdAt: Date; + updatedAt: Date | null; + status: number; + userId: bigint; +} // 추가 export interface MemoFolderType { - name: string; - id: bigint; - imageText: string; - createdAt: Date; - updatedAt: Date | null; - status: number; - userId: bigint; + name: string; + id: bigint; + imageText: string; + createdAt: Date; + updatedAt: Date | null; + status: number; + userId: bigint; } export interface ResponseMessage { - message: string; + message: string; } export interface BodyToMemoTextToUpdate { - memoText: string; -} \ No newline at end of file + memoText: string; +} diff --git a/src/models/memo-folder.model.tsoa.ts b/src/models/memo-folder.model.tsoa.ts new file mode 100644 index 0000000..8520dba --- /dev/null +++ b/src/models/memo-folder.model.tsoa.ts @@ -0,0 +1,171 @@ +export interface BodyToMemoFolder { + folderName: string; +} + +export interface ResponseFromMemoFolder { + id: string; + userId: bigint; + name: string; + imageText: string; + createdAt: Date; + updatedAt: Date | null; + status: number; +} + +export interface MemoFolderResponseDto { + id: string; + folderName: string; +} + +export interface MemoFolderImageResponseDto { + folderId: string; + folderName: string; + imageId: string; + imageUrl: string; +} + +export interface MemoFolderRequestDto { + folderName: string; +} + +export interface MemoFolderListResponseDto { + folderId: string; + folderName: string; + imageText: string; + imageCount: number; + firstImageId: string | null; + firstImageUrl: string | null; + createdAt: Date; +} + +export interface MemoTextImageListResponseDto { + folderId: string; + folderName: string; + imageText: string; + images: + | { + imageId: string; + imageUrl: string; + }[] + | null; +} + +export interface ResponseFromMemoFolderImage { + id: string; + userId: bigint; + name: string; + imageText: string; + createdAt: Date; + updatedAt: Date | null; + status: number; +} + +export interface ResponseFromMemoList { + id: string; + userId: bigint; + name: string; + imageText: string; + createdAt: Date; + updatedAt: Date | null; + status: number; + imageCount: number; + memoImages: { + id: string; + createdAt: Date; + updatedAt: Date | null; + status: number; + folderId: string; + url: string; + }[]; +} + +export interface ResponseFromMemo { + id: string; + userId: bigint; + name: string; + imageText: string; + createdAt: Date; + updatedAt: Date | null; + status: number; + memoImages: { + id: string; + createdAt: Date; + updatedAt: Date | null; + status: number; + folderId: string; + url: string; + }[]; +} + +export interface createdMemoFolderId { + id: bigint; +} + +export interface MemoFoler { + id: string; + name: string; + imageText: string; + createdAt: Date; + updatedAt: Date | null; + status: number; + userId: bigint; +} + +export interface MemoFolderList { + id: string; + imageCount: number; + memoImages: { + id: string; + folderId: string; + url: string; + createdAt: Date; + updatedAt: Date | null; + status: number; + }[]; + name: string; + imageText: string; + createdAt: Date; + updatedAt: Date | null; + status: number; + userId: bigint; + _count: { + memoImages: number; + }; +} + +export interface MemoTextImageList { + id: string; + memoImages: { + id: string; + folderId: string; + url: string; + createdAt: Date; + updatedAt: Date | null; + status: number; + }[]; + name: string; + imageText: string; + createdAt: Date; + updatedAt: Date | null; + status: number; + userId: bigint; +} + +// 추가 +export interface MemoFolderType { + name: string; + id: bigint; + imageText: string; + createdAt: Date; + updatedAt: Date | null; + status: number; + userId: bigint; +} + +export interface ResponseMessage { + message: string; +} + +export interface BodyToMemoTextToUpdate { + memoText: string; +} diff --git a/src/models/memo-image.model.tsoa.ts b/src/models/memo-image.model.tsoa.ts new file mode 100644 index 0000000..6c7a458 --- /dev/null +++ b/src/models/memo-image.model.tsoa.ts @@ -0,0 +1,34 @@ +export interface ResponseFromMemoImage { + id: string; + folderId: string; + url: string; + createdAt: Date; + updatedAt: Date | null; + status: number; +} + +export interface BodyToMemoImage { + url: string; +} + +export interface MemoImageRequestDto { + url: string; +} + +export interface MemoImage { + id: string; + folderId: string; + url: string; + createdAt: Date; + updatedAt: Date | null; + status: number; +} + +export interface BodyToMemoImagesToMove { + targetFolderId: string; + imageId: string[]; +} + +export interface BodyToMemoImagesToDelete { + imageId: string[]; +} diff --git a/src/repositories/memo-folder.repository.tsoa.ts b/src/repositories/memo-folder.repository.tsoa.ts new file mode 100644 index 0000000..f0c661d --- /dev/null +++ b/src/repositories/memo-folder.repository.tsoa.ts @@ -0,0 +1,351 @@ +import {prisma} from '../db.config.js'; +import { + BodyToMemoFolder, + BodyToMemoTextToUpdate, + MemoFolderList, + MemoFolderType, + MemoFoler, + MemoTextImageList, + ResponseMessage, +} from '../models/memo-folder.model.tsoa.js'; +import {getPresignedUrl} from '../s3/get-presigned-url.js'; +import {imageDeleter} from '../s3/image.deleter.js'; + +export const createMemoFolder = async ( + data: BodyToMemoFolder, + userId: bigint, +): Promise => { + const confirmMemoFolder = await prisma.memoFolder.findFirst({ + where: { + name: data.folderName, + userId, + }, + }); + if (confirmMemoFolder) { + return null; + } + const createdMemoFolder = await prisma.memoFolder.create({ + data: { + name: data.folderName, + userId, + imageText: '', + }, + }); + + return createdMemoFolder.id; +}; + +export const getMemoFolder = async ( + memoFolderId: bigint, +): Promise => { + const memoFolder = await prisma.memoFolder.findFirst({ + where: { + id: memoFolderId, + }, + select: { + id: true, + name: true, + imageText: true, + createdAt: true, + updatedAt: true, + status: true, + userId: true, + }, + }); + + if (memoFolder === null) { + return null; + } + + const formattedMemoFolder = { + ...memoFolder, + id: memoFolder.id.toString(), + }; + + return formattedMemoFolder; +}; + +export const getMemoFolderList = async ( + userId: bigint, +): Promise => { + const memoFolderList = await prisma.memoFolder.findMany({ + select: { + id: true, + userId: true, + name: true, + imageText: true, + createdAt: true, + updatedAt: true, + status: true, + _count: { + // 이미지 개수를 세는 필드 추가 + select: { + memoImages: true, // memoImages의 개수를 가져온다. + }, + }, + memoImages: { + take: 1, // 각 폴더에서 첫 번째 사진만 가져오기 + orderBy: { + createdAt: 'asc', // 업로드된 시간순으로 정렬 + }, + }, + }, + where: { + userId, + }, + }); + + const formattedMemoFolderList = await Promise.all( + // 비동기 작업이 완료될 때까지 기다린 후 처리된 결과를 배열로 반환 + memoFolderList.map(async memoFolder => ({ + ...memoFolder, + id: memoFolder.id.toString(), + imageCount: memoFolder._count?.memoImages || 0, // 이미지 개수 추가 + memoImages: await Promise.all( + memoFolder.memoImages.map(async memoImage => { + const presignedUrl = await getPresignedUrl(memoImage.url); + return { + ...memoImage, + id: memoImage.id.toString(), + folderId: memoImage.folderId.toString(), + url: presignedUrl, // Pre-signed URL로 변환 + }; + }), + ), + })), + ); + + return formattedMemoFolderList; +}; + +export const getSearchMemoList = async ( + userId: bigint, + searchKeyword: string, +): Promise => { + const memoSearchList = await prisma.memoFolder.findMany({ + select: { + id: true, + name: true, + imageText: true, + createdAt: true, + updatedAt: true, + status: true, + userId: true, + _count: { + // 이미지 개수를 세는 필드 추가 + select: { + memoImages: true, // memoImages의 개수를 가져온다. + }, + }, + memoImages: { + take: 1, + orderBy: { + createdAt: 'asc', + }, + }, + }, + where: { + // 폴더 이름이나 메모 텍스트에 검색어가 포함된 데이터 필터링 + AND: [ + { + userId, + }, + { + OR: [ + { + name: { + // name 필드에 searchKeyword가 포함된 데이터를 찾는다. + contains: searchKeyword, + }, + }, + { + imageText: { + // imageText 필드에 searchKeyword가 포함된 데이터를 찾는다. + contains: searchKeyword, + }, + }, + ], + }, + ], + }, + }); + + const formattedMemoSearchList = await Promise.all( + memoSearchList.map(async memoSearch => ({ + ...memoSearch, + id: memoSearch.id.toString(), + imageCount: memoSearch._count?.memoImages || 0, // 이미지 개수 추가 + memoImages: await Promise.all( + memoSearch.memoImages.map(async memoImage => { + const presignedUrl = await getPresignedUrl(memoImage.url); + return { + ...memoImage, + id: memoImage.id.toString(), + folderId: memoImage.folderId.toString(), + url: presignedUrl, // Pre-signed URL로 변환 + }; + }), + ), + })), + ); + + return formattedMemoSearchList; +}; + +export const getMemoTextImageList = async ( + userId: bigint, + folderId: bigint, +): Promise => { + const memoTextImageList = await prisma.memoFolder.findFirst({ + select: { + id: true, + userId: true, + name: true, + imageText: true, + createdAt: true, + updatedAt: true, + status: true, + memoImages: { + orderBy: { + createdAt: 'asc', // 업로드된 시간순으로 정렬 + }, + }, + }, + where: { + userId, + id: folderId, + }, + }); + + if (memoTextImageList === null) { + return null; + } + + const formattedMemoTextImageList = { + ...memoTextImageList, + id: memoTextImageList.id.toString(), + memoImages: await Promise.all( + memoTextImageList.memoImages.map(async memoImage => { + const presignedUrl = await getPresignedUrl(memoImage.url); + return { + ...memoImage, + id: memoImage.id.toString(), + folderId: memoImage.folderId.toString(), + url: presignedUrl, + }; + }), + ), + }; + + return formattedMemoTextImageList; +}; + +export const updateMemoFolder = async ( + userId: bigint, + folderId: bigint, + folderName: string, +): Promise => { + const checkFolder = await prisma.memoFolder.findFirst({ + where: { + name: folderName, + userId, + }, + }); + if (checkFolder) { + return null; + } + const folder = await prisma.memoFolder.update({ + where: { + userId, + id: folderId, + }, + data: { + name: folderName, + updatedAt: new Date(), + }, + }); + return folder; +}; + +export const deleteMemoFolder = async ( + userId: bigint, + folderId: bigint, +): Promise => { + const folder = await prisma.memoFolder.findFirst({ + where: { + id: folderId, + userId, + }, + }); + if (folder === null) { + return null; + } + const imageUrlToDelete = await prisma.memoImage.findMany({ + where: { + folderId, + memoFolder: { + userId, + }, + }, + }); + for (const imageUrl of imageUrlToDelete) { + imageDeleter(imageUrl.url); + } + await prisma.memoImage.deleteMany({ + where: { + folderId, + }, + }); + await prisma.memoFolder.delete({ + where: { + id: folderId, + userId, + }, + }); + return {message: '성공적으로 삭제하였습니다.'}; +}; + +export const updateMemoText = async ( + userId: bigint, + folderId: bigint, + data: BodyToMemoTextToUpdate, +): Promise => { + const memoText = await prisma.memoFolder.update({ + where: { + id: folderId, + userId, + }, + data: { + imageText: data.memoText, + updatedAt: new Date(), + }, + select: { + id: true, + userId: true, + name: true, + imageText: true, + createdAt: true, + updatedAt: true, + status: true, + memoImages: true, + }, + }); + + const formattedMemoText = { + ...memoText, + id: memoText.id.toString(), + memoImages: await Promise.all( + memoText.memoImages.map(async memoImage => { + const presignedUrl = await getPresignedUrl(memoImage.url); + return { + ...memoImage, + id: memoImage.id.toString(), + folderId: memoImage.folderId.toString(), + url: presignedUrl, + }; + }), + ), + }; + + return formattedMemoText; +}; diff --git a/src/repositories/memo-image.repository.tsoa.ts b/src/repositories/memo-image.repository.tsoa.ts new file mode 100644 index 0000000..ba34b13 --- /dev/null +++ b/src/repositories/memo-image.repository.tsoa.ts @@ -0,0 +1,148 @@ +import {prisma} from '../db.config.js'; +import {ResponseMessage} from '../models/memo-folder.model.tsoa.js'; +import { + BodyToMemoImagesToDelete, + BodyToMemoImagesToMove, + MemoImage, +} from '../models/memo-image.model.tsoa.js'; +import {getPresignedUrl} from '../s3/get-presigned-url.js'; +import {imageDeleter} from '../s3/image.deleter.js'; +import {imageMover} from '../s3/image.mover.js'; + +export const addMemoImage = async ( + memoFolderId: bigint, + imageUrl: string, +): Promise => { + const addedMemoImageId = await prisma.memoImage.create({ + data: { + folderId: memoFolderId, + url: imageUrl, + }, + }); + + return addedMemoImageId.id; +}; + +export const getMemoImage = async ( + memoImageId: bigint, +): Promise => { + const memoImage = await prisma.memoImage.findFirst({ + where: { + id: memoImageId, + }, + }); + + if (memoImage === null) { + return null; + } + + const presignedUrl = await getPresignedUrl(memoImage.url); // pre-signed URL 생성 + + const formattedMemoImage = { + ...memoImage, + id: memoImage.id.toString(), + folderId: memoImage.folderId.toString(), + url: presignedUrl, // pre-signed URL로 변환 + }; + + return formattedMemoImage; +}; + +export const moveMemoImages = async ( + userId: bigint, + folderId: bigint, + body: BodyToMemoImagesToMove, +): Promise => { + for (const imgId of body.imageId) { + const formattedImgId = BigInt(imgId); + const image = await prisma.memoImage.findFirst({ + where: { + id: formattedImgId, + folderId, + }, + }); + if (image === null) { + return formattedImgId; + } + } + + for (const imgId of body.imageId) { + const formattedImgId = BigInt(imgId); + const image = await prisma.memoImage.update({ + where: { + id: formattedImgId, + folderId, + }, + data: { + folderId: BigInt(body.targetFolderId), + updatedAt: new Date(), + }, + }); + + imageMover(userId, image.url, BigInt(body.targetFolderId)); + } + return {message: '성공적으로 이동하였습니다'}; +}; + +export const deleteMemoImages = async ( + userId: bigint, + folderId: bigint, + data: BodyToMemoImagesToDelete, +): Promise => { + for (const imgId of data.imageId) { + const formattedImgId = BigInt(imgId); + const image = await prisma.memoImage.findFirst({ + where: { + id: formattedImgId, + folderId, + memoFolder: { + userId, + }, + }, + }); + + if (image === null) { + return formattedImgId; + } + } + for (const imgId of data.imageId) { + const formattedImgId = BigInt(imgId); + const image = await prisma.memoImage.findFirst({ + where: { + id: formattedImgId, + folderId, + memoFolder: { + userId, + }, + }, + }); + + if (image === null) { + return null; + } + + imageDeleter(image.url); + + await prisma.memoImage.delete({ + where: { + id: formattedImgId, + }, + }); + } +}; + +export const updateMemoImageUrl = async ( + key: string, + targetKey: string, +): Promise => { + const image = await prisma.memoImage.findFirst({where: {url: key}}); + await prisma.memoImage.update({ + where: { + id: image!.id, + }, + data: { + url: targetKey, + updatedAt: new Date(), + }, + }); +}; diff --git a/src/routers/memo.router.ts b/src/routers/memo.router.ts index 4a2cafa..971fc76 100644 --- a/src/routers/memo.router.ts +++ b/src/routers/memo.router.ts @@ -1,7 +1,11 @@ import express from 'express'; export const memoFolderRouter = express.Router(); -import {handlerMemoFolderDelete, handlerMemoImageAdd, handlerMemoImageMove} from '../controllers/memo-image.controller.js'; +import { + handlerMemoFolderDelete, + handlerMemoImageAdd, + handlerMemoImageMove, +} from '../controllers/memo-image.controller.js'; import {createFolderOCR} from '../controllers/memo-createFolderOCR.Controller.js'; import {updateFolderOCR} from '../controllers/memo-updateFolderOCR.Controller.js'; import { @@ -14,23 +18,40 @@ import { handlerMemoTextImageList, handlerMemoTextUpdate, } from '../controllers/memo-folder.controller.js'; -import { imageUploader } from '../s3/image.uploader.js'; +import {imageUploader} from '../s3/image.uploader.js'; -memoFolderRouter.post('/folders', handlerMemoFolderAdd); -memoFolderRouter.get('/folders/:folderId', handlerMemoTextImageList); -memoFolderRouter.patch('/folders/:folderId', handlerMemoFolderUpdate); +memoFolderRouter.post('/folders/not-tsoa', handlerMemoFolderAdd); +memoFolderRouter.get('/folders/:folderId/not-tsoa', handlerMemoTextImageList); +memoFolderRouter.patch('/folders/:folderId/not-tsoa', handlerMemoFolderUpdate); -memoFolderRouter.post('/image-format/folders', imageUploader.single('image'), handlerMemoFolderImageCreate); -memoFolderRouter.post('/image-format/folders/:folderId', imageUploader.single('image'), handlerMemoImageAdd); +memoFolderRouter.post( + '/image-format/folders/not-tsoa', + imageUploader.single('image'), + handlerMemoFolderImageCreate, +); +memoFolderRouter.post( + '/image-format/folders/:folderId/not-tsoa', + imageUploader.single('image'), + handlerMemoImageAdd, +); -memoFolderRouter.get('/list', handlerMemoFolderList); -memoFolderRouter.get('/search', handlerMemoSearch); +memoFolderRouter.get('/list/not-tsoa', handlerMemoFolderList); +memoFolderRouter.get('/search/not-tsoa', handlerMemoSearch); -memoFolderRouter.patch('/folders/:folderId/images/move', handlerMemoImageMove); -memoFolderRouter.patch('/folders/:folderId/text', handlerMemoTextUpdate); -memoFolderRouter.delete('/folders/:folderId/images', handlerMemoImageDelete); +memoFolderRouter.patch( + '/folders/:folderId/images/move/not-tsoa', + handlerMemoImageMove, +); +memoFolderRouter.patch( + '/folders/:folderId/text/not-tsoa', + handlerMemoTextUpdate, +); +memoFolderRouter.post( + '/folders/:folderId/images/delete/not-tsoa', + handlerMemoImageDelete, +); -memoFolderRouter.delete('/folders/:folderId', handlerMemoFolderDelete); +memoFolderRouter.delete('/folders/:folderId/not-tsoa', handlerMemoFolderDelete); // OCR 요청 처리 추가 memoFolderRouter.post('/text-format/folders', createFolderOCR); // 새 폴더의 OCR 처리 diff --git a/src/routers/tsoaRoutes.ts b/src/routers/tsoaRoutes.ts index 1322955..dad5856 100644 --- a/src/routers/tsoaRoutes.ts +++ b/src/routers/tsoaRoutes.ts @@ -7,7 +7,13 @@ import { fetchMiddlewares, ExpressTemplateService } from '@tsoa/runtime'; import { TagsController } from './../controllers/tsoaTag.controller.js'; // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa import { ImagesController } from './../controllers/tsoaImage.controller.js'; +// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa +import { MemoImageController } from './../controllers/tsoa.memo-image.controller.js'; +// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa +import { MemoFolderController } from './../controllers/tsoa.memo-folder.controller.js'; import type { Request as ExRequest, Response as ExResponse, RequestHandler, Router } from 'express'; +import multer from 'multer'; + @@ -19,7 +25,7 @@ const models: TsoaRoute.Models = { "properties": { "resultType": {"dataType":"string","required":true}, "error": {"dataType":"enum","enums":[null],"required":true}, - "success": {"dataType":"nestedObjectLiteral","nestedProperties":{"data":{"dataType":"nestedObjectLiteral","nestedProperties":{"tags":{"dataType":"array","array":{"dataType":"string"},"required":true}},"required":true}},"required":true}, + "success": {"dataType":"nestedObjectLiteral","nestedProperties":{"tags":{"dataType":"array","array":{"dataType":"string"},"required":true}},"required":true}, }, "additionalProperties": false, }, @@ -33,7 +39,7 @@ const models: TsoaRoute.Models = { // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa "ErrorDetails": { "dataType": "refAlias", - "type": {"dataType":"union","subSchemas":[{"dataType":"nestedObjectLiteral","nestedProperties":{"folderName":{"dataType":"string"},"userId":{"dataType":"string"},"folderId":{"dataType":"string"}}},{"dataType":"nestedObjectLiteral","nestedProperties":{"imageUrl":{"dataType":"string"},"imageId":{"dataType":"string"}}},{"dataType":"nestedObjectLiteral","nestedProperties":{"userId":{"dataType":"string"},"challengeId":{"dataType":"string"}}},{"dataType":"nestedObjectLiteral","nestedProperties":{"longitude":{"dataType":"double"},"latitude":{"dataType":"double"}}},{"dataType":"nestedObjectLiteral","nestedProperties":{"reason":{"dataType":"string"}}},{"dataType":"nestedObjectLiteral","nestedProperties":{"searchKeyword":{"dataType":"string"}}},{"ref":"FieldErrors"},{"dataType":"nestedObjectLiteral","nestedProperties":{"extension":{"dataType":"string"}}},{"dataType":"enum","enums":[null]}],"validators":{}}, + "type": {"dataType":"union","subSchemas":[{"dataType":"nestedObjectLiteral","nestedProperties":{"folderName":{"dataType":"string"},"userId":{"dataType":"string"},"folderId":{"dataType":"string"}}},{"dataType":"nestedObjectLiteral","nestedProperties":{"imageUrl":{"dataType":"string"},"imageId":{"dataType":"string"}}},{"dataType":"nestedObjectLiteral","nestedProperties":{"userId":{"dataType":"string"},"challengeId":{"dataType":"string"}}},{"dataType":"nestedObjectLiteral","nestedProperties":{"longitude":{"dataType":"double"},"latitude":{"dataType":"double"}}},{"dataType":"nestedObjectLiteral","nestedProperties":{"reason":{"dataType":"string"}}},{"dataType":"nestedObjectLiteral","nestedProperties":{"searchKeyword":{"dataType":"string"}}},{"ref":"FieldErrors"},{"dataType":"nestedObjectLiteral","nestedProperties":{"extension":{"dataType":"string"}}},{"dataType":"nestedObjectLiteral","nestedProperties":{"imageId":{"dataType":"array","array":{"dataType":"string"}},"folderId":{"dataType":"string"}}},{"dataType":"enum","enums":[null]}],"validators":{}}, }, // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa "ITsoaErrorResponse": { @@ -51,7 +57,143 @@ const models: TsoaRoute.Models = { "properties": { "resultType": {"dataType":"string","required":true}, "error": {"dataType":"enum","enums":[null],"required":true}, - "success": {"dataType":"nestedObjectLiteral","nestedProperties":{"data":{"dataType":"array","array":{"dataType":"nestedObjectLiteral","nestedProperties":{"mediaId":{"dataType":"string","required":true},"id":{"dataType":"string","required":true}}},"required":true}},"required":true}, + "success": {"dataType":"array","array":{"dataType":"nestedObjectLiteral","nestedProperties":{"mediaId":{"dataType":"string","required":true},"id":{"dataType":"string","required":true}}},"required":true}, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "MemoFolderImageResponseDto": { + "dataType": "refObject", + "properties": { + "folderId": {"dataType":"string","required":true}, + "folderName": {"dataType":"string","required":true}, + "imageId": {"dataType":"string","required":true}, + "imageUrl": {"dataType":"string","required":true}, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "ITsoaSuccessResponse_MemoFolderImageResponseDto_": { + "dataType": "refObject", + "properties": { + "resultType": {"dataType":"string","required":true}, + "error": {"dataType":"enum","enums":[null],"required":true}, + "success": {"ref":"MemoFolderImageResponseDto","required":true}, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "MemoTextImageListResponseDto": { + "dataType": "refObject", + "properties": { + "folderId": {"dataType":"string","required":true}, + "folderName": {"dataType":"string","required":true}, + "imageText": {"dataType":"string","required":true}, + "images": {"dataType":"union","subSchemas":[{"dataType":"array","array":{"dataType":"nestedObjectLiteral","nestedProperties":{"imageUrl":{"dataType":"string","required":true},"imageId":{"dataType":"string","required":true}}}},{"dataType":"enum","enums":[null]}],"required":true}, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "ITsoaSuccessResponse_MemoTextImageListResponseDto_": { + "dataType": "refObject", + "properties": { + "resultType": {"dataType":"string","required":true}, + "error": {"dataType":"enum","enums":[null],"required":true}, + "success": {"ref":"MemoTextImageListResponseDto","required":true}, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "BodyToMemoImagesToMove": { + "dataType": "refObject", + "properties": { + "targetFolderId": {"dataType":"string","required":true}, + "imageId": {"dataType":"array","array":{"dataType":"string"},"required":true}, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "ResponseMessage": { + "dataType": "refObject", + "properties": { + "message": {"dataType":"string","required":true}, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "ITsoaSuccessResponse_ResponseMessage_": { + "dataType": "refObject", + "properties": { + "resultType": {"dataType":"string","required":true}, + "error": {"dataType":"enum","enums":[null],"required":true}, + "success": {"ref":"ResponseMessage","required":true}, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "MemoFolderResponseDto": { + "dataType": "refObject", + "properties": { + "id": {"dataType":"string","required":true}, + "folderName": {"dataType":"string","required":true}, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "ITsoaSuccessResponse_MemoFolderResponseDto_": { + "dataType": "refObject", + "properties": { + "resultType": {"dataType":"string","required":true}, + "error": {"dataType":"enum","enums":[null],"required":true}, + "success": {"ref":"MemoFolderResponseDto","required":true}, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "BodyToMemoFolder": { + "dataType": "refObject", + "properties": { + "folderName": {"dataType":"string","required":true}, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "MemoFolderListResponseDto": { + "dataType": "refObject", + "properties": { + "folderId": {"dataType":"string","required":true}, + "folderName": {"dataType":"string","required":true}, + "imageText": {"dataType":"string","required":true}, + "imageCount": {"dataType":"double","required":true}, + "firstImageId": {"dataType":"union","subSchemas":[{"dataType":"string"},{"dataType":"enum","enums":[null]}],"required":true}, + "firstImageUrl": {"dataType":"union","subSchemas":[{"dataType":"string"},{"dataType":"enum","enums":[null]}],"required":true}, + "createdAt": {"dataType":"datetime","required":true}, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "ITsoaSuccessResponse__data-MemoFolderListResponseDto-Array__": { + "dataType": "refObject", + "properties": { + "resultType": {"dataType":"string","required":true}, + "error": {"dataType":"enum","enums":[null],"required":true}, + "success": {"dataType":"nestedObjectLiteral","nestedProperties":{"data":{"dataType":"array","array":{"dataType":"refObject","ref":"MemoFolderListResponseDto"},"required":true}},"required":true}, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "BodyToMemoImagesToDelete": { + "dataType": "refObject", + "properties": { + "imageId": {"dataType":"array","array":{"dataType":"string"},"required":true}, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "BodyToMemoTextToUpdate": { + "dataType": "refObject", + "properties": { + "memoText": {"dataType":"string","required":true}, }, "additionalProperties": false, }, @@ -64,13 +206,14 @@ const templateService = new ExpressTemplateService(models, {"noImplicitAdditiona -export function RegisterRoutes(app: Router) { +export function RegisterRoutes(app: Router,opts?:{multer?:ReturnType}) { // ########################################################################################################### // NOTE: If you do not see routes for all of your controllers in this file, then you might not have informed tsoa of where to look // Please look into the "controllerPathGlobs" config option described in the readme: https://github.com/lukeautry/tsoa // ########################################################################################################### + const upload = opts?.multer || multer({"limits":{"fileSize":8388608}}); const argsTagsController_getTagListWithDate: Record = { @@ -137,6 +280,364 @@ export function RegisterRoutes(app: Router) { } }); // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + const argsMemoImageController_handlerMemoImageAdd: Record = { + req: {"in":"request","name":"req","required":true,"dataType":"object"}, + targetFolderId: {"in":"path","name":"folderId","required":true,"dataType":"string"}, + image: {"in":"formData","name":"image","required":true,"dataType":"file"}, + }; + app.post('/memo/image-format/folders/:folderId', + upload.fields([ + { + name: "image", + maxCount: 1 + } + ]), + ...(fetchMiddlewares(MemoImageController)), + ...(fetchMiddlewares(MemoImageController.prototype.handlerMemoImageAdd)), + + async function MemoImageController_handlerMemoImageAdd(request: ExRequest, response: ExResponse, next: any) { + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = templateService.getValidatedArgs({ args: argsMemoImageController_handlerMemoImageAdd, request, response }); + + const controller = new MemoImageController(); + + await templateService.apiHandler({ + methodName: 'handlerMemoImageAdd', + controller, + response, + next, + validatedArgs, + successStatus: 200, + }); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + const argsMemoImageController_handlerMemoImageMove: Record = { + req: {"in":"request","name":"req","required":true,"dataType":"object"}, + currentFolderId: {"in":"path","name":"folderId","required":true,"dataType":"string"}, + body: {"in":"body","name":"body","required":true,"ref":"BodyToMemoImagesToMove"}, + }; + app.patch('/memo/folders/:folderId/images', + ...(fetchMiddlewares(MemoImageController)), + ...(fetchMiddlewares(MemoImageController.prototype.handlerMemoImageMove)), + + async function MemoImageController_handlerMemoImageMove(request: ExRequest, response: ExResponse, next: any) { + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = templateService.getValidatedArgs({ args: argsMemoImageController_handlerMemoImageMove, request, response }); + + const controller = new MemoImageController(); + + await templateService.apiHandler({ + methodName: 'handlerMemoImageMove', + controller, + response, + next, + validatedArgs, + successStatus: 200, + }); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + const argsMemoImageController_handlerMemoFolderDelete: Record = { + req: {"in":"request","name":"req","required":true,"dataType":"object"}, + targetFolderId: {"in":"path","name":"folderId","required":true,"dataType":"string"}, + }; + app.delete('/memo/folders/:folderId', + ...(fetchMiddlewares(MemoImageController)), + ...(fetchMiddlewares(MemoImageController.prototype.handlerMemoFolderDelete)), + + async function MemoImageController_handlerMemoFolderDelete(request: ExRequest, response: ExResponse, next: any) { + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = templateService.getValidatedArgs({ args: argsMemoImageController_handlerMemoFolderDelete, request, response }); + + const controller = new MemoImageController(); + + await templateService.apiHandler({ + methodName: 'handlerMemoFolderDelete', + controller, + response, + next, + validatedArgs, + successStatus: 200, + }); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + const argsMemoFolderController_handlerMemoFolderImageAdd: Record = { + req: {"in":"request","name":"req","required":true,"dataType":"object"}, + folderName: {"in":"formData","name":"folderName","required":true,"dataType":"string"}, + image: {"in":"formData","name":"image","required":true,"dataType":"file"}, + }; + app.post('/memo/image-format/folders', + upload.fields([ + { + name: "image", + maxCount: 1 + } + ]), + ...(fetchMiddlewares(MemoFolderController)), + ...(fetchMiddlewares(MemoFolderController.prototype.handlerMemoFolderImageAdd)), + + async function MemoFolderController_handlerMemoFolderImageAdd(request: ExRequest, response: ExResponse, next: any) { + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = templateService.getValidatedArgs({ args: argsMemoFolderController_handlerMemoFolderImageAdd, request, response }); + + const controller = new MemoFolderController(); + + await templateService.apiHandler({ + methodName: 'handlerMemoFolderImageAdd', + controller, + response, + next, + validatedArgs, + successStatus: 200, + }); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + const argsMemoFolderController_handlerMemoFolderAdd: Record = { + req: {"in":"request","name":"req","required":true,"dataType":"object"}, + body: {"in":"body","name":"body","required":true,"ref":"BodyToMemoFolder"}, + }; + app.post('/memo/folders', + ...(fetchMiddlewares(MemoFolderController)), + ...(fetchMiddlewares(MemoFolderController.prototype.handlerMemoFolderAdd)), + + async function MemoFolderController_handlerMemoFolderAdd(request: ExRequest, response: ExResponse, next: any) { + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = templateService.getValidatedArgs({ args: argsMemoFolderController_handlerMemoFolderAdd, request, response }); + + const controller = new MemoFolderController(); + + await templateService.apiHandler({ + methodName: 'handlerMemoFolderAdd', + controller, + response, + next, + validatedArgs, + successStatus: 200, + }); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + const argsMemoFolderController_handlerMemoFolderList: Record = { + req: {"in":"request","name":"req","required":true,"dataType":"object"}, + }; + app.get('/memo/list', + ...(fetchMiddlewares(MemoFolderController)), + ...(fetchMiddlewares(MemoFolderController.prototype.handlerMemoFolderList)), + + async function MemoFolderController_handlerMemoFolderList(request: ExRequest, response: ExResponse, next: any) { + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = templateService.getValidatedArgs({ args: argsMemoFolderController_handlerMemoFolderList, request, response }); + + const controller = new MemoFolderController(); + + await templateService.apiHandler({ + methodName: 'handlerMemoFolderList', + controller, + response, + next, + validatedArgs, + successStatus: 200, + }); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + const argsMemoFolderController_handlerMemoSearch: Record = { + req: {"in":"request","name":"req","required":true,"dataType":"object"}, + keyword: {"in":"query","name":"keyword","required":true,"dataType":"string"}, + }; + app.get('/memo/search', + ...(fetchMiddlewares(MemoFolderController)), + ...(fetchMiddlewares(MemoFolderController.prototype.handlerMemoSearch)), + + async function MemoFolderController_handlerMemoSearch(request: ExRequest, response: ExResponse, next: any) { + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = templateService.getValidatedArgs({ args: argsMemoFolderController_handlerMemoSearch, request, response }); + + const controller = new MemoFolderController(); + + await templateService.apiHandler({ + methodName: 'handlerMemoSearch', + controller, + response, + next, + validatedArgs, + successStatus: 200, + }); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + const argsMemoFolderController_handlerMemoImageDelete: Record = { + req: {"in":"request","name":"req","required":true,"dataType":"object"}, + folderIdParam: {"in":"path","name":"folderId","required":true,"dataType":"string"}, + body: {"in":"body","name":"body","required":true,"ref":"BodyToMemoImagesToDelete"}, + }; + app.post('/memo/folders/:folderId/images/delete', + ...(fetchMiddlewares(MemoFolderController)), + ...(fetchMiddlewares(MemoFolderController.prototype.handlerMemoImageDelete)), + + async function MemoFolderController_handlerMemoImageDelete(request: ExRequest, response: ExResponse, next: any) { + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = templateService.getValidatedArgs({ args: argsMemoFolderController_handlerMemoImageDelete, request, response }); + + const controller = new MemoFolderController(); + + await templateService.apiHandler({ + methodName: 'handlerMemoImageDelete', + controller, + response, + next, + validatedArgs, + successStatus: 200, + }); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + const argsMemoFolderController_handlerMemoTextImageList: Record = { + req: {"in":"request","name":"req","required":true,"dataType":"object"}, + folderIdParam: {"in":"path","name":"folderId","required":true,"dataType":"string"}, + }; + app.get('/memo/folders/:folderId', + ...(fetchMiddlewares(MemoFolderController)), + ...(fetchMiddlewares(MemoFolderController.prototype.handlerMemoTextImageList)), + + async function MemoFolderController_handlerMemoTextImageList(request: ExRequest, response: ExResponse, next: any) { + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = templateService.getValidatedArgs({ args: argsMemoFolderController_handlerMemoTextImageList, request, response }); + + const controller = new MemoFolderController(); + + await templateService.apiHandler({ + methodName: 'handlerMemoTextImageList', + controller, + response, + next, + validatedArgs, + successStatus: 200, + }); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + const argsMemoFolderController_handlerMemoFolderUpdate: Record = { + req: {"in":"request","name":"req","required":true,"dataType":"object"}, + folderIdParam: {"in":"path","name":"folderId","required":true,"dataType":"string"}, + body: {"in":"body","name":"body","required":true,"ref":"BodyToMemoFolder"}, + }; + app.patch('/memo/folders/:folderId', + ...(fetchMiddlewares(MemoFolderController)), + ...(fetchMiddlewares(MemoFolderController.prototype.handlerMemoFolderUpdate)), + + async function MemoFolderController_handlerMemoFolderUpdate(request: ExRequest, response: ExResponse, next: any) { + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = templateService.getValidatedArgs({ args: argsMemoFolderController_handlerMemoFolderUpdate, request, response }); + + const controller = new MemoFolderController(); + + await templateService.apiHandler({ + methodName: 'handlerMemoFolderUpdate', + controller, + response, + next, + validatedArgs, + successStatus: 200, + }); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + const argsMemoFolderController_handlerMemoTextUpdate: Record = { + req: {"in":"request","name":"req","required":true,"dataType":"object"}, + folderIdParam: {"in":"path","name":"folderId","required":true,"dataType":"string"}, + body: {"in":"body","name":"body","required":true,"ref":"BodyToMemoTextToUpdate"}, + }; + app.patch('/memo/folders/:folderId/text', + ...(fetchMiddlewares(MemoFolderController)), + ...(fetchMiddlewares(MemoFolderController.prototype.handlerMemoTextUpdate)), + + async function MemoFolderController_handlerMemoTextUpdate(request: ExRequest, response: ExResponse, next: any) { + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = templateService.getValidatedArgs({ args: argsMemoFolderController_handlerMemoTextUpdate, request, response }); + + const controller = new MemoFolderController(); + + await templateService.apiHandler({ + methodName: 'handlerMemoTextUpdate', + controller, + response, + next, + validatedArgs, + successStatus: 200, + }); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa diff --git a/src/s3/image.mover.ts b/src/s3/image.mover.ts index 4e2e219..5fb3e07 100644 --- a/src/s3/image.mover.ts +++ b/src/s3/image.mover.ts @@ -1,35 +1,40 @@ -import { CopyObjectCommand, DeleteObjectCommand } from '@aws-sdk/client-s3'; -import { s3 } from './awsS3Client.js'; -import { updateMemoImageUrl } from '../repositories/memo-image.repository.js'; +import {CopyObjectCommand, DeleteObjectCommand} from '@aws-sdk/client-s3'; +import {s3} from './awsS3Client.js'; +import {updateMemoImageUrl} from '../repositories/memo-image.repository.js'; +import process from 'process'; // AWS S3에서 특정 이미지의 디렉토리를 변경하는 함수 -export const imageMover = async (userId: bigint, key: string, directory: bigint): Promise =>{ - // 원본 경로 - const bucketName = process.env.AWS_S3_BUCKET_NAME; // S3 버킷 이름 - const copySource = `${bucketName}/${key}`; // S3 복사 작업에서의 원본 객체(버킷이름/객체키) - const encodedCopySource = encodeURIComponent(copySource); +export const imageMover = async ( + userId: bigint, + key: string, + directory: bigint, +): Promise => { + // 원본 경로 + const bucketName = process.env.AWS_S3_BUCKET_NAME; // S3 버킷 이름 + const copySource = `${bucketName}/${key}`; // S3 복사 작업에서의 원본 객체(버킷이름/객체키) + const encodedCopySource = encodeURIComponent(copySource); - // 대상 경로 설정 - const directoryToMove = directory ? `${directory}/` : ''; // 이동할 디렉토리 - const imageOwnerId = userId; // 사용자 ID - const targetKey = `${imageOwnerId}/${directoryToMove}${key.replace(/^[^/]+\/[^/]+\//, '')}`; // 복사될 key(사용자ID/이동디렉토리/uuid_파일이름) + // 대상 경로 설정 + const directoryToMove = directory ? `${directory}/` : ''; // 이동할 디렉토리 + const imageOwnerId = userId; // 사용자 ID + const targetKey = `${imageOwnerId}/${directoryToMove}${key.replace(/^[^/]+\/[^/]+\//, '')}`; // 복사될 key(사용자ID/이동디렉토리/uuid_파일이름) - await updateMemoImageUrl(key, targetKey); - - // S3에서 파일을 복사하기 위한 설정 - const copyParams = { - Bucket: bucketName, // 복사할 대상 버킷 - CopySource: encodedCopySource, // 복사할 원본 파일의 경로 - Key: targetKey, // 복사될 대상 파일의 경로 - }; - const copyCommand = new CopyObjectCommand(copyParams); // 파일 복사 명령 생성 - await s3.send(copyCommand); // 파일 복사 요청 실행 + await updateMemoImageUrl(key, targetKey); - // 삭제할 원본 파일 설정 - const deleteParams = { - Bucket: bucketName, - Key: key, - }; - const deleteCommand = new DeleteObjectCommand(deleteParams); - await s3.send(deleteCommand); -}; \ No newline at end of file + // S3에서 파일을 복사하기 위한 설정 + const copyParams = { + Bucket: bucketName, // 복사할 대상 버킷 + CopySource: encodedCopySource, // 복사할 원본 파일의 경로 + Key: targetKey, // 복사될 대상 파일의 경로 + }; + const copyCommand = new CopyObjectCommand(copyParams); // 파일 복사 명령 생성 + await s3.send(copyCommand); // 파일 복사 요청 실행 + + // 삭제할 원본 파일 설정 + const deleteParams = { + Bucket: bucketName, + Key: key, + }; + const deleteCommand = new DeleteObjectCommand(deleteParams); + await s3.send(deleteCommand); +}; diff --git a/src/services/memo-folder.service.tsoa.ts b/src/services/memo-folder.service.tsoa.ts new file mode 100644 index 0000000..6a4c05a --- /dev/null +++ b/src/services/memo-folder.service.tsoa.ts @@ -0,0 +1,144 @@ +import { + responseFromMemoFolder, + responseFromMemoFolderImage, + responseFromMemoFolderList, + responseFromMemoTextImageList, +} from '../dtos/memo-folder.dto.tsoa.js'; +import { + FolderCreationError, + FolderDuplicateError, + FolderNameNotChangeError, + FolderNotFoundError, + FolderUpdateError, + MemoImageAdditionError, +} from '../errors.js'; +import { + BodyToMemoFolder, + BodyToMemoTextToUpdate, + MemoFolderImageResponseDto, + MemoFolderListResponseDto, + MemoFolderResponseDto, + MemoTextImageListResponseDto, +} from '../models/memo-folder.model.tsoa.js'; +import { + createMemoFolder, + getMemoFolder, + getMemoFolderList, + getMemoTextImageList, + getSearchMemoList, + updateMemoFolder, + updateMemoText, +} from '../repositories/memo-folder.repository.tsoa.js'; +import { + addMemoImage, + getMemoImage, +} from '../repositories/memo-image.repository.tsoa.js'; + +export const memoFolderCreate = async ( + userId: bigint, + body: BodyToMemoFolder, +): Promise => { + const createdMemoFolderId = await createMemoFolder(body, userId); + if (createdMemoFolderId === null) { + throw new FolderDuplicateError({folderName: body.folderName}); + } + const memoFolder = await getMemoFolder(createdMemoFolderId); + if (memoFolder === null) { + throw new FolderCreationError({ + userId: userId, + folderName: body.folderName, + }); + } + return responseFromMemoFolder(memoFolder); +}; + +export const memoFolderImageCreate = async ( + userId: bigint, + folderId: bigint, + imageUrl: string, + folderName: string, +): Promise => { + //const createdMemoFolderId = await createMemoFolder(body, userId); + const addedMemoImageId = await addMemoImage(folderId, imageUrl); + const memoFolder = await getMemoFolder(folderId); + const memoImage = await getMemoImage(addedMemoImageId); + if (memoFolder === null) { + throw new FolderCreationError({ + userId, + folderName, + }); + } + if (memoImage === null) { + throw new MemoImageAdditionError({folderId, imageUrl}); + } + return responseFromMemoFolderImage({memoFolder, memoImage}); +}; + +export const listMemoFolder = async ( + userId: bigint, +): Promise<{data: MemoFolderListResponseDto[]}> => { + const memoFolderList = await getMemoFolderList(userId); + return responseFromMemoFolderList(memoFolderList); +}; + +export const memoSearch = async ( + userId: bigint, + searchKeyword: string, +): Promise<{data: MemoFolderListResponseDto[]}> => { + const searchMemoList = await getSearchMemoList(userId, searchKeyword); + return responseFromMemoFolderList(searchMemoList); +}; + +export const listMemoTextImage = async ( + userId: bigint, + folderId: bigint, +): Promise => { + const memoTextImageList = await getMemoTextImageList(userId, folderId); + if (memoTextImageList === null) { + throw new FolderNotFoundError({folderId}); + } + return responseFromMemoTextImageList(memoTextImageList); +}; + +export const memoFolderUpdate = async ( + userId: bigint, + folderId: bigint, + body: BodyToMemoFolder, +): Promise => { + const currentFolder = await getMemoFolder(folderId); + if (currentFolder === null || currentFolder.userId !== userId) { + throw new FolderNotFoundError({folderId}); + } + if ( + currentFolder?.name === body.folderName && + currentFolder?.userId === userId + ) { + throw new FolderNameNotChangeError({folderName: body.folderName}); + } + const updatedFolder = await updateMemoFolder( + userId, + folderId, + body.folderName, + ); + if (updatedFolder === null) { + throw new FolderDuplicateError({folderName: body.folderName}); + } + const updatedMemoFolder = await getMemoTextImageList(userId, folderId); + if (updatedMemoFolder === null) { + throw new FolderUpdateError({folderId}); + } + return responseFromMemoTextImageList(updatedMemoFolder); +}; + +export const memoTextUpdate = async ( + userId: bigint, + folderId: bigint, + body: BodyToMemoTextToUpdate, +): Promise => { + const folder = await getMemoFolder(folderId); + if (folder === null || folder.userId !== userId) { + throw new FolderNotFoundError({folderId}); + } + const updatedMemoText = await updateMemoText(userId, folderId, body); + return responseFromMemoTextImageList(updatedMemoText); +}; diff --git a/src/services/memo-image.service.tsoa.ts b/src/services/memo-image.service.tsoa.ts new file mode 100644 index 0000000..1435c1f --- /dev/null +++ b/src/services/memo-image.service.tsoa.ts @@ -0,0 +1,124 @@ +import { + responseFromMessage, + responseFromMemoFolderImage, + responseFromMemoTextImageList, +} from '../dtos/memo-folder.dto.tsoa.js'; +import { + FolderNotChangeError, + FolderNotFoundError, + MemoImageAdditionError, + MemoImageMoveError, + PhotoDataNotFoundError, +} from '../errors.js'; +import { + ResponseMessage, + MemoFolderImageResponseDto, + MemoTextImageListResponseDto, +} from '../models/memo-folder.model.tsoa.js'; +import { + BodyToMemoImagesToDelete, + BodyToMemoImagesToMove, +} from '../models/memo-image.model.tsoa.js'; +import { + deleteMemoFolder, + getMemoFolder, + getMemoTextImageList, +} from '../repositories/memo-folder.repository.tsoa.js'; +import { + addMemoImage, + deleteMemoImages, + getMemoImage, + moveMemoImages, +} from '../repositories/memo-image.repository.tsoa.js'; + +export const memoImageAdd = async ( + folderId: bigint, + imageUrl: string, + userId: bigint, +): Promise => { + const folder = await getMemoFolder(folderId); + if (folder === null || folder.userId !== userId) { + throw new FolderNotFoundError({folderId}); + } + const addedMemoImageId = await addMemoImage(folderId, imageUrl); + const memoFolder = await getMemoFolder(folderId); + const memoImage = await getMemoImage(addedMemoImageId); + if (memoFolder === null) { + throw new FolderNotFoundError({folderId}); + } + if (memoImage === null) { + throw new MemoImageAdditionError({folderId, imageUrl}); + } + return responseFromMemoFolderImage({memoFolder, memoImage}); +}; + +export const memoFolderDelete = async ( + userId: bigint, + folderId: bigint, +): Promise => { + const deleteFolder = await deleteMemoFolder(userId, folderId); + if (deleteFolder === null) { + throw new FolderNotFoundError({folderId}); + } + return responseFromMessage(deleteFolder); +}; + +export const memoImagesMove = async ( + userId: bigint, + folderId: bigint, + body: BodyToMemoImagesToMove, +): Promise => { + const currentFolder = await getMemoFolder(folderId); + if (currentFolder === null || currentFolder.userId !== userId) { + throw new FolderNotFoundError({folderId}); + } + const checkTargetFolder = await getMemoFolder(BigInt(body.targetFolderId)); + if (checkTargetFolder === null || checkTargetFolder.userId !== userId) { + throw new FolderNotFoundError({folderId: BigInt(body.targetFolderId)}); + } + if (folderId === BigInt(body.targetFolderId)) { + throw new FolderNotChangeError({folderId}); + } + + const memoImagesToMove = await moveMemoImages(userId, folderId, body); + if ( + typeof memoImagesToMove === 'bigint' || + typeof memoImagesToMove === 'number' + ) { + throw new PhotoDataNotFoundError({imageId: memoImagesToMove.toString()}); + } + const movedMemoImages = await getMemoTextImageList( + userId, + BigInt(body.targetFolderId), + ); + if (movedMemoImages === null) { + throw new MemoImageMoveError({ + folderId, + imageId: body.imageId.map(imgId => BigInt(imgId)), + }); + } + return responseFromMemoTextImageList(movedMemoImages); +}; + +export const memoImageDelete = async ( + userId: bigint, + folderId: bigint, + body: BodyToMemoImagesToDelete, +): Promise => { + const folder = await getMemoFolder(folderId); + if (folder === null || folder.userId !== userId) { + throw new FolderNotFoundError({folderId}); + } + const memoImagesToDelete = await deleteMemoImages(userId, folderId, body); + if ( + typeof memoImagesToDelete === 'bigint' || + typeof memoImagesToDelete === 'number' + ) { + throw new PhotoDataNotFoundError({imageId: memoImagesToDelete.toString()}); + } + const memoFolder = await getMemoTextImageList(userId, folderId); + if (memoFolder === null) { + throw new FolderNotFoundError({folderId}); + } + return responseFromMemoTextImageList(memoFolder); +}; diff --git a/swagger/openapi.json b/swagger/openapi.json index 9f94772..0dfb368 100644 --- a/swagger/openapi.json +++ b/swagger/openapi.json @@ -1707,7 +1707,7 @@ } } }, - "/memo/folders": { + "/memo/folders/not-tsoa": { "post": { "tags": [ "memo-folder-controller" @@ -1858,7 +1858,7 @@ } } }, - "/memo/folders/{folderId}": { + "/memo/folders/{folderId}/not-tsoa": { "get": { "tags": [ "memo-folder-controller" @@ -2310,7 +2310,7 @@ } } }, - "/memo/image-format/folders": { + "/memo/image-format/folders/not-tsoa": { "post": { "tags": [ "memo-folder-controller" @@ -2525,7 +2525,7 @@ } } }, - "/memo/image-format/folders/{folderId}": { + "/memo/image-format/folders/{folderId}/not-tsoa": { "post": { "tags": [ "memo-image-controller" @@ -2738,7 +2738,7 @@ } } }, - "/memo/list": { + "/memo/list/not-tsoa": { "get": { "tags": [ "memo-folder-controller" @@ -2808,7 +2808,7 @@ } } }, - "/memo/search": { + "/memo/search/not-tsoa": { "get": { "tags": [ "memo-folder-controller" @@ -2932,7 +2932,7 @@ } } }, - "/memo/folders/{folderId}/images/move": { + "/memo/folders/{folderId}/images/move/not-tsoa": { "patch": { "tags": [ "memo-image-controller" @@ -3145,7 +3145,7 @@ } } }, - "/memo/folders/{folderId}/text": { + "/memo/folders/{folderId}/text/not-tsoa": { "patch": { "tags": [ "memo-folder-controller" @@ -3281,8 +3281,8 @@ } } }, - "/memo/folders/{folderId}/images": { - "delete": { + "/memo/folders/{folderId}/images/delete/not-tsoa": { + "post": { "tags": [ "memo-image-controller" ], @@ -3839,19 +3839,9 @@ } } }, - "/challenge/get/{userId}": { + "/challenge/get": { "get": { "description": "", - "parameters": [ - { - "name": "userId", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], "responses": { "default": { "description": "" @@ -4319,6 +4309,811 @@ } } }, + "/challenge/getGeocode": { + "get": { + "description": "", + "parameters": [ + { + "name": "hashedLocation", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "default": { + "description": "" + } + } + } + }, + "/user/mypage/": { + "get": { + "description": "", + "responses": { + "default": { + "description": "" + } + } + }, + "delete": { + "description": "", + "responses": { + "204": { + "description": "No Content" + } + } + } + }, + "/user/mypage/name": { + "patch": { + "tags": [ + "User" + ], + "summary": "사용자 이름 변경 API", + "description": "로그인한 사용자의 이름을 변경하는 API입니다.", + "responses": { + "200": { + "description": "사용자 이름 변경 성공", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "type": { + "type": "string", + "example": "object" + }, + "properties": { + "type": "object", + "properties": { + "resultType": { + "type": "object", + "properties": { + "type": { + "type": "string", + "example": "string" + }, + "example": { + "type": "string", + "example": "SUCCESS" + } + } + }, + "error": { + "type": "object", + "properties": { + "type": { + "type": "string", + "example": "object" + }, + "nullable": { + "type": "boolean", + "example": true + }, + "example": {} + } + }, + "success": { + "type": "object", + "properties": { + "type": { + "type": "string", + "example": "object" + }, + "properties": { + "type": "object", + "properties": { + "id": { + "type": "object", + "properties": { + "type": { + "type": "string", + "example": "integer" + }, + "example": { + "type": "number", + "example": 1 + } + } + }, + "name": { + "type": "object", + "properties": { + "type": { + "type": "string", + "example": "string" + }, + "example": { + "type": "string", + "example": "John Doe" + } + } + }, + "updatedAt": { + "type": "object", + "properties": { + "type": { + "type": "string", + "example": "string" + }, + "format": { + "type": "string", + "example": "date-time" + } + } + } + } + } + } + } + } + } + }, + "xml": { + "name": "main" + } + } + }, + "application/xml": { + "schema": { + "type": "object", + "properties": { + "type": { + "type": "string", + "example": "object" + }, + "properties": { + "type": "object", + "properties": { + "resultType": { + "type": "object", + "properties": { + "type": { + "type": "string", + "example": "string" + }, + "example": { + "type": "string", + "example": "SUCCESS" + } + } + }, + "error": { + "type": "object", + "properties": { + "type": { + "type": "string", + "example": "object" + }, + "nullable": { + "type": "boolean", + "example": true + }, + "example": {} + } + }, + "success": { + "type": "object", + "properties": { + "type": { + "type": "string", + "example": "object" + }, + "properties": { + "type": "object", + "properties": { + "id": { + "type": "object", + "properties": { + "type": { + "type": "string", + "example": "integer" + }, + "example": { + "type": "number", + "example": 1 + } + } + }, + "name": { + "type": "object", + "properties": { + "type": { + "type": "string", + "example": "string" + }, + "example": { + "type": "string", + "example": "John Doe" + } + } + }, + "updatedAt": { + "type": "object", + "properties": { + "type": { + "type": "string", + "example": "string" + }, + "format": { + "type": "string", + "example": "date-time" + } + } + } + } + } + } + } + } + } + }, + "xml": { + "name": "main" + } + } + } + } + }, + "400": { + "description": "잘못된 요청", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "type": { + "type": "string", + "example": "object" + }, + "properties": { + "type": "object", + "properties": { + "resultType": { + "type": "object", + "properties": { + "type": { + "type": "string", + "example": "string" + }, + "example": { + "type": "string", + "example": "FAILURE" + } + } + }, + "error": { + "type": "object", + "properties": { + "type": { + "type": "string", + "example": "object" + }, + "properties": { + "type": "object", + "properties": { + "reason": { + "type": "object", + "properties": { + "type": { + "type": "string", + "example": "string" + }, + "example": { + "type": "string", + "example": "Name is required." + } + } + } + } + } + } + }, + "success": { + "type": "object", + "properties": { + "type": { + "type": "string", + "example": "object" + }, + "nullable": { + "type": "boolean", + "example": true + }, + "example": {} + } + } + } + } + }, + "xml": { + "name": "main" + } + } + }, + "application/xml": { + "schema": { + "type": "object", + "properties": { + "type": { + "type": "string", + "example": "object" + }, + "properties": { + "type": "object", + "properties": { + "resultType": { + "type": "object", + "properties": { + "type": { + "type": "string", + "example": "string" + }, + "example": { + "type": "string", + "example": "FAILURE" + } + } + }, + "error": { + "type": "object", + "properties": { + "type": { + "type": "string", + "example": "object" + }, + "properties": { + "type": "object", + "properties": { + "reason": { + "type": "object", + "properties": { + "type": { + "type": "string", + "example": "string" + }, + "example": { + "type": "string", + "example": "Name is required." + } + } + } + } + } + } + }, + "success": { + "type": "object", + "properties": { + "type": { + "type": "string", + "example": "object" + }, + "nullable": { + "type": "boolean", + "example": true + }, + "example": {} + } + } + } + } + }, + "xml": { + "name": "main" + } + } + } + } + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "변경할 사용자 이름" + } + }, + "required": [ + "name" + ] + } + } + } + } + } + }, + "/user/mypage/goal": { + "patch": { + "tags": [ + "User" + ], + "summary": "사용자 목표 장수 변경 API", + "description": "로그인한 사용자의 목표 장수를 변경하는 API입니다.", + "responses": { + "200": { + "description": "목표 장수 변경 성공", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "type": { + "type": "string", + "example": "object" + }, + "properties": { + "type": "object", + "properties": { + "resultType": { + "type": "object", + "properties": { + "type": { + "type": "string", + "example": "string" + }, + "example": { + "type": "string", + "example": "SUCCESS" + } + } + }, + "error": { + "type": "object", + "properties": { + "type": { + "type": "string", + "example": "object" + }, + "nullable": { + "type": "boolean", + "example": true + }, + "example": {} + } + }, + "success": { + "type": "object", + "properties": { + "type": { + "type": "string", + "example": "object" + }, + "properties": { + "type": "object", + "properties": { + "id": { + "type": "object", + "properties": { + "type": { + "type": "string", + "example": "integer" + }, + "example": { + "type": "number", + "example": 1 + } + } + }, + "goalCount": { + "type": "object", + "properties": { + "type": { + "type": "string", + "example": "integer" + }, + "example": { + "type": "number", + "example": 5 + } + } + }, + "updatedAt": { + "type": "object", + "properties": { + "type": { + "type": "string", + "example": "string" + }, + "format": { + "type": "string", + "example": "date-time" + } + } + } + } + } + } + } + } + } + }, + "xml": { + "name": "main" + } + } + }, + "application/xml": { + "schema": { + "type": "object", + "properties": { + "type": { + "type": "string", + "example": "object" + }, + "properties": { + "type": "object", + "properties": { + "resultType": { + "type": "object", + "properties": { + "type": { + "type": "string", + "example": "string" + }, + "example": { + "type": "string", + "example": "SUCCESS" + } + } + }, + "error": { + "type": "object", + "properties": { + "type": { + "type": "string", + "example": "object" + }, + "nullable": { + "type": "boolean", + "example": true + }, + "example": {} + } + }, + "success": { + "type": "object", + "properties": { + "type": { + "type": "string", + "example": "object" + }, + "properties": { + "type": "object", + "properties": { + "id": { + "type": "object", + "properties": { + "type": { + "type": "string", + "example": "integer" + }, + "example": { + "type": "number", + "example": 1 + } + } + }, + "goalCount": { + "type": "object", + "properties": { + "type": { + "type": "string", + "example": "integer" + }, + "example": { + "type": "number", + "example": 5 + } + } + }, + "updatedAt": { + "type": "object", + "properties": { + "type": { + "type": "string", + "example": "string" + }, + "format": { + "type": "string", + "example": "date-time" + } + } + } + } + } + } + } + } + } + }, + "xml": { + "name": "main" + } + } + } + } + }, + "400": { + "description": "잘못된 요청", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "type": { + "type": "string", + "example": "object" + }, + "properties": { + "type": "object", + "properties": { + "resultType": { + "type": "object", + "properties": { + "type": { + "type": "string", + "example": "string" + }, + "example": { + "type": "string", + "example": "FAILURE" + } + } + }, + "error": { + "type": "object", + "properties": { + "type": { + "type": "string", + "example": "object" + }, + "properties": { + "type": "object", + "properties": { + "reason": { + "type": "object", + "properties": { + "type": { + "type": "string", + "example": "string" + }, + "example": { + "type": "string", + "example": "goal_count is required." + } + } + } + } + } + } + }, + "success": { + "type": "object", + "properties": { + "type": { + "type": "string", + "example": "object" + }, + "nullable": { + "type": "boolean", + "example": true + }, + "example": {} + } + } + } + } + }, + "xml": { + "name": "main" + } + } + }, + "application/xml": { + "schema": { + "type": "object", + "properties": { + "type": { + "type": "string", + "example": "object" + }, + "properties": { + "type": "object", + "properties": { + "resultType": { + "type": "object", + "properties": { + "type": { + "type": "string", + "example": "string" + }, + "example": { + "type": "string", + "example": "FAILURE" + } + } + }, + "error": { + "type": "object", + "properties": { + "type": { + "type": "string", + "example": "object" + }, + "properties": { + "type": "object", + "properties": { + "reason": { + "type": "object", + "properties": { + "type": { + "type": "string", + "example": "string" + }, + "example": { + "type": "string", + "example": "goal_count is required." + } + } + } + } + } + } + }, + "success": { + "type": "object", + "properties": { + "type": { + "type": "string", + "example": "object" + }, + "nullable": { + "type": "boolean", + "example": true + }, + "example": {} + } + } + } + } + }, + "xml": { + "name": "main" + } + } + } + } + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "goalCount": { + "type": "integer", + "description": "변경할 목표 장수" + } + }, + "required": [ + "goalCount" + ] + } + } + } + } + } + }, "/tag/": { "post": { "tags": [ diff --git a/swagger/swagger.json b/swagger/swagger.json index 8e52e76..7c5db81 100644 --- a/swagger/swagger.json +++ b/swagger/swagger.json @@ -21,23 +21,15 @@ }, "success": { "properties": { - "data": { - "properties": { - "tags": { - "items": { - "type": "string" - }, - "type": "array" - } + "tags": { + "items": { + "type": "string" }, - "required": [ - "tags" - ], - "type": "object" + "type": "array" } }, "required": [ - "data" + "tags" ], "type": "object" } @@ -143,6 +135,20 @@ } }, "type": "object" + }, + { + "properties": { + "imageId": { + "items": { + "type": "string" + }, + "type": "array" + }, + "folderId": { + "type": "string" + } + }, + "type": "object" } ], "nullable": true @@ -188,6 +194,304 @@ "additionalProperties": false }, "ITsoaSuccessResponse__id-string--mediaId-string_-Array_": { + "properties": { + "resultType": { + "type": "string" + }, + "error": { + "type": "number", + "enum": [ + null + ], + "nullable": true + }, + "success": { + "items": { + "properties": { + "mediaId": { + "type": "string" + }, + "id": { + "type": "string" + } + }, + "required": [ + "mediaId", + "id" + ], + "type": "object" + }, + "type": "array" + } + }, + "required": [ + "resultType", + "error", + "success" + ], + "type": "object", + "additionalProperties": false + }, + "MemoFolderImageResponseDto": { + "properties": { + "folderId": { + "type": "string" + }, + "folderName": { + "type": "string" + }, + "imageId": { + "type": "string" + }, + "imageUrl": { + "type": "string" + } + }, + "required": [ + "folderId", + "folderName", + "imageId", + "imageUrl" + ], + "type": "object", + "additionalProperties": false + }, + "ITsoaSuccessResponse_MemoFolderImageResponseDto_": { + "properties": { + "resultType": { + "type": "string" + }, + "error": { + "type": "number", + "enum": [ + null + ], + "nullable": true + }, + "success": { + "$ref": "#/components/schemas/MemoFolderImageResponseDto" + } + }, + "required": [ + "resultType", + "error", + "success" + ], + "type": "object", + "additionalProperties": false + }, + "MemoTextImageListResponseDto": { + "properties": { + "folderId": { + "type": "string" + }, + "folderName": { + "type": "string" + }, + "imageText": { + "type": "string" + }, + "images": { + "items": { + "properties": { + "imageUrl": { + "type": "string" + }, + "imageId": { + "type": "string" + } + }, + "required": [ + "imageUrl", + "imageId" + ], + "type": "object" + }, + "type": "array", + "nullable": true + } + }, + "required": [ + "folderId", + "folderName", + "imageText", + "images" + ], + "type": "object", + "additionalProperties": false + }, + "ITsoaSuccessResponse_MemoTextImageListResponseDto_": { + "properties": { + "resultType": { + "type": "string" + }, + "error": { + "type": "number", + "enum": [ + null + ], + "nullable": true + }, + "success": { + "$ref": "#/components/schemas/MemoTextImageListResponseDto" + } + }, + "required": [ + "resultType", + "error", + "success" + ], + "type": "object", + "additionalProperties": false + }, + "BodyToMemoImagesToMove": { + "properties": { + "targetFolderId": { + "type": "string" + }, + "imageId": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "targetFolderId", + "imageId" + ], + "type": "object", + "additionalProperties": false + }, + "ResponseMessage": { + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ], + "type": "object", + "additionalProperties": false + }, + "ITsoaSuccessResponse_ResponseMessage_": { + "properties": { + "resultType": { + "type": "string" + }, + "error": { + "type": "number", + "enum": [ + null + ], + "nullable": true + }, + "success": { + "$ref": "#/components/schemas/ResponseMessage" + } + }, + "required": [ + "resultType", + "error", + "success" + ], + "type": "object", + "additionalProperties": false + }, + "MemoFolderResponseDto": { + "properties": { + "id": { + "type": "string" + }, + "folderName": { + "type": "string" + } + }, + "required": [ + "id", + "folderName" + ], + "type": "object", + "additionalProperties": false + }, + "ITsoaSuccessResponse_MemoFolderResponseDto_": { + "properties": { + "resultType": { + "type": "string" + }, + "error": { + "type": "number", + "enum": [ + null + ], + "nullable": true + }, + "success": { + "$ref": "#/components/schemas/MemoFolderResponseDto" + } + }, + "required": [ + "resultType", + "error", + "success" + ], + "type": "object", + "additionalProperties": false + }, + "BodyToMemoFolder": { + "properties": { + "folderName": { + "type": "string" + } + }, + "required": [ + "folderName" + ], + "type": "object", + "additionalProperties": false + }, + "MemoFolderListResponseDto": { + "properties": { + "folderId": { + "type": "string" + }, + "folderName": { + "type": "string" + }, + "imageText": { + "type": "string" + }, + "imageCount": { + "type": "number", + "format": "double" + }, + "firstImageId": { + "type": "string", + "nullable": true + }, + "firstImageUrl": { + "type": "string", + "nullable": true + }, + "createdAt": { + "type": "string", + "format": "date-time" + } + }, + "required": [ + "folderId", + "folderName", + "imageText", + "imageCount", + "firstImageId", + "firstImageUrl", + "createdAt" + ], + "type": "object", + "additionalProperties": false + }, + "ITsoaSuccessResponse__data-MemoFolderListResponseDto-Array__": { "properties": { "resultType": { "type": "string" @@ -203,19 +507,7 @@ "properties": { "data": { "items": { - "properties": { - "mediaId": { - "type": "string" - }, - "id": { - "type": "string" - } - }, - "required": [ - "mediaId", - "id" - ], - "type": "object" + "$ref": "#/components/schemas/MemoFolderListResponseDto" }, "type": "array" } @@ -233,6 +525,33 @@ ], "type": "object", "additionalProperties": false + }, + "BodyToMemoImagesToDelete": { + "properties": { + "imageId": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "imageId" + ], + "type": "object", + "additionalProperties": false + }, + "BodyToMemoTextToUpdate": { + "properties": { + "memoText": { + "type": "string" + } + }, + "required": [ + "memoText" + ], + "type": "object", + "additionalProperties": false } }, "securitySchemes": { @@ -510,6 +829,1149 @@ } ] } + }, + "/memo/image-format/folders/{folderId}": { + "post": { + "operationId": "HandlerMemoImageAdd", + "responses": { + "200": { + "description": "사진 저장 성공 응답", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ITsoaSuccessResponse_MemoFolderImageResponseDto_" + }, + "examples": { + "Example 1": { + "value": { + "resultType": "SUCCESS", + "error": null, + "success": { + "folderId": "1", + "folderName": "string", + "imageId": "1", + "imageUrl": "string" + } + } + } + } + } + } + }, + "400": { + "description": "유효하지 않은 확장자 에러", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ITsoaErrorResponse" + }, + "examples": { + "Example 1": { + "value": { + "resultType": "FAIL", + "error": { + "errorCode": "MEM-400", + "reason": "메모 사진 추가 중 오류가 발생했습니다.", + "data": { + "folderId": "1", + "imageUrl": "string" + } + }, + "success": null + } + }, + "Example 2": { + "value": { + "resultType": "FAIL", + "error": { + "errorCode": "PHO-400", + "reason": "사진 데이터가 유효하지 않습니다.", + "data": { + "extension": "string" + } + }, + "success": null + } + } + } + } + } + }, + "404": { + "description": "존재하지 않은 데이터 조회 에러", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ITsoaErrorResponse" + }, + "examples": { + "Example 1": { + "value": { + "resultType": "FAIL", + "error": { + "errorCode": "FOL-404", + "reason": "해당 폴더를 찾을 수 없습니다.", + "data": { + "folderId": "1" + } + }, + "success": null + } + }, + "Example 2": { + "value": { + "resultType": "FAIL", + "error": { + "errorCode": "PHO-404", + "reason": "해당 사진 데이터가 없습니다.", + "data": { + "imageId": "1" + } + }, + "success": null + } + } + } + } + } + } + }, + "description": "특정 폴더에 사진을 저장하는 API입니다.", + "summary": "사진 저장 API", + "tags": [ + "memo-image-controller" + ], + "security": [], + "parameters": [ + { + "description": "폴더 ID", + "in": "path", + "name": "folderId", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "multipart/form-data": { + "schema": { + "type": "object", + "properties": { + "image": { + "type": "string", + "format": "binary" + } + }, + "required": [ + "image" + ] + } + } + } + } + } + }, + "/memo/folders/{folderId}/images": { + "patch": { + "operationId": "HandlerMemoImageMove", + "responses": { + "200": { + "description": "사진 이동 성공 응답", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ITsoaSuccessResponse_MemoTextImageListResponseDto_" + }, + "examples": { + "Example 1": { + "value": { + "resultType": "SUCCESS", + "error": null, + "success": { + "folderId": "1", + "folderName": "string", + "imageText": "string", + "images": [ + { + "imageId": "1", + "imageUrl": "string" + } + ] + } + } + } + } + } + } + }, + "400": { + "description": "유효하지 않은 데이터 조회 에러", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ITsoaErrorResponse" + }, + "examples": { + "Example 1": { + "value": { + "resultType": "FAIL", + "error": { + "errorCode": "MEM-400", + "reason": "메모 사진 이동 중 오류가 발생했습니다.", + "data": { + "folderId": "1", + "imageId": [ + "1" + ] + } + }, + "success": null + } + } + } + } + } + }, + "404": { + "description": "존재하지 않은 데이터 조회 에러", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ITsoaErrorResponse" + }, + "examples": { + "Example 1": { + "value": { + "resultType": "FAIL", + "error": { + "errorCode": "FOL-404", + "reason": "해당 폴더를 찾을 수 없습니다.", + "data": { + "folderId": "1" + } + }, + "success": null + } + }, + "Example 2": { + "value": { + "resultType": "FAIL", + "error": { + "errorCode": "PHO-404", + "reason": "해당 사진 데이터가 없습니다.", + "data": { + "imageId": "1" + } + }, + "success": null + } + } + } + } + } + } + }, + "description": "특정 폴더의 사진을 이동하는 API입니다.", + "summary": "사진 이동 API", + "tags": [ + "memo-image-controller" + ], + "security": [], + "parameters": [ + { + "description": "현재 폴더 ID", + "in": "path", + "name": "folderId", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "description": "이동할 폴더 ID, 이동할 사진 배열", + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BodyToMemoImagesToMove", + "description": "이동할 폴더 ID, 이동할 사진 배열" + } + } + } + } + } + }, + "/memo/folders/{folderId}": { + "delete": { + "operationId": "HandlerMemoFolderDelete", + "responses": { + "200": { + "description": "폴더 삭제 성공 응답", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ITsoaSuccessResponse_ResponseMessage_" + }, + "examples": { + "Example 1": { + "value": { + "resultType": "SUCCESS", + "error": null, + "success": { + "message": "string" + } + } + } + } + } + } + }, + "404": { + "description": "존재하지 않은 데이터 조회 에러", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ITsoaErrorResponse" + }, + "examples": { + "Example 1": { + "value": { + "resultType": "FAIL", + "error": { + "errorCode": "FOL-404", + "reason": "해당 폴더를 찾을 수 없습니다.", + "data": { + "folderId": "1" + } + }, + "success": null + } + } + } + } + } + } + }, + "description": "특정 폴더의 사진을 삭제하는 API입니다.", + "summary": "폴더 삭제 API", + "tags": [ + "memo-folder-controller" + ], + "security": [], + "parameters": [ + { + "description": "폴더 ID", + "in": "path", + "name": "folderId", + "required": true, + "schema": { + "type": "string" + } + } + ] + }, + "get": { + "operationId": "HandlerMemoTextImageList", + "responses": { + "200": { + "description": "메모 조회 성공 응답", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ITsoaSuccessResponse_MemoTextImageListResponseDto_" + }, + "examples": { + "Example 1": { + "value": { + "resultType": "SUCCESS", + "error": null, + "success": { + "folderId": "1", + "folderName": "string", + "imageText": "string", + "images": [ + { + "imageId": "1", + "imageUrl": "string" + } + ] + } + } + } + } + } + } + }, + "404": { + "description": "존재하지 않은 데이터 조회 에러", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ITsoaErrorResponse" + }, + "examples": { + "Example 1": { + "value": { + "resultType": "FAIL", + "error": { + "errorCode": "FOL-404", + "reason": "해당 폴더를 찾을 수 없습니다.", + "data": { + "folderId": "1" + } + }, + "success": null + } + } + } + } + } + } + }, + "description": "특정 폴더의 모든 메모(텍스트 및 사진)을 조회하는 API입니다.", + "summary": "특정 폴더의 메모 조회 API", + "tags": [ + "memo-folder-controller" + ], + "security": [], + "parameters": [ + { + "description": "폴더 ID", + "in": "path", + "name": "folderId", + "required": true, + "schema": { + "type": "string" + } + } + ] + }, + "patch": { + "operationId": "HandlerMemoFolderUpdate", + "responses": { + "200": { + "description": "폴더 이름 수정 성공 응답", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ITsoaSuccessResponse_MemoTextImageListResponseDto_" + }, + "examples": { + "Example 1": { + "value": { + "resultType": "SUCCESS", + "error": null, + "success": { + "folderId": "1", + "folderName": "string", + "imageText": "string", + "images": [ + { + "imageId": "1", + "imageUrl": "string" + } + ] + } + } + } + } + } + } + }, + "400": { + "description": "유효하지 않은 데이터 에러", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ITsoaErrorResponse" + }, + "examples": { + "Example 1": { + "value": { + "resultType": "FAIL", + "success": null, + "error": { + "errorCode": "FOL-400", + "reason": "폴더 업데이트 중 오류가 발생했습니다.", + "data": { + "folderId": "1" + } + } + } + }, + "Example 2": { + "value": { + "resultType": "FAIL", + "success": null, + "error": { + "errorCode": "FOL-400", + "reason": "변경 전의 폴더 이름과 같습니다.", + "data": { + "folderName": "string" + } + } + } + } + } + } + } + }, + "404": { + "description": "존재하지 않은 데이터 조회 에러", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ITsoaErrorResponse" + }, + "examples": { + "Example 1": { + "value": { + "resultType": "FAIL", + "error": { + "errorCode": "FOL-404", + "reason": "해당 폴더를 찾을 수 없습니다.", + "data": { + "folderId": "1" + } + }, + "success": null + } + } + } + } + } + }, + "409": { + "description": "중복 데이터 에러", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ITsoaErrorResponse" + }, + "examples": { + "Example 1": { + "value": { + "resultType": "FAIL", + "success": null, + "error": { + "errorCode": "FOL-409", + "reason": "이미 존재하는 폴더 이름입니다.", + "data": { + "folderName": "string" + } + } + } + } + } + } + } + } + }, + "description": "특정 폴더의 이름을 수정하는 API입니다.", + "summary": "메모 폴더 이름 수정 API", + "tags": [ + "memo-folder-controller" + ], + "security": [], + "parameters": [ + { + "description": "폴더 ID", + "in": "path", + "name": "folderId", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "description": "폴더 이름 수정 내용", + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BodyToMemoFolder", + "description": "폴더 이름 수정 내용" + } + } + } + } + } + }, + "/memo/image-format/folders": { + "post": { + "operationId": "HandlerMemoFolderImageAdd", + "responses": { + "200": { + "description": "폴더 생성 및 사진 저장 성공 응답", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ITsoaSuccessResponse_MemoFolderImageResponseDto_" + }, + "examples": { + "Example 1": { + "value": { + "resultType": "SUCCESS", + "error": null, + "success": { + "folderId": "1", + "folderName": "string", + "imageId": "1", + "imageUrl": "string" + } + } + } + } + } + } + }, + "400": { + "description": "유효하지 않은 데이터 에러", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ITsoaErrorResponse" + }, + "examples": { + "Example 1": { + "value": { + "resultType": "FAIL", + "success": null, + "error": { + "errorCode": "FOL-400", + "reason": "폴더 생성 중 오류가 발생했습니다.", + "data": { + "userId": "1", + "folderName": "string" + } + } + } + }, + "Example 2": { + "value": { + "resultType": "FAIL", + "error": { + "errorCode": "PHO-400", + "reason": "사진 데이터가 유효하지 않습니다.", + "data": { + "reason": "저장할 사진이 없습니다." + } + }, + "success": null + } + }, + "Example 3": { + "value": { + "resultType": "FAIL", + "error": { + "errorCode": "MEM-400", + "reason": "메모 사진 추가 중 오류가 발생했습니다.", + "data": { + "folderId": "1", + "imageUrl": "string" + } + }, + "success": null + } + }, + "Example 4": { + "value": { + "resultType": "FAIL", + "error": { + "errorCode": "PHO-400", + "reason": "사진 데이터가 유효하지 않습니다.", + "data": { + "extension": "string" + } + }, + "success": null + } + } + } + } + } + }, + "409": { + "description": "중복 데이터 에러", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ITsoaErrorResponse" + }, + "examples": { + "Example 1": { + "value": { + "resultType": "FAIL", + "success": null, + "error": { + "errorCode": "FOL-409", + "reason": "이미 존재하는 폴더 이름입니다.", + "data": { + "folderName": "string" + } + } + } + } + } + } + } + } + }, + "description": "폴더 생성과 동시에 파일을 저장하는 API입니다.", + "summary": "폴더 생성 및 사진 저장 API", + "tags": [ + "memo-folder-controller" + ], + "security": [], + "parameters": [], + "requestBody": { + "required": true, + "content": { + "multipart/form-data": { + "schema": { + "type": "object", + "properties": { + "folderName": { + "type": "string", + "description": "생성할 폴더 이름" + }, + "image": { + "type": "string", + "format": "binary", + "description": "파일 업로드" + } + }, + "required": [ + "folderName", + "image" + ] + } + } + } + } + } + }, + "/memo/folders": { + "post": { + "operationId": "HandlerMemoFolderAdd", + "responses": { + "200": { + "description": "폴더 생성 성공 응답", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ITsoaSuccessResponse_MemoFolderResponseDto_" + }, + "examples": { + "Example 1": { + "value": { + "resultType": "SUCCESS", + "error": null, + "success": { + "id": "1", + "folderName": "string" + } + } + } + } + } + } + }, + "400": { + "description": "유효하지 않은 데이터 에러", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ITsoaErrorResponse" + }, + "examples": { + "Example 1": { + "value": { + "resultType": "FAIL", + "success": null, + "error": { + "errorCode": "FOL-400", + "reason": "폴더 생성 중 오류가 발생했습니다.", + "data": { + "userId": "1", + "folderName": "string" + } + } + } + } + } + } + } + }, + "409": { + "description": "중복 데이터 에러", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ITsoaErrorResponse" + }, + "examples": { + "Example 1": { + "value": { + "resultType": "FAIL", + "success": null, + "error": { + "errorCode": "FOL-409", + "reason": "이미 존재하는 폴더 이름입니다.", + "data": { + "folderName": "string" + } + } + } + } + } + } + } + } + }, + "description": "폴더를 생성하는 API입니다.", + "summary": "폴더 생성 API", + "tags": [ + "memo-folder-controller" + ], + "security": [], + "parameters": [], + "requestBody": { + "description": "생성할 폴더 이름", + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BodyToMemoFolder", + "description": "생성할 폴더 이름" + } + } + } + } + } + }, + "/memo/list": { + "get": { + "operationId": "HandlerMemoFolderList", + "responses": { + "200": { + "description": "메모 조회 성공 응답", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ITsoaSuccessResponse__data-MemoFolderListResponseDto-Array__" + }, + "examples": { + "Example 1": { + "value": { + "resultType": "SUCCESS", + "error": null, + "success": { + "data": [ + { + "folderId": "1", + "folderName": "string", + "imageCount": 0, + "imageText": "string", + "firstImageId": "1", + "firstImageUrl": "string", + "createdAt": "2025-01-17T03:50:25.923Z" + } + ] + } + } + } + } + } + } + } + }, + "description": "모든 메모를 조회하는 API입니다.", + "summary": "모든 메모 조회 API", + "tags": [ + "memo-folder-controller" + ], + "security": [], + "parameters": [] + } + }, + "/memo/search": { + "get": { + "operationId": "HandlerMemoSearch", + "responses": { + "200": { + "description": "메모 검색 성공 응답", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ITsoaSuccessResponse__data-MemoFolderListResponseDto-Array__" + }, + "examples": { + "Example 1": { + "value": { + "resultType": "SUCCESS", + "error": null, + "success": { + "data": [ + { + "folderId": "1", + "folderName": "string", + "imageCount": 0, + "imageText": "string", + "firstImageId": "1", + "firstImageUrl": "string", + "createdAt": "2025-01-17T03:50:25.923Z" + } + ] + } + } + } + } + } + } + }, + "400": { + "description": "유효하지 않은 검색 키워드 에러", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ITsoaErrorResponse" + }, + "examples": { + "Example 1": { + "value": { + "resultType": "FAIL", + "success": null, + "error": { + "errorCode": "SRH-400", + "reason": "입력 데이터가 유효하지 않습니다.", + "data": { + "reason": "검색어를 1자 이상 입력하세요." + } + } + } + } + } + } + } + } + }, + "description": "메모를 검색 및 조회하는 API입니다.", + "summary": "메모 검색 API", + "tags": [ + "memo-folder-controller" + ], + "security": [], + "parameters": [ + { + "description": "검색 키워드", + "in": "query", + "name": "keyword", + "required": true, + "schema": { + "type": "string" + } + } + ] + } + }, + "/memo/folders/{folderId}/images/delete": { + "post": { + "operationId": "HandlerMemoImageDelete", + "responses": { + "200": { + "description": "사진 삭제 성공 응답", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ITsoaSuccessResponse_MemoTextImageListResponseDto_" + }, + "examples": { + "Example 1": { + "value": { + "resultType": "SUCCESS", + "error": null, + "success": { + "folderId": "1", + "folderName": "string", + "imageText": "string", + "images": [ + { + "imageId": "1", + "imageUrl": "string" + } + ] + } + } + } + } + } + } + }, + "404": { + "description": "존재하지 않은 데이터 조회 에러", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ITsoaErrorResponse" + }, + "examples": { + "Example 1": { + "value": { + "resultType": "FAIL", + "error": { + "errorCode": "FOL-404", + "reason": "해당 폴더를 찾을 수 없습니다.", + "data": { + "folderId": "1" + } + }, + "success": null + } + }, + "Example 2": { + "value": { + "resultType": "FAIL", + "error": { + "errorCode": "PHO-404", + "reason": "해당 사진 데이터가 없습니다.", + "data": { + "imageId": "1" + } + }, + "success": null + } + } + } + } + } + } + }, + "description": "특정 폴더의 사진을 삭제하는 API입니다.", + "summary": "사진 삭제 API", + "tags": [ + "memo-image-controller" + ], + "security": [], + "parameters": [ + { + "description": "폴더 ID", + "in": "path", + "name": "folderId", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "description": "이동할 사진 ID 배열", + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BodyToMemoImagesToDelete", + "description": "이동할 사진 ID 배열" + } + } + } + } + } + }, + "/memo/folders/{folderId}/text": { + "patch": { + "operationId": "HandlerMemoTextUpdate", + "responses": { + "200": { + "description": "메모 텍스트 수정 성공 응답", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ITsoaSuccessResponse_MemoTextImageListResponseDto_" + }, + "examples": { + "Example 1": { + "value": { + "resultType": "SUCCESS", + "error": null, + "success": { + "folderId": "1", + "folderName": "string", + "imageText": "string", + "images": [ + { + "imageId": "1", + "imageUrl": "string" + } + ] + } + } + } + } + } + } + }, + "404": { + "description": "존재하지 않은 데이터 조회 에러", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ITsoaErrorResponse" + }, + "examples": { + "Example 1": { + "value": { + "resultType": "FAIL", + "success": null, + "error": { + "errorCode": "FOL-404", + "reason": "해당 폴더를 찾을 수 없습니다.", + "data": { + "folderId": "1" + } + } + } + } + } + } + } + } + }, + "description": "특정 폴더의 메모 텍스트를 수정하는 API입니다.", + "summary": "특정 폴더의 메모 텍스트 수정 API", + "tags": [ + "memo-folder-controller" + ], + "security": [], + "parameters": [ + { + "description": "폴더 ID", + "in": "path", + "name": "folderId", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "description": "메모 텍스트 수정 내용", + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BodyToMemoTextToUpdate", + "description": "메모 텍스트 수정 내용" + } + } + } + } + } } }, "servers": [ From 1cc2c2fd365b1a0f595533d0804c4e2c9c447ddb Mon Sep 17 00:00:00 2001 From: asjasj3964 <84120715+asjasj3964@users.noreply.github.com> Date: Sun, 9 Feb 2025 17:54:08 +0900 Subject: [PATCH 2/4] =?UTF-8?q?fix:=20=EC=82=AC=EC=A7=84=20=EC=A0=80?= =?UTF-8?q?=EC=9E=A5=ED=95=98=EA=B8=B0=20=EC=A0=84=EC=97=90=20=EC=A1=B4?= =?UTF-8?q?=EC=9E=AC=ED=95=98=EB=8A=94=20=ED=8F=B4=EB=8D=94=EC=9D=B8?= =?UTF-8?q?=EC=A7=80=20=EA=B2=80=EC=82=AC=20(#170)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [SWEP-85] 메모장 API tsoa 적용 * [SWEP-85] 사진 저장하기 전에 존재하는 폴더인지 검사 --- src/s3/image.uploader.ts | 104 +++++++++++++++++++++++++-------------- 1 file changed, 68 insertions(+), 36 deletions(-) diff --git a/src/s3/image.uploader.ts b/src/s3/image.uploader.ts index 9162e12..d2397a3 100644 --- a/src/s3/image.uploader.ts +++ b/src/s3/image.uploader.ts @@ -6,43 +6,75 @@ import path from 'path'; // 확장자 처리 import process from 'process'; import {s3} from './awsS3Client.js'; -import {createMemoFolder} from '../repositories/memo-folder.repository.js'; +import { + createMemoFolder, + getMemoFolder, +} from '../repositories/memo-folder.repository.js'; import {bodyToMemoFolder} from '../dtos/memo-folder.dto.js'; -import {FolderDuplicateError, PhotoValidationError} from '../errors.js'; +import { + FolderDuplicateError, + FolderNotFoundError, + PhotoValidationError, +} from '../errors.js'; -const allowedExtensions = ['.png', '.jpg', '.jpeg', '.bmp', '.PNG', '.JPG', '.webp']; // 확장자 검사 목록 -export const imageUploader = multer({ // 파일 업로드 미들웨어 설정 - storage: multerS3({ // multerS3 저장소 설정 - s3: s3, // AWS S3 객체 설정 - bucket: process.env.AWS_S3_BUCKET_NAME, // 업로드할 S3 버킷 이름 - contentType: multerS3.AUTO_CONTENT_TYPE, // 업로드 파일의 MIME 타입 자동 설정 - key: async (req: Request, file, callback) => { // S3 버킷에 저장될 경로와 이름 정의 - const userId = req.user!.id; // 사용자 ID +const allowedExtensions = [ + '.png', + '.jpg', + '.jpeg', + '.bmp', + '.PNG', + '.JPG', + '.webp', +]; // 확장자 검사 목록 +export const imageUploader = multer({ + // 파일 업로드 미들웨어 설정 + storage: multerS3({ + // multerS3 저장소 설정 + s3: s3, // AWS S3 객체 설정 + bucket: process.env.AWS_S3_BUCKET_NAME, // 업로드할 S3 버킷 이름 + contentType: multerS3.AUTO_CONTENT_TYPE, // 업로드 파일의 MIME 타입 자동 설정 + key: async (req: Request, file, callback) => { + // S3 버킷에 저장될 경로와 이름 정의 + const userId = req.user!.id; // 사용자 ID - const uuid = uuidv4(); // UUID 생성 - const extension = path.extname(file.originalname); // 파일 이름(확장자) 추출 - if (!allowedExtensions.includes(extension)) { // 업로드 파일의 확장자가 허용 목록에 없을 경우 - return callback(new PhotoValidationError({extension: extension})); - } - - // 디렉토리 path 설정 과정 - let uploadDirectory = null; - if (req.body.folderName) { - const createdMemoFolderId = await createMemoFolder(bodyToMemoFolder(req.body), userId); - if (createdMemoFolderId === null) { - return callback(new FolderDuplicateError({folderName: req.body.folderName})); - } - uploadDirectory = createdMemoFolderId; - req.uploadDirectory = uploadDirectory; // 디렉토리 정보 저장 - } - else { - const folderId = req.params.folderId; - uploadDirectory = folderId; - } + const uuid = uuidv4(); // UUID 생성 + const extension = path.extname(file.originalname); // 파일 이름(확장자) 추출 + if (!allowedExtensions.includes(extension)) { + // 업로드 파일의 확장자가 허용 목록에 없을 경우 + return callback(new PhotoValidationError({extension: extension})); + } - callback(null, `${userId}/${uploadDirectory}/${uuid}_${file.originalname}`); // S3 버킷에서 파일이 저장될 key - }, - acl: 'private', // 비공개 설정 (업로드 파일을 버킷 소유자만 접근 가능) - }), - limits: { fileSize: 5 * 1024 * 1024 }, // 이미지 용량 제한 (5MB) -}); \ No newline at end of file + // 디렉토리 path 설정 과정 + let uploadDirectory = null; + if (req.body.folderName) { + const createdMemoFolderId = await createMemoFolder( + bodyToMemoFolder(req.body), + userId, + ); + if (createdMemoFolderId === null) { + return callback( + new FolderDuplicateError({folderName: req.body.folderName}), + ); + } + uploadDirectory = createdMemoFolderId; + req.uploadDirectory = uploadDirectory; // 디렉토리 정보 저장 + } else { + const folderId = req.params.folderId; + const checkFolder = await getMemoFolder(BigInt(folderId)); + if (checkFolder === null || checkFolder.userId !== userId) { + return callback( + new FolderNotFoundError({folderId: BigInt(folderId)}), + ); + } + uploadDirectory = folderId; + } + + callback( + null, + `${userId}/${uploadDirectory}/${uuid}_${file.originalname}`, + ); // S3 버킷에서 파일이 저장될 key + }, + acl: 'private', // 비공개 설정 (업로드 파일을 버킷 소유자만 접근 가능) + }), + limits: {fileSize: 5 * 1024 * 1024}, // 이미지 용량 제한 (5MB) +}); From 6057ca1d3e10dcb9b902728cb5f63ffbf028eef5 Mon Sep 17 00:00:00 2001 From: asjasj3964 <84120715+asjasj3964@users.noreply.github.com> Date: Sun, 9 Feb 2025 19:07:01 +0900 Subject: [PATCH 3/4] =?UTF-8?q?fix:=20=EA=B2=80=EC=83=89=EC=96=B4=EA=B0=80?= =?UTF-8?q?=20=EB=B9=88=20=EB=AC=B8=EC=9E=90=EC=97=B4=EC=9D=B4=EA=B1=B0?= =?UTF-8?q?=EB=82=98=20=EA=B3=B5=EB=B0=B1=EB=A7=8C=20=EC=9E=85=EB=A0=A5?= =?UTF-8?q?=EB=90=A0=20=EA=B2=BD=EC=9A=B0=20=EC=97=90=EB=9F=AC=20=EC=B2=98?= =?UTF-8?q?=EB=A6=AC=20(#171)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [SWEP-85] 메모장 API tsoa 적용 * [SWEP-85] 사진 저장하기 전에 존재하는 폴더인지 검사 * [SWEP-85] 검색어가 빈 문자열이거나 공백만 입력될 경우 에러 처리 --- src/controllers/tsoa.memo-folder.controller.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/controllers/tsoa.memo-folder.controller.ts b/src/controllers/tsoa.memo-folder.controller.ts index 605472e..ef0b4a8 100644 --- a/src/controllers/tsoa.memo-folder.controller.ts +++ b/src/controllers/tsoa.memo-folder.controller.ts @@ -306,7 +306,7 @@ export class MemoFolderController extends Controller { try { const userId = BigInt(req.user!.id); const searchKeyword = keyword?.toString(); - if (searchKeyword === null) { + if (searchKeyword === null || searchKeyword.trim().length === 0) { throw new DataValidationError({ reason: '검색어를 1자 이상 입력하세요.', }); From b417ca08077976c6954e0656aa1c2dded2e3091a Mon Sep 17 00:00:00 2001 From: asjasj3964 Date: Sun, 9 Feb 2025 21:07:11 +0900 Subject: [PATCH 4/4] =?UTF-8?q?[SWEP-85]=20tsoaRoutes.ts=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/routers/tsoaRoutes.ts | 507 +++++++++++++++++++++++++++++++++++++- 1 file changed, 504 insertions(+), 3 deletions(-) diff --git a/src/routers/tsoaRoutes.ts b/src/routers/tsoaRoutes.ts index 81ae311..93d773e 100644 --- a/src/routers/tsoaRoutes.ts +++ b/src/routers/tsoaRoutes.ts @@ -7,7 +7,13 @@ import { fetchMiddlewares, ExpressTemplateService } from '@tsoa/runtime'; import { TagsController } from './../controllers/tsoaTag.controller.js'; // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa import { ImagesController } from './../controllers/tsoaImage.controller.js'; +// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa +import { MemoImageController } from './../controllers/tsoa.memo-image.controller.js'; +// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa +import { MemoFolderController } from './../controllers/tsoa.memo-folder.controller.js'; import type { Request as ExRequest, Response as ExResponse, RequestHandler, Router } from 'express'; +import multer from 'multer'; + @@ -41,7 +47,7 @@ const models: TsoaRoute.Models = { // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa "ErrorDetails": { "dataType": "refAlias", - "type": {"dataType":"union","subSchemas":[{"dataType":"nestedObjectLiteral","nestedProperties":{"folderName":{"dataType":"string"},"userId":{"dataType":"string"},"folderId":{"dataType":"string"}}},{"dataType":"nestedObjectLiteral","nestedProperties":{"imageUrl":{"dataType":"string"},"imageId":{"dataType":"string"}}},{"dataType":"nestedObjectLiteral","nestedProperties":{"userId":{"dataType":"string"},"challengeId":{"dataType":"string"}}},{"dataType":"nestedObjectLiteral","nestedProperties":{"longitude":{"dataType":"double"},"latitude":{"dataType":"double"}}},{"dataType":"nestedObjectLiteral","nestedProperties":{"reason":{"dataType":"string"}}},{"dataType":"nestedObjectLiteral","nestedProperties":{"searchKeyword":{"dataType":"string"}}},{"ref":"FieldErrors"},{"dataType":"nestedObjectLiteral","nestedProperties":{"extension":{"dataType":"string"}}},{"dataType":"enum","enums":[null]}],"validators":{}}, + "type": {"dataType":"union","subSchemas":[{"dataType":"nestedObjectLiteral","nestedProperties":{"folderName":{"dataType":"string"},"userId":{"dataType":"string"},"folderId":{"dataType":"string"}}},{"dataType":"nestedObjectLiteral","nestedProperties":{"imageUrl":{"dataType":"string"},"imageId":{"dataType":"string"}}},{"dataType":"nestedObjectLiteral","nestedProperties":{"userId":{"dataType":"string"},"challengeId":{"dataType":"string"}}},{"dataType":"nestedObjectLiteral","nestedProperties":{"longitude":{"dataType":"double"},"latitude":{"dataType":"double"}}},{"dataType":"nestedObjectLiteral","nestedProperties":{"reason":{"dataType":"string"}}},{"dataType":"nestedObjectLiteral","nestedProperties":{"searchKeyword":{"dataType":"string"}}},{"ref":"FieldErrors"},{"dataType":"nestedObjectLiteral","nestedProperties":{"extension":{"dataType":"string"}}},{"dataType":"nestedObjectLiteral","nestedProperties":{"imageId":{"dataType":"array","array":{"dataType":"string"}},"folderId":{"dataType":"string"}}},{"dataType":"enum","enums":[null]}],"validators":{}}, }, // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa "ITsoaErrorResponse": { @@ -82,6 +88,142 @@ const models: TsoaRoute.Models = { "additionalProperties": false, }, // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "MemoFolderImageResponseDto": { + "dataType": "refObject", + "properties": { + "folderId": {"dataType":"string","required":true}, + "folderName": {"dataType":"string","required":true}, + "imageId": {"dataType":"string","required":true}, + "imageUrl": {"dataType":"string","required":true}, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "ITsoaSuccessResponse_MemoFolderImageResponseDto_": { + "dataType": "refObject", + "properties": { + "resultType": {"dataType":"string","required":true}, + "error": {"dataType":"enum","enums":[null],"required":true}, + "success": {"ref":"MemoFolderImageResponseDto","required":true}, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "MemoTextImageListResponseDto": { + "dataType": "refObject", + "properties": { + "folderId": {"dataType":"string","required":true}, + "folderName": {"dataType":"string","required":true}, + "imageText": {"dataType":"string","required":true}, + "images": {"dataType":"union","subSchemas":[{"dataType":"array","array":{"dataType":"nestedObjectLiteral","nestedProperties":{"imageUrl":{"dataType":"string","required":true},"imageId":{"dataType":"string","required":true}}}},{"dataType":"enum","enums":[null]}],"required":true}, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "ITsoaSuccessResponse_MemoTextImageListResponseDto_": { + "dataType": "refObject", + "properties": { + "resultType": {"dataType":"string","required":true}, + "error": {"dataType":"enum","enums":[null],"required":true}, + "success": {"ref":"MemoTextImageListResponseDto","required":true}, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "BodyToMemoImagesToMove": { + "dataType": "refObject", + "properties": { + "targetFolderId": {"dataType":"string","required":true}, + "imageId": {"dataType":"array","array":{"dataType":"string"},"required":true}, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "ResponseMessage": { + "dataType": "refObject", + "properties": { + "message": {"dataType":"string","required":true}, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "ITsoaSuccessResponse_ResponseMessage_": { + "dataType": "refObject", + "properties": { + "resultType": {"dataType":"string","required":true}, + "error": {"dataType":"enum","enums":[null],"required":true}, + "success": {"ref":"ResponseMessage","required":true}, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "MemoFolderResponseDto": { + "dataType": "refObject", + "properties": { + "id": {"dataType":"string","required":true}, + "folderName": {"dataType":"string","required":true}, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "ITsoaSuccessResponse_MemoFolderResponseDto_": { + "dataType": "refObject", + "properties": { + "resultType": {"dataType":"string","required":true}, + "error": {"dataType":"enum","enums":[null],"required":true}, + "success": {"ref":"MemoFolderResponseDto","required":true}, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "BodyToMemoFolder": { + "dataType": "refObject", + "properties": { + "folderName": {"dataType":"string","required":true}, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "MemoFolderListResponseDto": { + "dataType": "refObject", + "properties": { + "folderId": {"dataType":"string","required":true}, + "folderName": {"dataType":"string","required":true}, + "imageText": {"dataType":"string","required":true}, + "imageCount": {"dataType":"double","required":true}, + "firstImageId": {"dataType":"union","subSchemas":[{"dataType":"string"},{"dataType":"enum","enums":[null]}],"required":true}, + "firstImageUrl": {"dataType":"union","subSchemas":[{"dataType":"string"},{"dataType":"enum","enums":[null]}],"required":true}, + "createdAt": {"dataType":"datetime","required":true}, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "ITsoaSuccessResponse__data-MemoFolderListResponseDto-Array__": { + "dataType": "refObject", + "properties": { + "resultType": {"dataType":"string","required":true}, + "error": {"dataType":"enum","enums":[null],"required":true}, + "success": {"dataType":"nestedObjectLiteral","nestedProperties":{"data":{"dataType":"array","array":{"dataType":"refObject","ref":"MemoFolderListResponseDto"},"required":true}},"required":true}, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "BodyToMemoImagesToDelete": { + "dataType": "refObject", + "properties": { + "imageId": {"dataType":"array","array":{"dataType":"string"},"required":true}, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "BodyToMemoTextToUpdate": { + "dataType": "refObject", + "properties": { + "memoText": {"dataType":"string","required":true}, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa }; const templateService = new ExpressTemplateService(models, {"noImplicitAdditionalProperties":"throw-on-extras","bodyCoercion":true}); @@ -90,13 +232,14 @@ const templateService = new ExpressTemplateService(models, {"noImplicitAdditiona -export function RegisterRoutes(app: Router) { +export function RegisterRoutes(app: Router,opts?:{multer?:ReturnType}) { // ########################################################################################################### // NOTE: If you do not see routes for all of your controllers in this file, then you might not have informed tsoa of where to look // Please look into the "controllerPathGlobs" config option described in the readme: https://github.com/lukeautry/tsoa // ########################################################################################################### + const upload = opts?.multer || multer({"limits":{"fileSize":8388608}}); const argsTagsController_getTagListWithDate: Record = { @@ -194,6 +337,364 @@ export function RegisterRoutes(app: Router) { } }); // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + const argsMemoImageController_handlerMemoImageAdd: Record = { + req: {"in":"request","name":"req","required":true,"dataType":"object"}, + targetFolderId: {"in":"path","name":"folderId","required":true,"dataType":"string"}, + image: {"in":"formData","name":"image","required":true,"dataType":"file"}, + }; + app.post('/memo/image-format/folders/:folderId', + upload.fields([ + { + name: "image", + maxCount: 1 + } + ]), + ...(fetchMiddlewares(MemoImageController)), + ...(fetchMiddlewares(MemoImageController.prototype.handlerMemoImageAdd)), + + async function MemoImageController_handlerMemoImageAdd(request: ExRequest, response: ExResponse, next: any) { + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = templateService.getValidatedArgs({ args: argsMemoImageController_handlerMemoImageAdd, request, response }); + + const controller = new MemoImageController(); + + await templateService.apiHandler({ + methodName: 'handlerMemoImageAdd', + controller, + response, + next, + validatedArgs, + successStatus: 200, + }); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + const argsMemoImageController_handlerMemoImageMove: Record = { + req: {"in":"request","name":"req","required":true,"dataType":"object"}, + currentFolderId: {"in":"path","name":"folderId","required":true,"dataType":"string"}, + body: {"in":"body","name":"body","required":true,"ref":"BodyToMemoImagesToMove"}, + }; + app.patch('/memo/folders/:folderId/images', + ...(fetchMiddlewares(MemoImageController)), + ...(fetchMiddlewares(MemoImageController.prototype.handlerMemoImageMove)), + + async function MemoImageController_handlerMemoImageMove(request: ExRequest, response: ExResponse, next: any) { + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = templateService.getValidatedArgs({ args: argsMemoImageController_handlerMemoImageMove, request, response }); + + const controller = new MemoImageController(); + + await templateService.apiHandler({ + methodName: 'handlerMemoImageMove', + controller, + response, + next, + validatedArgs, + successStatus: 200, + }); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + const argsMemoImageController_handlerMemoFolderDelete: Record = { + req: {"in":"request","name":"req","required":true,"dataType":"object"}, + targetFolderId: {"in":"path","name":"folderId","required":true,"dataType":"string"}, + }; + app.delete('/memo/folders/:folderId', + ...(fetchMiddlewares(MemoImageController)), + ...(fetchMiddlewares(MemoImageController.prototype.handlerMemoFolderDelete)), + + async function MemoImageController_handlerMemoFolderDelete(request: ExRequest, response: ExResponse, next: any) { + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = templateService.getValidatedArgs({ args: argsMemoImageController_handlerMemoFolderDelete, request, response }); + + const controller = new MemoImageController(); + + await templateService.apiHandler({ + methodName: 'handlerMemoFolderDelete', + controller, + response, + next, + validatedArgs, + successStatus: 200, + }); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + const argsMemoFolderController_handlerMemoFolderImageAdd: Record = { + req: {"in":"request","name":"req","required":true,"dataType":"object"}, + folderName: {"in":"formData","name":"folderName","required":true,"dataType":"string"}, + image: {"in":"formData","name":"image","required":true,"dataType":"file"}, + }; + app.post('/memo/image-format/folders', + upload.fields([ + { + name: "image", + maxCount: 1 + } + ]), + ...(fetchMiddlewares(MemoFolderController)), + ...(fetchMiddlewares(MemoFolderController.prototype.handlerMemoFolderImageAdd)), + + async function MemoFolderController_handlerMemoFolderImageAdd(request: ExRequest, response: ExResponse, next: any) { + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = templateService.getValidatedArgs({ args: argsMemoFolderController_handlerMemoFolderImageAdd, request, response }); + + const controller = new MemoFolderController(); + + await templateService.apiHandler({ + methodName: 'handlerMemoFolderImageAdd', + controller, + response, + next, + validatedArgs, + successStatus: 200, + }); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + const argsMemoFolderController_handlerMemoFolderAdd: Record = { + req: {"in":"request","name":"req","required":true,"dataType":"object"}, + body: {"in":"body","name":"body","required":true,"ref":"BodyToMemoFolder"}, + }; + app.post('/memo/folders', + ...(fetchMiddlewares(MemoFolderController)), + ...(fetchMiddlewares(MemoFolderController.prototype.handlerMemoFolderAdd)), + + async function MemoFolderController_handlerMemoFolderAdd(request: ExRequest, response: ExResponse, next: any) { + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = templateService.getValidatedArgs({ args: argsMemoFolderController_handlerMemoFolderAdd, request, response }); + + const controller = new MemoFolderController(); + + await templateService.apiHandler({ + methodName: 'handlerMemoFolderAdd', + controller, + response, + next, + validatedArgs, + successStatus: 200, + }); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + const argsMemoFolderController_handlerMemoFolderList: Record = { + req: {"in":"request","name":"req","required":true,"dataType":"object"}, + }; + app.get('/memo/list', + ...(fetchMiddlewares(MemoFolderController)), + ...(fetchMiddlewares(MemoFolderController.prototype.handlerMemoFolderList)), + + async function MemoFolderController_handlerMemoFolderList(request: ExRequest, response: ExResponse, next: any) { + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = templateService.getValidatedArgs({ args: argsMemoFolderController_handlerMemoFolderList, request, response }); + + const controller = new MemoFolderController(); + + await templateService.apiHandler({ + methodName: 'handlerMemoFolderList', + controller, + response, + next, + validatedArgs, + successStatus: 200, + }); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + const argsMemoFolderController_handlerMemoSearch: Record = { + req: {"in":"request","name":"req","required":true,"dataType":"object"}, + keyword: {"in":"query","name":"keyword","required":true,"dataType":"string"}, + }; + app.get('/memo/search', + ...(fetchMiddlewares(MemoFolderController)), + ...(fetchMiddlewares(MemoFolderController.prototype.handlerMemoSearch)), + + async function MemoFolderController_handlerMemoSearch(request: ExRequest, response: ExResponse, next: any) { + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = templateService.getValidatedArgs({ args: argsMemoFolderController_handlerMemoSearch, request, response }); + + const controller = new MemoFolderController(); + + await templateService.apiHandler({ + methodName: 'handlerMemoSearch', + controller, + response, + next, + validatedArgs, + successStatus: 200, + }); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + const argsMemoFolderController_handlerMemoImageDelete: Record = { + req: {"in":"request","name":"req","required":true,"dataType":"object"}, + folderIdParam: {"in":"path","name":"folderId","required":true,"dataType":"string"}, + body: {"in":"body","name":"body","required":true,"ref":"BodyToMemoImagesToDelete"}, + }; + app.post('/memo/folders/:folderId/images/delete', + ...(fetchMiddlewares(MemoFolderController)), + ...(fetchMiddlewares(MemoFolderController.prototype.handlerMemoImageDelete)), + + async function MemoFolderController_handlerMemoImageDelete(request: ExRequest, response: ExResponse, next: any) { + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = templateService.getValidatedArgs({ args: argsMemoFolderController_handlerMemoImageDelete, request, response }); + + const controller = new MemoFolderController(); + + await templateService.apiHandler({ + methodName: 'handlerMemoImageDelete', + controller, + response, + next, + validatedArgs, + successStatus: 200, + }); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + const argsMemoFolderController_handlerMemoTextImageList: Record = { + req: {"in":"request","name":"req","required":true,"dataType":"object"}, + folderIdParam: {"in":"path","name":"folderId","required":true,"dataType":"string"}, + }; + app.get('/memo/folders/:folderId', + ...(fetchMiddlewares(MemoFolderController)), + ...(fetchMiddlewares(MemoFolderController.prototype.handlerMemoTextImageList)), + + async function MemoFolderController_handlerMemoTextImageList(request: ExRequest, response: ExResponse, next: any) { + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = templateService.getValidatedArgs({ args: argsMemoFolderController_handlerMemoTextImageList, request, response }); + + const controller = new MemoFolderController(); + + await templateService.apiHandler({ + methodName: 'handlerMemoTextImageList', + controller, + response, + next, + validatedArgs, + successStatus: 200, + }); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + const argsMemoFolderController_handlerMemoFolderUpdate: Record = { + req: {"in":"request","name":"req","required":true,"dataType":"object"}, + folderIdParam: {"in":"path","name":"folderId","required":true,"dataType":"string"}, + body: {"in":"body","name":"body","required":true,"ref":"BodyToMemoFolder"}, + }; + app.patch('/memo/folders/:folderId', + ...(fetchMiddlewares(MemoFolderController)), + ...(fetchMiddlewares(MemoFolderController.prototype.handlerMemoFolderUpdate)), + + async function MemoFolderController_handlerMemoFolderUpdate(request: ExRequest, response: ExResponse, next: any) { + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = templateService.getValidatedArgs({ args: argsMemoFolderController_handlerMemoFolderUpdate, request, response }); + + const controller = new MemoFolderController(); + + await templateService.apiHandler({ + methodName: 'handlerMemoFolderUpdate', + controller, + response, + next, + validatedArgs, + successStatus: 200, + }); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + const argsMemoFolderController_handlerMemoTextUpdate: Record = { + req: {"in":"request","name":"req","required":true,"dataType":"object"}, + folderIdParam: {"in":"path","name":"folderId","required":true,"dataType":"string"}, + body: {"in":"body","name":"body","required":true,"ref":"BodyToMemoTextToUpdate"}, + }; + app.patch('/memo/folders/:folderId/text', + ...(fetchMiddlewares(MemoFolderController)), + ...(fetchMiddlewares(MemoFolderController.prototype.handlerMemoTextUpdate)), + + async function MemoFolderController_handlerMemoTextUpdate(request: ExRequest, response: ExResponse, next: any) { + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = templateService.getValidatedArgs({ args: argsMemoFolderController_handlerMemoTextUpdate, request, response }); + + const controller = new MemoFolderController(); + + await templateService.apiHandler({ + methodName: 'handlerMemoTextUpdate', + controller, + response, + next, + validatedArgs, + successStatus: 200, + }); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa @@ -201,4 +702,4 @@ export function RegisterRoutes(app: Router) { // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa } -// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa \ No newline at end of file +// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa