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
648 changes: 648 additions & 0 deletions dist/swagger.json

Large diffs are not rendered by default.

13 changes: 13 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// index.js
import express from 'express';
import morgan from 'morgan';

const app = express();
app.use(morgan('dev')); // 로그 포맷: dev
app.use(express.json());
app.use(express.urlencoded({ extended: false }));


app.get('/test', (req, res) => {
res.send('Hello!');
});
40 changes: 40 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
{
"name": "10th_node",
"version": "1.0.0",
"type": "module",
"description": "본인의 브랜치에서만 작업하고, `main` 브랜치는 직접 수정하지 않습니다.",
"main": "index.js",
"scripts": {
"start": "tsoa spec-and-routes && tsx src/index.ts",
"dev": "tsoa spec-and-routes && nodemon --exec tsx src/index.ts"
},
"repository": {
"type": "git",
"url": "git+https://github.com/SWU-UMC/10th_Node.js.git"
},
"keywords": [],
"author": "",
"license": "ISC",
"bugs": {
"url": "https://github.com/SWU-UMC/10th_Node.js/issues"
},
"homepage": "https://github.com/SWU-UMC/10th_Node.js#readme",
"dependencies": {
"@prisma/adapter-mariadb": "^7.8.0",
"@prisma/client": "^6.19.3",
"@types/cookie-parser": "^1.4.10",
"@types/morgan": "^1.9.10",
"bcrypt": "^6.0.0",
"cookie-parser": "^1.4.7",
"dotenv": "^17.4.2",
"express": "^5.2.1",
"morgan": "^1.10.1",
"prisma": "^6.19.3",
"swagger-ui-express": "^5.0.1",
"tsoa": "^7.0.0-alpha.0"
},
"devDependencies": {
"@types/bcrypt": "^6.0.0",
"@types/swagger-ui-express": "^4.1.8"
}
}
39 changes: 39 additions & 0 deletions prisma/migrations/20260507051229_init_database/migration.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
-- CreateTable
CREATE TABLE `user` (
`id` INTEGER NOT NULL AUTO_INCREMENT,
`email` VARCHAR(255) NOT NULL,
`name` VARCHAR(100) NOT NULL,
`gender` VARCHAR(15) NOT NULL,
`birth` DATE NOT NULL,
`address` VARCHAR(255) NOT NULL,
`detail_address` VARCHAR(255) NULL,
`phone_number` VARCHAR(15) NOT NULL,

UNIQUE INDEX `email`(`email`),
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

-- CreateTable
CREATE TABLE `food_category` (
`id` INTEGER NOT NULL AUTO_INCREMENT,
`name` VARCHAR(100) NOT NULL,

PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

-- CreateTable
CREATE TABLE `user_favor_category` (
`id` INTEGER NOT NULL AUTO_INCREMENT,
`user_id` INTEGER NOT NULL,
`food_category_id` INTEGER NOT NULL,

INDEX `f_category_id`(`food_category_id`),
INDEX `user_id`(`user_id`),
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

-- AddForeignKey
ALTER TABLE `user_favor_category` ADD CONSTRAINT `user_favor_category_user_id_fkey` FOREIGN KEY (`user_id`) REFERENCES `user`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;

-- AddForeignKey
ALTER TABLE `user_favor_category` ADD CONSTRAINT `user_favor_category_food_category_id_fkey` FOREIGN KEY (`food_category_id`) REFERENCES `food_category`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
23 changes: 23 additions & 0 deletions prisma/migrations/20260507051802_add_store_review/migration.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
-- CreateTable
CREATE TABLE `store` (
`id` INTEGER NOT NULL AUTO_INCREMENT,
`name` VARCHAR(100) NOT NULL,

PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

-- CreateTable
CREATE TABLE `user_store_review` (
`id` INTEGER NOT NULL AUTO_INCREMENT,
`content` TEXT NOT NULL,
`store_id` INTEGER NOT NULL,
`user_id` INTEGER NOT NULL,

PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

-- AddForeignKey
ALTER TABLE `user_store_review` ADD CONSTRAINT `user_store_review_store_id_fkey` FOREIGN KEY (`store_id`) REFERENCES `store`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;

-- AddForeignKey
ALTER TABLE `user_store_review` ADD CONSTRAINT `user_store_review_user_id_fkey` FOREIGN KEY (`user_id`) REFERENCES `user`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
Warnings:

- You are about to drop the `user_store_review` table. If the table is not empty, all the data it contains will be lost.

*/
-- DropForeignKey
ALTER TABLE `user_store_review` DROP FOREIGN KEY `user_store_review_store_id_fkey`;

-- DropForeignKey
ALTER TABLE `user_store_review` DROP FOREIGN KEY `user_store_review_user_id_fkey`;

-- DropTable
DROP TABLE `user_store_review`;

-- CreateTable
CREATE TABLE `UserStoreReview` (
`id` INTEGER NOT NULL AUTO_INCREMENT,
`content` TEXT NOT NULL,
`rating` DOUBLE NOT NULL,
`createdAt` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
`storeId` INTEGER NOT NULL,
`userId` INTEGER NOT NULL,

PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

-- AddForeignKey
ALTER TABLE `UserStoreReview` ADD CONSTRAINT `UserStoreReview_storeId_fkey` FOREIGN KEY (`storeId`) REFERENCES `store`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;

-- AddForeignKey
ALTER TABLE `UserStoreReview` ADD CONSTRAINT `UserStoreReview_userId_fkey` FOREIGN KEY (`userId`) REFERENCES `user`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
29 changes: 29 additions & 0 deletions prisma/migrations/20260508160714_init_mission/migration.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
-- CreateTable
CREATE TABLE `mission` (
`id` INTEGER NOT NULL AUTO_INCREMENT,
`store_id` INTEGER NOT NULL,
`condition_text` VARCHAR(191) NOT NULL,
`reward_point` INTEGER NOT NULL,

PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

-- CreateTable
CREATE TABLE `user_mission` (
`id` INTEGER NOT NULL AUTO_INCREMENT,
`user_id` INTEGER NOT NULL,
`mission_id` INTEGER NOT NULL,
`status` VARCHAR(191) NOT NULL,

UNIQUE INDEX `user_mission_user_id_mission_id_key`(`user_id`, `mission_id`),
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

-- AddForeignKey
ALTER TABLE `mission` ADD CONSTRAINT `mission_store_id_fkey` FOREIGN KEY (`store_id`) REFERENCES `store`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;

-- AddForeignKey
ALTER TABLE `user_mission` ADD CONSTRAINT `user_mission_user_id_fkey` FOREIGN KEY (`user_id`) REFERENCES `user`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;

-- AddForeignKey
ALTER TABLE `user_mission` ADD CONSTRAINT `user_mission_mission_id_fkey` FOREIGN KEY (`mission_id`) REFERENCES `mission`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
3 changes: 3 additions & 0 deletions 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 = "mysql"
93 changes: 93 additions & 0 deletions prisma/schema.prisma
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
generator client {
provider = "prisma-client-js"
}

datasource db {
provider = "mysql"
url = env("DATABASE_URL")
}

model User {
id Int @id @default(autoincrement())
email String @unique(map: "email") @db.VarChar(255)
name String @db.VarChar(100)
gender String @db.VarChar(15)
birth DateTime @db.Date
address String @db.VarChar(255)
detailAddress String? @map("detail_address") @db.VarChar(255)
phoneNumber String @map("phone_number") @db.VarChar(15)

userFavorCategories UserFavorCategory[]
storeReviews UserStoreReview[]
userMissions UserMission[]

@@map("user")
}

model FoodCategory {
id Int @id @default(autoincrement())
name String @db.VarChar(100)

userFavorCategories UserFavorCategory[]

@@map("food_category")
}

model UserFavorCategory {
id Int @id @default(autoincrement())
user User @relation(fields: [userId], references: [id])
userId Int @map("user_id")
foodCategory FoodCategory @relation(fields: [foodCategoryId], references: [id])
foodCategoryId Int @map("food_category_id")

@@index([foodCategoryId], map: "f_category_id")
@@index([userId], map: "user_id")
@@map("user_favor_category")
}

model Store {
id Int @id @default(autoincrement())
name String @db.VarChar(100)

reviews UserStoreReview[]
missions Mission[]

@@map("store")
}

model UserStoreReview {
id Int @id @default(autoincrement())
content String @db.Text
rating Float
createdAt DateTime @default(now())

store Store @relation(fields: [storeId], references: [id])
storeId Int
user User @relation(fields: [userId], references: [id])
userId Int
}

model Mission {
id Int @id @default(autoincrement())
storeId Int @map("store_id")
conditionText String @map("condition_text")
rewardPoint Int @map("reward_point")

store Store @relation(fields: [storeId], references: [id])
userMissions UserMission[]

@@map("mission")
}

model UserMission {
id Int @id @default(autoincrement())
userId Int @map("user_id")
missionId Int @map("mission_id")
status String

user User @relation(fields: [userId], references: [id])
mission Mission @relation(fields: [missionId], references: [id])

@@unique([userId, missionId])
@@map("user_mission")
}
16 changes: 16 additions & 0 deletions src/common/dto/api.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// src/common/dto/api.dto.ts
export interface SuccessResponse<T> {
resultType: "SUCCESS";
error: null;
data: T;
}

export interface ErrorResponse {
resultType: "FAILED";
error: {
errorCode: string;
message: string;
data?: any;
};
data: null;
}
11 changes: 11 additions & 0 deletions src/common/dto/error.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export interface ErrorDetail {
errorCode: string;
message: string;
data?: any;
}

export interface ErrorResponse {
resultType: "FAILED";
error: ErrorDetail;
data: null;
}
18 changes: 18 additions & 0 deletions src/common/errors/app.error.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// src/common/errors/app.error.ts
export class AppError extends Error {
public readonly errorCode: string;
public readonly statusCode: number;
public readonly data?: any;

constructor(params?: {
errorCode: string;
message: string;
statusCode: number;
data?: any;
}) {
super(params?.message);
this.errorCode = params?.errorCode ?? "UNKNOWN";
this.statusCode = params?.statusCode ?? 500;
this.data = params?.data ?? null;
}
}
33 changes: 33 additions & 0 deletions src/common/errors/error.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// src/common/errors/error.ts
import { AppError } from "./app.error.js";

export class DuplicateUserEmailError extends AppError {
constructor(message: string, data?: unknown) {
super({
errorCode: "U001",
statusCode: 409,
message,
data,
});
}
}

export class MissionAlreadyInProgressError extends AppError {
constructor() {
super({
errorCode: "M001",
statusCode: 400,
message: "이미 도전 중인 미션입니다.",
});
}
}

export class MissionAlreadyCompletedError extends AppError {
constructor() {
super({
errorCode: "M002",
statusCode: 400,
message: "이미 완료한 미션입니다.",
});
}
}
20 changes: 20 additions & 0 deletions src/common/middlewares/auth.middleware.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// src/common/middlewares/auth.middleware.ts
import { Request, Response, NextFunction } from "express";

export function authorizeUser() {
return async (req: Request, res: Response, next: NextFunction) => {
const { username } = req.cookies;

if (username) {
console.log(`[인증 성공] ${username}님, 환영합니다.`);
next();
} else {
console.log("[인증 실패] 로그인이 필요합니다.");
res
.status(401)
.send(
'<script>alert("로그인이 필요합니다!");location.href="/api/v1/users/login";</script>',
);
}
};
}
11 changes: 11 additions & 0 deletions src/common/responses/response.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// src/common/responses/response.ts
export interface ApiResponse<T> {
resultType: "SUCCESS";
error: null;
data: T;
}
export const success = <T>(data: T): ApiResponse<T> => ({
resultType: "SUCCESS",
error: null,
data,
});
Loading