Skip to content
Open
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
node_modules
1 change: 1 addition & 0 deletions sprint-mission-3
Submodule sprint-mission-3 added at fffc17
5 changes: 5 additions & 0 deletions sprint-mission-4/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
PORT=3000
JWT_ACCESS_TOKEN_SECRET=your_jwt_access_token_secret
JWT_REFRESH_TOKEN_SECRET=your_jwt_refresh_token_secret
ACCESS_TOKEN_COOKIE_NAME=access-token
REFRESH_TOKEN_COOKIE_NAME=refresh-token
16 changes: 16 additions & 0 deletions sprint-mission-4/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
!.vscode/*.code-snippets
!*.code-workspace

# Built Visual Studio Code Extensions
*.vsix
node_modules/
package-lock.json
test.http
.githooks/
.vscode/
.env
55 changes: 55 additions & 0 deletions sprint-mission-4/app.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import express from 'express';
import cookieParser from 'cookie-parser';
import cors from 'cors';
import helmet from 'helmet';
import authRouter from './routers/auth.router.js';
import usersRouter from './routers/users.router.js';
import productsRouter from './routers/products.router.js';
import articlesRouter from './routers/articles.router.js';
import postsRouter from './routers/posts.router.js'; // 새로 만든 posts.router.js 임포트
import commentsRouter from './routers/comments.router.js';
import { PORT } from './lib/constants.js';

const app = express();

// Security middleware
app.use(helmet());

// CORS middleware
app.use(cors());

// Middleware for logging requests
app.use((req, res, next) => {
console.log(`${new Date().toISOString()} - ${req.method} ${req.url}`);
next();
});

app.use(cookieParser());
app.use(express.json());

// API routes
app.use('/api/auth', authRouter);
app.use('/api/users', usersRouter);
app.use('/api/products', productsRouter);
app.use('/api/articles', articlesRouter);
app.use('/api/posts', postsRouter); // /api/posts 경로에 새로운 postsRouter 할당
app.use('/api/comments', commentsRouter);

// 404 handler for unmatched routes
app.use((req, res) => {
res.status(404).json({ message: 'Route not found' });
});

// Global error handler
app.use((err, req, res, next) => {
// Log the error for debugging purposes. Consider using a logger like Winston.
console.error(err);

// Send a generic error message to the client
// Avoid sending stack trace in production
res.status(500).json({ message: 'Internal server error' });
});

app.listen(PORT, () => {
console.log(`Server is running on http://localhost:${PORT}`);
});
Binary file added sprint-mission-4/dev.db
Binary file not shown.
21 changes: 21 additions & 0 deletions sprint-mission-4/lib/constants.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import dotenv from 'dotenv';

dotenv.config();

const NODE_ENV = process.env.NODE_ENV || 'development';
const PORT = process.env.PORT || 3000;
const JWT_ACCESS_TOKEN_SECRET =
process.env.JWT_ACCESS_TOKEN_SECRET || 'your_jwt_access_token_secret';
const JWT_REFRESH_TOKEN_SECRET =
process.env.JWT_REFRESH_TOKEN_SECRET || 'your_jwt_refresh_token_secret';
const ACCESS_TOKEN_COOKIE_NAME = 'access-token';
const REFRESH_TOKEN_COOKIE_NAME = 'refresh-token';

export {
NODE_ENV,
PORT,
JWT_ACCESS_TOKEN_SECRET,
JWT_REFRESH_TOKEN_SECRET,
ACCESS_TOKEN_COOKIE_NAME,
REFRESH_TOKEN_COOKIE_NAME,
};
5 changes: 5 additions & 0 deletions sprint-mission-4/lib/prisma.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { PrismaClient } from '@prisma/client';

const prisma = new PrismaClient();

export default prisma;
27 changes: 27 additions & 0 deletions sprint-mission-4/lib/token.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import jwt from 'jsonwebtoken';
import {
JWT_ACCESS_TOKEN_SECRET,
JWT_REFRESH_TOKEN_SECRET,
} from './constants.js';

function generateTokens(userId) {
const accessToken = jwt.sign({ id: userId }, JWT_ACCESS_TOKEN_SECRET, {
expiresIn: '1h',
});
const refreshToken = jwt.sign({ id: userId }, JWT_REFRESH_TOKEN_SECRET, {
expiresIn: '1d',
});
return { accessToken, refreshToken };
}

function verifyAccessToken(token) {
const decoded = jwt.verify(token, JWT_ACCESS_TOKEN_SECRET);
return { userId: decoded.id };
}

function verifyRefreshToken(token) {
const decoded = jwt.verify(token, JWT_REFRESH_TOKEN_SECRET);
return { userId: decoded.id };
}

export { generateTokens, verifyAccessToken, verifyRefreshToken };
55 changes: 55 additions & 0 deletions sprint-mission-4/middlewares/auth.middleware.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { verifyAccessToken } from '../lib/token.js';
import prisma from '../lib/prisma.js';
import { ACCESS_TOKEN_COOKIE_NAME } from '../lib/constants.js';

export const authMiddleware = async (req, res, next) => {
try {
const accessToken = req.cookies[ACCESS_TOKEN_COOKIE_NAME];
if (!accessToken) {
throw new Error('No access token found');
}

const { userId } = verifyAccessToken(accessToken);
if (!userId) {
throw new Error('Invalid access token');
}

const user = await prisma.user.findUnique({
where: { id: userId },
});

if (!user) {
throw new Error('User not found');
}

req.user = user;
next();
} catch (error) {
res.status(401).json({ message: 'Unauthorized: ' + error.message });
}
};

export const softAuthMiddleware = async (req, res, next) => {
try {
const accessToken = req.cookies[ACCESS_TOKEN_COOKIE_NAME];
if (!accessToken) {
return next();
}

const { userId } = verifyAccessToken(accessToken);
if (!userId) {
return next();
}

const user = await prisma.user.findUnique({
where: { id: userId },
});

if (user) {
req.user = user;
}
next();
} catch (error) {
next();
}
};
18 changes: 18 additions & 0 deletions sprint-mission-4/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"type": "module",
"scripts": {
"postinstall": "npx prisma generate && npx prisma migrate dev",
"start": "node app.js"
},
"dependencies": {
"@prisma/client": "^6.4.0",
"bcrypt": "^5.1.1",
"cookie-parser": "^1.4.7",
"cors": "^2.8.5",
"dotenv": "^16.4.7",
"express": "^4.21.2",
"helmet": "^8.1.0",
"jsonwebtoken": "^9.0.2",
"prisma": "^6.4.0"
}
}
17 changes: 17 additions & 0 deletions sprint-mission-4/prisma/migrations/20250220102901_/migration.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
-- CreateTable
CREATE TABLE "User" (
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
"username" TEXT NOT NULL,
"password" TEXT NOT NULL
);

-- CreateTable
CREATE TABLE "Post" (
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
"content" TEXT NOT NULL,
"authorId" INTEGER NOT NULL,
CONSTRAINT "Post_authorId_fkey" FOREIGN KEY ("authorId") REFERENCES "User" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
);

-- CreateIndex
CREATE UNIQUE INDEX "User_username_key" ON "User"("username");
113 changes: 113 additions & 0 deletions sprint-mission-4/prisma/migrations/20251124112513_/migration.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
/*
Warnings:

- You are about to drop the `Post` table. If the table is not empty, all the data it contains will be lost.
- You are about to drop the column `username` on the `User` table. All the data in the column will be lost.
- Added the required column `email` to the `User` table without a default value. This is not possible if the table is not empty.
- Added the required column `nickname` to the `User` table without a default value. This is not possible if the table is not empty.
- Added the required column `updatedAt` to the `User` table without a default value. This is not possible if the table is not empty.

*/
-- DropTable
PRAGMA foreign_keys=off;
DROP TABLE "Post";
PRAGMA foreign_keys=on;

