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
107 changes: 107 additions & 0 deletions Week10/s0-yeon/Mission/src/auth.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import dotenv from "dotenv";
import { Strategy as GoogleStrategy } from "passport-google-oauth20";
import { prisma } from "./db.config.js";
import jwt from "jsonwebtoken"; // JWT ์ƒ์„ฑ์„ ์œ„ํ•ด import

dotenv.config();
const secret = process.env.JWT_SECRET; // .env์˜ ๋น„๋ฐ€ ํ‚ค

export const generateAccessToken = (user) => {
return jwt.sign(
{ id: user.id, email: user.email },
secret,
{ expiresIn: '1h' }
);
};

export const generateRefreshToken = (user) => {
return jwt.sign(
{ id: user.id },
secret,
{ expiresIn: '14d' }
);
};

// GoogleVerify
const googleVerify = async (profile) => {
const email = profile.emails?.[0]?.value;
if (!email) {
throw new Error(`profile.email was not found: ${profile}`);
}

const user = await prisma.user.findFirst({ where: { email } });
if (user !== null) {
return { id: user.id, email: user.email, name: user.name };
}

const created = await prisma.user.create({
data: {
email,
name: profile.displayName,
gender: "์ถ”ํ›„ ์ˆ˜์ •",
birth: new Date(1970, 0, 1),
address: "์ถ”ํ›„ ์ˆ˜์ •",
detailAddress: "์ถ”ํ›„ ์ˆ˜์ •",
phoneNumber: "์ถ”ํ›„ ์ˆ˜์ •",
},
});

return { id: created.id, email: created.email, name: created.name };
};

// GoogleStrategy

const GOOGLE_CALLBACK_URL = "http://umc-9th.p-e.kr:3000/oauth2/callback/google";

export const googleStrategy = new GoogleStrategy(
{
clientID: process.env.PASSPORT_GOOGLE_CLIENT_ID,
clientSecret: process.env.PASSPORT_GOOGLE_CLIENT_SECRET,
callbackURL: process.env.GOOGLE_CALLBACK_URL || GOOGLE_CALLBACK_URL,
scope: ["email", "profile"],
},


async (accessToken, refreshToken, profile, cb) => {
try {

const user = await googleVerify(profile);


const jwtAccessToken = generateAccessToken(user);
const jwtRefreshToken = generateRefreshToken(user);



return cb(null, {
accessToken: jwtAccessToken,
refreshToken: jwtRefreshToken,
});

} catch (err) {
return cb(err);
}
}
);

import { Strategy as JwtStrategy, ExtractJwt } from 'passport-jwt';

const jwtOptions = {
// ์š”์ฒญ ํ—ค๋”์˜ 'Authorization'์—์„œ 'Bearer <token>' ํ† ํฐ์„ ์ถ”์ถœ
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
secretOrKey: process.env.JWT_SECRET,
};

export const jwtStrategy = new JwtStrategy(jwtOptions, async (payload, done) => {
try {
const user = await prisma.user.findFirst({ where: { id: payload.id } });

if (user) {
return done(null, user);
} else {
return done(null, false);
}
} catch (err) {
return done(err, false);
}
});
207 changes: 207 additions & 0 deletions Week10/s0-yeon/Mission/src/controllers/mission.controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
import { requestToMission, responseFromMission } from "../dtos/mission.dto.js";
import { addMission, listMissionsByStore } from "../services/mission.service.js";
import { StatusCodes } from "http-status-codes";

