Skip to content

Commit

Permalink
v.1.1.3 (#16)
Browse files Browse the repository at this point in the history
Modify: Swagger AWS server 주소 변경
Modify: getfeedList API의 query name 변경
  - page => index
  • Loading branch information
inchanS authored Apr 13, 2023
1 parent d77f662 commit f35a0a5
Show file tree
Hide file tree
Showing 5 changed files with 124 additions and 66 deletions.
2 changes: 1 addition & 1 deletion api-docs
4 changes: 2 additions & 2 deletions src/controllers/feeds.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,10 +155,10 @@ const deleteFeed = async (req: Request, res: Response) => {
const getFeedList = async (req: Request, res: Response) => {
const categoryId: number = Number(req.query.categoryId);

const page: number = Number(req.query.page);
const index: number = Number(req.query.index);
const limit: number = Number(req.query.limit);

const result = await feedsService.getFeedList(categoryId, page, limit);
const result = await feedsService.getFeedList(categoryId, index, limit);

res.status(200).json(result);
};
Expand Down
75 changes: 50 additions & 25 deletions src/services/feeds.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,35 +32,23 @@ const getTempFeedList = async (userId: number) => {
};

// 임시저장 및 게시글 저장 -----------------------------------------------------------
const createFeed = async (
const maxTransactionAttempts = 3;
const executeTransactionWithRetry = async (
attempt: number,
feedInfo: TempFeedDto | FeedDto,
fileLinks: string[],
options?: FeedOption
options: FeedOption
): Promise<Feed> => {
if (feedInfo.status === 2) {
feedInfo = plainToInstance(TempFeedDto, feedInfo);
} else {
feedInfo = plainToInstance(FeedDto, feedInfo);
feedInfo.posted_at = new Date();
}

await validateOrReject(feedInfo).catch(errors => {
throw { status: 500, message: errors[0].constraints };
});

// transaction으로 묶어주기
const queryRunner = dataSource.createQueryRunner();
await queryRunner.connect();
await queryRunner.startTransaction();

try {
// feed 저장
const newFeedInstance = plainToInstance(Feed, feedInfo);
const newFeed = await queryRunner.manager
.withRepository(FeedRepository)
.createFeed(newFeedInstance);

// uploadFile에 feed의 ID를 연결해주는 함수
if (fileLinks) {
await uploadFileService.updateFileLinks(queryRunner, newFeed, fileLinks);

Expand All @@ -85,16 +73,53 @@ const createFeed = async (
.getFeed(newFeed.id, options);

await queryRunner.commitTransaction();

return result;
} catch (err) {
} catch (err: any) {
await queryRunner.rollbackTransaction();
throw new Error(`createFeed TRANSACTION error: ${err}`);
} finally {
await queryRunner.release();

if (
err.message.includes('Lock wait timeout') &&
attempt < maxTransactionAttempts
) {
console.log(`createFeed TRANSACTION retry: ${attempt}`);
return await executeTransactionWithRetry(
attempt + 1,
feedInfo,
fileLinks,
options
);
} else {
throw new Error(`createFeed TRANSACTION error: ${err}`);
}
}
};

const createFeed = async (
feedInfo: TempFeedDto | FeedDto,
fileLinks: string[],
options?: FeedOption
): Promise<Feed> => {
if (feedInfo.status === 2) {
feedInfo = plainToInstance(TempFeedDto, feedInfo);
} else {
feedInfo = plainToInstance(FeedDto, feedInfo);
feedInfo.posted_at = new Date();
}

await validateOrReject(feedInfo).catch(errors => {
throw { status: 500, message: errors[0].constraints };
});

const result = await executeTransactionWithRetry(
1,
feedInfo,
fileLinks,
options
);

return result;
};

// 임시게시글 및 게시글 수정 -----------------------------------------------------------
const updateFeed = async (
userId: number,
Expand Down Expand Up @@ -211,7 +236,7 @@ const getFeed = async (
// 게시글 리스트 --------------------------------------------------------------
const getFeedList = async (
categoryId: number,
page: number,
index: number,
limit: number
): Promise<FeedList[]> => {
// query로 전달된 categoryId가 0이거나 없을 경우 undefined로 변경 처리
Expand All @@ -224,10 +249,10 @@ const getFeedList = async (
limit = 10;
}

if (!page) {
page = 1;
if (!index) {
index = 1;
}
const startIndex: number = (page - 1) * limit;
const startIndex: number = (index - 1) * limit;
return await FeedListRepository.getFeedList(categoryId, startIndex, limit);
};

Expand Down
107 changes: 70 additions & 37 deletions src/services/upload.service.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,38 @@
import sharp from 'sharp';
import {
DeleteObjectsCommand,
GetObjectCommand,
PutObjectCommand,
} from '@aws-sdk/client-s3';
import { DeleteObjectsCommand, PutObjectCommand } from '@aws-sdk/client-s3';
import { s3 } from '../middleware/uploadToS3';
import dataSource from '../repositories/data-source';
import { UploadFiles } from '../entities/uploadFiles.entity';
import crypto from 'crypto';
import { UserRepository } from '../repositories/user.repository';
import AWS from 'aws-sdk';

const lambda = new AWS.Lambda({
region: process.env.AWS_REGION,
});

const invokeLambda = async (param: { Bucket: string; Key: string }) => {
const lambdaParams = {
FunctionName: 'checkFileAccessLambda',
Payload: JSON.stringify(param),
};

console.log(param);
console.log(lambdaParams);

// FIXME : invokeLambda 함수에서 에러가 발생하면, 에러를 잡아서 처리해야 한다.(작동은 하는것 같은데 예외처리가 안된다. 확인 필요)

return new Promise((resolve, reject) => {
lambda.invoke(lambdaParams, function (err, data: any) {
if (err) {
reject(err);
} else {
console.log('invokeLambda data: ', data);
resolve(JSON.parse(data.Payload));
}
});
});
};

const uploadFiles = async (
userId: number,
Expand Down Expand Up @@ -105,6 +129,32 @@ const uploadFiles = async (
}
return files_link;
};
// uploadFiles 업로드된 파일을 삭제하는 함수 ---------------------------------------------------

// mySQL에서 file_link를 통해 uploadFile의 ID를 찾는 함수
const findFile = async (file_link: string) => {
try {
return await dataSource.manager.findOneOrFail<UploadFiles>(UploadFiles, {
where: { file_link: file_link },
});
} catch (err) {
throw { status: 404, message: 'NOT_FOUND_UPLOAD_FILE' };
}
};

// AWS S3에서 파일의 유무를 확인하는 함수
// const checkFileAccess = async (param: any) => {
// try {
// await s3.send(new GetObjectCommand(param));
// } catch (err: any) {
// if (err.Code === 'AccessDenied' || err.$metadata.httpStatusCode === 404) {
// throw {
// status: 404,
// message: `DELETE_UPLOADED_FILE_IS_NOT_EXISTS: ${err}`,
// };
// }
// }
// };

const deleteUploadFile = async (
userId: number,
Expand All @@ -127,31 +177,6 @@ const deleteUploadFile = async (
(file_link: string) => file_link !== 'DELETE_FROM_UPLOAD_FILES_TABLE'
);

// mySQL에서 file_link를 통해 uploadFile의 ID를 찾는 함수
const findFile = async (file_link: string) => {
try {
return await dataSource.manager.findOneOrFail<UploadFiles>(UploadFiles, {
where: { file_link: file_link },
});
} catch (err) {
throw { status: 404, message: 'NOT_FOUND_UPLOAD_FILE' };
}
};

// AWS S3에서 파일의 유무를 확인하는 함수
const checkFileAccess = async (param: any) => {
try {
await s3.send(new GetObjectCommand(param));
} catch (err: any) {
if (err.Code === 'AccessDenied' || err.$metadata.httpStatusCode === 404) {
throw {
status: 404,
message: `DELETE_UPLOADED_FILE_IS_NOT_EXISTS: ${err}`,
};
}
}
};

const deleteFiles = async (newFileLinks: string[], userId: number) => {
const findAndCheckPromises = newFileLinks.map(async file_link => {
const findFileResult = await findFile(file_link);
Expand All @@ -161,23 +186,22 @@ const deleteUploadFile = async (
throw { status: 403, message: 'DELETE_UPLOADED_FILE_IS_NOT_YOURS' };
}

const param = {
const param: { Bucket: string; Key: string } = {
Bucket: process.env.AWS_S3_BUCKET,
Key: findFileResult.file_link.split('.com/')[1],
};

keyArray.push({ Key: param.Key });
uploadFileIdArray.push(findFileResult.id);

await checkFileAccess(param);
// FIXME 여기가 지울 파일이 많아지면 병목현상?인지 여튼 오래걸리면서 transaction이 잠기는 현상이 발생한다.
// await checkFileAccess(param);
await invokeLambda(param);
});

await Promise.all(findAndCheckPromises);

// 이후 작업들을 수행하세요.
};

// 이 함수를 호출하여 파일을 삭제하세요:
// 이 함수를 호출하여 파일을 삭제
await deleteFiles(newFileLinks, userId);

const params = {
Expand All @@ -199,7 +223,16 @@ const deleteUploadFile = async (
// file_links에 'DELETE_FROM_UPLOAD_FILES_TABLE'가 포함되어있으면 mySQL 테이블에서도 개체 삭제
if (file_links.includes('DELETE_FROM_UPLOAD_FILES_TABLE')) {
// mySQL에서 개체 삭제
await dataSource.manager.softDelete(UploadFiles, uploadFileIdArray);
const queryRunner = dataSource.createQueryRunner();
await queryRunner.connect();
await queryRunner.startTransaction();
try {
await dataSource.manager.softDelete(UploadFiles, uploadFileIdArray);
await queryRunner.commitTransaction();
} catch (err) {
await queryRunner.rollbackTransaction();
throw new Error(`DELETE_UPLOAD_FILE_FAIL: ${err}`);
}
}
};

Expand Down
2 changes: 1 addition & 1 deletion src/utils/swagger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ const options = {
},
{
description: 'project_review AWS RDS Test API document',
url: 'http://15.164.86.242:8005',
url: 'http://3.38.6.179:8000',
},
],
},
Expand Down

0 comments on commit f35a0a5

Please sign in to comment.