-- CreateTable
CREATE TABLE "Product" (
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
"title" TEXT NOT NULL,
"description" TEXT NOT NULL,
"price" INTEGER NOT NULL,
"image" TEXT,
"authorId" INTEGER NOT NULL,
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" DATETIME NOT NULL,
CONSTRAINT "Product_authorId_fkey" FOREIGN KEY ("authorId") REFERENCES "User" ("id") ON DELETE CASCADE ON UPDATE CASCADE
);

-- CreateTable
CREATE TABLE "Article" (
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
"title" TEXT NOT NULL,
"content" TEXT NOT NULL,
"image" TEXT,
"authorId" INTEGER NOT NULL,
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" DATETIME NOT NULL,
CONSTRAINT "Article_authorId_fkey" FOREIGN KEY ("authorId") REFERENCES "User" ("id") ON DELETE CASCADE ON UPDATE CASCADE
);

-- CreateTable
CREATE TABLE "Comment" (
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
"content" TEXT NOT NULL,
"authorId" INTEGER NOT NULL,
"productId" INTEGER,
"articleId" INTEGER,
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" DATETIME NOT NULL,
CONSTRAINT "Comment_authorId_fkey" FOREIGN KEY ("authorId") REFERENCES "User" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT "Comment_productId_fkey" FOREIGN KEY ("productId") REFERENCES "Product" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT "Comment_articleId_fkey" FOREIGN KEY ("articleId") REFERENCES "Article" ("id") ON DELETE CASCADE ON UPDATE CASCADE
);

