Skip to content

Commit fbd8fed

Browse files
authored
[SWEP-94] 태그 라벨링 AI 멀티파트 구현 (#197) (#198)
1 parent 499c05f commit fbd8fed

File tree

4 files changed

+96
-85
lines changed

4 files changed

+96
-85
lines changed

src/app.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {sessionAuthMiddleware} from './auth.config.js';
1919
import cookieParser from 'cookie-parser';
2020
import {ValidateError} from 'tsoa';
2121
import {labelDetectionController} from './controllers/tags-ai.controller.js';
22+
import upload from './ai/ai-upload.js';
2223

2324
// routers
2425
import {RegisterRoutes} from './routers/tsoaRoutes.js';
@@ -115,7 +116,7 @@ app.use('/challenge', challengeRouter);
115116
app.use('/user/mypage', myPageRouter);
116117
app.use('/tag', tagRouter);
117118
app.use('/trust', trustRouter);
118-
app.post('/image/ai', labelDetectionController);
119+
app.post('/image/ai', upload.single('base64_image'), labelDetectionController);
119120

120121
RegisterRoutes(app);
121122

Lines changed: 89 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -1,103 +1,113 @@
11
import {NextFunction, Request, Response} from 'express';
22
import {detectLabels} from '../services/tags-ai.service.js';
33
import {StatusCodes} from 'http-status-codes';
4-
import {DataValidationError} from '../errors.js';
4+
import {PhotoDataNotFoundError, PhotoValidationError} from '../errors.js';
55

66
export const labelDetectionController = async (
77
req: Request,
88
res: Response,
99
next: NextFunction,
1010
): Promise<void> => {
1111
/*
12-
#swagger.tags = ['label-detection']
13-
#swagger.summary = '이미지 라벨링'
14-
#swagger.description = 'Base64 데이터를 JSON으로 받아 이미지를 분석하여 상위 3개의 라벨과 정확도를 반환합니다.'
15-
#swagger.requestBody = {
16-
required: true,
17-
content: {
18-
"application/json": {
19-
schema: {
20-
type: "object",
21-
properties: {
22-
base64_image: {
23-
type: "string",
24-
description: "Base64 인코딩된 이미지 데이터",
25-
example: "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD..."
26-
}
27-
}
28-
}
29-
}
30-
}
31-
}
32-
#swagger.responses[200] = {
33-
description: "라벨링 결과 반환",
34-
content: {
35-
"application/json": {
36-
schema: {
37-
type: "object",
38-
properties: {
39-
topLabels: {
40-
type: "array",
41-
items: {
42-
type: "object",
43-
properties: {
44-
description: { type: "string", example: "Mountain" },
45-
score: { type: "number", example: 0.95 }
46-
}
47-
}
48-
}
49-
}
50-
}
51-
}
52-
}
53-
}
54-
#swagger.responses[400] = {
55-
description: "잘못된 요청 데이터",
56-
content: {
57-
"application/json": {
58-
schema: {
59-
type: "object",
60-
properties: {
61-
error: { type: "string", example: "Base64 이미지 데이터가 제공되지 않았습니다." }
62-
}
63-
}
64-
}
65-
}
66-
}
67-
#swagger.responses[500] = {
68-
description: "서버 내부 오류",
69-
content: {
70-
"application/json": {
71-
schema: {
72-
type: "object",
73-
properties: {
74-
error: { type: "string", example: "라벨링 중 오류가 발생했습니다." }
75-
}
76-
}
77-
}
78-
}
79-
}
80-
*/
12+
#swagger.tags = ['label-detection']
13+
#swagger.summary = '이미지 라벨링'
14+
#swagger.description = 'Base64 데이터를 JSON으로 받아 이미지를 분석하여 상위 3개의 라벨과 정확도를 반환합니다.'
15+
#swagger.requestBody = {
16+
required: true,
17+
content: {
18+
"multipart/form-data": {
19+
schema: {
20+
type: "object",
21+
properties: {
22+
base64_image: {
23+
type: "string",
24+
format: "binary",
25+
description: "업로드할 이미지 파일"
26+
}
27+
}
28+
}
29+
}
30+
}
31+
}
32+
#swagger.responses[200] = {
33+
description: "라벨링 결과 반환",
34+
content: {
35+
"application/json": {
36+
schema: {
37+
type: "object",
38+
properties: {
39+
topLabels: {
40+
type: "array",
41+
items: {
42+
type: "object",
43+
properties: {
44+
description: { type: "string", example: "Mountain" },
45+
score: { type: "number", example: 0.95 }
46+
}
47+
}
48+
}
49+
}
50+
}
51+
}
52+
}
53+
}
54+
#swagger.responses[400] = {
55+
description: "잘못된 요청 데이터",
56+
content: {
57+
"application/json": {
58+
schema: {
59+
type: "object",
60+
properties: {
61+
error: { type: "string", example: "Base64 이미지 데이터가 제공되지 않았습니다." }
62+
}
63+
}
64+
}
65+
}
66+
}
67+
#swagger.responses[500] = {
68+
description: "서버 내부 오류",
69+
content: {
70+
"application/json": {
71+
schema: {
72+
type: "object",
73+
properties: {
74+
error: { type: "string", example: "라벨링 중 오류가 발생했습니다." }
75+
}
76+
}
77+
}
78+
}
79+
}
80+
*/
8181

