Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 76 additions & 0 deletions src/controllers/challenge.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,11 @@ import {
Get,
Query,
Response,
Post,
} from 'tsoa';
import {
serviceAcceptChallenge,
serviceChallengeImageUpload,
serviceCompleteChallenge,
serviceDeleteChallenge,
serviceGetByUserId,
Expand Down Expand Up @@ -438,4 +440,78 @@ export class ChallengeController extends Controller {

return new TsoaSuccessResponse(result);
}

/**
* 챌린지의 이미지들을 업로드합니다.
*
* @summary 챌린지 이미지 업로드 API
* @param challengeId 챌린지 ID
* @param req
* @param body 이미지ID의 배열(string[])
* @returns 업로드 성공 유무
*/
@Post('/images/upload/:challengeId')
@Tags('Challenge')
@SuccessResponse(StatusCodes.OK, '챌린지 이미지 업로드 성공 응답')
@Response<ITsoaErrorResponse>(StatusCodes.BAD_REQUEST, 'Not Found', {
resultType: 'FAIL',
error: {
errorCode: 'SRH-400',
reason: 'req.user 정보가 없습니다.',
data: null,
},
success: null,
})
@Response<ITsoaErrorResponse>(StatusCodes.BAD_REQUEST, 'Not Found', {
resultType: 'FAIL',
error: {
errorCode: 'CHL-400',
reason: '이미지 업로드 중 문제가 발생했습니다.',
data: null,
},
success: null,
})
@Response<ITsoaErrorResponse>(StatusCodes.NOT_FOUND, 'Not Found', {
resultType: 'FAIL',
error: {
errorCode: 'CHL-404',
reason: '이미지가 서버에 존재하지 않습니다.',
data: null,
},
success: null,
})
@Response<ITsoaErrorResponse>(
StatusCodes.INTERNAL_SERVER_ERROR,
'Internal Server Error',
{
resultType: 'FAIL',
error: {
errorCode: 'SER-001',
reason: '내부 서버 오류입니다.',
data: null,
},
success: null,
},
)
public async handleChallengeImageUpload(
@Path('challengeId') challengeId: string,
@Request() req: ExpressRequest,
@Body() body: string[]
): Promise<ITsoaSuccessResponse<string>>{
if (!req.user) {
throw new DataValidationError({reason: 'req.user 정보가 없습니다.'});
}

await serviceChallengeImageUpload(body, challengeId, req.user.id)
.then(() => {return;})
.catch(err => {
if (!(err instanceof BaseError)) {
throw new ServerError();
} else {
throw err;
}
});

return new TsoaSuccessResponse(`${challengeId}챌린지 이미지 업로드 성공`);
}
}
20 changes: 19 additions & 1 deletion src/dtos/challenge.dtos.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,15 +83,21 @@ export const responseFromGetByUserId = (
): ResponseFromGetByUserIdReform[] => {
return challenges.map((value: ResponseFromGetByUserId) => {
const {id, title, context, requiredCount, remainingCount, userId,
createdAt, updatedAt, acceptedAt, completedAt, status, locationChallenge, dateChallenge
createdAt, updatedAt, acceptedAt, completedAt, status, locationChallenge, dateChallenge, images
} = value;

const imageList: string[] = images.map((value: {image: {mediaId: bigint}}) =>
{
return value.image.mediaId.toString();
});

return {
id: id.toString(),
title,
context,
challengeLocation: locationChallenge?.challengeLocation,
challengeDate: dateChallenge?.challengeDate,
images: imageList,
requiredCount,
remainingCount,
userId: userId.toString(),
Expand Down Expand Up @@ -128,4 +134,16 @@ export const bodyToWeeklyCreation = (data: BodyToWeeklyCreation, userId: bigint)
challengeDate,
required
};
};

export const challengeImageUplaodBody = (imageIdList: {id: bigint}[], challengeId: bigint): {
imageId: bigint,
challengeId: bigint
}[] => {
return imageIdList.map((value: {id: bigint}) => {
return {
imageId: value.id,
challengeId
};
});
};
14 changes: 14 additions & 0 deletions src/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,20 @@ export class DateChallengeNotFoundError extends BaseError {
}
}

// 챌린지 이미지 업로드 에러
export class ChallengeImageUploadError extends BaseError {
constructor(details: {reason: string}) {
super(400, 'CHL-400', '이미지 업로드 중 문제가 발생했습니다.', details);
}
}

// 챌린지 이미지 존재하지 않음 에러
export class ChallengeImageMissingError extends BaseError {
constructor(details: {reason: string}) {
super(404, 'CHL-404', '이미지가 서버에 존재하지 않습니다.', details);
}
}

// 네이버 API 관련 에러
export class NaverGeoCodeError extends BaseError {
constructor(details: {reason: string}) {
Expand Down
4 changes: 4 additions & 0 deletions src/models/challenge.entities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,9 @@ export interface ResponseFromGetByUserId {
dateChallenge: {
challengeDate: Date;
} | null;
images: {
image: {mediaId: bigint}
}[];

id: bigint;
userId: bigint;
Expand All @@ -111,6 +114,7 @@ export interface ResponseFromGetByUserIdReform {
context: string;
challengeLocation: string | undefined;
challengeDate: Date | undefined;
images: string[];
requiredCount: number;
remainingCount: number;
createdAt: Date;
Expand Down
56 changes: 54 additions & 2 deletions src/repositories/challenge.repositories.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import { challengeImageUplaodBody } from 'src/dtos/challenge.dtos.js';
import {prisma} from '../db.config.js';
import {
ChallengeAcceptError,
ChallengeCompleteError,
ChallengeDeletionError,
ChallengeImageMissingError,
ChallengeImageUploadError,
ChallengeUpdateError
} from '../errors.js';
import {
Expand Down Expand Up @@ -39,8 +42,6 @@ export const challengeExist = async (userId: bigint): Promise<Boolean> => {
}
});

console.log(isExistChallenge);

if(isExistChallenge){
return true;
}
Expand Down Expand Up @@ -216,8 +217,59 @@ export const getChallengeByUserId = async (
challengeDate: true,
},
},
images: {
select: {
image: {
select: {
mediaId: true
}
}
}
}
},
});