-- CreateTable
CREATE TABLE "RefreshToken" (
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
"token" TEXT NOT NULL,
"userId" INTEGER NOT NULL,
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" DATETIME NOT NULL,
CONSTRAINT "RefreshToken_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User" ("id") ON DELETE CASCADE ON UPDATE CASCADE
);

-- CreateTable
CREATE TABLE "_ProductLikes" (
"A" INTEGER NOT NULL,
"B" INTEGER NOT NULL,
CONSTRAINT "_ProductLikes_A_fkey" FOREIGN KEY ("A") REFERENCES "Product" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT "_ProductLikes_B_fkey" FOREIGN KEY ("B") REFERENCES "User" ("id") ON DELETE CASCADE ON UPDATE CASCADE
);

-- CreateTable
CREATE TABLE "_ArticleLikes" (
"A" INTEGER NOT NULL,
"B" INTEGER NOT NULL,
CONSTRAINT "_ArticleLikes_A_fkey" FOREIGN KEY ("A") REFERENCES "Article" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT "_ArticleLikes_B_fkey" FOREIGN KEY ("B") REFERENCES "User" ("id") ON DELETE CASCADE ON UPDATE CASCADE
);

-- RedefineTables
PRAGMA defer_foreign_keys=ON;
PRAGMA foreign_keys=OFF;
CREATE TABLE "new_User" (
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
"email" TEXT NOT NULL,
"nickname" TEXT NOT NULL,
"image" TEXT,
"password" TEXT NOT NULL,
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" DATETIME NOT NULL
);
INSERT INTO "new_User" ("id", "password") SELECT "id", "password" FROM "User";
DROP TABLE "User";
ALTER TABLE "new_User" RENAME TO "User";
CREATE UNIQUE INDEX "User_email_key" ON "User"("email");
PRAGMA foreign_keys=ON;
PRAGMA defer_foreign_keys=OFF;

-- CreateIndex
CREATE UNIQUE INDEX "RefreshToken_token_key" ON "RefreshToken"("token");

-- CreateIndex
CREATE UNIQUE INDEX "_ProductLikes_AB_unique" ON "_ProductLikes"("A", "B");

-- CreateIndex
CREATE INDEX "_ProductLikes_B_index" ON "_ProductLikes"("B");

-- CreateIndex
CREATE UNIQUE INDEX "_ArticleLikes_AB_unique" ON "_ArticleLikes"("A", "B");

-- CreateIndex
CREATE INDEX "_ArticleLikes_B_index" ON "_ArticleLikes"("B");
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
-- CreateTable
CREATE TABLE "Post" (
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
"title" TEXT NOT NULL,
"content" TEXT NOT NULL,
"image" TEXT,
"authorId" INTEGER NOT NULL,
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" DATETIME NOT NULL,
CONSTRAINT "Post_authorId_fkey" FOREIGN KEY ("authorId") REFERENCES "User" ("id") ON DELETE CASCADE ON UPDATE CASCADE
);

-- CreateTable
CREATE TABLE "_LikedPosts" (
"A" INTEGER NOT NULL,
"B" INTEGER NOT NULL,
CONSTRAINT "_LikedPosts_A_fkey" FOREIGN KEY ("A") REFERENCES "Post" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT "_LikedPosts_B_fkey" FOREIGN KEY ("B") REFERENCES "User" ("id") ON DELETE CASCADE ON UPDATE CASCADE
);

-- CreateIndex
CREATE UNIQUE INDEX "_LikedPosts_AB_unique" ON "_LikedPosts"("A", "B");

-- CreateIndex
CREATE INDEX "_LikedPosts_B_index" ON "_LikedPosts"("B");
3 changes: 3 additions & 0 deletions sprint-mission-4/prisma/migrations/migration_lock.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Please do not edit this file manually
# It should be added in your version-control system (e.g., Git)
provider = "sqlite"
Loading