8282
try {
8383
// Base64 이미지 데이터가 요청에 포함되었는지 확인
84-
const {base64_image} = req.body;
85-
86-
if (!base64_image) {
87-
throw new DataValidationError({
88-
reason: 'Base64 이미지 데이터가 제공되지 않았습니다.',
84+
if (!req.file) {
85+
throw new PhotoDataNotFoundError({
86+
reason: '라벨링을 추출 할 사진이 없습니다',
8987
});
9088
}
9189

92-
// Base64 데이터에서 MIME 타입 제거
93-
const base64Data = base64_image.replace(/^data:image\/\w+;base64,/, '');
90+
const base64_image = req.file.buffer.toString('base64');
9491

95-
// 서비스 호출
96-
const labels = await detectLabels(base64Data);
92+
if (!isValidBase64(base64_image)) {
93+
throw new PhotoValidationError({
94+
reason: '올바른 Base64 이미지 형식이 아닙니다.',
95+
});
96+
}
97+
98+
//서비스 호출
99+
const labels = await detectLabels(base64_image);
97100

98101
// 라벨 반환
99102
res.status(StatusCodes.OK).json({topLabels: labels});
100103
} catch (error) {
101104
next(error);
102105
}
103106
};
107+
108+
const isValidBase64 = (base64String: string): boolean => {
109+
// base64 문자열 검증 함수
110+
const base64Regex =
111+
/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/;
112+
return base64Regex.test(base64String);
113+
};

src/routers/tsoaRoutes.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,9 @@ import { MemoImageController } from './../controllers/tsoa.memo-image.controller
1212
// 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
1313
import { MemoFolderController } from './../controllers/tsoa.memo-folder.controller.js';
1414
// 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
15-
import { MemoCreateFolderOCRController } from '../controllers/memo-updateFolderOCR.controller.js';
15+
import { MemoCreateFolderOCRController } from './../controllers/memo-updateFolderOCR.controller.js';
1616
// 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
17-
import { MemoCreateUpdateOCRController } from '../controllers/memo-createFolderOCR.controller.js';
17+
import { MemoCreateUpdateOCRController } from './../controllers/memo-createFolderOCR.controller.js';
1818
// 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
1919
import { MostTaggedController } from './../controllers/history.controller.js';
2020
// 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

swagger/openapi.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -88,14 +88,14 @@
8888
"requestBody": {
8989
"required": true,
9090
"content": {
91-
"application/json": {
91+
"multipart/form-data": {
9292
"schema": {
9393
"type": "object",
9494
"properties": {
9595
"base64_image": {
9696
"type": "string",
97-
"description": "Base64 인코딩된 이미지 데이터",
98-
"example": "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD..."
97+
"format": "binary",
98+
"description": "업로드할 이미지 파일"
9999
}
100100
}
101101
}

0 commit comments

Comments
 (0)