//console.log(challenges[0].images);

return challenges;
};

export const challengeImageUpload = async (
imageIdList: bigint[],
challengeId: bigint,
userId: bigint
): Promise<{count: number}> => {
const duplicateChallenge = await prisma.challengeImage.findFirst({
where: {
challengeId: challengeId
}
});

if(duplicateChallenge){
throw new ChallengeImageUploadError({reason: `${challengeId}챌린지에 이미지가 이미 존재합니다.`});
}

const foundImage = await prisma.image.findMany({
where: {
mediaId: {
in: imageIdList
},
userId: userId
},
select: {
id: true
}
});

if(foundImage.length !== imageIdList.length){
throw new ChallengeImageMissingError({reason: '서버에 존재하지 않는 이미지가 있습니다.'});
}

const inputData = challengeImageUplaodBody(foundImage, challengeId);

const upload = await prisma.challengeImage.createMany({
data: inputData
});

return upload;
};
33 changes: 33 additions & 0 deletions src/routers/tsoaRoutes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -535,6 +535,7 @@ const models: TsoaRoute.Models = {
"context": {"dataType":"string","required":true},
"challengeLocation": {"dataType":"union","subSchemas":[{"dataType":"string"},{"dataType":"undefined"}],"required":true},
"challengeDate": {"dataType":"union","subSchemas":[{"dataType":"datetime"},{"dataType":"undefined"}],"required":true},
"images": {"dataType":"array","array":{"dataType":"string"},"required":true},
"requiredCount": {"dataType":"double","required":true},
"remainingCount": {"dataType":"double","required":true},
"createdAt": {"dataType":"datetime","required":true},
Expand Down Expand Up @@ -1842,6 +1843,38 @@ export function RegisterRoutes(app: Router) {
}
});
// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
const argsChallengeController_handleChallengeImageUpload: Record<string, TsoaRoute.ParameterSchema> = {
challengeId: {"in":"path","name":"challengeId","required":true,"dataType":"string"},
req: {"in":"request","name":"req","required":true,"dataType":"object"},
body: {"in":"body","name":"body","required":true,"dataType":"array","array":{"dataType":"string"}},
};
app.post('/challenge/images/upload/:challengeId',
...(fetchMiddlewares<RequestHandler>(ChallengeController)),
...(fetchMiddlewares<RequestHandler>(ChallengeController.prototype.handleChallengeImageUpload)),

async function ChallengeController_handleChallengeImageUpload(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: argsChallengeController_handleChallengeImageUpload, request, response });

const controller = new ChallengeController();

await templateService.apiHandler({
methodName: 'handleChallengeImageUpload',
controller,
response,
next,
validatedArgs,
successStatus: 200,
});
} catch (err) {
return next(err);
}
});
// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa

// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa

Expand Down
16 changes: 16 additions & 0 deletions src/services/challenge.services.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
acceptChallenge,
getChallengeByUserId,
completeChallenge,
challengeImageUpload,
} from '../repositories/challenge.repositories.js';
import {
ChallengeUpdateError,
Expand Down Expand Up @@ -107,3 +108,18 @@ export const serviceGetByUserId = async (
throw error;
}
};

export const serviceChallengeImageUpload = async (
imageIdList: string[],
challengeId: string,
userId: bigint
): Promise<void> => {
const imageList: bigint[] = imageIdList.map((value: string) => {return BigInt(value);});
const challenge: bigint = BigInt(challengeId);

const result: {count: number}= await challengeImageUpload(imageList, challenge, userId);

if(imageIdList.length !== result.count){
throw new Error('챌린지에 이미지 업로드 중 문제가 생겼습니다.');
}
};
Loading