diff --git a/src/app.ts b/src/app.ts index 14f746d..4c6b200 100644 --- a/src/app.ts +++ b/src/app.ts @@ -19,6 +19,7 @@ import {sessionAuthMiddleware} from './auth.config.js'; import cookieParser from 'cookie-parser'; import {ValidateError} from 'tsoa'; import {labelDetectionController} from './controllers/tags-ai.controller.js'; +import upload from './ai/ai-upload.js'; // routers import {RegisterRoutes} from './routers/tsoaRoutes.js'; @@ -115,7 +116,7 @@ app.use('/challenge', challengeRouter); app.use('/user/mypage', myPageRouter); app.use('/tag', tagRouter); app.use('/trust', trustRouter); -app.post('/image/ai', labelDetectionController); +app.post('/image/ai', upload.single('base64_image'), labelDetectionController); RegisterRoutes(app); diff --git a/src/controllers/tags-ai.controller.ts b/src/controllers/tags-ai.controller.ts index 64f651a..062f499 100644 --- a/src/controllers/tags-ai.controller.ts +++ b/src/controllers/tags-ai.controller.ts @@ -1,7 +1,7 @@ import {NextFunction, Request, Response} from 'express'; import {detectLabels} from '../services/tags-ai.service.js'; import {StatusCodes} from 'http-status-codes'; -import {DataValidationError} from '../errors.js'; +import {PhotoDataNotFoundError, PhotoValidationError} from '../errors.js'; export const labelDetectionController = async ( req: Request, @@ -9,91 +9,94 @@ export const labelDetectionController = async ( next: NextFunction, ): Promise => { /* - #swagger.tags = ['label-detection'] - #swagger.summary = '이미지 라벨링' - #swagger.description = 'Base64 데이터를 JSON으로 받아 이미지를 분석하여 상위 3개의 라벨과 정확도를 반환합니다.' - #swagger.requestBody = { - required: true, - content: { - "application/json": { - schema: { - type: "object", - properties: { - base64_image: { - type: "string", - description: "Base64 인코딩된 이미지 데이터", - example: "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD..." - } - } - } - } - } - } - #swagger.responses[200] = { - description: "라벨링 결과 반환", - content: { - "application/json": { - schema: { - type: "object", - properties: { - topLabels: { - type: "array", - items: { - type: "object", - properties: { - description: { type: "string", example: "Mountain" }, - score: { type: "number", example: 0.95 } - } - } - } - } - } - } - } - } - #swagger.responses[400] = { - description: "잘못된 요청 데이터", - content: { - "application/json": { - schema: { - type: "object", - properties: { - error: { type: "string", example: "Base64 이미지 데이터가 제공되지 않았습니다." } - } - } - } - } - } - #swagger.responses[500] = { - description: "서버 내부 오류", - content: { - "application/json": { - schema: { - type: "object", - properties: { - error: { type: "string", example: "라벨링 중 오류가 발생했습니다." } - } - } - } - } - } - */ + #swagger.tags = ['label-detection'] + #swagger.summary = '이미지 라벨링' + #swagger.description = 'Base64 데이터를 JSON으로 받아 이미지를 분석하여 상위 3개의 라벨과 정확도를 반환합니다.' + #swagger.requestBody = { + required: true, + content: { + "multipart/form-data": { + schema: { + type: "object", + properties: { + base64_image: { + type: "string", + format: "binary", + description: "업로드할 이미지 파일" + } + } + } + } + } + } + #swagger.responses[200] = { + description: "라벨링 결과 반환", + content: { + "application/json": { + schema: { + type: "object", + properties: { + topLabels: { + type: "array", + items: { + type: "object", + properties: { + description: { type: "string", example: "Mountain" }, + score: { type: "number", example: 0.95 } + } + } + } + } + } + } + } + } + #swagger.responses[400] = { + description: "잘못된 요청 데이터", + content: { + "application/json": { + schema: { + type: "object", + properties: { + error: { type: "string", example: "Base64 이미지 데이터가 제공되지 않았습니다." } + } + } + } + } + } + #swagger.responses[500] = { + description: "서버 내부 오류", + content: { + "application/json": { + schema: { + type: "object", + properties: { + error: { type: "string", example: "라벨링 중 오류가 발생했습니다." } + } + } + } + } + } + */ try { // Base64 이미지 데이터가 요청에 포함되었는지 확인 - const {base64_image} = req.body; - - if (!base64_image) { - throw new DataValidationError({ - reason: 'Base64 이미지 데이터가 제공되지 않았습니다.', + if (!req.file) { + throw new PhotoDataNotFoundError({ + reason: '라벨링을 추출 할 사진이 없습니다', }); } - // Base64 데이터에서 MIME 타입 제거 - const base64Data = base64_image.replace(/^data:image\/\w+;base64,/, ''); + const base64_image = req.file.buffer.toString('base64'); - // 서비스 호출 - const labels = await detectLabels(base64Data); + if (!isValidBase64(base64_image)) { + throw new PhotoValidationError({ + reason: '올바른 Base64 이미지 형식이 아닙니다.', + }); + } + + //서비스 호출 + const labels = await detectLabels(base64_image); // 라벨 반환 res.status(StatusCodes.OK).json({topLabels: labels}); @@ -101,3 +104,10 @@ export const labelDetectionController = async ( next(error); } }; + +const isValidBase64 = (base64String: string): boolean => { + // base64 문자열 검증 함수 + const base64Regex = + /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/; + return base64Regex.test(base64String); +}; diff --git a/src/routers/tsoaRoutes.ts b/src/routers/tsoaRoutes.ts index 6ef3295..26682ff 100644 --- a/src/routers/tsoaRoutes.ts +++ b/src/routers/tsoaRoutes.ts @@ -12,9 +12,9 @@ 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'; +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'; +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 diff --git a/swagger/openapi.json b/swagger/openapi.json index addaa34..58472b6 100644 --- a/swagger/openapi.json +++ b/swagger/openapi.json @@ -88,14 +88,14 @@ "requestBody": { "required": true, "content": { - "application/json": { + "multipart/form-data": { "schema": { "type": "object", "properties": { "base64_image": { "type": "string", - "description": "Base64 인코딩된 이미지 데이터", - "example": "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD..." + "format": "binary", + "description": "업로드할 이미지 파일" } } }