Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
fb81c77
seed 생성
aowjarkwk May 21, 2024
de043b5
상품 등록, 상품 목록 조회
aowjarkwk May 21, 2024
2f75548
스키마의 필드 순서 변경
aowjarkwk May 22, 2024
75f7202
상품 상세 조회, 상품 수정
aowjarkwk May 22, 2024
7f34ae2
상품 삭제, 좋아요, 좋아요 취소
aowjarkwk May 22, 2024
d5dbbdf
환경 변수 설정
aowjarkwk May 22, 2024
b26cab1
cors 설정
aowjarkwk May 22, 2024
3d86d69
Mongo DB를 PostgreSQL로 변경
aowjarkwk May 22, 2024
77a4ec5
상품 등록, 상품 수정, 상품 삭제
aowjarkwk May 22, 2024
175c432
seed 생성
aowjarkwk May 22, 2024
ca82e28
쿼리 파라미터 처리 기능 추가
aowjarkwk May 22, 2024
8000c77
오류 처리 - 중고마켓 API 구현
aowjarkwk May 22, 2024
16a5a58
게시글 목록 조회, 게시글 상세 조회
aowjarkwk May 22, 2024
30b2980
상품 등록 api 오류 수정
aowjarkwk May 22, 2024
ee6daef
게시글 등록, 수정, 삭제
aowjarkwk May 22, 2024
d632f32
게시글 좋아요, 좋아요 취소
aowjarkwk May 22, 2024
883250b
댓글 모델, seed, mock 데이터 생성
aowjarkwk May 23, 2024
fe528aa
중고마켓 댓글 목록 조회, 등록, 수정, 삭제
aowjarkwk May 23, 2024
2de35b2
자유게시판 댓글 목록 조회, 등록, 수정, 삭제
aowjarkwk May 23, 2024
4d09d8c
데이터 유효성 검사, 에러메시지
aowjarkwk May 23, 2024
5e3948a
writer 필드를 optional로 수정
aowjarkwk May 23, 2024
a758889
[#M10] feat : 이미지 업로드 기능 구현
aowjarkwk May 27, 2024
48704d7
[#M10] feat : 폴더 정리
aowjarkwk May 27, 2024
e1f7038
[#M10] feat : 회원가입, 로그인 기능 구현
aowjarkwk May 27, 2024
6fb96d5
[#M10] feat : 라우트 경로 통합, API에 인가 적용
aowjarkwk May 27, 2024
3a16ac9
[#M10] feat : jwt sliding session 적용
aowjarkwk May 27, 2024
98e2a96
[#M10] fix : 상품 좋아요, 좋아요 취소시 유저 확인하도록 수정
aowjarkwk May 27, 2024
94a9855
[#M10] fix : 상품 등록 시 인가 된 userId 연동
aowjarkwk May 27, 2024
00ab551
[#M10] feat : morgan을 사용하여 모든 요청의 상세 정보를 로그로 기록
aowjarkwk May 28, 2024
2f4ae01
[#M10] fix : 상품 관련 API들이 userId를 사용하도록 수정
aowjarkwk May 28, 2024
33d0c39
[#M10] fix : 자유게시판 관련 API들이 userId를 사용하도록 수정
aowjarkwk May 28, 2024
1e79c48
[#M10] feat : 좋아요, 좋아요 취소 기능에 transaction 적용
aowjarkwk May 28, 2024
3d46d25
[#M10] feat : 에러 핸들러 미들웨어 구현
aowjarkwk May 28, 2024
500f34e
[#M10] fix : MVC 패턴 적용 및 CORS 미들웨어 추가
aowjarkwk May 28, 2024
520f751
[#M10] feat: 구글 OAuth 회원가입 및 로그인 구현
aowjarkwk May 28, 2024
acec3d6
[#M10] feat: Swagger API 명세서 작성
aowjarkwk May 28, 2024
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
134 changes: 134 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*

# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json

# Runtime data
pids
*.pid
*.seed
*.pid.lock

# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov

# Coverage directory used by tools like istanbul
coverage
*.lcov

# nyc test coverage
.nyc_output

# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt

# Bower dependency directory (https://bower.io/)
bower_components

# node-waf configuration
.lock-wscript

# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release

# Dependency directories
node_modules/
jspm_packages/

# Snowpack dependency directory (https://snowpack.dev/)
web_modules/

# TypeScript cache
*.tsbuildinfo

# Optional npm cache directory
.npm

# Optional eslint cache
.eslintcache

# Optional stylelint cache
.stylelintcache

# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/

# Optional REPL history
.node_repl_history

# Output of 'npm pack'
*.tgz

# Yarn Integrity file
.yarn-integrity

# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local

# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache

# Next.js build output
.next
out

# Nuxt.js build / generate output
.nuxt
dist

# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public

# vuepress build output
.vuepress/dist

# vuepress v2.x temp and cache directory
.temp
.cache

# Docusaurus cache and generated files
.docusaurus

# Serverless directories
.serverless/

# FuseBox cache
.fusebox/

# DynamoDB Local files
.dynamodb/

# TernJS port file
.tern-port

# Stores VSCode versions used for testing VSCode extensions
.vscode-test

# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*

# env
.env
env.js
57 changes: 57 additions & 0 deletions app.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import cors from "cors";
import dotenv from "dotenv";
import express from "express";
import session from "express-session";
import fs from "fs";
import moment from "moment-timezone";
import morgan from "morgan";
import path, { dirname } from "path";
import swaggerJsdoc from "swagger-jsdoc";
import swaggerUi from "swagger-ui-express";
import { fileURLToPath } from "url";
import passport from "./config/passport.js";
import errorHandler from "./middlewares/errorHandler.js";
import articlesRouter from "./routes/articleRoutes.js";
import authRouter from "./routes/authRoutes.js";
import imagesRouter from "./routes/imageRoutes.js";
import productsRouter from "./routes/productRoutes.js";
import swaggerOptions from "./swagger/swaggerOptions.js";
dotenv.config();
const { JWT_SECRET } = process.env;
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);

const app = express();

const specs = swaggerJsdoc(swaggerOptions);

app.use("/api-docs", swaggerUi.serve, swaggerUi.setup(specs));

morgan.token("date", (req, res, tz) => {
return moment().tz(tz).format("YYYY-MM-DD HH:mm:ss");
});

const customFormat = ":method :url :status :res[content-length] - :response-time ms - :date[Asia/Seoul]";

const accessLogStream = fs.createWriteStream(path.join(__dirname, "access.log"), { flags: "a" });

app.use(morgan(customFormat, { stream: accessLogStream }));
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use(cors());
app.use(session({ secret: JWT_SECRET, resave: false, saveUninitialized: true }));
app.use(passport.initialize());
app.use(passport.session());

app.use("/products", productsRouter);
app.use("/articles", articlesRouter);
app.use("/images", imagesRouter);
app.use("/auth", authRouter);

app.use(errorHandler);

const PORT = process.env.PORT || 3000;

app.listen(PORT, () => {
console.log("Server is running on port 3000");
});
59 changes: 59 additions & 0 deletions config/passport.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { PrismaClient } from "@prisma/client";
import dotenv from "dotenv";
import passport from "passport";
import { Strategy as GoogleStrategy } from "passport-google-oauth20";
import { generateAccessToken, generateRefreshToken } from "../utils/tokens.js";

dotenv.config();
const prisma = new PrismaClient();

const GOOGLE_CLIENT_ID = process.env.GOOGLE_CLIENT_ID;
const GOOGLE_CLIENT_SECRET = process.env.GOOGLE_CLIENT_SECRET;

passport.use(
new GoogleStrategy(
{
clientID: GOOGLE_CLIENT_ID,
clientSecret: GOOGLE_CLIENT_SECRET,
callbackURL: "http://localhost:3000/auth/google/callback",
},
async (accessToken, refreshToken, profile, done) => {
const { id, displayName, emails } = profile;

try {
let user = await prisma.user.findUnique({
where: { googleId: id },
});

if (!user) {
user = await prisma.user.create({
data: {
googleId: id,
email: emails[0].value,
name: displayName,
nickname: displayName,
password: null,
},
});
}

const accessTokenJwt = generateAccessToken(user);
const refreshTokenJwt = generateRefreshToken(user);

done(null, { user, accessToken: accessTokenJwt, refreshToken: refreshTokenJwt });
} catch (error) {
done(error, null);
}
}
)
);

passport.serializeUser((userWithTokens, done) => {
done(null, userWithTokens);
});

passport.deserializeUser((userWithTokens, done) => {
done(null, userWithTokens);
});

export default passport;
86 changes: 86 additions & 0 deletions controllers/articleController.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import { assert } from "superstruct";
import * as articleService from "../services/articleService.js";
import { CreateArticle, PatchArticle } from "../structs.js";
import asyncHandler from "../utils/asyncHandler.js";
import AppError from "../utils/errors.js";

export const getArticles = asyncHandler(async (req, res) => {
const { offset = 0, limit = 10, orderBy = "recent", keyword = "" } = req.query;
const articles = await articleService.getArticles({ offset, limit, orderBy, keyword });
const bestArticles = await articleService.getBestArticles();
res.send({ articles, bestArticles });
});

export const createArticle = asyncHandler(async (req, res) => {
assert(req.body, CreateArticle);
const { userId } = req;
const article = await articleService.createArticle({ ...req.body, userId });
res.status(201).send(article);
});

export const getArticleById = asyncHandler(async (req, res) => {
const { id } = req.params;
const article = await articleService.getArticleById(id);
res.send(article);
});

export const updateArticle = asyncHandler(async (req, res, next) => {
assert(req.body, PatchArticle);

const { id: articleId } = req.params;
const { userId } = req;

try {
const updatedArticle = await articleService.updateArticle(articleId, userId, req.body);
res.send(updatedArticle);
} catch (error) {
if (error instanceof AppError) {
return res.status(error.statusCode).json({ message: error.message });
}
}
});

export const deleteArticle = asyncHandler(async (req, res, next) => {
const { id: articleId } = req.params;
const { userId } = req;

try {
await articleService.deleteArticle(articleId, userId);
res.sendStatus(204);
} catch (error) {
if (error instanceof AppError) {
return res.status(error.statusCode).json({ message: error.message });
}
next(error);
}
});

export const likeArticle = asyncHandler(async (req, res, next) => {
const { id: articleId } = req.params;
const { userId } = req;

try {
const updatedArticle = await articleService.likeArticle(articleId, userId);
res.send(updatedArticle);
} catch (error) {
if (error instanceof AppError) {
return res.status(error.statusCode).json({ message: error.message });
}
next(error);
}
});

export const unlikeArticle = asyncHandler(async (req, res, next) => {
const { id: articleId } = req.params;
const { userId } = req;

try {
const updatedArticle = await articleService.unlikeArticle(articleId, userId);
res.send(updatedArticle);
} catch (error) {
if (error instanceof AppError) {
return res.status(error.statusCode).json({ message: error.message });
}
next(error);
}
});
Loading