diff --git a/config/tsoa.json b/config/tsoa.json index 2b1b37b..a0b45f9 100644 --- a/config/tsoa.json +++ b/config/tsoa.json @@ -6,6 +6,7 @@ "outputDirectory": "./swagger", "specVersion": 3, "validate": true, + "specMerging": "recursive", "spec": { "servers": [ { @@ -16,7 +17,93 @@ "url": "http://3.37.137.212:3000", "description": "Sweepic server" } - ] + ], + "paths": { + "/memo/image-format/folders": { + "post": { + "requestBody": { + "required": true, + "content": { + "multipart/form-data": { + "schema": { + "type": "object", + "properties": { + "image": { + "type": "string", + "format": "binary", + "description": "파일 업로드" + } + } + } + } + } + } + } + }, + "/memo/image-format/folders/{folderId}": { + "post": { + "requestBody": { + "required": true, + "content": { + "multipart/form-data": { + "schema": { + "type": "object", + "properties": { + "image": { + "type": "string", + "format": "binary", + "description": "파일 업로드" + } + } + } + } + } + } + } + }, + "/memo/text-format/folders/{folderId}": { + "patch": { + "requestBody": { + "required": true, + "content": { + "multipart/form-data": { + "schema": { + "type": "object", + "properties": { + "base64_image": { + "type": "string", + "format": "binary", + "description": "OCR 처리를 위한 이미지 업로드" + } + } + } + } + } + } + } + }, + "/memo/text-format/folders": { + "post": { + "requestBody": { + "required": true, + "content": { + "multipart/form-data": { + "schema": { + "type": "object", + "properties": { + "base64_image": { + "type": "string", + "format": "binary", + "description": "OCR 처리를 위한 이미지 업로드" + } + } + } + } + } + } + } + } + } }, "securityDefinitions": { "sessionAuth": { diff --git a/src/app.ts b/src/app.ts index f3dcb9e..bc534e3 100644 --- a/src/app.ts +++ b/src/app.ts @@ -21,7 +21,6 @@ import {ValidateError} from 'tsoa'; import {labelDetectionController} from './controllers/tags-ai.controller.js'; // routers -import {memoFolderRouter} from './routers/memo.router.js'; import {RegisterRoutes} from './routers/tsoaRoutes.js'; import {challengeRouter} from './routers/challenge.router.js'; import {authRouter} from './routers/auth.routers.js'; @@ -30,6 +29,7 @@ import {tagRouter} from './routers/tag.router.js'; import {myPageRouter} from './routers/mypage.routers.js'; import {imageUploader} from './s3/image.uploader.js'; import {trustRouter} from './routers/trust.router.js'; +import upload from './ai/ai-upload.js'; dotenv.config(); @@ -113,14 +113,17 @@ app.use(sessionAuthMiddleware); // 로그인 후 app.use('/onboarding', userRouter); -app.use('/memo', memoFolderRouter); app.use('/challenge', challengeRouter); app.use('/user/mypage', myPageRouter); app.use('/tag', tagRouter); -app.use('/trust',trustRouter); +app.use('/trust', trustRouter); app.post('/image/ai', labelDetectionController); -RegisterRoutes(app, {multer: imageUploader}); +app.post('/memo/text-format/folders', upload.single('base64_image')); +app.patch('/memo/text-format/folders/:folderId', upload.single('base64_image')); +app.post('/memo/image-format/folders', imageUploader.single('image')); +app.post('/memo/image-format/folders/:folderId', imageUploader.single('image')); +RegisterRoutes(app); app.get('/', (req: Request, res: Response) => { res.send('Sweepic'); diff --git a/src/controllers/memo-createFolderOCR.Controller.ts b/src/controllers/memo-createFolderOCR.Controller.ts index 691d11b..d1f517f 100644 --- a/src/controllers/memo-createFolderOCR.Controller.ts +++ b/src/controllers/memo-createFolderOCR.Controller.ts @@ -1,135 +1,155 @@ -import {NextFunction, Request, Response} from 'express'; +import {Request as ExpressRequest} from 'express'; import {processOCRAndSave} from '../services/memo-ocrService.js'; import {StatusCodes} from 'http-status-codes'; -import {DataValidationError, PhotoValidationError} from '../errors.js'; +import { + DataValidationError, + PhotoDataNotFoundError, + PhotoValidationError, +} from '../errors.js'; +import { + Controller, + Example, + FormField, + Request, + Route, + SuccessResponse, + Response, + Tags, + Post, +} from 'tsoa'; +import { + ITsoaErrorResponse, + ITsoaSuccessResponse, + TsoaSuccessResponse, +} from '../models/tsoaResponse.js'; -export const createFolderOCR = async ( - req: Request, - res: Response, - next: NextFunction, -): Promise => { - /* - #swagger.tags = ['memo-ai'] - #swagger.summary = '폴더 생성 및 OCR 수행' - #swagger.description = '새로운 폴더를 생성하고, 이미지에서 OCR 텍스트를 추출하여 이미지와 텍스트를 저장하는 API입니다.' - #swagger.requestBody = { - required: true, - content: { - "multipart/form-data": { - schema: { - type: "object", - required: ["base64_image", "folder_name"], - properties: { - base64_image: { - type: "string", - description: "OCR 처리를 위한 이미지 URL", - example: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUA..." - }, - - folder_name: { - type: "string", - description: "생성할 폴더의 이름", - example: "공부" - } - } - } - } - } - } - #swagger.responses[201] = { - description: "폴더 생성 및 텍스트 변환", - content: { - "application/json": { - schema: { - type: "object", - properties: { - resultType: { type: "string", example: "SUCCESS" }, - error: { type: "object", nullable: true, example: null }, - success: { - type: "object", - properties: { - folder_id: { type: "string", example: "1" }, - image_text: { type: "string", example: "이번 수업 시간은 사회 과학 시간이다." }, - } - } - } - } - } - } - } - #swagger.responses[400] = { - description: "잘못된 요청 데이터", - content: { - "application/json": { - schema: { - type: "object", - properties: { - error: { - type: "string", - example: "폴더의 이름을 입력해주세요." - } - } - } - } - } - } - #swagger.responses[500] = { - description: "서버 내부 오류", - content: { - "application/json": { - schema: { - type: "object", - properties: { - error: { - type: "string", - example: "서버에러발생." - } - } - } - } - } - } - */ +@Route('memo') +export class MemoCreateUpdateOCRController extends Controller { + /** + * 새로운 폴더를 생성하고, 이미지에서 OCR 텍스트를 추출하여 이미지와 텍스트를 저장하는 API입니다. + * + * @summary 폴더 생성 및 OCR 수행 + * @param req + * @param folderName 생성할 폴더의 이름 + * @returns 성공 시 폴더를 생성하고 텍스트를 저장한 결과를 반환합니다. + */ + @Post('/text-format/folders') + @Tags('memo-ai') + @Response(StatusCodes.BAD_REQUEST, '잘못된 요청 데이터', { + resultType: 'FAIL', + success: null, + error: { + errorCode: 'SRH-400', + reason: '입력 데이터가 유효하지 않습니다.', + data: {reason: 'base64_image, user_id, folder_name이 필요합니다.'}, + }, + }) + @Response(StatusCodes.BAD_REQUEST, '잘못된 요청 데이터', { + resultType: 'FAIL', + success: null, + error: { + errorCode: 'PHO-400', + reason: '사진 데이터가 유효하지 않습니다.', + data: {reason: '올바른 Base64 이미지 형식이 아닙니다.'}, + }, + }) + @Response(StatusCodes.BAD_REQUEST, '잘못된 요청 데이터', { + resultType: 'FAIL', + success: null, + error: { + errorCode: 'PHO-400', + reason: '사진 데이터가 유효하지 않습니다.', + data: {reason: '텍스트를 추출할 사진이 없습니다.'}, + }, + }) + @Response(StatusCodes.BAD_REQUEST, '잘못된 요청 데이터', { + resultType: 'FAIL', + success: null, + error: { + errorCode: 'PHO-400', + reason: '사진 데이터가 유효하지 않습니다.', + data: {reason: '이미지에서 텍스트를 찾지 못하였습니다.'}, + }, + }) + @Response( + StatusCodes.NOT_FOUND, + '존재하지 않은 데이터 조회 에러', + { + resultType: 'FAIL', + success: null, + error: { + errorCode: 'FOL-404', + reason: '해당 폴더를 찾을 수 없습니다.', + data: {folderId: '1'}, + }, + }, + ) + @Response(StatusCodes.CONFLICT, '중복 데이터 에러', { + resultType: 'FAIL', + success: null, + error: { + errorCode: 'FOL-409', + reason: '이미 존재하는 폴더 이름입니다.', + data: {folderName: 'string'}, + }, + }) + @SuccessResponse(StatusCodes.OK, '폴더 생성 및 텍스트 변환') + @Example({ + resultType: 'SUCCESS', + error: null, + success: { + folder_id: '1', + image_text: '이번 수업 시간은 사회 과학 시간이다.', + }, + }) + public async createFolderOCR( + @Request() req: ExpressRequest, + @FormField('folder_name') folderName: string, + ): Promise> { + try { + if (!req.file) { + throw new PhotoDataNotFoundError({ + reason: '텍스트를 추출할 사진이 없습니다.', + }); + } - try { - let base64_image: string | undefined = req.body.base64_image; + //파일 업로드가 있는 경우 base64 변환 + const base64_image = `data:image/png;base64,${req.file.buffer.toString('base64')}`; - //파일 업로드가 있는 경우 base64 변환 - if (req.file) { - base64_image = `data:image/png;base64,${req.file.buffer.toString('base64')}`; - } + const user_id = BigInt(req.user!.id); + const folder_name = folderName; - const user_id = BigInt(req.user!.id); - const {folder_name} = req.body; + // 유효성 검사-데이터가 하나라도 빠지지 않도록 + // 유효성 검사 + if (!base64_image || !user_id || !folder_name) { + throw new DataValidationError({ + reason: 'base64_image, user_id, folder_name이 필요합니다.', + }); + } + //base64 이미지 검증 - 올바른 형태인지 + if (!isValidBase64(base64_image)) { + throw new PhotoValidationError({ + reason: '올바른 Base64 이미지 형식이 아닙니다.', + }); + } - // 유효성 검사-데이터가 하나라도 빠지지 않도록 - // 유효성 검사 - if (!base64_image || !user_id || !folder_name) { - throw new DataValidationError({ - reason: 'base64_image, user_id, folder_name이 필요합니다.', + // 서비스 호출 + const result = await processOCRAndSave({ + base64_image, + user_id, + folder_name, }); - } - //base64 이미지 검증 - 올바른 형태인지 - if (!isValidBase64(base64_image)) { - throw new PhotoValidationError({ - reason: '올바른 Base64 이미지 형식이 아닙니다.', + + // 성공 응답 + return new TsoaSuccessResponse({ + folder_id: result.folder_id, + image_text: result.image_text, }); + } catch (error) { + throw error; } - - // 서비스 호출 - const result = await processOCRAndSave({ - base64_image, - user_id, - folder_name, - }); - - // 성공 응답 - res.status(StatusCodes.CREATED).success(result); - } catch (error) { - next(error); } -}; - +} const isValidBase64 = (base64String: string): boolean => { // base64 문자열 검증 함수 const base64Regex = /^data:image\/(jpeg|png|jpg);base64,[A-Za-z0-9+/=]+$/; diff --git a/src/controllers/memo-folder.controller.ts b/src/controllers/memo-folder.controller.ts deleted file mode 100644 index 82bcacb..0000000 --- a/src/controllers/memo-folder.controller.ts +++ /dev/null @@ -1,971 +0,0 @@ -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, - res: Response, - next: NextFunction, -): Promise => { - /* - #swagger.tags = ['memo-folder-controller'] - #swagger.summary = '폴더 생성 및 사진 저장 API'; - #swagger.description = '폴더 생성과 동시에 파일을 저장하는 API입니다.' - #swagger.requestBody = { - required: true, - content: { - "multipart/form-data": { - schema: { - type: "object", - required: ['folderName'], - properties: { - folderName: { type: "string", description: "폴더 이름" }, - image: { - type: "string", - format: "binary", - description: "파일 업로드" - } - } - } - } - } - }; - #swagger.responses[200] = { - description: "폴더 생성 및 사진 저장 성공 응답", - content: { - "application/json": { - schema: { - type: "object", - properties: { - resultType: { type: "string", example: "SUCCESS" }, - error: { type: "object", nullable: true, example: null }, - success: { - type: "object", - properties: { - folderId: { type: "string", example: "1" }, - folderName: { type: "string" }, - imageId: { type: "string", example: "1" }, - imageUrl: { type: "string" }, - } - } - } - } - } - } - }; - #swagger.responses[400] = { - description: "유효하지 않은 데이터 에러", - content: { - "application/json": { - schema: { - type: "object", - properties: { - resultType: { type: "string", example: "FAIL" }, - error: { - type: "object", - properties: { - errorCode: { type: "string", example: "400" }, - reason: { type: "string" }, - data: {}, - } - }, - success: { type: "object", nullable: true, example: null }, - } - }, - examples: { - "폴더 생성 에러": { - summary: "폴더 생성 에러", - description: "폴더 생성 중 오류가 발생했습니다.", - value: { - resultType: "FAIL", - error: { - errorCode: "FOL-400", - reason: "폴더 생성 중 오류가 발생했습니다.", - data: { - userId: "1", - folderName: "string" - } - }, - success: null - } - }, - "유효하지 않은 사진 데이터 에러": { - summary: "유효하지 않은 사진 데이터 에러", - description: "저장할 사진이 없습니다.", - value: { - resultType: "FAIL", - error: { - errorCode: "PHO-400", - reason: "사진 데이터가 유효하지 않습니다.", - data: { - reason: "저장할 사진이 없습니다." - } - }, - success: null - } - }, - "사진 추가 에러": { - summary: "사진 추가 에러", - description: "메모 사진 추가 중 오류가 발생했습니다.", - value: { - resultType: "FAIL", - error: { - errorCode: "MEM-400", - reason: "메모 사진 추가 중 오류가 발생했습니다.", - data: { - folderId: "1", - imageUrl: "string" - } - }, - success: null - } - }, - "유효하지 않은 확장자 에러": { - summary: "유효하지 않은 확장자 에러", - description: "이미지 확장자가 유효하지 않습니다.", - value: { - resultType: "FAIL", - error: { - errorCode: "PHO-400", - reason: "사진 데이터가 유효하지 않습니다.", - data: { - extension: "string" - } - }, - success: null - } - } - } - } - } - }; - #swagger.responses[409] = { - description: "폴더명 중복 에러", - content: { - "application/json": { - schema: { - type: "object", - properties: { - resultType: { type: "string", example: "FAIL" }, - error: { - type: "object", - properties: { - errorCode: { type: "string", example: "FOL-409" }, - reason: { type: "string", example: "이미 존재하는 폴더 이름입니다." }, - data: { - type: "object", - properties: { - folderName: { type: "string" }, - } - } - } - }, - success: { type: "object", nullable: true, example: null }, - } - } - } - } - }; - */ - 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, - res: Response, - next: NextFunction, -): Promise => { - /* - #swagger.tags = ['memo-folder-controller'] - #swagger.summary = '폴더 생성 API'; - #swagger.description = '폴더를 생성하는 API입니다.' - #swagger.requestBody = { - required: true, - content: { - "application/json": { - schema: { - type: "object", - required: ['folderName'], - properties: { - folderName: { type: "string", description: "폴더 이름" } - } - } - } - } - }; - #swagger.responses[200] = { - description: "폴더 생성 성공 응답", - content: { - "application/json": { - schema: { - type: "object", - properties: { - resultType: { type: "string", example: "SUCCESS" }, - error: { type: "object", nullable: true, example: null }, - success: { - type: "object", - properties: { - id: { type: "string", example: "1" }, - folderName: { type: "string" }, - } - } - } - } - } - } - }; - #swagger.responses[400] = { - description: "사진 추가 에러", - content: { - "application/json": { - schema: { - type: "object", - properties: { - resultType: { type: "string", example: "FAIL" }, - error: { - type: "object", - properties: { - errorCode: { type: "string", example: "FOL-400" }, - reason: { type: "string", example: "폴더 생성 중 오류가 발생했습니다." }, - data: { - type: "object", - properties: { - userId: { type: "string", example: "1" }, - folderName: { type: "string" }, - } - } - } - }, - success: { type: "object", nullable: true, example: null }, - } - }, - } - } - }; - #swagger.responses[409] = { - description: "폴더명 중복 에러", - content: { - "application/json": { - schema: { - type: "object", - properties: { - resultType: { type: "string", example: "FAIL" }, - error: { - type: "object", - properties: { - errorCode: { type: "string", example: "FOL-409" }, - reason: { type: "string", example: "이미 존재하는 폴더 이름입니다." }, - data: { - type: "object", - properties: { - folderName: { type: "string" }, - } - } - } - }, - success: { type: "object", nullable: true, example: null }, - } - } - } - } - }; - */ - 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 ( - req: Request, - res: Response, - next: NextFunction, -): Promise => { - /* - #swagger.tags = ['memo-folder-controller'] - #swagger.summary = '모든 메모 조회 API'; - #swagger.description = '모든 메모를 조회하는 API입니다.' - #swagger.responses[200] = { - description: "메모 조회 성공 응답", - content: { - "application/json": { - schema: { - type: "object", - properties: { - resultType: { type: "string", example: "SUCCESS" }, - error: { type: "object", nullable: true, example: null }, - success: { - type: "object", - properties: { - data: { - type: "array", - items: { - type: "object", - properties: { - folderId: { type: "string", example: "1" }, - folderName: { type: "string" }, - imageText: { type: "string" }, - imageCount: { type: "integer", example: 0 }, - firstImageId: { type: "string", example: "1" }, - firstImageUrl: { type: "string" }, - createdAt: { type: "string", example: "2025-01-17T03:50:25.923Z"} - } - } - } - } - } - } - } - } - } - }; - */ - 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 ( - req: Request, - res: Response, - next: NextFunction, -): Promise => { - /* - #swagger.tags = ['memo-folder-controller'] - #swagger.summary = '메모 검색 API'; - #swagger.description = '메모를 검색 및 조회하는 API입니다.' - #swagger.parameters['keyword'] = { - in: 'query', - required: true, - description: "검색 키워드", - '@schema': { - type: "string", - } - }; - #swagger.responses[200] = { - description: "메모 검색 성공 응답", - content: { - "application/json": { - schema: { - type: "object", - properties: { - resultType: { type: "string", example: "SUCCESS" }, - error: { type: "object", nullable: true, example: null }, - success: { - type: "object", - properties: { - data: { - type: "array", - items: { - type: "object", - properties: { - folderId: { type: "string", example: "1" }, - folderName: { type: "string" }, - imageCount: { type: "integer", example: 0 }, - imageText: { type: "string" }, - firstImageId: { type: "string", example: "1" }, - firstImageUrl: { type: "string" }, - createdAt: { type: "string", example: "2025-01-17T03:50:25.923Z"} - } - } - } - } - } - } - } - } - } - }; - #swagger.responses[400] = { - description: "유효하지 않은 입력 데이터 에러", - content: { - "application/json": { - schema: { - type: "object", - properties: { - resultType: { type: "string", example: "FAIL" }, - error: { - type: "object", - properties: { - errorCode: { type: "string", example: "SRH-400" }, - reason: { type: "string", example: "입력 데이터가 유효하지 않습니다." }, - data: { - type: "object", - properties: { - reason: { type: "string", example: "검색어를 1자 이상 입력하세요." }, - } - } - } - }, - success: { type: "object", nullable: true, example: null }, - } - } - } - } - }; - */ - 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 => { - /* - #swagger.tags = ['memo-image-controller'] - #swagger.summary = '사진 삭제 API'; - #swagger.description = '특정 폴더의 사진을 삭제하는 API입니다.' - #swagger.parameters['folderId'] = { - in: 'path', - required: true, - description: "폴더 ID 입력", - '@schema': { - type: "integer", - format: "int64" - } - }; - #swagger.requestBody = { - required: true, - content: { - "application/json": { - schema: { - type: "object", - required: ['imageId'], - properties: { - imageId: { type: "array", items: { type: "integer" } } - } - } - } - } - }; - #swagger.responses[200] = { - description: "사진 삭제 성공 응답", - content: { - "application/json": { - schema: { - type: "object", - properties: { - resultType: { type: "string", example: "SUCCESS" }, - error: { type: "object", nullable: true, example: null }, - success: { - type: "object", - properties: { - folderId: { type: "string", example: "1" }, - folderName: { type: "string" }, - imageText: { type: "string" }, - images: { - type: "array", - items: { - type: "object", - properties: { - imageId: { type: "string", example: "1"}, - imageUrl: { type: "string" } - } - } - } - } - } - } - } - } - } - }; - #swagger.responses[404] = { - description: "조회할 수 없는 데이터 에러", - content: { - "application/json": { - schema: { - type: "object", - properties: { - resultType: { type: "string", example: "FAIL" }, - error: { - type: "object", - properties: { - errorCode: { type: "string", example: "404" }, - reason: { type: "string" }, - data: {}, - } - }, - success: { type: "object", nullable: true, example: null }, - } - }, - examples: { - "폴더 조회 에러": { - summary: "폴더 조회 에러", - description: "해당 폴더를 찾을 수 없습니다.", - value: { - resultType: "FAIL", - error: { - errorCode: "FOL-404", - reason: "해당 폴더를 찾을 수 없습니다.", - data: { - folderId: "1", - } - }, - success: null - } - }, - "사진 조회 에러": { - summary: "사진 조회 에러", - description: "해당 사진 데이터가 없습니다.", - value: { - resultType: "FAIL", - error: { - errorCode: "PHO-404", - reason: "해당 사진 데이터가 없습니다.", - data: { - imageId: "1" - } - }, - success: null - } - }, - } - } - } - }; - */ - 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, - res: Response, - next: NextFunction, -): Promise => { - /* - #swagger.tags = ['memo-folder-controller'] - #swagger.summary = '특정 폴더의 메모 조회 API'; - #swagger.description = '특정 폴더의 모든 메모(텍스트 및 사진)을 조회하는 API입니다.' - #swagger.parameters['folderId'] = { - in: 'path', - required: true, - description: "폴더 ID 입력", - '@schema': { - type: "integer", - format: "int64" - } - }; - #swagger.responses[200] = { - description: "메모 조회 성공 응답", - content: { - "application/json": { - schema: { - type: "object", - properties: { - resultType: { type: "string", example: "SUCCESS" }, - error: { type: "object", nullable: true, example: null }, - success: { - type: "object", - properties: { - folderId: { type: "string", example: "1" }, - folderName: { type: "string" }, - imageText: { type: "string" }, - images: { - type: "array", - items: { - type: "object", - properties: { - imageId: {type: "string", example: "1"}, - imageUrl: {type: "string" } - } - } - } - } - } - } - } - } - } - }; - #swagger.responses[404] = { - description: "폴더 조회 에러", - content: { - "application/json": { - schema: { - type: "object", - properties: { - resultType: { type: "string", example: "FAIL" }, - error: { - type: "object", - properties: { - errorCode: { type: "string", example: "FOL-404" }, - reason: { type: "string", example: "해당 폴더를 찾을 수 없습니다." }, - data: { - type: "object", - properties: { - folderId: { type: "string", example: "1" }, - } - } - } - }, - success: { type: "object", nullable: true, example: null }, - } - } - } - } - }; - */ - 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 => { - /* - #swagger.tags = ['memo-folder-controller'] - #swagger.summary = '메모 폴더 이름 수정 API'; - #swagger.description = '특정 폴더의 이름을 수정하는 API입니다.' - #swagger.parameters['folderId'] = { - in: 'path', - required: true, - description: "폴더 ID 입력", - '@schema': { - type: "integer", - format: "int64" - } - }; - #swagger.requestBody = { - required: true, - content: { - "application/json": { - schema: { - type: "object", - required: ['folderName'], - properties: { - folderName: { type: "string", description: "폴더 이름" } - } - } - } - } - }; - #swagger.responses[200] = { - description: "폴더 이름 수정 성공 응답", - content: { - "application/json": { - schema: { - type: "object", - properties: { - resultType: { type: "string", example: "SUCCESS" }, - error: { type: "object", nullable: true, example: null }, - success: { - type: "object", - properties: { - folderId: { type: "string", example: "1" }, - folderName: { type: "string" }, - imageText: { type: "string" }, - images: { - type: "array", - items: { - type: "object", - properties: { - imageId: {type: "string", example: "1"}, - imageUrl: {type: "string" } - } - } - } - } - } - } - } - } - } - }; - #swagger.responses[404] = { - description: "폴더 조회 에러", - content: { - "application/json": { - schema: { - type: "object", - properties: { - resultType: { type: "string", example: "FAIL" }, - error: { - type: "object", - properties: { - errorCode: { type: "string", example: "FOL-404" }, - reason: { type: "string", example: "해당 폴더를 찾을 수 없습니다." }, - data: { - type: "object", - properties: { - folderId: { type: "string", example: "1" }, - } - } - } - }, - success: { type: "object", nullable: true, example: null }, - } - } - } - } - }; - #swagger.responses[409] = { - description: "폴더명 중복 에러", - content: { - "application/json": { - schema: { - type: "object", - properties: { - resultType: { type: "string", example: "FAIL" }, - error: { - type: "object", - properties: { - errorCode: { type: "string", example: "FOL-409" }, - reason: { type: "string", example: "이미 존재하는 폴더 이름입니다." }, - data: { - type: "object", - properties: { - folderName: { type: "string" }, - } - } - } - }, - success: { type: "object", nullable: true, example: null }, - } - } - } - } - }; - #swagger.responses[400] = { - description: "유효하지 않은 데이터 에러", - content: { - "application/json": { - schema: { - type: "object", - properties: { - resultType: { type: "string", example: "FAIL" }, - error: { - type: "object", - properties: { - errorCode: { type: "string", example: "400" }, - reason: { type: "string" }, - data: {}, - } - }, - success: { type: "object", nullable: true, example: null }, - } - }, - examples: { - "폴더명 업데이트 에러": { - summary: "폴더명 업데이트 에러", - description: "폴더 업데이트 중 오류가 발생했습니다.", - value: { - resultType: "FAIL", - error: { - errorCode: "FOL-400", - reason: "폴더 업데이트 중 오류가 발생했습니다.", - data: { - folderId: "1", - } - }, - success: null - } - }, - "변경 전과 동일한 폴더명 에러": { - summary: "변경 전과 동일한 폴더명 에러", - description: "변경 전의 폴더 이름과 같습니다.", - value: { - resultType: "FAIL", - error: { - errorCode: "FOL-400", - reason: "변경 전의 폴더 이름과 같습니다.", - data: { - folderName: "string" - } - }, - success: null - } - } - } - } - } - }; - */ - 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 => { - /* - #swagger.tags = ['memo-folder-controller'] - #swagger.summary = '특정 폴더의 메모 텍스트 수정 API'; - #swagger.description = '특정 폴더의 메모 텍스트를 수정하는 API입니다.' - #swagger.parameters['folderId'] = { - in: 'path', - required: true, - description: "폴더 ID 입력", - '@schema': { - type: "integer", - format: "int64" - } - }; - #swagger.requestBody = { - required: true, - content: { - "application/json": { - schema: { - type: "object", - required: ['memoText'], - properties: { - memoText: { type: "string", description: "메모 텍스트" } - } - } - } - } - }; - #swagger.responses[200] = { - description: "메모 텍스트 수정 응답", - content: { - "application/json": { - schema: { - type: "object", - properties: { - resultType: { type: "string", example: "SUCCESS" }, - error: { type: "object", nullable: true, example: null }, - success: { - type: "object", - properties: { - folderId: { type: "string", example: "1" }, - folderName: { type: "string" }, - imageText: { type: "string" }, - images: { - type: "array", - items: { - type: "object", - properties: { - imageId: {type: "string", example: "1"}, - imageUrl: {type: "string" } - } - } - } - } - } - } - } - } - } - }; - #swagger.responses[404] = { - description: "폴더 조회 에러", - content: { - "application/json": { - schema: { - type: "object", - properties: { - resultType: { type: "string", example: "FAIL" }, - error: { - type: "object", - properties: { - errorCode: { type: "string", example: "FOL-404" }, - reason: { type: "string", example: "해당 폴더를 찾을 수 없습니다." }, - data: { - type: "object", - properties: { - folderId: { type: "string", example: "1" }, - } - } - } - }, - success: { type: "object", nullable: true, example: null }, - } - } - } - } - }; - */ - 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 deleted file mode 100644 index bc7ca65..0000000 --- a/src/controllers/memo-image.controller.ts +++ /dev/null @@ -1,425 +0,0 @@ -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, - res: Response, - next: NextFunction, -): Promise => { - /* - #swagger.tags = ['memo-image-controller'] - #swagger.summary = '사진 저장 API'; - #swagger.description = '특정 폴더에 사진을 저장하는 API입니다.' - #swagger.parameters['folderId'] = { - in: 'path', - required: true, - description: "폴더 ID 입력", - '@schema': { - type: "integer", - format: "int64" - } - }; - #swagger.requestBody = { - required: true, - content: { - "multipart/form-data": { - schema: { - type: "object", - properties: { - image: { - type: "string", - format: "binary", - description: "파일 업로드" - } - } - } - } - } - }; - #swagger.responses[200] = { - description: "사진 저장 성공 응답", - content: { - "application/json": { - schema: { - type: "object", - properties: { - resultType: { type: "string", example: "SUCCESS" }, - error: { type: "object", nullable: true, example: null }, - success: { - type: "object", - properties: { - folderId: { type: "string", example: "1" }, - folderName: { type: "string" }, - imageId: { type: "string", example: "1" }, - imageUrl: { type: "string" }, - } - } - } - } - } - } - }; - #swagger.responses[404] = { - description: "조회할 수 없는 데이터 에러", - content: { - "application/json": { - schema: { - type: "object", - properties: { - resultType: { type: "string", example: "FAIL" }, - error: { - type: "object", - properties: { - errorCode: { type: "string", example: "404" }, - reason: { type: "string" }, - data: {}, - } - }, - success: { type: "object", nullable: true, example: null }, - } - }, - examples: { - "폴더 조회 에러": { - summary: "폴더 조회 에러", - description: "해당 폴더를 찾을 수 없습니다.", - value: { - resultType: "FAIL", - error: { - errorCode: "FOL-404", - reason: "해당 폴더를 찾을 수 없습니다.", - data: { - folderId: "1", - } - }, - success: null - } - }, - "사진 조회 에러": { - summary: "사진 조회 에러", - description: "해당 사진 데이터가 없습니다.", - value: { - resultType: "FAIL", - error: { - errorCode: "PHO-404", - reason: "해당 사진 데이터가 없습니다.", - data: { - reason: "저장할 사진이 없습니다." - } - }, - success: null - } - }, - } - } - } - }; - #swagger.responses[400] = { - description: "유효하지 않은 데이터 에러", - content: { - "application/json": { - schema: { - type: "object", - properties: { - resultType: { type: "string", example: "FAIL" }, - error: { - type: "object", - properties: { - errorCode: { type: "string", example: "400" }, - reason: { type: "string" }, - data: {}, - } - }, - success: { type: "object", nullable: true, example: null }, - } - }, - examples: { - "사진 추가 에러": { - summary: "사진 추가 에러", - description: "메모 사진 추가 중 오류가 발생했습니다.", - value: { - resultType: "FAIL", - error: { - errorCode: "MEM-400", - reason: "메모 사진 추가 중 오류가 발생했습니다.", - data: { - folderId: "1", - imageUrl: "string" - } - }, - success: null - } - }, - "유효하지 않은 확장자 에러": { - summary: "유효하지 않은 확장자 에러", - description: "이미지 확장자가 유효하지 않습니다.", - value: { - resultType: "FAIL", - error: { - errorCode: "PHO-400", - reason: "사진 데이터가 유효하지 않습니다.", - data: { - extension: "string" - } - }, - success: null - } - } - } - } - } - }; - */ - 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 => { - /* - #swagger.tags = ['memo-image-controller'] - #swagger.summary = '사진 이동 API'; - #swagger.description = '특정 폴더의 사진을 이동하는 API입니다.' - #swagger.requestBody = { - required: true, - content: { - "application/json": { - schema: { - type: "object", - required: ['folderId', 'imageId'], - properties: { - targetFolderId: { type: "integer" }, - imageId: { type: "array", items: { type: "integer" } } - } - } - } - } - }; - #swagger.responses[200] = { - description: "사진 이동 성공 응답", - content: { - "application/json": { - schema: { - type: "object", - properties: { - resultType: { type: "string", example: "SUCCESS" }, - error: { type: "object", nullable: true, example: null }, - success: { - type: "object", - properties: { - folderId: { type: "string", example: "1" }, - folderName: { type: "string" }, - imageText: { type: "string" }, - images: { - type: "array", - items: { - type: "object", - properties: { - imageId: { type: "string", example: "1"}, - imageUrl: { type: "string" } - } - } - } - } - } - } - } - } - } - }; - #swagger.responses[404] = { - description: "조회할 수 없는 데이터 에러", - content: { - "application/json": { - schema: { - type: "object", - properties: { - resultType: { type: "string", example: "FAIL" }, - error: { - type: "object", - properties: { - errorCode: { type: "string", example: "404" }, - reason: { type: "string" }, - data: {}, - } - }, - success: { type: "object", nullable: true, example: null }, - } - }, - examples: { - "폴더 조회 에러": { - summary: "폴더 조회 에러", - description: "해당 폴더를 찾을 수 없습니다.", - value: { - resultType: "FAIL", - error: { - errorCode: "FOL-404", - reason: "해당 폴더를 찾을 수 없습니다.", - data: { - folderId: "1", - } - }, - success: null - } - }, - "사진 조회 에러": { - summary: "사진 조회 에러", - description: "해당 사진 데이터가 없습니다.", - value: { - resultType: "FAIL", - error: { - errorCode: "PHO-404", - reason: "해당 사진 데이터가 없습니다.", - data: { - imageId: "1" - } - }, - success: null - } - }, - } - } - } - }; - #swagger.responses[400] = { - description: "사진 이동 에러", - content: { - "application/json": { - schema: { - type: "object", - properties: { - resultType: { type: "string", example: "FAIL" }, - error: { - type: "object", - properties: { - errorCode: { type: "string", example: "MEM-400" }, - reason: { type: "string", example: "메모 사진 이동 중 오류가 발생했습니다." }, - data: { - type: "object", - properties: { - folderId: { type: "string", example: "1" }, - imageId: { - type: "array", - items: { - type: "string", - example: "1" - } - } - } - } - } - }, - success: { type: "object", nullable: true, example: null }, - } - } - } - } - }; - */ - 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 => { - /* - #swagger.tags = ['memo-folder-controller'] - #swagger.summary = '폴더 삭제 API'; - #swagger.description = '특정 폴더를 삭제하는 API입니다.' - #swagger.parameters['folderId'] = { - in: 'path', - required: true, - description: "폴더 ID 입력", - '@schema': { - type: "integer", - format: "int64" - } - }; - #swagger.responses[200] = { - description: "폴더 삭제 성공 응답", - content: { - "application/json": { - schema: { - type: "object", - properties: { - resultType: { type: "string", example: "SUCCESS" }, - error: { type: "object", nullable: true, example: null }, - success: { - type: "object", - properties: { - message: { type: "string" } - } - } - } - } - } - } - }; - #swagger.responses[404] = { - description: "폴더 조회 에러", - content: { - "application/json": { - schema: { - type: "object", - properties: { - resultType: { type: "string", example: "FAIL" }, - error: { - type: "object", - properties: { - errorCode: { type: "string", example: "FOL-404" }, - reason: { type: "string", example: "해당 폴더를 찾을 수 없습니다." }, - data: { - type: "object", - properties: { - folderId: { type: "string", example: "1" }, - } - } - } - }, - success: { type: "object", nullable: true, example: null }, - } - } - } - } - }; - */ - 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/memo-updateFolderOCR.Controller.ts b/src/controllers/memo-updateFolderOCR.Controller.ts index 8553bf1..fdf2eb5 100644 --- a/src/controllers/memo-updateFolderOCR.Controller.ts +++ b/src/controllers/memo-updateFolderOCR.Controller.ts @@ -1,144 +1,172 @@ -import {NextFunction, Request, Response} from 'express'; +import {Request as ExpressRequest} from 'express'; import {processOCRAndSave} from '../services/memo-ocrService.js'; +import { + DataValidationError, + PhotoDataNotFoundError, + PhotoValidationError, +} from '../errors.js'; +import { + ITsoaErrorResponse, + ITsoaSuccessResponse, + TsoaSuccessResponse, +} from '../models/tsoaResponse.js'; +import { + Controller, + Route, + Request, + Path, + Tags, + Response, + SuccessResponse, + Example, + Patch, +} from 'tsoa'; import {StatusCodes} from 'http-status-codes'; -import {DataValidationError, PhotoValidationError} from '../errors.js'; -export const updateFolderOCR = async ( - req: Request, - res: Response, - next: NextFunction, -): Promise => { - /* - #swagger.tags = ['memo-ai'] - #swagger.summary = '폴더 업데이트 및 OCR 수행' - #swagger.description = '기존 폴더에 이미지를 추가하고, OCR 텍스트를 업데이트하는 API입니다.' - #swagger.parameters['folderId'] = { - in: 'path', - required: true, - description: "업데이트할 폴더의 ID", - '@schema': { - type: "number", - example: 1 - } - } - #swagger.requestBody = { - required: true, - content: { - "multipart/form-data": { - schema: { - type: "object", - required: ["base64_image"], - properties: { - base64_image: { - type: "string", - description: "OCR 처리를 위한 이미지 URL", - example: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUA..." - } - - } - } - } - } - } - #swagger.responses[200] = { - description: "폴더 업데이트 및 텍스트 변환", - content: { - "application/json": { - schema: { - type: "object", - properties: { - resultType: { type: "string", example: "SUCCESS" }, - error: { type: "object", nullable: true, example: null }, - success: { - type: "object", - properties: { - folder_id: { type: "string", example: "1" }, - image_text: { type: "string", example: "이번 수업 시간은 사회 과학 시간이다." }, - - } - } - } - } - } - } - } - #swagger.responses[400] = { - description: "잘못된 요청 데이터", - content: { - "application/json": { - schema: { - type: "object", - properties: { - error: { - type: "string", - example: "image_url이 존재하지 않습니다." - } - } - } - } - } - } - #swagger.responses[500] = { - description: "서버 내부 오류", - content: { - "application/json": { - schema: { - type: "object", - properties: { - error: { - type: "string", - example: "서버 에러 발생." - } - } - } - } - } - } - */ - try { - let base64_image: string | undefined = req.body.base64_image; - // 파일이 존재하는 경우 base64 변환 - if (req.file) { - base64_image = `data:image/png;base64,${req.file.buffer.toString('base64')}`; - } +@Route('memo') +export class MemoCreateFolderOCRController extends Controller { + /** + * 기존 폴더에 이미지를 추가하고, OCR 텍스트를 업데이트하는 API입니다. + * + * @summary 폴더 업데이트 및 OCR 수행 + * @param req + * @param base64Image OCR 처리를 위한 이미지 URL + * @param folderId 업데이트할 폴더의 ID + * @returns 성공 시 특정 폴더에 텍스트를 저장한 결과를 반환합니다. + */ + @Patch('/text-format/folders/:folderId') + @Tags('memo-ai') + @Response(StatusCodes.BAD_REQUEST, '잘못된 요청 데이터', { + resultType: 'FAIL', + success: null, + error: { + errorCode: 'SRH-400', + reason: '입력 데이터가 유효하지 않습니다.', + data: {reason: 'folder_ID가 필요합니다.'}, + }, + }) + @Response(StatusCodes.BAD_REQUEST, '잘못된 요청 데이터', { + resultType: 'FAIL', + success: null, + error: { + errorCode: 'SRH-400', + reason: '입력 데이터가 유효하지 않습니다.', + data: {reason: 'base64_image가 필요합니다.'}, + }, + }) + @Response(StatusCodes.BAD_REQUEST, '잘못된 요청 데이터', { + resultType: 'FAIL', + success: null, + error: { + errorCode: 'SRH-400', + reason: '입력 데이터가 유효하지 않습니다.', + data: {reason: 'user_id가 필요합니다.'}, + }, + }) + @Response(StatusCodes.BAD_REQUEST, '잘못된 요청 데이터', { + resultType: 'FAIL', + success: null, + error: { + errorCode: 'PHO-400', + reason: '사진 데이터가 유효하지 않습니다.', + data: {reason: '올바른 Base64 이미지 형식이 아닙니다.'}, + }, + }) + @Response(StatusCodes.BAD_REQUEST, '잘못된 요청 데이터', { + resultType: 'FAIL', + success: null, + error: { + errorCode: 'PHO-400', + reason: '사진 데이터가 유효하지 않습니다.', + data: {reason: '텍스트를 추출할 사진이 없습니다.'}, + }, + }) + @Response(StatusCodes.BAD_REQUEST, '잘못된 요청 데이터', { + resultType: 'FAIL', + success: null, + error: { + errorCode: 'PHO-400', + reason: '사진 데이터가 유효하지 않습니다.', + data: {reason: '이미지에서 텍스트를 찾지 못하였습니다.'}, + }, + }) + @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: { + folder_id: '1', + image_text: '이번 수업 시간은 사회 과학 시간이다.', + }, + }) + public async updateFolderOCR( + @Request() req: ExpressRequest, + @Path('folderId') folderId: number, + ): Promise> { + try { + if (!req.file) { + throw new PhotoDataNotFoundError({ + reason: '텍스트를 추출할 사진이 없습니다.', + }); + } - const {folderId} = req.params; // URL 매개변수에서 folderId 가져오기 - const user_id = BigInt(req.user!.id); + // 파일이 존재하는 경우 base64 변환 + const base64_image = `data:image/png;base64,${req.file.buffer.toString('base64')}`; - // 유효성 검사 - if (!folderId) { - throw new DataValidationError({ - reason: 'folder_ID가 필요합니다.', - }); - } + //const {folderId} = req.params; // URL 매개변수에서 folderId 가져오기 + const user_id = BigInt(req.user!.id); - if (!base64_image) { - throw new DataValidationError({ - reason: 'base64_image가 필요합니다.', + // 유효성 검사 + if (!folderId) { + throw new DataValidationError({ + reason: 'folder_ID가 필요합니다.', + }); + } + + if (!base64_image) { + throw new DataValidationError({ + reason: 'base64_image가 필요합니다.', + }); + } + + if (!user_id) { + throw new DataValidationError({reason: 'user_id가 필요합니다.'}); + } + //bae64 이미지 형태 검증 + if (!isValidBase64(base64_image)) { + throw new PhotoValidationError({ + reason: '올바른 Base64 이미지 형식이 아닙니다.', + }); + } + + const result = await processOCRAndSave({ + folder_id: Number(folderId), + base64_image, + user_id, }); - } - if (!user_id) { - throw new DataValidationError({reason: 'user_id가 필요합니다.'}); - } - //bae64 이미지 형태 검증 - if (!isValidBase64(base64_image)) { - throw new PhotoValidationError({ - reason: '올바른 Base64 이미지 형식이 아닙니다.', + return new TsoaSuccessResponse({ + folder_id: result.folder_id, + image_text: result.image_text, }); + } catch (error) { + throw error; } - - const result = await processOCRAndSave({ - folder_id: Number(folderId), - base64_image, - user_id, - }); - - res.status(StatusCodes.OK).success(result); - } catch (error) { - next(error); } -}; +} const isValidBase64 = (base64String: string): boolean => { // MIME 타입 검증 (jpeg, png, jpg 허용) diff --git a/src/controllers/tsoa.memo-folder.controller.ts b/src/controllers/tsoa.memo-folder.controller.ts index ef0b4a8..2680553 100644 --- a/src/controllers/tsoa.memo-folder.controller.ts +++ b/src/controllers/tsoa.memo-folder.controller.ts @@ -30,7 +30,6 @@ import { Patch, Example, FormField, - UploadedFile, } from 'tsoa'; import { BodyToMemoFolder, @@ -144,14 +143,14 @@ export class MemoFolderController extends Controller { public async handlerMemoFolderImageAdd( @Request() req: ExpressRequest, @FormField() folderName: string, - @UploadedFile() image: Express.MulterS3File, ): Promise> { try { const userId = BigInt(req.user!.id); - if (!image) { + + if (!req.file) { throw new PhotoValidationError({reason: '저장할 사진이 없습니다.'}); } - const imageUrl = image.key; + const imageUrl = (req.file as Express.MulterS3File).key; const folderId = req.uploadDirectory; const memoFolderImage = await memoFolderImageCreate( userId, diff --git a/src/controllers/tsoa.memo-image.controller.ts b/src/controllers/tsoa.memo-image.controller.ts index 5cf1375..b8f369c 100644 --- a/src/controllers/tsoa.memo-image.controller.ts +++ b/src/controllers/tsoa.memo-image.controller.ts @@ -18,7 +18,6 @@ import { Tags, Example, Post, - UploadedFile, } from 'tsoa'; import { ITsoaErrorResponse, @@ -121,15 +120,14 @@ export class MemoImageController extends Controller { 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) { + if (!req.file) { throw new PhotoDataNotFoundError({reason: '저장할 사진이 없습니다.'}); } - const imageUrl = image.key; + const imageUrl = (req.file as Express.MulterS3File).key; const memoImage = await memoImageAdd(folderId, imageUrl, userId); return new TsoaSuccessResponse(memoImage); } catch (error) { diff --git a/src/dtos/memo-folder.dto.ts b/src/dtos/memo-folder.dto.ts deleted file mode 100644 index f87caf4..0000000 --- a/src/dtos/memo-folder.dto.ts +++ /dev/null @@ -1,108 +0,0 @@ -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 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.ts b/src/dtos/memo-image.dto.ts deleted file mode 100644 index 45fd173..0000000 --- a/src/dtos/memo-image.dto.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { BodyToMemoImage, BodyToMemoImagesToDelete, BodyToMemoImagesToMove, MemoImageRequestDto } from '../models/memo-image.model.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 - }; -}; \ No newline at end of file diff --git a/src/models/memo-folder.model.ts b/src/models/memo-folder.model.ts deleted file mode 100644 index 8520dba..0000000 --- a/src/models/memo-folder.model.ts +++ /dev/null @@ -1,171 +0,0 @@ -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.ts b/src/models/memo-image.model.ts deleted file mode 100644 index 335c5f1..0000000 --- a/src/models/memo-image.model.ts +++ /dev/null @@ -1,34 +0,0 @@ -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: bigint; - imageId: bigint[]; -} - -export interface BodyToMemoImagesToDelete { - imageId: bigint[]; -} \ No newline at end of file diff --git a/src/repositories/memo-folder.repository.ts b/src/repositories/memo-folder.repository.ts deleted file mode 100644 index eeb64b2..0000000 --- a/src/repositories/memo-folder.repository.ts +++ /dev/null @@ -1,312 +0,0 @@ -import { prisma } from '../db.config.js'; -import { BodyToMemoFolder, BodyToMemoTextToUpdate, MemoFolderList, MemoFolderType, MemoFoler, MemoTextImageList, ResponseMessage } from '../models/memo-folder.model.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; -}; \ No newline at end of file diff --git a/src/repositories/memo-image.repository.ts b/src/repositories/memo-image.repository.ts deleted file mode 100644 index 2c14b6d..0000000 --- a/src/repositories/memo-image.repository.ts +++ /dev/null @@ -1,120 +0,0 @@ -import { prisma } from '../db.config.js'; -import { ResponseMessage } from '../models/memo-folder.model.js'; -import { BodyToMemoImagesToDelete, BodyToMemoImagesToMove, MemoImage } from '../models/memo-image.model.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 image = await prisma.memoImage.findFirst({ - where: { - id: imgId, - folderId - }, - }); - if (image === null) { - return imgId; - } - } - - for (const imgId of body.imageId) { - const image = await prisma.memoImage.update({ - where: { - id: imgId, - folderId - }, - data: { - folderId: body.targetFolderId, - updatedAt: new Date() - } - }); - - imageMover(userId, image.url, body.targetFolderId); - } - return { message: '성공적으로 이동하였습니다'}; -}; - -export const deleteMemoImages = async (userId: bigint, folderId: bigint, data: BodyToMemoImagesToDelete) :Promise=> { - for (const imgId of data.imageId) { - const image = await prisma.memoImage.findFirst({ - where: { - id: imgId, - folderId, - memoFolder: { - userId - } - } - }); - - if (image === null) { return imgId; } - } - for (const imgId of data.imageId) { - const image = await prisma.memoImage.findFirst({ - where: { - id: imgId, - folderId, - memoFolder: { - userId - } - } - }); - - if (image === null) { return null; } - - imageDeleter(image.url); - - await prisma.memoImage.delete({ - where: { - id: imgId - } - }); - } -}; - -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() - } - }); -}; \ No newline at end of file diff --git a/src/routers/memo.router.ts b/src/routers/memo.router.ts deleted file mode 100644 index 983d131..0000000 --- a/src/routers/memo.router.ts +++ /dev/null @@ -1,68 +0,0 @@ -import express from 'express'; -export const memoFolderRouter = express.Router(); - -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 { - handlerMemoFolderAdd, - handlerMemoFolderImageCreate, - handlerMemoFolderList, - handlerMemoFolderUpdate, - handlerMemoImageDelete, - handlerMemoSearch, - handlerMemoTextImageList, - handlerMemoTextUpdate, -} from '../controllers/memo-folder.controller.js'; -import {imageUploader} from '../s3/image.uploader.js'; -import upload from '../ai/ai-upload.js'; - -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/not-tsoa', - imageUploader.single('image'), - handlerMemoFolderImageCreate, -); -memoFolderRouter.post( - '/image-format/folders/:folderId/not-tsoa', - imageUploader.single('image'), - handlerMemoImageAdd, -); - -memoFolderRouter.get('/list/not-tsoa', handlerMemoFolderList); -memoFolderRouter.get('/search/not-tsoa', handlerMemoSearch); - -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/not-tsoa', handlerMemoFolderDelete); - -// OCR 요청 처리 추가 -memoFolderRouter.post( - '/text-format/folders', - upload.single('base64_image'), // OCR을 위한 파일 업로드 - createFolderOCR, -); // 새 폴더의 OCR 처리 - -memoFolderRouter.patch( - '/text-format/folders/:folderId', - upload.single('base64_image'), // OCR을 위한 파일 업로드 - updateFolderOCR, -); // 기존 폴더의 OCR 처리 diff --git a/src/routers/tsoaRoutes.ts b/src/routers/tsoaRoutes.ts index 2a18ce0..25781e2 100644 --- a/src/routers/tsoaRoutes.ts +++ b/src/routers/tsoaRoutes.ts @@ -12,12 +12,14 @@ import { MemoImageController } from './../controllers/tsoa.memo-image.controller // 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'; // 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 { MemoCreateFolderOCRController } from './../controllers/memo-updateFolderOCR.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 { MemoCreateUpdateOCRController } from './../controllers/memo-createFolderOCR.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 { MostTaggedController } from './../controllers/history.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 { AwardController } from './../controllers/history.controller.js'; import type { Request as ExRequest, Response as ExResponse, RequestHandler, Router } from 'express'; -import multer from 'multer'; - @@ -254,6 +256,16 @@ 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 + "ITsoaSuccessResponse__folder_id-string--image_text-string__": { + "dataType": "refObject", + "properties": { + "resultType": {"dataType":"string","required":true}, + "error": {"dataType":"enum","enums":[null],"required":true}, + "success": {"dataType":"nestedObjectLiteral","nestedProperties":{"image_text":{"dataType":"string","required":true},"folder_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 "ResponseFromMostTagToClient": { "dataType": "refObject", "properties": { @@ -337,14 +349,13 @@ const templateService = new ExpressTemplateService(models, {"noImplicitAdditiona -export function RegisterRoutes(app: Router,opts?:{multer?:ReturnType}) { +export function RegisterRoutes(app: Router) { // ########################################################################################################### // 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 = { @@ -476,15 +487,8 @@ export function RegisterRoutes(app: Router,opts?:{multer?:ReturnType = { 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)), @@ -577,15 +581,8 @@ export function RegisterRoutes(app: Router,opts?:{multer?:ReturnType = { 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)), @@ -831,6 +828,68 @@ export function RegisterRoutes(app: Router,opts?:{multer?:ReturnType = { + req: {"in":"request","name":"req","required":true,"dataType":"object"}, + folderId: {"in":"path","name":"folderId","required":true,"dataType":"double"}, + }; + app.patch('/memo/text-format/folders/:folderId', + ...(fetchMiddlewares(MemoCreateFolderOCRController)), + ...(fetchMiddlewares(MemoCreateFolderOCRController.prototype.updateFolderOCR)), + + async function MemoCreateFolderOCRController_updateFolderOCR(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: argsMemoCreateFolderOCRController_updateFolderOCR, request, response }); + + const controller = new MemoCreateFolderOCRController(); + + await templateService.apiHandler({ + methodName: 'updateFolderOCR', + 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 argsMemoCreateUpdateOCRController_createFolderOCR: Record = { + req: {"in":"request","name":"req","required":true,"dataType":"object"}, + folderName: {"in":"formData","name":"folder_name","required":true,"dataType":"string"}, + }; + app.post('/memo/text-format/folders', + ...(fetchMiddlewares(MemoCreateUpdateOCRController)), + ...(fetchMiddlewares(MemoCreateUpdateOCRController.prototype.createFolderOCR)), + + async function MemoCreateUpdateOCRController_createFolderOCR(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: argsMemoCreateUpdateOCRController_createFolderOCR, request, response }); + + const controller = new MemoCreateUpdateOCRController(); + + await templateService.apiHandler({ + methodName: 'createFolderOCR', + 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 argsMostTaggedController_getMostTagged: Record = { req: {"in":"request","name":"req","required":true,"dataType":"object"}, }; diff --git a/src/s3/image.mover.ts b/src/s3/image.mover.ts index 5fb3e07..1387059 100644 --- a/src/s3/image.mover.ts +++ b/src/s3/image.mover.ts @@ -1,6 +1,6 @@ import {CopyObjectCommand, DeleteObjectCommand} from '@aws-sdk/client-s3'; import {s3} from './awsS3Client.js'; -import {updateMemoImageUrl} from '../repositories/memo-image.repository.js'; +import {updateMemoImageUrl} from '../repositories/memo-image.repository.tsoa.js'; import process from 'process'; // AWS S3에서 특정 이미지의 디렉토리를 변경하는 함수 diff --git a/src/s3/image.uploader.ts b/src/s3/image.uploader.ts index d2397a3..e5feffe 100644 --- a/src/s3/image.uploader.ts +++ b/src/s3/image.uploader.ts @@ -9,8 +9,8 @@ import {s3} from './awsS3Client.js'; import { createMemoFolder, getMemoFolder, -} from '../repositories/memo-folder.repository.js'; -import {bodyToMemoFolder} from '../dtos/memo-folder.dto.js'; +} from '../repositories/memo-folder.repository.tsoa.js'; +import {bodyToMemoFolder} from '../dtos/memo-folder.dto.tsoa.js'; import { FolderDuplicateError, FolderNotFoundError, diff --git a/src/services/memo-folder.service.ts b/src/services/memo-folder.service.ts deleted file mode 100644 index b546189..0000000 --- a/src/services/memo-folder.service.ts +++ /dev/null @@ -1,138 +0,0 @@ -import { - responseFromMemoFolder, - responseFromMemoFolderImage, - responseFromMemoFolderList, - responseFromMemoTextImageList, -} from '../dtos/memo-folder.dto.js'; -import { - FolderCreationError, - FolderDuplicateError, - FolderNameNotChangeError, - FolderNotFoundError, - FolderUpdateError, - MemoImageAdditionError, -} from '../errors.js'; -import { - BodyToMemoFolder, - BodyToMemoTextToUpdate, - MemoFolderImageResponseDto, - MemoFolderListResponseDto, - MemoFolderResponseDto, - MemoTextImageListResponseDto, -} from '../models/memo-folder.model.js'; -import { - createMemoFolder, - getMemoFolder, - getMemoFolderList, - getMemoTextImageList, - getSearchMemoList, - updateMemoFolder, - updateMemoText, -} from '../repositories/memo-folder.repository.js'; -import { - addMemoImage, - getMemoImage, -} from '../repositories/memo-image.repository.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, folderName: body.folderName}); - } - return responseFromMemoFolder(memoFolder); -}; - -export const memoFolderImageCreate = async ( - userId: bigint, - folderId: bigint, - imageUrl: string, - body: BodyToMemoFolder, -): 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: body.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) { - 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) { - throw new FolderNotFoundError({folderId}); - } - const updatedMemoText = await updateMemoText(userId, folderId, body); - return responseFromMemoTextImageList(updatedMemoText); -}; diff --git a/src/services/memo-image.service.ts b/src/services/memo-image.service.ts deleted file mode 100644 index c259a07..0000000 --- a/src/services/memo-image.service.ts +++ /dev/null @@ -1,111 +0,0 @@ -import { - responseFromMessage, - responseFromMemoFolderImage, - responseFromMemoTextImageList, -} from '../dtos/memo-folder.dto.js'; -import { - FolderNotChangeError, - FolderNotFoundError, - MemoImageAdditionError, - MemoImageMoveError, - PhotoDataNotFoundError, -} from '../errors.js'; -import { - ResponseMessage, - MemoFolderImageResponseDto, - MemoTextImageListResponseDto, -} from '../models/memo-folder.model.js'; -import { - BodyToMemoImagesToDelete, - BodyToMemoImagesToMove, -} from '../models/memo-image.model.js'; -import { - deleteMemoFolder, - getMemoFolder, - getMemoTextImageList, -} from '../repositories/memo-folder.repository.js'; -import { - addMemoImage, - deleteMemoImages, - getMemoImage, - moveMemoImages, -} from '../repositories/memo-image.repository.js'; - -export const memoImageAdd = async ( - folderId: bigint, - imageUrl: string, -): Promise => { - const folder = await getMemoFolder(folderId); - if (folder === null) { - 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 checkTargetFolder = await getMemoFolder(body.targetFolderId); - if (checkTargetFolder === null) { - throw new FolderNotFoundError({folderId}); - } - const memoImagesToMove = await moveMemoImages(userId, folderId, body); - if (folderId === body.targetFolderId) { - throw new FolderNotChangeError({folderId}); - } - if ( - typeof memoImagesToMove === 'bigint' || - typeof memoImagesToMove === 'number' - ) { - throw new PhotoDataNotFoundError({imageId: memoImagesToMove.toString()}); - } - const movedMemoImages = await getMemoTextImageList( - userId, - body.targetFolderId, - ); - if (movedMemoImages === null) { - throw new MemoImageMoveError({folderId, imageId: body.imageId}); - } - return responseFromMemoTextImageList(movedMemoImages); -}; - -export const memoImageDelete = async ( - userId: bigint, - folderId: bigint, - body: BodyToMemoImagesToDelete, -): Promise => { - 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 0dfb368..dda0c0b 100644 --- a/swagger/openapi.json +++ b/swagger/openapi.json @@ -104,6 +104,66 @@ } } }, + "/memo/text-format/folders": { + "post": { + "description": "", + "responses": { + "default": { + "description": "" + } + } + } + }, + "/memo/text-format/folders/{folderId}": { + "patch": { + "description": "", + "parameters": [ + { + "name": "folderId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "default": { + "description": "" + } + } + } + }, + "/memo/image-format/folders": { + "post": { + "description": "", + "responses": { + "default": { + "description": "" + } + } + } + }, + "/memo/image-format/folders/{folderId}": { + "post": { + "description": "", + "parameters": [ + { + "name": "folderId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "default": { + "description": "" + } + } + } + }, "/": { "get": { "description": "", @@ -1707,16 +1767,16 @@ } } }, - "/memo/folders/not-tsoa": { - "post": { + "/challenge/update": { + "patch": { "tags": [ - "memo-folder-controller" + "challenge-controller" ], - "summary": "폴더 생성 API", - "description": "폴더를 생성하는 API입니다.", + "summary": "챌린지 수정 API", + "description": "챌린지를 수정하는 API입니다. 수정되는 내용은 정리 장수, 남은 장수 입니다.", "responses": { "200": { - "description": "폴더 생성 성공 응답", + "description": "챌린지 수정 성공 응답", "content": { "application/json": { "schema": { @@ -1738,98 +1798,15 @@ "type": "string", "example": "1" }, - "folderName": { - "type": "string" - } - } - } - } - } - } - } - }, - "400": { - "description": "사진 추가 에러", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "resultType": { - "type": "string", - "example": "FAIL" - }, - "error": { - "type": "object", - "properties": { - "errorCode": { - "type": "string", - "example": "FOL-400" - }, - "reason": { - "type": "string", - "example": "폴더 생성 중 오류가 발생했습니다." - }, - "data": { - "type": "object", - "properties": { - "userId": { - "type": "string", - "example": "1" - }, - "folderName": { - "type": "string" - } - } - } - } - }, - "success": { - "type": "object", - "nullable": true, - "example": null - } - } - } - } - } - }, - "409": { - "description": "폴더명 중복 에러", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "resultType": { - "type": "string", - "example": "FAIL" - }, - "error": { - "type": "object", - "properties": { - "errorCode": { + "required": { "type": "string", - "example": "FOL-409" + "example": 10 }, - "reason": { + "remaining": { "type": "string", - "example": "이미 존재하는 폴더 이름입니다." - }, - "data": { - "type": "object", - "properties": { - "folderName": { - "type": "string" - } - } + "example": 10 } } - }, - "success": { - "type": "object", - "nullable": true, - "example": null } } } @@ -1843,13 +1820,18 @@ "application/json": { "schema": { "type": "object", - "required": [ - "folderName" - ], "properties": { - "folderName": { + "id": { "type": "string", - "description": "폴더 이름" + "description": "챌린지 ID" + }, + "required": { + "type": "number", + "description": "챌린지 장 수" + }, + "remaining": { + "type": "number", + "description": "챌린지 남은 수" } } } @@ -1858,28 +1840,87 @@ } } }, - "/memo/folders/{folderId}/not-tsoa": { + "/challenge/delete/{id}": { + "delete": { + "description": "", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "default": { + "description": "" + } + } + } + }, + "/challenge/accept/{id}": { + "patch": { + "description": "", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "default": { + "description": "" + } + } + } + }, + "/challenge/complete/{id}": { + "patch": { + "description": "", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "default": { + "description": "" + } + } + } + }, + "/challenge/get": { "get": { "tags": [ - "memo-folder-controller" + "challenge-controller" ], - "summary": "특정 폴더의 메모 조회 API", - "description": "특정 폴더의 모든 메모(텍스트 및 사진)을 조회하는 API입니다.", + "summary": "특정 유저의 챌린지 조회 API", + "description": "특정 유저의 모든 챌린지를 조회하는 API입니다.", "parameters": [ { - "name": "folderId", + "name": "userId", "in": "path", "required": true, + "description": "유저 ID 입력", "schema": { - "type": "integer", - "format": "int64" - }, - "description": "폴더 ID 입력" + "type": "string" + } } ], "responses": { "200": { - "description": "메모 조회 성공 응답", + "description": "유저 챌린지 조회 성공 응답", "content": { "application/json": { "schema": { @@ -1895,31 +1936,54 @@ "example": null }, "success": { - "type": "object", - "properties": { - "folderId": { - "type": "string", - "example": "1" - }, - "folderName": { - "type": "string" - }, - "imageText": { - "type": "string" - }, - "images": { - "type": "array", - "items": { - "type": "object", - "properties": { - "imageId": { - "type": "string", - "example": "1" - }, - "imageUrl": { - "type": "string" - } - } + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string", + "example": "1" + }, + "title": { + "type": "string" + }, + "context": { + "type": "string" + }, + "challengeLocation": { + "type": "string" + }, + "challengeDate": { + "type": "string", + "format": "date-time" + }, + "requiredCount": { + "type": "number" + }, + "remainingCount": { + "type": "number" + }, + "userId": { + "type": "string" + }, + "createdAt": { + "type": "string", + "format": "date-time" + }, + "updatedAt": { + "type": "string", + "format": "date-time" + }, + "acceptedAt": { + "type": "string", + "format": "date-time" + }, + "completedAt": { + "type": "string", + "format": "date-time" + }, + "status": { + "type": "number" } } } @@ -1928,73 +1992,43 @@ } } } - }, - "404": { - "description": "폴더 조회 에러", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "resultType": { - "type": "string", - "example": "FAIL" - }, - "error": { - "type": "object", - "properties": { - "errorCode": { - "type": "string", - "example": "FOL-404" - }, - "reason": { - "type": "string", - "example": "해당 폴더를 찾을 수 없습니다." - }, - "data": { - "type": "object", - "properties": { - "folderId": { - "type": "string", - "example": "1" - } - } - } - } - }, - "success": { - "type": "object", - "nullable": true, - "example": null - } - } - } - } - } } + }, + "requestBody": { + "required": false } - }, - "patch": { - "tags": [ - "memo-folder-controller" - ], - "summary": "메모 폴더 이름 수정 API", - "description": "특정 폴더의 이름을 수정하는 API입니다.", + } + }, + "/challenge/location_challenge/get/{id}": { + "get": { + "description": "", "parameters": [ { - "name": "folderId", + "name": "id", "in": "path", "required": true, "schema": { - "type": "integer", - "format": "int64" - }, - "description": "폴더 ID 입력" + "type": "string" + } + } + ], + "responses": { + "default": { + "description": "" } + } + } + }, + "/challenge/location_logic/test": { + "post": { + "tags": [ + "challenge-location-controller" ], + "summary": "위치 기반 챌린지 사진 판별 API", + "description": "위치 기반 챌린지를 위한 사진을 골라내는 API입니다.", "responses": { "200": { - "description": "폴더 이름 수정 성공 응답", + "description": "사진 위치 판별 응답", "content": { "application/json": { "schema": { @@ -2010,31 +2044,34 @@ "example": null }, "success": { - "type": "object", - "properties": { - "folderId": { - "type": "string", - "example": "1" - }, - "folderName": { - "type": "string" - }, - "imageText": { - "type": "string" - }, - "images": { - "type": "array", - "items": { - "type": "object", - "properties": { - "imageId": { - "type": "string", - "example": "1" - }, - "imageUrl": { - "type": "string" - } - } + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string", + "example": "100000001" + }, + "displayName": { + "type": "string", + "example": "20250116_123456.jpg" + }, + "latitude": { + "type": "number", + "example": 37.123456 + }, + "longitude": { + "type": "number", + "example": 127.123456 + }, + "timestamp": { + "type": "string", + "format": "date-time", + "example": "2025-01-16T12:34:56Z" + }, + "location": { + "type": "string", + "example": "wydg0y" } } } @@ -2043,75 +2080,56 @@ } } } - }, - "400": { - "description": "유효하지 않은 데이터 에러", - "content": { - "application/json": { - "schema": { + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { "type": "object", "properties": { - "resultType": { + "id": { "type": "string", - "example": "FAIL" + "description": "사진 ID" }, - "error": { - "type": "object", - "properties": { - "errorCode": { - "type": "string", - "example": "400" - }, - "reason": { - "type": "string" - }, - "data": {} - } + "displayName": { + "type": "string", + "description": "사진 이름" }, - "success": { - "type": "object", - "nullable": true, - "example": null - } - } - }, - "examples": { - "폴더명 업데이트 에러": { - "summary": "폴더명 업데이트 에러", - "description": "폴더 업데이트 중 오류가 발생했습니다.", - "value": { - "resultType": "FAIL", - "error": { - "errorCode": "FOL-400", - "reason": "폴더 업데이트 중 오류가 발생했습니다.", - "data": { - "folderId": "1" - } - }, - "success": null - } - }, - "변경 전과 동일한 폴더명 에러": { - "summary": "변경 전과 동일한 폴더명 에러", - "description": "변경 전의 폴더 이름과 같습니다.", - "value": { - "resultType": "FAIL", - "error": { - "errorCode": "FOL-400", - "reason": "변경 전의 폴더 이름과 같습니다.", - "data": { - "folderName": "string" - } - }, - "success": null + "latitude": { + "type": "number", + "description": "사진 위도" + }, + "longitude": { + "type": "number", + "description": "사진 경도" + }, + "timestamp": { + "type": "string", + "format": "date-time", + "description": "사진 날짜" } } } } } - }, - "404": { - "description": "폴더 조회 에러", + } + } + } + }, + "/challenge/location_challenge/create": { + "post": { + "tags": [ + "challenge-location-controller" + ], + "summary": "위치 기반 챌린지 생성 API", + "description": "위치 기반 챌린지를 생성하는 API입니다.", + "responses": { + "200": { + "description": "위치 챌린지 생성 성공 응답", "content": { "application/json": { "schema": { @@ -2119,76 +2137,65 @@ "properties": { "resultType": { "type": "string", - "example": "FAIL" + "example": "SUCCESS" }, "error": { + "type": "object", + "nullable": true, + "example": null + }, + "success": { "type": "object", "properties": { - "errorCode": { + "id": { "type": "string", - "example": "FOL-404" + "example": "1" }, - "reason": { + "title": { "type": "string", - "example": "해당 폴더를 찾을 수 없습니다." + "example": "challenge-title" }, - "data": { - "type": "object", - "properties": { - "folderId": { - "type": "string", - "example": "1" - } - } + "context": { + "type": "string", + "example": "challenge-context" + }, + "requiredCount": { + "type": "number", + "example": 10 + }, + "remainingCount": { + "type": "number", + "example": 10 + }, + "userId": { + "type": "string", + "example": "1" + }, + "createdAt": { + "type": "string", + "format": "date-time", + "example": "2025-01-20T18:19:47.415Z" + }, + "updatedAt": { + "type": "string", + "format": "date-time", + "example": "2025-01-20T18:19:47.415Z" + }, + "acceptedAt": { + "type": "string", + "format": "date-time", + "example": "2025-01-20T18:19:47.415Z" + }, + "completedAt": { + "type": "string", + "format": "date-time", + "example": "2025-01-20T18:19:47.415Z" + }, + "status": { + "type": "number", + "example": 1 } } - }, - "success": { - "type": "object", - "nullable": true, - "example": null - } - } - } - } - } - }, - "409": { - "description": "폴더명 중복 에러", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "resultType": { - "type": "string", - "example": "FAIL" - }, - "error": { - "type": "object", - "properties": { - "errorCode": { - "type": "string", - "example": "FOL-409" - }, - "reason": { - "type": "string", - "example": "이미 존재하는 폴더 이름입니다." - }, - "data": { - "type": "object", - "properties": { - "folderName": { - "type": "string" - } - } - } - } - }, - "success": { - "type": "object", - "nullable": true, - "example": null } } } @@ -2202,41 +2209,47 @@ "application/json": { "schema": { "type": "object", - "required": [ - "folderName" - ], "properties": { - "folderName": { + "context": { + "type": "string", + "description": "챌린지 내용" + }, + "location": { "type": "string", - "description": "폴더 이름" + "description": "챌린지 위치" + }, + "required": { + "type": "number", + "description": "챌린지 장수" } } } } } } - }, - "delete": { + } + }, + "/challenge/weekly_challenge/get/{id}": { + "get": { "tags": [ - "memo-folder-controller" + "challenge-weekly-controller" ], - "summary": "폴더 삭제 API", - "description": "특정 폴더를 삭제하는 API입니다.", + "summary": "날짜 기반 챌린지 불러오기 API", + "description": "날짜 기반 챌린지를 불러오는 API입니다.", "parameters": [ { - "name": "folderId", + "name": "id", "in": "path", "required": true, "schema": { - "type": "integer", - "format": "int64" + "type": "string" }, - "description": "폴더 ID 입력" + "description": "챌린지 ID 입력" } ], "responses": { "200": { - "description": "폴더 삭제 성공 응답", + "description": "날짜 챌린지 불러오기 성공 응답", "content": { "application/json": { "schema": { @@ -2254,2839 +2267,149 @@ "success": { "type": "object", "properties": { - "message": { - "type": "string" - } - } - } - } - } - } - } - }, - "404": { - "description": "폴더 조회 에러", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "resultType": { - "type": "string", - "example": "FAIL" - }, - "error": { - "type": "object", - "properties": { - "errorCode": { + "id": { + "type": "string", + "example": "1" + }, + "title": { "type": "string", - "example": "FOL-404" + "example": "challenge-title" }, - "reason": { + "context": { "type": "string", - "example": "해당 폴더를 찾을 수 없습니다." + "example": "challenge-context" }, - "data": { - "type": "object", - "properties": { - "folderId": { - "type": "string", - "example": "1" - } - } - } - } - }, - "success": { - "type": "object", - "nullable": true, - "example": null - } - } - } - } - } - } - } - } - }, - "/memo/image-format/folders/not-tsoa": { - "post": { - "tags": [ - "memo-folder-controller" - ], - "summary": "폴더 생성 및 사진 저장 API", - "description": "폴더 생성과 동시에 파일을 저장하는 API입니다.", - "responses": { - "200": { - "description": "폴더 생성 및 사진 저장 성공 응답", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "resultType": { - "type": "string", - "example": "SUCCESS" - }, - "error": { - "type": "object", - "nullable": true, - "example": null - }, - "success": { - "type": "object", - "properties": { - "folderId": { + "challengeDate": { "type": "string", - "example": "1" + "format": "date-time", + "example": "challenge-location" + }, + "requiredCount": { + "type": "number", + "example": 10 }, - "folderName": { - "type": "string" + "remainingCount": { + "type": "number", + "example": 10 }, - "imageId": { + "userId": { "type": "string", "example": "1" }, - "imageUrl": { - "type": "string" - } - } - } - } - } - } - } - }, - "400": { - "description": "유효하지 않은 데이터 에러", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "resultType": { - "type": "string", - "example": "FAIL" - }, - "error": { - "type": "object", - "properties": { - "errorCode": { + "createdAt": { "type": "string", - "example": "400" + "format": "date-time", + "example": "2025-01-20T18:19:47.415Z" }, - "reason": { - "type": "string" + "updatedAt": { + "type": "string", + "format": "date-time", + "example": "2025-01-20T18:19:47.415Z" }, - "data": {} - } - }, - "success": { - "type": "object", - "nullable": true, - "example": null - } - } - }, - "examples": { - "폴더 생성 에러": { - "summary": "폴더 생성 에러", - "description": "폴더 생성 중 오류가 발생했습니다.", - "value": { - "resultType": "FAIL", - "error": { - "errorCode": "FOL-400", - "reason": "폴더 생성 중 오류가 발생했습니다.", - "data": { - "userId": "1", - "folderName": "string" - } - }, - "success": null - } - }, - "유효하지 않은 사진 데이터 에러": { - "summary": "유효하지 않은 사진 데이터 에러", - "description": "저장할 사진이 없습니다.", - "value": { - "resultType": "FAIL", - "error": { - "errorCode": "PHO-400", - "reason": "사진 데이터가 유효하지 않습니다.", - "data": { - "reason": "저장할 사진이 없습니다." - } - }, - "success": null - } - }, - "사진 추가 에러": { - "summary": "사진 추가 에러", - "description": "메모 사진 추가 중 오류가 발생했습니다.", - "value": { - "resultType": "FAIL", - "error": { - "errorCode": "MEM-400", - "reason": "메모 사진 추가 중 오류가 발생했습니다.", - "data": { - "folderId": "1", - "imageUrl": "string" - } - }, - "success": null - } - }, - "유효하지 않은 확장자 에러": { - "summary": "유효하지 않은 확장자 에러", - "description": "이미지 확장자가 유효하지 않습니다.", - "value": { - "resultType": "FAIL", - "error": { - "errorCode": "PHO-400", - "reason": "사진 데이터가 유효하지 않습니다.", - "data": { - "extension": "string" - } - }, - "success": null - } - } - } - } - } - }, - "409": { - "description": "폴더명 중복 에러", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "resultType": { - "type": "string", - "example": "FAIL" - }, - "error": { - "type": "object", - "properties": { - "errorCode": { + "acceptedAt": { "type": "string", - "example": "FOL-409" + "format": "date-time", + "example": "2025-01-20T18:19:47.415Z" }, - "reason": { + "completedAt": { "type": "string", - "example": "이미 존재하는 폴더 이름입니다." + "format": "date-time", + "example": "2025-01-20T18:19:47.415Z" }, - "data": { - "type": "object", - "properties": { - "folderName": { - "type": "string" - } - } - } - } - }, - "success": { - "type": "object", - "nullable": true, - "example": null - } - } - } - } - } - } - }, - "requestBody": { - "required": true, - "content": { - "multipart/form-data": { - "schema": { - "type": "object", - "required": [ - "folderName" - ], - "properties": { - "folderName": { - "type": "string", - "description": "폴더 이름" - }, - "image": { - "type": "string", - "format": "binary", - "description": "파일 업로드" - } - } - } - } - } - } - } - }, - "/memo/image-format/folders/{folderId}/not-tsoa": { - "post": { - "tags": [ - "memo-image-controller" - ], - "summary": "사진 저장 API", - "description": "특정 폴더에 사진을 저장하는 API입니다.", - "parameters": [ - { - "name": "folderId", - "in": "path", - "required": true, - "schema": { - "type": "integer", - "format": "int64" - }, - "description": "폴더 ID 입력" - } - ], - "responses": { - "200": { - "description": "사진 저장 성공 응답", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "resultType": { - "type": "string", - "example": "SUCCESS" - }, - "error": { - "type": "object", - "nullable": true, - "example": null - }, - "success": { - "type": "object", - "properties": { - "folderId": { - "type": "string", - "example": "1" - }, - "folderName": { - "type": "string" - }, - "imageId": { - "type": "string", - "example": "1" - }, - "imageUrl": { - "type": "string" - } - } - } - } - } - } - } - }, - "400": { - "description": "유효하지 않은 데이터 에러", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "resultType": { - "type": "string", - "example": "FAIL" - }, - "error": { - "type": "object", - "properties": { - "errorCode": { - "type": "string", - "example": "400" - }, - "reason": { - "type": "string" - }, - "data": {} - } - }, - "success": { - "type": "object", - "nullable": true, - "example": null - } - } - }, - "examples": { - "사진 추가 에러": { - "summary": "사진 추가 에러", - "description": "메모 사진 추가 중 오류가 발생했습니다.", - "value": { - "resultType": "FAIL", - "error": { - "errorCode": "MEM-400", - "reason": "메모 사진 추가 중 오류가 발생했습니다.", - "data": { - "folderId": "1", - "imageUrl": "string" - } - }, - "success": null - } - }, - "유효하지 않은 확장자 에러": { - "summary": "유효하지 않은 확장자 에러", - "description": "이미지 확장자가 유효하지 않습니다.", - "value": { - "resultType": "FAIL", - "error": { - "errorCode": "PHO-400", - "reason": "사진 데이터가 유효하지 않습니다.", - "data": { - "extension": "string" - } - }, - "success": null - } - } - } - } - } - }, - "404": { - "description": "조회할 수 없는 데이터 에러", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "resultType": { - "type": "string", - "example": "FAIL" - }, - "error": { - "type": "object", - "properties": { - "errorCode": { - "type": "string", - "example": "404" - }, - "reason": { - "type": "string" - }, - "data": {} - } - }, - "success": { - "type": "object", - "nullable": true, - "example": null - } - } - }, - "examples": { - "폴더 조회 에러": { - "summary": "폴더 조회 에러", - "description": "해당 폴더를 찾을 수 없습니다.", - "value": { - "resultType": "FAIL", - "error": { - "errorCode": "FOL-404", - "reason": "해당 폴더를 찾을 수 없습니다.", - "data": { - "folderId": "1" - } - }, - "success": null - } - }, - "사진 조회 에러": { - "summary": "사진 조회 에러", - "description": "해당 사진 데이터가 없습니다.", - "value": { - "resultType": "FAIL", - "error": { - "errorCode": "PHO-404", - "reason": "해당 사진 데이터가 없습니다.", - "data": { - "reason": "저장할 사진이 없습니다." - } - }, - "success": null - } - } - } - } - } - } - }, - "requestBody": { - "required": true, - "content": { - "multipart/form-data": { - "schema": { - "type": "object", - "properties": { - "image": { - "type": "string", - "format": "binary", - "description": "파일 업로드" - } - } - } - } - } - } - } - }, - "/memo/list/not-tsoa": { - "get": { - "tags": [ - "memo-folder-controller" - ], - "summary": "모든 메모 조회 API", - "description": "모든 메모를 조회하는 API입니다.", - "responses": { - "200": { - "description": "메모 조회 성공 응답", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "resultType": { - "type": "string", - "example": "SUCCESS" - }, - "error": { - "type": "object", - "nullable": true, - "example": null - }, - "success": { - "type": "object", - "properties": { - "data": { - "type": "array", - "items": { - "type": "object", - "properties": { - "folderId": { - "type": "string", - "example": "1" - }, - "folderName": { - "type": "string" - }, - "imageText": { - "type": "string" - }, - "imageCount": { - "type": "integer", - "example": 0 - }, - "firstImageId": { - "type": "string", - "example": "1" - }, - "firstImageUrl": { - "type": "string" - }, - "createdAt": { - "type": "string", - "example": "2025-01-17T03:50:25.923Z" - } - } - } - } - } - } - } - } - } - } - } - } - } - }, - "/memo/search/not-tsoa": { - "get": { - "tags": [ - "memo-folder-controller" - ], - "summary": "메모 검색 API", - "description": "메모를 검색 및 조회하는 API입니다.", - "parameters": [ - { - "name": "keyword", - "in": "query", - "required": true, - "description": "검색 키워드", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "메모 검색 성공 응답", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "resultType": { - "type": "string", - "example": "SUCCESS" - }, - "error": { - "type": "object", - "nullable": true, - "example": null - }, - "success": { - "type": "object", - "properties": { - "data": { - "type": "array", - "items": { - "type": "object", - "properties": { - "folderId": { - "type": "string", - "example": "1" - }, - "folderName": { - "type": "string" - }, - "imageCount": { - "type": "integer", - "example": 0 - }, - "imageText": { - "type": "string" - }, - "firstImageId": { - "type": "string", - "example": "1" - }, - "firstImageUrl": { - "type": "string" - }, - "createdAt": { - "type": "string", - "example": "2025-01-17T03:50:25.923Z" - } - } - } - } - } - } - } - } - } - } - }, - "400": { - "description": "유효하지 않은 입력 데이터 에러", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "resultType": { - "type": "string", - "example": "FAIL" - }, - "error": { - "type": "object", - "properties": { - "errorCode": { - "type": "string", - "example": "SRH-400" - }, - "reason": { - "type": "string", - "example": "입력 데이터가 유효하지 않습니다." - }, - "data": { - "type": "object", - "properties": { - "reason": { - "type": "string", - "example": "검색어를 1자 이상 입력하세요." - } - } - } - } - }, - "success": { - "type": "object", - "nullable": true, - "example": null - } - } - } - } - } - } - } - } - }, - "/memo/folders/{folderId}/images/move/not-tsoa": { - "patch": { - "tags": [ - "memo-image-controller" - ], - "summary": "사진 이동 API", - "description": "특정 폴더의 사진을 이동하는 API입니다.", - "parameters": [ - { - "name": "folderId", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "사진 이동 성공 응답", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "resultType": { - "type": "string", - "example": "SUCCESS" - }, - "error": { - "type": "object", - "nullable": true, - "example": null - }, - "success": { - "type": "object", - "properties": { - "folderId": { - "type": "string", - "example": "1" - }, - "folderName": { - "type": "string" - }, - "imageText": { - "type": "string" - }, - "images": { - "type": "array", - "items": { - "type": "object", - "properties": { - "imageId": { - "type": "string", - "example": "1" - }, - "imageUrl": { - "type": "string" - } - } - } - } - } - } - } - } - } - } - }, - "400": { - "description": "사진 이동 에러", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "resultType": { - "type": "string", - "example": "FAIL" - }, - "error": { - "type": "object", - "properties": { - "errorCode": { - "type": "string", - "example": "MEM-400" - }, - "reason": { - "type": "string", - "example": "메모 사진 이동 중 오류가 발생했습니다." - }, - "data": { - "type": "object", - "properties": { - "folderId": { - "type": "string", - "example": "1" - }, - "imageId": { - "type": "array", - "items": { - "type": "string", - "example": "1" - } - } - } - } - } - }, - "success": { - "type": "object", - "nullable": true, - "example": null - } - } - } - } - } - }, - "404": { - "description": "조회할 수 없는 데이터 에러", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "resultType": { - "type": "string", - "example": "FAIL" - }, - "error": { - "type": "object", - "properties": { - "errorCode": { - "type": "string", - "example": "404" - }, - "reason": { - "type": "string" - }, - "data": {} - } - }, - "success": { - "type": "object", - "nullable": true, - "example": null - } - } - }, - "examples": { - "폴더 조회 에러": { - "summary": "폴더 조회 에러", - "description": "해당 폴더를 찾을 수 없습니다.", - "value": { - "resultType": "FAIL", - "error": { - "errorCode": "FOL-404", - "reason": "해당 폴더를 찾을 수 없습니다.", - "data": { - "folderId": "1" - } - }, - "success": null - } - }, - "사진 조회 에러": { - "summary": "사진 조회 에러", - "description": "해당 사진 데이터가 없습니다.", - "value": { - "resultType": "FAIL", - "error": { - "errorCode": "PHO-404", - "reason": "해당 사진 데이터가 없습니다.", - "data": { - "imageId": "1" - } - }, - "success": null - } - } - } - } - } - } - }, - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": [ - "folderId", - "imageId" - ], - "properties": { - "targetFolderId": { - "type": "integer" - }, - "imageId": { - "type": "array", - "items": { - "type": "integer" - } - } - } - } - } - } - } - } - }, - "/memo/folders/{folderId}/text/not-tsoa": { - "patch": { - "tags": [ - "memo-folder-controller" - ], - "summary": "특정 폴더의 메모 텍스트 수정 API", - "description": "특정 폴더의 메모 텍스트를 수정하는 API입니다.", - "parameters": [ - { - "name": "folderId", - "in": "path", - "required": true, - "schema": { - "type": "integer", - "format": "int64" - }, - "description": "폴더 ID 입력" - } - ], - "responses": { - "200": { - "description": "메모 텍스트 수정 응답", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "resultType": { - "type": "string", - "example": "SUCCESS" - }, - "error": { - "type": "object", - "nullable": true, - "example": null - }, - "success": { - "type": "object", - "properties": { - "folderId": { - "type": "string", - "example": "1" - }, - "folderName": { - "type": "string" - }, - "imageText": { - "type": "string" - }, - "images": { - "type": "array", - "items": { - "type": "object", - "properties": { - "imageId": { - "type": "string", - "example": "1" - }, - "imageUrl": { - "type": "string" - } - } - } - } - } - } - } - } - } - } - }, - "404": { - "description": "폴더 조회 에러", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "resultType": { - "type": "string", - "example": "FAIL" - }, - "error": { - "type": "object", - "properties": { - "errorCode": { - "type": "string", - "example": "FOL-404" - }, - "reason": { - "type": "string", - "example": "해당 폴더를 찾을 수 없습니다." - }, - "data": { - "type": "object", - "properties": { - "folderId": { - "type": "string", - "example": "1" - } - } - } - } - }, - "success": { - "type": "object", - "nullable": true, - "example": null - } - } - } - } - } - } - }, - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": [ - "memoText" - ], - "properties": { - "memoText": { - "type": "string", - "description": "메모 텍스트" - } - } - } - } - } - } - } - }, - "/memo/folders/{folderId}/images/delete/not-tsoa": { - "post": { - "tags": [ - "memo-image-controller" - ], - "summary": "사진 삭제 API", - "description": "특정 폴더의 사진을 삭제하는 API입니다.", - "parameters": [ - { - "name": "folderId", - "in": "path", - "required": true, - "schema": { - "type": "integer", - "format": "int64" - }, - "description": "폴더 ID 입력" - } - ], - "responses": { - "200": { - "description": "사진 삭제 성공 응답", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "resultType": { - "type": "string", - "example": "SUCCESS" - }, - "error": { - "type": "object", - "nullable": true, - "example": null - }, - "success": { - "type": "object", - "properties": { - "folderId": { - "type": "string", - "example": "1" - }, - "folderName": { - "type": "string" - }, - "imageText": { - "type": "string" - }, - "images": { - "type": "array", - "items": { - "type": "object", - "properties": { - "imageId": { - "type": "string", - "example": "1" - }, - "imageUrl": { - "type": "string" - } - } - } - } - } - } - } - } - } - } - }, - "404": { - "description": "조회할 수 없는 데이터 에러", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "resultType": { - "type": "string", - "example": "FAIL" - }, - "error": { - "type": "object", - "properties": { - "errorCode": { - "type": "string", - "example": "404" - }, - "reason": { - "type": "string" - }, - "data": {} - } - }, - "success": { - "type": "object", - "nullable": true, - "example": null - } - } - }, - "examples": { - "폴더 조회 에러": { - "summary": "폴더 조회 에러", - "description": "해당 폴더를 찾을 수 없습니다.", - "value": { - "resultType": "FAIL", - "error": { - "errorCode": "FOL-404", - "reason": "해당 폴더를 찾을 수 없습니다.", - "data": { - "folderId": "1" - } - }, - "success": null - } - }, - "사진 조회 에러": { - "summary": "사진 조회 에러", - "description": "해당 사진 데이터가 없습니다.", - "value": { - "resultType": "FAIL", - "error": { - "errorCode": "PHO-404", - "reason": "해당 사진 데이터가 없습니다.", - "data": { - "imageId": "1" - } - }, - "success": null - } - } - } - } - } - } - }, - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": [ - "imageId" - ], - "properties": { - "imageId": { - "type": "array", - "items": { - "type": "integer" - } - } - } - } - } - } - } - } - }, - "/memo/text-format/folders": { - "post": { - "tags": [ - "memo-ai" - ], - "summary": "폴더 생성 및 OCR 수행", - "description": "새로운 폴더를 생성하고, 이미지에서 OCR 텍스트를 추출하여 이미지와 텍스트를 저장하는 API입니다.", - "responses": { - "201": { - "description": "폴더 생성 및 텍스트 변환", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "resultType": { - "type": "string", - "example": "SUCCESS" - }, - "error": { - "type": "object", - "nullable": true, - "example": null - }, - "success": { - "type": "object", - "properties": { - "folder_id": { - "type": "string", - "example": "1" - }, - "image_text": { - "type": "string", - "example": "이번 수업 시간은 사회 과학 시간이다." - } - } - } - } - } - } - } - }, - "400": { - "description": "잘못된 요청 데이터", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "error": { - "type": "string", - "example": "폴더의 이름을 입력해주세요." - } - } - } - } - } - }, - "500": { - "description": "서버 내부 오류", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "error": { - "type": "string", - "example": "서버에러발생." - } - } - } - } - } - } - }, - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": [ - "image_url", - "user_id", - "folder_name" - ], - "properties": { - "base64_image": { - "type": "string", - "description": "OCR 처리를 위한 이미지 URL", - "example": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUA..." - }, - "user_id": { - "type": "number", - "description": "사용자 ID", - "example": 1 - }, - "folder_name": { - "type": "string", - "description": "생성할 폴더의 이름", - "example": "공부" - } - } - } - } - } - } - } - }, - "/memo/text-format/folders/{folderId}": { - "patch": { - "tags": [ - "memo-ai" - ], - "summary": "폴더 업데이트 및 OCR 수행", - "description": "기존 폴더에 이미지를 추가하고, OCR 텍스트를 업데이트하는 API입니다.", - "parameters": [ - { - "name": "folderId", - "in": "path", - "required": true, - "schema": { - "type": "number", - "example": 1 - }, - "description": "업데이트할 폴더의 ID" - } - ], - "responses": { - "200": { - "description": "폴더 업데이트 및 텍스트 변환", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "resultType": { - "type": "string", - "example": "SUCCESS" - }, - "error": { - "type": "object", - "nullable": true, - "example": null - }, - "success": { - "type": "object", - "properties": { - "folder_id": { - "type": "string", - "example": "1" - }, - "image_text": { - "type": "string", - "example": "이번 수업 시간은 사회 과학 시간이다." - } - } - } - } - } - } - } - }, - "400": { - "description": "잘못된 요청 데이터", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "error": { - "type": "string", - "example": "image_url이 존재하지 않습니다." - } - } - } - } - } - }, - "500": { - "description": "서버 내부 오류", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "error": { - "type": "string", - "example": "서버 에러 발생." - } - } - } - } - } - } - }, - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": [ - "base64_image", - "user_id" - ], - "properties": { - "base64_image": { - "type": "string", - "description": "OCR 처리를 위한 이미지 URL", - "example": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUA..." - }, - "user_id": { - "type": "number", - "description": "사용자 ID", - "example": 1 - } - } - } - } - } - } - } - }, - "/challenge/update": { - "patch": { - "tags": [ - "challenge-controller" - ], - "summary": "챌린지 수정 API", - "description": "챌린지를 수정하는 API입니다. 수정되는 내용은 정리 장수, 남은 장수 입니다.", - "responses": { - "200": { - "description": "챌린지 수정 성공 응답", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "resultType": { - "type": "string", - "example": "SUCCESS" - }, - "error": { - "type": "object", - "nullable": true, - "example": null - }, - "success": { - "type": "object", - "properties": { - "id": { - "type": "string", - "example": "1" - }, - "required": { - "type": "string", - "example": 10 - }, - "remaining": { - "type": "string", - "example": 10 - } - } - } - } - } - } - } - } - }, - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "id": { - "type": "string", - "description": "챌린지 ID" - }, - "required": { - "type": "number", - "description": "챌린지 장 수" - }, - "remaining": { - "type": "number", - "description": "챌린지 남은 수" - } - } - } - } - } - } - } - }, - "/challenge/delete": { - "delete": { - "tags": [ - "challenge-controller" - ], - "summary": "챌린지 삭제 API", - "description": "챌린지를 삭제하는 API입니다.", - "responses": { - "200": { - "description": "챌린지 삭제 성공 응답", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "resultType": { - "type": "string", - "example": "SUCCESS" - }, - "error": { - "type": "object", - "nullable": true, - "example": null - }, - "success": { - "type": "object", - "properties": { - "id": { - "type": "string", - "example": "1" - } - } - } - } - } - } - } - } - }, - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": [ - "id" - ], - "properties": { - "id": { - "type": "string", - "description": "챌린지 ID" - } - } - } - } - } - } - } - }, - "/challenge/accept/{id}": { - "patch": { - "description": "", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "default": { - "description": "" - } - } - } - }, - "/challenge/complete/{id}": { - "patch": { - "description": "", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "default": { - "description": "" - } - } - } - }, - "/challenge/get": { - "get": { - "description": "", - "responses": { - "default": { - "description": "" - } - } - } - }, - "/challenge/location_challenge/get/{id}": { - "get": { - "description": "", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "default": { - "description": "" - } - } - } - }, - "/challenge/location_logic/test": { - "post": { - "tags": [ - "challenge-location-controller" - ], - "summary": "위치 기반 챌린지 사진 판별 API", - "description": "위치 기반 챌린지를 위한 사진을 골라내는 API입니다.", - "responses": { - "200": { - "description": "사진 위치 판별 응답", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "resultType": { - "type": "string", - "example": "SUCCESS" - }, - "error": { - "type": "object", - "nullable": true, - "example": null - }, - "success": { - "type": "array", - "items": { - "type": "object", - "properties": { - "id": { - "type": "string", - "example": "100000001" - }, - "displayName": { - "type": "string", - "example": "20250116_123456.jpg" - }, - "latitude": { - "type": "number", - "example": 37.123456 - }, - "longitude": { - "type": "number", - "example": 127.123456 - }, - "timestamp": { - "type": "string", - "format": "date-time", - "example": "2025-01-16T12:34:56Z" - }, - "location": { - "type": "string", - "example": "wydg0y" - } - } - } - } - } - } - } - } - } - }, - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "array", - "items": { - "type": "object", - "properties": { - "id": { - "type": "string", - "description": "사진 ID" - }, - "displayName": { - "type": "string", - "description": "사진 이름" - }, - "latitude": { - "type": "number", - "description": "사진 위도" - }, - "longitude": { - "type": "number", - "description": "사진 경도" - }, - "timestamp": { - "type": "string", - "format": "date-time", - "description": "사진 날짜" - } - } - } - } - } - } - } - } - }, - "/challenge/location_challenge/create": { - "post": { - "tags": [ - "challenge-location-controller" - ], - "summary": "위치 기반 챌린지 생성 API", - "description": "위치 기반 챌린지를 생성하는 API입니다.", - "responses": { - "200": { - "description": "위치 챌린지 생성 성공 응답", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "resultType": { - "type": "string", - "example": "SUCCESS" - }, - "error": { - "type": "object", - "nullable": true, - "example": null - }, - "success": { - "type": "object", - "properties": { - "id": { - "type": "string", - "example": "1" - }, - "title": { - "type": "string", - "example": "challenge-title" - }, - "context": { - "type": "string", - "example": "challenge-context" - }, - "requiredCount": { - "type": "number", - "example": 10 - }, - "remainingCount": { - "type": "number", - "example": 10 - }, - "userId": { - "type": "string", - "example": "1" - }, - "createdAt": { - "type": "string", - "format": "date-time", - "example": "2025-01-20T18:19:47.415Z" - }, - "updatedAt": { - "type": "string", - "format": "date-time", - "example": "2025-01-20T18:19:47.415Z" - }, - "acceptedAt": { - "type": "string", - "format": "date-time", - "example": "2025-01-20T18:19:47.415Z" - }, - "completedAt": { - "type": "string", - "format": "date-time", - "example": "2025-01-20T18:19:47.415Z" - }, - "status": { - "type": "number", - "example": 1 - } - } - } - } - } - } - } - } - }, - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "userId": { - "type": "string", - "description": "유저 Id" - }, - "title": { - "type": "string", - "description": "챌린지 제목" - }, - "context": { - "type": "string", - "description": "챌린지 내용" - }, - "location": { - "type": "string", - "description": "챌린지 위치" - }, - "required": { - "type": "number", - "description": "챌린지 장수" - } - } - } - } - } - } - } - }, - "/challenge/weekly_challenge/get/{id}": { - "get": { - "tags": [ - "challenge-weekly-controller" - ], - "summary": "날짜 기반 챌린지 불러오기 API", - "description": "날짜 기반 챌린지를 불러오는 API입니다.", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "string" - }, - "description": "챌린지 ID 입력" - } - ], - "responses": { - "200": { - "description": "날짜 챌린지 불러오기 성공 응답", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "resultType": { - "type": "string", - "example": "SUCCESS" - }, - "error": { - "type": "object", - "nullable": true, - "example": null - }, - "success": { - "type": "object", - "properties": { - "id": { - "type": "string", - "example": "1" - }, - "title": { - "type": "string", - "example": "challenge-title" - }, - "context": { - "type": "string", - "example": "challenge-context" - }, - "challengeDate": { - "type": "string", - "format": "date-time", - "example": "challenge-location" - }, - "requiredCount": { - "type": "number", - "example": 10 - }, - "remainingCount": { - "type": "number", - "example": 10 - }, - "userId": { - "type": "string", - "example": "1" - }, - "createdAt": { - "type": "string", - "format": "date-time", - "example": "2025-01-20T18:19:47.415Z" - }, - "updatedAt": { - "type": "string", - "format": "date-time", - "example": "2025-01-20T18:19:47.415Z" - }, - "acceptedAt": { - "type": "string", - "format": "date-time", - "example": "2025-01-20T18:19:47.415Z" - }, - "completedAt": { - "type": "string", - "format": "date-time", - "example": "2025-01-20T18:19:47.415Z" - }, - "status": { - "type": "number", - "example": 1 - } - } - } - } - } - } - } - } - }, - "requestBody": { - "required": false - } - } - }, - "/challenge/weekly_challenge/create": { - "post": { - "tags": [ - "challenge-weekly-controller" - ], - "summary": "날짜 기반 챌린지 생성 API", - "description": "날짜 기반 챌린지를 생성하는 API입니다.", - "responses": { - "200": { - "description": "날짜 챌린지 생성 성공 응답", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "resultType": { - "type": "string", - "example": "SUCCESS" - }, - "error": { - "type": "object", - "nullable": true, - "example": null - }, - "success": { - "type": "object", - "properties": { - "id": { - "type": "string", - "example": "1" - }, - "title": { - "type": "string", - "example": "challenge-title" - }, - "context": { - "type": "string", - "example": "challenge-context" - }, - "requiredCount": { - "type": "number", - "example": 10 - }, - "remainingCount": { - "type": "number", - "example": 10 - }, - "userId": { - "type": "string", - "example": "1" - }, - "createdAt": { - "type": "string", - "format": "date-time", - "example": "2025-01-20T18:19:47.415Z" - }, - "updatedAt": { - "type": "string", - "format": "date-time", - "example": "2025-01-20T18:19:47.415Z" - }, - "acceptedAt": { - "type": "string", - "format": "date-time", - "example": "2025-01-20T18:19:47.415Z" - }, - "completedAt": { - "type": "string", - "format": "date-time", - "example": "2025-01-20T18:19:47.415Z" - }, - "status": { - "type": "number", - "example": 1 - } - } - } - } - } - } - } - } - }, - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "userId": { - "type": "string", - "description": "유저 Id" - }, - "title": { - "type": "string", - "description": "챌린지 제목" - }, - "context": { - "type": "string", - "description": "챌린지 내용" - }, - "challengeDate": { - "type": "string", - "format": "date-time", - "description": "챌린지 날짜" - }, - "required": { - "type": "number", - "description": "챌린지 장수" - } - } - } - } - } - } - } - }, - "/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": {} - } + "status": { + "type": "number", + "example": 1 } } } - }, - "xml": { - "name": "main" } } - }, - "application/xml": { + } + } + } + }, + "requestBody": { + "required": false + } + } + }, + "/challenge/weekly_challenge/create": { + "post": { + "tags": [ + "challenge-weekly-controller" + ], + "summary": "날짜 기반 챌린지 생성 API", + "description": "날짜 기반 챌린지를 생성하는 API입니다.", + "responses": { + "200": { + "description": "날짜 챌린지 생성 성공 응답", + "content": { + "application/json": { "schema": { "type": "object", "properties": { - "type": { + "resultType": { "type": "string", - "example": "object" + "example": "SUCCESS" }, - "properties": { + "error": { + "type": "object", + "nullable": true, + "example": null + }, + "success": { "type": "object", "properties": { - "resultType": { - "type": "object", - "properties": { - "type": { - "type": "string", - "example": "string" - }, - "example": { - "type": "string", - "example": "FAILURE" - } - } + "id": { + "type": "string", + "example": "1" }, - "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." - } - } - } - } - } - } + "title": { + "type": "string", + "example": "challenge-title" }, - "success": { - "type": "object", - "properties": { - "type": { - "type": "string", - "example": "object" - }, - "nullable": { - "type": "boolean", - "example": true - }, - "example": {} - } + "context": { + "type": "string", + "example": "challenge-context" + }, + "requiredCount": { + "type": "number", + "example": 10 + }, + "remainingCount": { + "type": "number", + "example": 10 + }, + "userId": { + "type": "string", + "example": "1" + }, + "createdAt": { + "type": "string", + "format": "date-time", + "example": "2025-01-20T18:19:47.415Z" + }, + "updatedAt": { + "type": "string", + "format": "date-time", + "example": "2025-01-20T18:19:47.415Z" + }, + "acceptedAt": { + "type": "string", + "format": "date-time", + "example": "2025-01-20T18:19:47.415Z" + }, + "completedAt": { + "type": "string", + "format": "date-time", + "example": "2025-01-20T18:19:47.415Z" + }, + "status": { + "type": "number", + "example": 1 } } } - }, - "xml": { - "name": "main" } } } @@ -5100,20 +2423,73 @@ "schema": { "type": "object", "properties": { - "goalCount": { - "type": "integer", - "description": "변경할 목표 장수" + "context": { + "type": "string", + "description": "챌린지 내용" + }, + "challengeDate": { + "type": "string", + "format": "date-time", + "description": "챌린지 날짜" + }, + "required": { + "type": "number", + "description": "챌린지 장수" } - }, - "required": [ - "goalCount" - ] + } } } } } } }, + "/challenge/getGeocode": { + "get": { + "description": "", + "parameters": [ + { + "name": "hashedLocation", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "default": { + "description": "" + } + } + } + }, + "/user/mypage/": { + "get": { + "description": "", + "responses": { + "default": { + "description": "" + } + } + }, + "patch": { + "description": "", + "responses": { + "204": { + "description": "No Content" + } + } + } + }, + "/user/mypage/logout": { + "get": { + "description": "", + "responses": { + "204": { + "description": "No Content" + } + } + } + }, "/tag/": { "post": { "tags": [ @@ -5237,6 +2613,64 @@ } } } + }, + "/trust/active": { + "patch": { + "description": "", + "responses": { + "default": { + "description": "" + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "mediaId": { + "example": "any" + } + } + } + } + } + } + } + }, + "/trust/restore": { + "patch": { + "description": "", + "responses": { + "default": { + "description": "" + } + } + } + }, + "/trust/": { + "delete": { + "description": "", + "responses": { + "default": { + "description": "" + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "mediaIds": { + "example": "any" + } + } + } + } + } + } + } } } } \ No newline at end of file diff --git a/swagger/swagger.json b/swagger/swagger.json index c78f3e4..eb5f609 100644 --- a/swagger/swagger.json +++ b/swagger/swagger.json @@ -668,6 +668,42 @@ "type": "object", "additionalProperties": false }, + "ITsoaSuccessResponse__folder_id-string--image_text-string__": { + "properties": { + "resultType": { + "type": "string" + }, + "error": { + "type": "number", + "enum": [ + null + ], + "nullable": true + }, + "success": { + "properties": { + "image_text": { + "type": "string" + }, + "folder_id": { + "type": "string" + } + }, + "required": [ + "image_text", + "folder_id" + ], + "type": "object" + } + }, + "required": [ + "resultType", + "error", + "success" + ], + "type": "object", + "additionalProperties": false + }, "ResponseFromMostTagToClient": { "properties": { "_count": { @@ -889,6 +925,16 @@ }, "contact": {} }, + "servers": [ + { + "url": "http://localhost:3000", + "description": "Sweepic local server" + }, + { + "url": "http://3.37.137.212:3000", + "description": "Sweepic server" + } + ], "paths": { "/tags/date": { "get": { @@ -1406,150 +1452,6 @@ } } }, - "/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", @@ -1978,16 +1880,16 @@ } } }, - "/memo/image-format/folders": { + "/memo/folders": { "post": { - "operationId": "HandlerMemoFolderImageAdd", + "operationId": "HandlerMemoFolderAdd", "responses": { "200": { - "description": "폴더 생성 및 사진 저장 성공 응답", + "description": "폴더 생성 성공 응답", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/ITsoaSuccessResponse_MemoFolderImageResponseDto_" + "$ref": "#/components/schemas/ITsoaSuccessResponse_MemoFolderResponseDto_" }, "examples": { "Example 1": { @@ -1995,10 +1897,8 @@ "resultType": "SUCCESS", "error": null, "success": { - "folderId": "1", - "folderName": "string", - "imageId": "1", - "imageUrl": "string" + "id": "1", + "folderName": "string" } } } @@ -2027,46 +1927,6 @@ } } } - }, - "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 - } } } } @@ -2098,130 +1958,18 @@ } } }, - "description": "폴더 생성과 동시에 파일을 저장하는 API입니다.", - "summary": "폴더 생성 및 사진 저장 API", + "description": "폴더를 생성하는 API입니다.", + "summary": "폴더 생성 API", "tags": [ "memo-folder-controller" ], "security": [], "parameters": [], "requestBody": { + "description": "생성할 폴더 이름", "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": { + "application/json": { "schema": { "$ref": "#/components/schemas/BodyToMemoFolder", "description": "생성할 폴더 이름" @@ -2954,16 +2702,654 @@ "security": [], "parameters": [] } - } - }, - "servers": [ - { - "url": "http://localhost:3000", - "description": "Sweepic local server" }, - { - "url": "http://3.37.137.212:3000", - "description": "Sweepic server" + "/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": { + "required": [ + "folderName" + ], + "type": "object", + "properties": { + "folderName": { + "type": "string", + "description": "생성할 폴더 이름" + }, + "image": { + "type": "string", + "format": "binary", + "description": "파일 업로드" + } + } + } + } + } + } + } + }, + "/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", + "description": "파일 업로드" + } + } + } + } + } + } + } + }, + "/memo/text-format/folders/{folderId}": { + "patch": { + "operationId": "UpdateFolderOCR", + "responses": { + "200": { + "description": "폴더 업데이트 및 텍스트 변환", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ITsoaSuccessResponse__folder_id-string--image_text-string__" + }, + "examples": { + "Example 1": { + "value": { + "resultType": "SUCCESS", + "error": null, + "success": { + "folder_id": "1", + "image_text": "이번 수업 시간은 사회 과학 시간이다." + } + } + } + } + } + } + }, + "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": "folder_ID가 필요합니다." + } + } + } + }, + "Example 2": { + "value": { + "resultType": "FAIL", + "success": null, + "error": { + "errorCode": "SRH-400", + "reason": "입력 데이터가 유효하지 않습니다.", + "data": { + "reason": "base64_image가 필요합니다." + } + } + } + }, + "Example 3": { + "value": { + "resultType": "FAIL", + "success": null, + "error": { + "errorCode": "SRH-400", + "reason": "입력 데이터가 유효하지 않습니다.", + "data": { + "reason": "user_id가 필요합니다." + } + } + } + }, + "Example 4": { + "value": { + "resultType": "FAIL", + "success": null, + "error": { + "errorCode": "PHO-400", + "reason": "사진 데이터가 유효하지 않습니다.", + "data": { + "reason": "올바른 Base64 이미지 형식이 아닙니다." + } + } + } + }, + "Example 5": { + "value": { + "resultType": "FAIL", + "success": null, + "error": { + "errorCode": "PHO-400", + "reason": "사진 데이터가 유효하지 않습니다.", + "data": { + "reason": "텍스트를 추출할 사진이 없습니다." + } + } + } + }, + "Example 6": { + "value": { + "resultType": "FAIL", + "success": null, + "error": { + "errorCode": "PHO-400", + "reason": "사진 데이터가 유효하지 않습니다.", + "data": { + "reason": "이미지에서 텍스트를 찾지 못하였습니다." + } + } + } + } + } + } + } + }, + "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": "기존 폴더에 이미지를 추가하고, OCR 텍스트를 업데이트하는 API입니다.", + "summary": "폴더 업데이트 및 OCR 수행", + "tags": [ + "memo-ai" + ], + "security": [], + "parameters": [ + { + "description": "업데이트할 폴더의 ID", + "in": "path", + "name": "folderId", + "required": true, + "schema": { + "format": "double", + "type": "number" + } + } + ], + "requestBody": { + "required": true, + "content": { + "multipart/form-data": { + "schema": { + "type": "object", + "properties": { + "base64_image": { + "type": "string", + "format": "binary", + "description": "OCR 처리를 위한 이미지 업로드" + } + } + } + } + } + } + } + }, + "/memo/text-format/folders": { + "post": { + "operationId": "CreateFolderOCR", + "responses": { + "200": { + "description": "폴더 생성 및 텍스트 변환", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ITsoaSuccessResponse__folder_id-string--image_text-string__" + }, + "examples": { + "Example 1": { + "value": { + "resultType": "SUCCESS", + "error": null, + "success": { + "folder_id": "1", + "image_text": "이번 수업 시간은 사회 과학 시간이다." + } + } + } + } + } + } + }, + "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": "base64_image, user_id, folder_name이 필요합니다." + } + } + } + }, + "Example 2": { + "value": { + "resultType": "FAIL", + "success": null, + "error": { + "errorCode": "PHO-400", + "reason": "사진 데이터가 유효하지 않습니다.", + "data": { + "reason": "올바른 Base64 이미지 형식이 아닙니다." + } + } + } + }, + "Example 3": { + "value": { + "resultType": "FAIL", + "success": null, + "error": { + "errorCode": "PHO-400", + "reason": "사진 데이터가 유효하지 않습니다.", + "data": { + "reason": "텍스트를 추출할 사진이 없습니다." + } + } + } + }, + "Example 4": { + "value": { + "resultType": "FAIL", + "success": null, + "error": { + "errorCode": "PHO-400", + "reason": "사진 데이터가 유효하지 않습니다.", + "data": { + "reason": "이미지에서 텍스트를 찾지 못하였습니다." + } + } + } + } + } + } + } + }, + "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" + } + } + } + } + } + } + } + }, + "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": "새로운 폴더를 생성하고, 이미지에서 OCR 텍스트를 추출하여 이미지와 텍스트를 저장하는 API입니다.", + "summary": "폴더 생성 및 OCR 수행", + "tags": [ + "memo-ai" + ], + "security": [], + "parameters": [], + "requestBody": { + "required": true, + "content": { + "multipart/form-data": { + "schema": { + "required": [ + "folder_name" + ], + "type": "object", + "properties": { + "folder_name": { + "type": "string", + "description": "생성할 폴더의 이름" + }, + "base64_image": { + "type": "string", + "format": "binary", + "description": "OCR 처리를 위한 이미지 업로드" + } + } + } + } + } + } + } } - ] + } } \ No newline at end of file