Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

밈 기본 API 수정 #3

Merged
merged 15 commits into from
Jun 18, 2024
Merged
Show file tree
Hide file tree
Changes from 8 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
32 changes: 32 additions & 0 deletions src/app.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import express, { Application } from 'express';
import mongoose from 'mongoose';

import router from './routes';
import config from './util/config';
import { attachRequestId, logger } from './util/logger';

const DATABASE_URL = `${config.DB_URL}`;

const app: Application = express();

app.use(express.json());
app.use(express.urlencoded({ extended: true }));

app.get('/', (_, res) => {
res.send('Hello! We are Farmeme');
});

app.use(attachRequestId);
app.use('/', router);

export const connectToDatabase = async () => {
try {
await mongoose.connect(DATABASE_URL);
logger.info('Connected to MongoDB');
} catch (err) {
logger.error('Error connecting to MongoDB', err);
throw err;
}
};

export default app;
55 changes: 43 additions & 12 deletions src/controller/meme.controller.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,36 @@
import { Request, Response, NextFunction } from 'express';
import _ from 'lodash';
import mongoose from 'mongoose';

import CustomError from '../errors/CustomError';
import { HttpCode } from '../errors/HttpCode';
import { CustomMemeRequest } from '../middleware/requestedInfo';
import { IMeme, IMemeCreatePayload } from '../model/meme';
import { IMemeCreatePayload } from '../model/meme';
import * as MemeService from '../service/meme.service';
import { logger } from '../util/logger';

const getMeme = async (req: Request, res: Response, next: NextFunction) => {
const memeId = req.params?.memeId || req.body?.memeId || null;
const memeId = req.params?.memeId || null;

const meme = await MemeService.getMeme(memeId);
logger.info(`Get meme - ${memeId})`);
return res.json({ ...meme });
if (_.isNull(memeId)) {
return next(new CustomError(`'memeId' field should be provided`, HttpCode.BAD_REQUEST));
}

if (!mongoose.Types.ObjectId.isValid(memeId)) {
return next(new CustomError(`'memeId' is not a valid ObjectId`, HttpCode.BAD_REQUEST));
}

try {
const meme = await MemeService.getMeme(memeId);
if (_.isNull(meme)) {
return next(new CustomError(`Meme(${memeId}) not found.`, HttpCode.NOT_FOUND));
}

logger.info(`Get meme - ${memeId})`);
return res.json({ ...meme });
} catch (err) {
return next(new CustomError(err.message, err.status));
}
};