export const handleAddMission = async (req, res, next) => {
/*
#swagger.summary = '๋ฏธ์…˜ ๋“ฑ๋ก API';

#swagger.parameters['storeId'] = {
in: 'path',
description: '๋ฏธ์…˜์„ ๋“ฑ๋กํ•  ๊ฐ€๊ฒŒ ID',
required: true,
type: 'number'
};

#swagger.requestBody = {
required: true,
content: {
"application/json": {
schema: {
type: "object",
properties: {
region: { type: "string", example: "์„œ์šธ" },
missionContent: { type: "string", example: "์•„๋ฉ”๋ฆฌ์นด๋…ธ ๊ตฌ๋งค ์‹œ ์Šคํƒฌํ”„ ์ ๋ฆฝ" },
givePoint: { type: "number", example: 100 },
price: { type: "number", example: 4500 }
},
required: ["region", "missionContent", "givePoint", "price"]
}
}
}
};

#swagger.responses[201] = {
description: "๋ฏธ์…˜ ๋“ฑ๋ก ์„ฑ๊ณต",
content: {
"application/json": {
schema: {
type: "object",
properties: {
resultType: { type: "string", example: "SUCCESS" },
error: { type: "object", nullable: true, example: null },
success: {
type: "object",
properties: {
data: {
type: "object",
properties: {
missionId: { type: "number", example: 1 },
storeId: { type: "number", example: 3 },
region: { type: "string", example: "์„œ์šธ" },
missionContent: { type: "string", example: "์•„๋ฉ”๋ฆฌ์นด๋…ธ ๊ตฌ๋งค ์‹œ ์Šคํƒฌํ”„ ์ ๋ฆฝ" },
givePoint: { type: "number", example: 100 },
price: { type: "number", example: 4500 },
createdAt: { type: "string", format: "date-time" }
}
}
}
}
}
}
}
}
};

#swagger.responses[400] = {
description: "๋ฏธ์…˜ ๋“ฑ๋ก ์‹คํŒจ",
content: {
"application/json": {
schema: {
type: "object",
properties: {
resultType: { type: "string", example: "FAIL" },
error: {
type: "object",
properties: {
errorCode: { type: "string", example: "M001" },
reason: { type: "string", example: "๊ฐ€๊ฒฉ์€ 0๋ณด๋‹ค ์ปค์•ผ ํ•ฉ๋‹ˆ๋‹ค." },
data: { type: "object" }
}
},
success: { type: "object", nullable: true, example: null }
}
}
}
}
};
*/
try {
const { storeId } = req.params;
const missionData = requestToMission(req.body, storeId);

const newMission = await addMission(missionData);

res.status(StatusCodes.CREATED).success({
message: "๋ฏธ์…˜ ๋“ฑ๋ก ์„ฑ๊ณต",
data: responseFromMission(newMission),
});
} catch (error) {
next(error); // โœ… ์—๋Ÿฌ ๋ฏธ๋“ค์›จ์–ด๋กœ ์ „๋‹ฌ
}
};

// โœ… 2. ํŠน์ • ๊ฐ€๊ฒŒ์˜ ๋ฏธ์…˜ ๋ชฉ๋ก ์กฐํšŒ
export const handleListMissionsByStore = async (req, res, next) => {
/*
#swagger.summary = 'ํŠน์ • ๊ฐ€๊ฒŒ์˜ ๋ฏธ์…˜ ๋ชฉ๋ก ์กฐํšŒ API';

#swagger.parameters['storeId'] = {
in: 'path',
description: '๋ฏธ์…˜์„ ์กฐํšŒํ•  ๊ฐ€๊ฒŒ ID',
required: true,
type: 'number'
};

#swagger.parameters['cursor'] = {
in: 'query',
description: '์ปค์„œ ๊ธฐ๋ฐ˜ ํŽ˜์ด์ง€๋„ค์ด์…˜ ๊ฐ’ (๋งˆ์ง€๋ง‰ ๋ฏธ์…˜ ID)',
required: false,
type: 'number'
};

#swagger.responses[200] = {
description: "๋ฏธ์…˜ ๋ชฉ๋ก ์กฐํšŒ ์„ฑ๊ณต",
content: {
"application/json": {
schema: {
type: "object",
properties: {
resultType: { type: "string", example: "SUCCESS" },
error: { type: "object", nullable: true, example: null },
success: {
type: "object",
properties: {
data: {
type: "array",
items: {
type: "object",
properties: {
missionId: { type: "number", example: 10 },
region: { type: "string", example: "์„œ์šธ" },
missionContent: { type: "string", example: "์•„๋ฉ”๋ฆฌ์นด๋…ธ ๊ตฌ๋งค ์‹œ 100ํฌ์ธํŠธ ์ง€๊ธ‰" },
givePoint: { type: "number", example: 100 },
price: { type: "number", example: 4500 },
createdAt: { type: "string", format: "date-time" },
store: {
type: "object",
properties: {
name: { type: "string", example: "์นดํŽ˜ ๋ณด๋ผ" }
}
}
}
}
},
pagination: {
type: "object",
properties: {
cursor: { type: "number", nullable: true, example: 5 }
}
}
}
}
}
}
}
}
};

#swagger.responses[400] = {
description: "์ž˜๋ชป๋œ ์š”์ฒญ ๋˜๋Š” ์ž…๋ ฅ๊ฐ’ ์˜ค๋ฅ˜",
content: {
"application/json": {
schema: {
type: "object",
properties: {
resultType: { type: "string", example: "FAIL" },
error: {
type: "object",
properties: {
errorCode: { type: "string", example: "M002" },
reason: { type: "string", example: "storeId๋Š” ์ˆซ์ž์—ฌ์•ผ ํ•ฉ๋‹ˆ๋‹ค." },
data: { type: "object" }
}
},
success: { type: "object", nullable: true, example: null }
}
}
}
}
};
*/

try {
const { storeId } = req.params;
const cursor = req.query.cursor ? parseInt(req.query.cursor) : 0;
const missions = await listMissionsByStore(Number(storeId), cursor);

res.status(StatusCodes.OK).success({
data: missions,
pagination: {
cursor: missions.length ? missions[missions.length - 1].missionId : null,
}
});
} catch (error) {
next(error);
}
};
Loading