const createMeme = async (req: Request, res: Response, next: NextFunction) => {
Expand Down Expand Up @@ -42,7 +59,7 @@ const updateMeme = async (req: CustomMemeRequest, res: Response, next: NextFunct
const updateInfo: IMemeCreatePayload = req.body;

try {
const updatedMeme = await MemeService.updateMeme(meme._id, updateInfo);
const updatedMeme = await MemeService.updateMeme(meme._id as string, updateInfo);
return res.json({ ...updatedMeme });
} catch (err) {
return next(new CustomError(err.message, err.status));
Expand All @@ -52,16 +69,23 @@ const updateMeme = async (req: CustomMemeRequest, res: Response, next: NextFunct
const deleteMeme = async (req: CustomMemeRequest, res: Response, next: NextFunction) => {
const meme = req.requestedMeme;
try {
const deletedMeme = await MemeService.deleteMeme(meme._id);
const deletedMeme = await MemeService.deleteMeme(meme._id as string);
return res.json({ result: deletedMeme });
} catch (err) {
return next(new CustomError(err.message, err.status));
}
};

const getAllMemeList = async (req: Request, res: Response, next: NextFunction) => {
const page = parseInt(req?.params.page) || 1;
const size = parseInt(req?.params.size) || 10;
const page = parseInt(req.query.page as string) || 1;
if (page < 1) {
return next(new CustomError(`Invalid 'page' parameter`, HttpCode.BAD_REQUEST));
}

const size = parseInt(req.query.size as string) || 10;
if (size < 1) {
return next(new CustomError(`Invalid 'size' parameter`, HttpCode.BAD_REQUEST));
}

try {
const memeList = await MemeService.getAllMemeList(page, size);
Expand All @@ -72,10 +96,17 @@ const getAllMemeList = async (req: Request, res: Response, next: NextFunction) =
};

const getTodayMemeList = async (req: Request, res: Response, next: NextFunction) => {
const limit = _.get(req.body, 'limit', 5);
const size = parseInt(req.query.size as string) || 5;

if (size > 5) {
return next(
new CustomError(`Invalid 'size' parameter. Today Meme max size is 5.`, HttpCode.BAD_REQUEST),
);
}

try {
const todayMemeList = await MemeService.getTodayMemeList(limit);
return res.json(todayMemeList);
const todayMemeList = await MemeService.getTodayMemeList(size);
return res.json({ data: todayMemeList });
} catch (err) {
return next(new CustomError(err.message, err.status));
}
Expand Down
47 changes: 17 additions & 30 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,31 +1,18 @@
import express, { Application, Request, Response, json, urlencoded } from 'express';
import mongoose from 'mongoose';

import router from './routes';
import app, { connectToDatabase } from './app';
import config from './util/config';
import { attachRequestId, logger } from './util/logger';

const DATABASE_URL = `${config.DB_URL}`;

async function startServer() {
const app: Application = express();
await mongoose.connect(DATABASE_URL);
logger.info('Database Connected');

app.use(json());
app.use(urlencoded({ extended: true }));

app.get('/', (_req: Request, res: Response) => {
res.send('Hello PPAC');
});

app.use(attachRequestId);

app.use('/', router);

app.listen(3000, () => {
logger.info('Ready to start server');
});
}

setImmediate(startServer);
import { logger } from './util/logger';

const port = config.PORT;

const startServer = async () => {
try {
await connectToDatabase();
app.listen(port, () => {
logger.info(`Server started on port ${port}`);
});
} catch (err) {
logger.error('Failed to start server', err);
}
};

startServer();
10 changes: 7 additions & 3 deletions src/middleware/requestedInfo.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import { NextFunction, Request, Response } from 'express';
import _ from 'lodash';
import mongoose from 'mongoose';

import CustomError from '../errors/CustomError';
import { HttpCode } from '../errors/HttpCode';
import { IMeme } from '../model/meme';
import { IMemeDocument } from '../model/meme';
import { getMeme } from '../service/meme.service';

export interface CustomMemeRequest extends Request {
requestedMeme?: IMeme;
requestedMeme?: IMemeDocument;
}

export const getRequestedMemeInfo = async (
Expand All @@ -21,8 +22,11 @@ export const getRequestedMemeInfo = async (
return next(new CustomError(`'memeId' should be provided`, HttpCode.BAD_REQUEST));
}

const meme = await getMeme(memeId);
if (!mongoose.Types.ObjectId.isValid(memeId)) {
return next(new CustomError(`'memeId' is not a valid ObjectId`, HttpCode.BAD_REQUEST));
}

const meme = await getMeme(memeId);
if (_.isNull(meme)) {
return next(new CustomError(`Meme(${memeId}) does not exist`, HttpCode.NOT_FOUND));
}
Expand Down
15 changes: 11 additions & 4 deletions src/model/meme.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,21 @@
import mongoose, { Schema } from 'mongoose';
import mongoose, { Schema, Document } from 'mongoose';

export interface IMemeCreatePayload {
keywords: string[];
image: string;
source?: string;
source: string;
isTodayMeme: boolean;
}

export interface IMeme {
_id: string;
keywords: string[];
image: string;
reaction: number;
source: string;
isTodayMeme: boolean;
}

export interface IMemeDocument extends Document {
keywords: string[];
image: string;
reaction: number;
Expand All @@ -35,4 +42,4 @@ const MemeSchema: Schema = new Schema(
},
);

export const MemeModel = mongoose.model<IMeme>('Meme', MemeSchema);
export const MemeModel = mongoose.model<IMemeDocument>('Meme', MemeSchema);
3 changes: 0 additions & 3 deletions src/routes/meme.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,9 @@ import {
getAllMemeList,
} from '../controller/meme.controller';
import { getRequestedMemeInfo } from '../middleware/requestedInfo';
import { loggerMiddleware } from '../util/logger';

const router = express.Router();

router.use(loggerMiddleware);

router.get('/list', getAllMemeList); // meme 목록 전체 조회
router.get('/todayMeme', getTodayMemeList); // 오늘의 추천 밈 (5개)

Expand Down
29 changes: 19 additions & 10 deletions src/service/meme.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,38 +2,47 @@ import _ from 'lodash';

import CustomError from '../errors/CustomError';
import { HttpCode } from '../errors/HttpCode';
import { IMeme, IMemeCreatePayload, MemeModel } from '../model/meme';
import { IMemeCreatePayload, IMemeDocument, MemeModel } from '../model/meme';
import { logger } from '../util/logger';

async function getMeme(memeId: string): Promise<IMeme> {
async function getMeme(memeId: string): Promise<IMemeDocument | null> {
try {
const meme = await MemeModel.findOne({ _id: memeId, isDeleted: false });
return meme.toObject();
if (!meme) {
logger.info(`Meme(${memeId}) not found.`);
return null;
}

return meme.toObject() as IMemeDocument;
} catch (err) {
logger.info(`Failed to get a meme(${memeId})`);
logger.error(`Failed to get a meme(${memeId}): ${err.message}`);
throw new CustomError(`Failed to get a meme(${memeId})`, HttpCode.INTERNAL_SERVER_ERROR);
}
}

async function getTodayMemeList(limit: number = 5): Promise<IMeme[]> {
async function getTodayMemeList(limit: number = 5): Promise<IMemeDocument[]> {
const todayMemeList = await MemeModel.find({ isTodayMeme: true, isDeleted: false })
.limit(limit)
.lean();

logger.info(`Get all today meme list(${todayMemeList}), limit(${limit})`);
const memeIds = todayMemeList.map((meme) => meme._id);
logger.info(
`Get all today meme list(${todayMemeList.length}) - memeIds(${memeIds}), limit(${limit})`,
);
return todayMemeList;
}

async function getAllMemeList(
page: number,
size: number,
): Promise<{ total: number; page: number; totalPages: number; data: IMeme[] }> {
): Promise<{ total: number; page: number; totalPages: number; data: IMemeDocument[] }> {
const totalMemes = await MemeModel.countDocuments();

const memeList = await MemeModel.find({ isDeleted: false })
.skip((page - 1) * size)
.limit(size)
.sort({ createdAt: -1 });
logger.info(`Get all meme list(${memeList}), page(${page}), size(${size}), total(${totalMemes})`);
logger.info(`Get all meme list - page(${page}), size(${size}), total(${totalMemes})`);

return {
total: totalMemes,
Expand All @@ -43,7 +52,7 @@ async function getAllMemeList(
};
}

async function createMeme(info: IMemeCreatePayload): Promise<IMeme> {
async function createMeme(info: IMemeCreatePayload): Promise<IMemeDocument> {
const meme = await MemeModel.create({
...info,
});
Expand All @@ -53,7 +62,7 @@ async function createMeme(info: IMemeCreatePayload): Promise<IMeme> {
return meme.toObject();
}

async function updateMeme(memeId: string, updateInfo: any): Promise<IMeme> {
async function updateMeme(memeId: string, updateInfo: any): Promise<IMemeDocument> {
const meme = await MemeModel.findOneAndUpdate(
{ _id: memeId, isDeleted: false },
{ $set: updateInfo },
Expand Down