diff --git a/src/config/cookieOptions.js b/src/config/cookieOptions.js new file mode 100644 index 0000000..014952d --- /dev/null +++ b/src/config/cookieOptions.js @@ -0,0 +1,6 @@ +export const COOKIE_OPTIONS = { + httpOnly: true, + secure: true, + sameSite: 'None', + domain: '.codeit-momentum.shop' +}; \ No newline at end of file diff --git a/src/controllers/authControllers.js b/src/controllers/authControllers.js index 3869dc2..abdd210 100644 --- a/src/controllers/authControllers.js +++ b/src/controllers/authControllers.js @@ -1,186 +1,183 @@ import { PrismaClient } from '@prisma/client'; import axios from 'axios'; -import { generateAccessToken, generateRefreshToken } from './jwtControllers.js'; +import { generateAccessToken, generateRefreshToken } from '../service/jwtService.js'; +import { COOKIE_OPTIONS } from '../config/cookieOptions.js'; const prisma = new PrismaClient(); const getKoreaNow = () => { - const now = new Date(); // 현재 UTC 기준 시간 - now.setHours(now.getHours() + 9); // 9시간 추가 (UTC → KST 변환) - return now; + const now = new Date(); // 현재 UTC 기준 시간 + now.setHours(now.getHours() + 9); // 9시간 추가 (UTC → KST 변환) + return now; }; // 카카오 로그인 페이지로 리디렉션 (프론트에서 하는 작업) (테스트용) export const redirectToKakaoLogin = (req, res) => { - const origin = req.headers.origin || 'https://codeit-momentum'; // 요청 헤더의 Origin 감지 - // 동적으로 REDIRECT_URI 설정 - const redirectUri = - origin.includes('codeit-momentum') // 배포 환경 - ? process.env.REDIRECT_URI_DEPLOY // 배포 환경의 리다이렉션 URI - : process.env.REDIRECT_URI_LOCAL; // 로컬 환경의 리다이렉션 URI (프론트엔드) - - const kakaoAuthUrl = `https://kauth.kakao.com/oauth/authorize?response_type=code&client_id=${process.env.REST_API_KEY}&redirect_uri=${redirectUri}`; - res.redirect(kakaoAuthUrl); // 사용자 브라우저를 카카오 로그인 페이지로 리디렉션 + const origin = req.headers.origin || 'https://codeit-momentum'; // 요청 헤더의 Origin 감지 + // 동적으로 REDIRECT_URI 설정 + const redirectUri = + origin.includes('codeit-momentum') // 배포 환경 + ? process.env.REDIRECT_URI_DEPLOY // 배포 환경의 리다이렉션 URI + : process.env.REDIRECT_URI_LOCAL; // 로컬 환경의 리다이렉션 URI (프론트엔드) + + const kakaoAuthUrl = `https://kauth.kakao.com/oauth/authorize?response_type=code&client_id=${process.env.REST_API_KEY}&redirect_uri=${redirectUri}`; + res.redirect(kakaoAuthUrl); // 사용자 브라우저를 카카오 로그인 페이지로 리디렉션 }; // 인가 코드 콜백 처리 (프론트에서 하는 작업) (테스트용) export const handleKakaoCallback = (req, res) => { - const { code } = req.query; + const { code } = req.query; - if (!code) { - return res.status(400).send('Authorization code not provided'); - } + if (!code) { + return res.status(400).send('Authorization code not provided'); + } - // 받은 인가 코드를 클라이언트로 반환하거나 액세스 토큰 요청으로 연결 - res.json({ authorization_code: code }); + // 받은 인가 코드를 클라이언트로 반환하거나 액세스 토큰 요청으로 연결 + res.json({ authorization_code: code }); }; export const kakaoLogin = async (req, res) => { - const { code } = req.body; // 클라이언트에서 받은 인가 코드 - const origin = req.headers.origin || 'https://codeit-momentum'; // 요청 헤더의 Origin 감지 - - const redirectUri = - origin.includes('codeit-momentum') // 배포 환경 - ? process.env.REDIRECT_URI_DEPLOY // 배포 환경의 리다이렉션 URI - : process.env.REDIRECT_URI_LOCAL; // 로컬 환경의 리다이렉션 URI (프론트엔드) - try { - const response = await axios.post('https://kauth.kakao.com/oauth/token', null, { - params: { - grant_type: 'authorization_code', - client_id: process.env.REST_API_KEY, - redirect_uri: redirectUri, // 감지된 리다이렉션 URI 사용 - code: code, - }, - }); - - const { - token_type, - access_token, - expires_in, - refresh_token, - refresh_token_expires_in, - scope, - } = response.data; - - res.json({ - token_type, - access_token, - expires_in, - refresh_token, - refresh_token_expires_in, - scope, - }); // 클라이언트로 액세스 토큰, 리프래쉬 토큰큰 반환 - } catch (error) { - console.error("카카오 액세스 토큰 요청 실패: ", error.response?.data || error.message); - res.status(500).send('Token request failed'); - } + const { code } = req.body; // 클라이언트에서 받은 인가 코드 + const origin = req.headers.origin || 'https://codeit-momentum'; // 요청 헤더의 Origin 감지 + + const redirectUri = + origin.includes('codeit-momentum') // 배포 환경 + ? process.env.REDIRECT_URI_DEPLOY // 배포 환경의 리다이렉션 URI + : process.env.REDIRECT_URI_LOCAL; // 로컬 환경의 리다이렉션 URI (프론트엔드) + try { + const response = await axios.post('https://kauth.kakao.com/oauth/token', null, { + params: { + grant_type: 'authorization_code', + client_id: process.env.REST_API_KEY, + redirect_uri: redirectUri, // 감지된 리다이렉션 URI 사용 + code: code, + }, + }); + + const { + token_type, + access_token, + expires_in, + refresh_token, + refresh_token_expires_in, + scope, + } = response.data; + + res.json({ + token_type, + access_token, + expires_in, + refresh_token, + refresh_token_expires_in, + scope, + }); // 클라이언트로 액세스 토큰, 리프래쉬 토큰큰 반환 + } catch (error) { + console.error("카카오 액세스 토큰 요청 실패: ", error.response?.data || error.message); + res.status(500).send('Token request failed'); + } }; // 카카오 사용자 정보 가져오기 export const getKakaoUser = async (accessToken) => { - try { - // 액세스 토큰을 사용하여 카카오 사용자 정보를 가져옴 - const response = await axios.get('https://kapi.kakao.com/v2/user/me', { - headers: { - Authorization: `Bearer ${accessToken}`, - }, - }); - - return response.data; // 사용자 정보 반환 - } catch (error) { - console.error('카카오 사용자 정보 요청 실패:', error.response?.data || error.message); - throw new Error('Failed to retrieve user info'); - } + try { + // 액세스 토큰을 사용하여 카카오 사용자 정보를 가져옴 + const response = await axios.get('https://kapi.kakao.com/v2/user/me', { + headers: { + Authorization: `Bearer ${accessToken}`, + }, + }); + + return response.data; // 사용자 정보 반환 + } catch (error) { + console.error('카카오 사용자 정보 요청 실패:', error.response?.data || error.message); + throw new Error('Failed to retrieve user info'); + } }; // 랜덤 4자리 문자열(대문자) + 4자리 숫자를 생성 const generateFriendCode = () => { - const randomLetters = Array.from({ length: 4 }, () => - String.fromCharCode(65 + Math.floor(Math.random() * 26)) // A-Z - ).join(''); - const randomNumbers = Array.from({ length: 4 }, () => - Math.floor(Math.random() * 10) // 0-9 - ).join(''); - return `${randomLetters}${randomNumbers}`; // 조합된 코드 반환 + const randomLetters = Array.from({ length: 4 }, () => + String.fromCharCode(65 + Math.floor(Math.random() * 26)) // A-Z + ).join(''); + const randomNumbers = Array.from({ length: 4 }, () => + Math.floor(Math.random() * 10) // 0-9 + ).join(''); + return `${randomLetters}${randomNumbers}`; // 조합된 코드 반환 }; // 카카오 사용자 처리 (데이터베이스 저장/조회) + (JWT 토큰 발급) export const handleKakaoUser = async (req, res) => { - const { kakaoAccessToken } = req.body; // 클라이언트에서 받은 액세스 토큰 - - try { - // 카카오 사용자 정보 가져오기 - const userInfo = await getKakaoUser(kakaoAccessToken); - - const { id: userID, kakao_account } = userInfo; // 카카오 사용자 ID 및 계정 정보 - const email = kakao_account.email; - - const koreaNow = getKoreaNow(); - - // 데이터베이스에서 사용자 확인 또는 새 사용자 생성 - let user = await prisma.user.findUnique({ where: { email } }); - - if (!user) { - // 친구 코드 생성 (중복되지 않도록 반복 시도) - let friendCode; - while (true) { - friendCode = generateFriendCode(); // 랜덤 코드 생성 - const existingUser = await prisma.user.findUnique({ where: { friendCode } }); - if (!existingUser) break; // 고유 코드 확인 - } - - // 기본 닉네임 생성 (사용자의 이메일 @ 앞 부분을 따옴) - const nickname = email.split('@')[0]; // 이메일의 @ 앞부분 반환 - - // 기본 프로필 이미지 URL 설정 (랜덤 선택) - const profileImages = [ - "https://momentum-s3-bucket.s3.ap-northeast-2.amazonaws.com/profile/basic/Momentum_Icon_NullProfile.png", - "https://momentum-s3-bucket.s3.ap-northeast-2.amazonaws.com/profile/basic/Momentum_Icon_NullProfile(Black).png" - ]; - const defaultProfileImageUrl = profileImages[Math.floor(Math.random() * profileImages.length)]; - - // 사용자가 없으면 새로 생성 - user = await prisma.user.create({ - data: { - userID: userID.toString(), // 카카오 ID => 문자열 - email, // 사용자 이메일 - nickname, // 생성된 닉네임 - friendCode, // 친구 코드 - profileImageUrl: defaultProfileImageUrl, // 기본 프로필 이미지 - createdAt: koreaNow, - updatedAt: koreaNow - }, - }); - } - - // JWT 토큰 생성 - const accessToken = generateAccessToken(user); - const refreshToken = generateRefreshToken(user); - - // Refresh Token을 HttpOnly 쿠키에 저장 - res.cookie('refreshToken', refreshToken, { - httpOnly: true, - secure: true, - sameSite: 'None', - domain: '.codeit-momentum.shop', - - }); - - // 사용자 정보 + JWT 토큰 반환 - res.json({ - user: { - id: user.id, - userID: user.userID, - email: user.email, - nickname: user.nickname, - friendCode: user.friendCode, - profileImageUrl: user.profileImageUrl, - }, - accessToken - }); - } catch (error) { - console.error('사용자 처리 실패:', error.message); - res.status(500).send('Failed to process user'); + const { kakaoAccessToken } = req.body; // 클라이언트에서 받은 액세스 토큰 + + try { + // 카카오 사용자 정보 가져오기 + const userInfo = await getKakaoUser(kakaoAccessToken); + + const { id: userID, kakao_account } = userInfo; // 카카오 사용자 ID 및 계정 정보 + const email = kakao_account.email; + + const koreaNow = getKoreaNow(); + + // 데이터베이스에서 사용자 확인 또는 새 사용자 생성 + let user = await prisma.user.findUnique({ where: { email } }); + + if (!user) { + // 친구 코드 생성 (중복되지 않도록 반복 시도) + let friendCode; + while (true) { + friendCode = generateFriendCode(); // 랜덤 코드 생성 + const existingUser = await prisma.user.findUnique({ where: { friendCode } }); + if (!existingUser) break; // 고유 코드 확인 + } + + // 기본 닉네임 생성 (사용자의 이메일 @ 앞 부분을 따옴) + const nickname = email.split('@')[0]; // 이메일의 @ 앞부분 반환 + + // 기본 프로필 이미지 URL 설정 (랜덤 선택) + const profileImages = [ + "https://momentum-s3-bucket.s3.ap-northeast-2.amazonaws.com/profile/basic/Momentum_Icon_NullProfile.png", + "https://momentum-s3-bucket.s3.ap-northeast-2.amazonaws.com/profile/basic/Momentum_Icon_NullProfile(Black).png" + ]; + const defaultProfileImageUrl = profileImages[Math.floor(Math.random() * profileImages.length)]; + + // 사용자가 없으면 새로 생성 + user = await prisma.user.create({ + data: { + userID: userID.toString(), // 카카오 ID => 문자열 + email, // 사용자 이메일 + nickname, // 생성된 닉네임 + friendCode, // 친구 코드 + profileImageUrl: defaultProfileImageUrl, // 기본 프로필 이미지 + createdAt: koreaNow, + updatedAt: koreaNow + }, + }); } + + // JWT 토큰 생성 + const accessToken = generateAccessToken(user); + const refreshToken = generateRefreshToken(user); + + // Refresh Token을 HttpOnly 쿠키에 저장 + res.cookie('refreshToken', refreshToken, { + ...COOKIE_OPTIONS, + }); + + // 사용자 정보 + JWT 토큰 반환 + res.json({ + user: { + id: user.id, + userID: user.userID, + email: user.email, + nickname: user.nickname, + friendCode: user.friendCode, + profileImageUrl: user.profileImageUrl, + }, + accessToken + }); + } catch (error) { + console.error('사용자 처리 실패:', error.message); + res.status(500).send('Failed to process user'); + } }; \ No newline at end of file diff --git a/src/controllers/bucketControllers.js b/src/controllers/bucketControllers.js index f8a2acd..90d29b1 100644 --- a/src/controllers/bucketControllers.js +++ b/src/controllers/bucketControllers.js @@ -3,48 +3,48 @@ import { PrismaClient } from '@prisma/client'; import { s3Client } from '../config/s3config.js'; const getKoreaNow = () => { - const now = new Date(); // 현재 UTC 기준 시간 - now.setHours(now.getHours() + 9); // 9시간 추가 (UTC → KST 변환) - return now; + const now = new Date(); // 현재 UTC 기준 시간 + now.setHours(now.getHours() + 9); // 9시간 추가 (UTC → KST 변환) + return now; }; const prisma = new PrismaClient(); //버킷리스트 생성 export const createBucket = async (req, res) => { - try { - const userID = req.user.userID; - const { type, content } = req.body; - const koreaNow = getKoreaNow(); - - if (!type || !content) { - return res.status(400).json({ - success: false, - error: { code: 400, message: 'type과 content는 필수입니다.' }, - }); - } - - const newBucket = await prisma.bucket.create({ - data: { - userID, - type, - content, - createdAt: koreaNow, - }, - }); + try { + const userID = req.user.userID; + const { type, content } = req.body; + const koreaNow = getKoreaNow(); - return res.status(201).json({ - success: true, - message: '버킷리스트가 생성되었습니다.', - bucket: newBucket, - }); - } catch (error) { - console.error('버킷 생성 실패:', error); - return res.status(500).json({ + if (!type || !content) { + return res.status(400).json({ success: false, - error: { code: 500, message: '서버 내부 오류가 발생했습니다.' }, - }); + error: { code: 400, message: 'type과 content는 필수입니다.' }, + }); } + + const newBucket = await prisma.bucket.create({ + data: { + userID, + type, + content, + createdAt: koreaNow, + }, + }); + + return res.status(201).json({ + success: true, + message: '버킷리스트가 생성되었습니다.', + bucket: newBucket, + }); + } catch (error) { + console.error('버킷 생성 실패:', error); + return res.status(500).json({ + success: false, + error: { code: 500, message: '서버 내부 오류가 발생했습니다.' }, + }); + } }; @@ -53,72 +53,72 @@ export const createBucket = async (req, res) => { * - 달성형 버킷에만 적용. */ export const uploadAchievementPhoto = async (req, res) => { - try { - const userID = req.user.userID; - const { bucketID } = req.params; - const koreaNow = getKoreaNow(); - - const bucket = await prisma.bucket.findUnique({ where: { bucketID } }); - if (!bucket) { - return res.status(404).json({ - success: false, - error: { code: 404, message: '버킷을 찾을 수 없습니다.' }, - }); - } - if (bucket.userID !== userID) { - return res.status(403).json({ - success: false, - error: { code: 403, message: '버킷 수정 권한이 없습니다.' }, - }); - } - - // 달성형인지 - if (bucket.type !== 'ACHIEVEMENT') { - return res.status(400).json({ - success: false, - error: { code: 400, message: '이 버킷은 사진 인증으로 완료할 수 없는 타입입니다.(반복형)' }, - }); - } - - // Multer-S3 업로드 결과 - let photoUrl = null; + try { + const userID = req.user.userID; + const { bucketID } = req.params; + const koreaNow = getKoreaNow(); + + const bucket = await prisma.bucket.findUnique({ where: { bucketID } }); + if (!bucket) { + return res.status(404).json({ + success: false, + error: { code: 404, message: '버킷을 찾을 수 없습니다.' }, + }); + } + if (bucket.userID !== userID) { + return res.status(403).json({ + success: false, + error: { code: 403, message: '버킷 수정 권한이 없습니다.' }, + }); + } - if (req.file) { - const bucketName = process.env.AWS_S3_BUCKET_NAME; - const key = `bucket/${userID}/${bucketID}/${Date.now()}`; + // 달성형인지 + if (bucket.type !== 'ACHIEVEMENT') { + return res.status(400).json({ + success: false, + error: { code: 400, message: '이 버킷은 사진 인증으로 완료할 수 없는 타입입니다.(반복형)' }, + }); + } - const command = new PutObjectCommand({ - Bucket: bucketName, - Key: key, - Body: req.file.buffer, // Multer는 파일 데이터를 buffer로 제공 - ContentType: req.file.mimetype, // 파일의 MIME 타입 - }); + // Multer-S3 업로드 결과 + let photoUrl = null; - await s3Client.send(command); - photoUrl = `https://${bucketName}.s3.${process.env.AWS_REGION}.amazonaws.com/${key}`; - } + if (req.file) { + const bucketName = process.env.AWS_S3_BUCKET_NAME; + const key = `bucket/${userID}/${bucketID}/${Date.now()}`; - const updatedBucket = await prisma.bucket.update({ - where: { bucketID }, - data: { - photoUrl: photoUrl, - isCompleted: true, // 달성 완료 - updatedAt: koreaNow, - }, - }); + const command = new PutObjectCommand({ + Bucket: bucketName, + Key: key, + Body: req.file.buffer, // Multer는 파일 데이터를 buffer로 제공 + ContentType: req.file.mimetype, // 파일의 MIME 타입 + }); - return res.status(200).json({ - success: true, - message: '달성형 버킷이 인증되어 완료 처리되었습니다.', - bucket: updatedBucket, - }); - } catch (error) { - console.error('달성형 버킷 인증사진 업로드 실패:', error); - return res.status(500).json({ - success: false, - error: { code: 500, message: '서버 내부 오류가 발생했습니다.' }, - }); + await s3Client.send(command); + photoUrl = `https://${bucketName}.s3.${process.env.AWS_REGION}.amazonaws.com/${key}`; } + + const updatedBucket = await prisma.bucket.update({ + where: { bucketID }, + data: { + photoUrl: photoUrl, + isCompleted: true, // 달성 완료 + updatedAt: koreaNow, + }, + }); + + return res.status(200).json({ + success: true, + message: '달성형 버킷이 인증되어 완료 처리되었습니다.', + bucket: updatedBucket, + }); + } catch (error) { + console.error('달성형 버킷 인증사진 업로드 실패:', error); + return res.status(500).json({ + success: false, + error: { code: 500, message: '서버 내부 오류가 발생했습니다.' }, + }); + } }; @@ -128,382 +128,382 @@ export const uploadAchievementPhoto = async (req, res) => { * - 동시에 도전 중인 REPEAT 버킷이 3개 이상이면 불가 */ export const activateBucketChallenge = async (req, res) => { - try { - const userID = req.user.userID; - const { bucketID } = req.params; - const koreaNow = getKoreaNow(); - - const bucket = await prisma.bucket.findUnique({ where: { bucketID } }); - if (!bucket) { - return res.status(404).json({ success: false, error: { code: 404, message: '버킷 없음' }}); - } - if (bucket.userID !== userID) { - return res.status(403).json({ success: false, error: { code: 403, message: '권한 없음' }}); - } - if (bucket.type !== 'REPEAT') { - return res.status(400).json({ - success: false, - error: { code: 400, message: '달성형 버킷은 도전 중으로 설정할 수 없습니다.' }, - }); - } - if (bucket.isChallenging) { - return res.status(400).json({ - success: false, - error: { code: 400, message: '이미 도전 중인 버킷입니다.' }, - }); - } - - // 현재 도전 중인 repeat 버킷 개수 - const countChallenging = await prisma.bucket.count({ - where: { userID, type: 'REPEAT', isChallenging: true }, - }); - if (countChallenging >= 3) { - return res.status(400).json({ - success: false, - error: { code: 400, message: '도전 중 버킷은 최대 3개까지 가능합니다.' }, - }); - } - - const updated = await prisma.bucket.update({ - where: { bucketID }, - data: { isChallenging: true, updatedAt: koreaNow }, - }); - - const newCount = await prisma.bucket.count({ - where: { userID, type: 'REPEAT', isChallenging: true }, - }); + try { + const userID = req.user.userID; + const { bucketID } = req.params; + const koreaNow = getKoreaNow(); + + const bucket = await prisma.bucket.findUnique({ where: { bucketID } }); + if (!bucket) { + return res.status(404).json({ success: false, error: { code: 404, message: '버킷 없음' } }); + } + if (bucket.userID !== userID) { + return res.status(403).json({ success: false, error: { code: 403, message: '권한 없음' } }); + } + if (bucket.type !== 'REPEAT') { + return res.status(400).json({ + success: false, + error: { code: 400, message: '달성형 버킷은 도전 중으로 설정할 수 없습니다.' }, + }); + } + if (bucket.isChallenging) { + return res.status(400).json({ + success: false, + error: { code: 400, message: '이미 도전 중인 버킷입니다.' }, + }); + } - return res.status(200).json({ - success: true, - message: '반복형 버킷이 도전 중으로 활성화되었습니다.', - bucket: updated, - challengingCount: newCount, - }); - } catch (error) { - console.error('도전 상태 활성화 실패:', error); - return res.status(500).json({ - success: false, - error: { code: 500, message: '서버 내부 오류가 발생했습니다.' }, - }); + // 현재 도전 중인 repeat 버킷 개수 + const countChallenging = await prisma.bucket.count({ + where: { userID, type: 'REPEAT', isChallenging: true }, + }); + if (countChallenging >= 3) { + return res.status(400).json({ + success: false, + error: { code: 400, message: '도전 중 버킷은 최대 3개까지 가능합니다.' }, + }); } + + const updated = await prisma.bucket.update({ + where: { bucketID }, + data: { isChallenging: true, updatedAt: koreaNow }, + }); + + const newCount = await prisma.bucket.count({ + where: { userID, type: 'REPEAT', isChallenging: true }, + }); + + return res.status(200).json({ + success: true, + message: '반복형 버킷이 도전 중으로 활성화되었습니다.', + bucket: updated, + challengingCount: newCount, + }); + } catch (error) { + console.error('도전 상태 활성화 실패:', error); + return res.status(500).json({ + success: false, + error: { code: 500, message: '서버 내부 오류가 발생했습니다.' }, + }); + } }; - /** - * 이건 고려 필요 - * [PATCH] /buckets/:bucketID/un-challenge - * - 도전 중인 반복형 버킷 해제 => isChallenging=false - */ +/** + * 이건 고려 필요 + * [PATCH] /buckets/:bucketID/un-challenge + * - 도전 중인 반복형 버킷 해제 => isChallenging=false + */ export const deactivateBucketChallenge = async (req, res) => { - try { - const userID = req.user.userID; - const { bucketID } = req.params; - const koreaNow = getKoreaNow(); - - const bucket = await prisma.bucket.findUnique({ where: { bucketID } }); - if (!bucket) { - return res.status(404).json({ success: false, error: { code: 404, message: '버킷 없음' }}); - } - if (bucket.userID !== userID) { - return res.status(403).json({ success: false, error: { code: 403, message: '권한 없음' }}); - } - if (bucket.type !== 'REPEAT') { - return res.status(400).json({ - success: false, - error: { code: 400, message: '달성형 버킷은 도전 안함으로 설정할 수 없습니다.' }, - }); - } - if (!bucket.isChallenging) { - return res.status(400).json({ - success: false, - error: { code: 400, message: '이미 도전 중이 아닌 버킷입니다.' }, - }); - } - - const updated = await prisma.bucket.update({ - where: { bucketID }, - data: { isChallenging: false, updatedAt: koreaNow }, - }); - - const newCount = await prisma.bucket.count({ - where: { userID, type: 'REPEAT', isChallenging: true }, - }); - - // 자리가 비었는지(= 3개에서 2개 이하가 되었는지) 확인 - const freedSlot = (newCount < 3); - - return res.status(200).json({ - success: true, - message: '해당 버킷이 도전 중에서 해제되었습니다.', - bucket: updated, - challengingCount: newCount, - freeSlot: freedSlot, - }); - } catch (error) { - console.error('도전 상태 해제 실패:', error); - return res.status(500).json({ - success: false, - error: { code: 500, message: '서버 내부 오류가 발생했습니다.' }, - }); + try { + const userID = req.user.userID; + const { bucketID } = req.params; + const koreaNow = getKoreaNow(); + + const bucket = await prisma.bucket.findUnique({ where: { bucketID } }); + if (!bucket) { + return res.status(404).json({ success: false, error: { code: 404, message: '버킷 없음' } }); } + if (bucket.userID !== userID) { + return res.status(403).json({ success: false, error: { code: 403, message: '권한 없음' } }); + } + if (bucket.type !== 'REPEAT') { + return res.status(400).json({ + success: false, + error: { code: 400, message: '달성형 버킷은 도전 안함으로 설정할 수 없습니다.' }, + }); + } + if (!bucket.isChallenging) { + return res.status(400).json({ + success: false, + error: { code: 400, message: '이미 도전 중이 아닌 버킷입니다.' }, + }); + } + + const updated = await prisma.bucket.update({ + where: { bucketID }, + data: { isChallenging: false, updatedAt: koreaNow }, + }); + + const newCount = await prisma.bucket.count({ + where: { userID, type: 'REPEAT', isChallenging: true }, + }); + + // 자리가 비었는지(= 3개에서 2개 이하가 되었는지) 확인 + const freedSlot = (newCount < 3); + + return res.status(200).json({ + success: true, + message: '해당 버킷이 도전 중에서 해제되었습니다.', + bucket: updated, + challengingCount: newCount, + freeSlot: freedSlot, + }); + } catch (error) { + console.error('도전 상태 해제 실패:', error); + return res.status(500).json({ + success: false, + error: { code: 500, message: '서버 내부 오류가 발생했습니다.' }, + }); + } }; // 버킷 상세 조회 export const getBucketDetail = async (req, res) => { - try { - const userID = req.user.userID; - const { bucketID } = req.params; - const koreaNow = getKoreaNow(); - - const result = await prisma.$transaction(async (tx) => { - // 버킷 + 모멘트 가져오기 - const bucket = await tx.bucket.findUnique({ - where: { bucketID }, - include: { moments: true }, - }); - - if (!bucket) { - throw new Error('버킷을 찾을 수 없습니다.'); - } - - // 소유자 체크 - if (bucket.userID !== userID) { - throw new Error('버킷 조회 권한이 없습니다.'); - } - // "모든 모멘트 완료" 상태 업데이트 - const momentsCount = bucket.moments.length; - const completedMomentsCount = bucket.moments.filter((moment) => moment.isCompleted === true).length; - - - if (completedMomentsCount === momentsCount && !bucket.isCompleted && momentsCount !== 0) { - await tx.bucket.update({ - where: { bucketID }, - data: { isCompleted: true, updatedAt: koreaNow }, - }); - } - - return { bucket, momentsCount, completedMomentsCount }; - }); - - return res.status(200).json({ - success: true, - bucket: result.bucket, - momentsCount: result.momentsCount, - completedMomentsCount: result.completedMomentsCount, - }); - } catch (error) { - console.error('버킷 상세 조회 실패:', error.message); - return res.status(500).json({ - success: false, - error: { code: 500, message: '서버 내부 오류가 발생했습니다.' }, - }); - } + try { + const userID = req.user.userID; + const { bucketID } = req.params; + const koreaNow = getKoreaNow(); + + const result = await prisma.$transaction(async (tx) => { + // 버킷 + 모멘트 가져오기 + const bucket = await tx.bucket.findUnique({ + where: { bucketID }, + include: { moments: true }, + }); + + if (!bucket) { + throw new Error('버킷을 찾을 수 없습니다.'); + } + + // 소유자 체크 + if (bucket.userID !== userID) { + throw new Error('버킷 조회 권한이 없습니다.'); + } + // "모든 모멘트 완료" 상태 업데이트 + const momentsCount = bucket.moments.length; + const completedMomentsCount = bucket.moments.filter((moment) => moment.isCompleted === true).length; + + + if (completedMomentsCount === momentsCount && !bucket.isCompleted && momentsCount !== 0) { + await tx.bucket.update({ + where: { bucketID }, + data: { isCompleted: true, updatedAt: koreaNow }, + }); + } + + return { bucket, momentsCount, completedMomentsCount }; + }); + + return res.status(200).json({ + success: true, + bucket: result.bucket, + momentsCount: result.momentsCount, + completedMomentsCount: result.completedMomentsCount, + }); + } catch (error) { + console.error('버킷 상세 조회 실패:', error.message); + return res.status(500).json({ + success: false, + error: { code: 500, message: '서버 내부 오류가 발생했습니다.' }, + }); + } }; export const updateBucket = async (req, res) => { - try { - const userID = req.user.userID; // JWT 인증 후 주입된 값 - const { bucketID } = req.params; - const { content } = req.body; - const koreaNow = getKoreaNow(); - - // 1) 현재 버킷 조회 - const existingBucket = await prisma.bucket.findUnique({ - where: { bucketID }, - }); - if (!existingBucket) { - return res.status(404).json({ - success: false, - error: { code: 404, message: '해당 버킷을 찾을 수 없습니다.' }, - }); - } - - // 2) 소유자 체크 - if (existingBucket.userID !== userID) { - return res.status(403).json({ - success: false, - error: { code: 403, message: '버킷 수정 권한이 없습니다.' }, - }); - } - - // 3) 업데이트할 데이터 준비 - // 필요한 필드만 골라서 DB에 반영 - // (type 변경 허용 여부는 기획에 따라) - const updateData = {}; - if (typeof content === 'string') updateData.content = content; - - // 4) DB 업데이트 - const updatedBucket = await prisma.bucket.update({ - where: { bucketID }, - data: { - ...updateData, - updatedAt: koreaNow, - }, - }); + try { + const userID = req.user.userID; // JWT 인증 후 주입된 값 + const { bucketID } = req.params; + const { content } = req.body; + const koreaNow = getKoreaNow(); + + // 1) 현재 버킷 조회 + const existingBucket = await prisma.bucket.findUnique({ + where: { bucketID }, + }); + if (!existingBucket) { + return res.status(404).json({ + success: false, + error: { code: 404, message: '해당 버킷을 찾을 수 없습니다.' }, + }); + } - return res.status(200).json({ - success: true, - message: '버킷리스트가 성공적으로 수정되었습니다.', - bucket: updatedBucket, - }); - } catch (error) { - console.error('버킷 수정 실패:', error); - return res.status(500).json({ - success: false, - error: { code: 500, message: '서버 내부 오류가 발생했습니다.' }, - }); + // 2) 소유자 체크 + if (existingBucket.userID !== userID) { + return res.status(403).json({ + success: false, + error: { code: 403, message: '버킷 수정 권한이 없습니다.' }, + }); } + + // 3) 업데이트할 데이터 준비 + // 필요한 필드만 골라서 DB에 반영 + // (type 변경 허용 여부는 기획에 따라) + const updateData = {}; + if (typeof content === 'string') updateData.content = content; + + // 4) DB 업데이트 + const updatedBucket = await prisma.bucket.update({ + where: { bucketID }, + data: { + ...updateData, + updatedAt: koreaNow, + }, + }); + + return res.status(200).json({ + success: true, + message: '버킷리스트가 성공적으로 수정되었습니다.', + bucket: updatedBucket, + }); + } catch (error) { + console.error('버킷 수정 실패:', error); + return res.status(500).json({ + success: false, + error: { code: 500, message: '서버 내부 오류가 발생했습니다.' }, + }); + } }; // 완료된 버킷리스트 개수 출력 export const getCompletedBuckets = async (req, res) => { - try { - const userID = req.user.userID; - - // `isCompleted: true`인 버킷리스트 개수 조회 - const completedCount = await prisma.bucket.count({ - where: { - userID, - isCompleted: true, - }, - }); - - return res.status(200).json({ - success: true, - message: '달성된 버킷리스트 개수 조회 성공', - completedCount, - }); - } catch (error) { - console.error('달성된 버킷리스트 개수 조회 실패:', error); - return res.status(500).json({ - success: false, - error: { code: 500, message: '서버 내부 오류가 발생했습니다.' }, - }); - } + try { + const userID = req.user.userID; + + // `isCompleted: true`인 버킷리스트 개수 조회 + const completedCount = await prisma.bucket.count({ + where: { + userID, + isCompleted: true, + }, + }); + + return res.status(200).json({ + success: true, + message: '달성된 버킷리스트 개수 조회 성공', + completedCount, + }); + } catch (error) { + console.error('달성된 버킷리스트 개수 조회 실패:', error); + return res.status(500).json({ + success: false, + error: { code: 500, message: '서버 내부 오류가 발생했습니다.' }, + }); + } }; export const getRepeatBuckets = async (req, res) => { - try { - const userID = req.user.userID; - // 1) Bucket 중 type=REPEAT 만 조회 - const buckets = await prisma.bucket.findMany({ - where: { - type: 'REPEAT', - userID, - }, - select: { - bucketID: true, // PK (필요시) - content: true, - isCompleted: true, - isChallenging: true, - }, - orderBy: { - createdAt: 'desc', - }, - }); - - // 2) 응답 - return res.status(200).json({ - success: true, - user: userID, - count: buckets.length, - type: 'REPEAT 반복형', - buckets, - }); - } catch (error) { - console.error('반복형 버킷 조회 실패:', error); - return res.status(500).json({ - success: false, - error: { code: 500, message: '서버 내부 오류가 발생했습니다.' }, - }); - } + try { + const userID = req.user.userID; + // 1) Bucket 중 type=REPEAT 만 조회 + const buckets = await prisma.bucket.findMany({ + where: { + type: 'REPEAT', + userID, + }, + select: { + bucketID: true, // PK (필요시) + content: true, + isCompleted: true, + isChallenging: true, + }, + orderBy: { + createdAt: 'desc', + }, + }); + + // 2) 응답 + return res.status(200).json({ + success: true, + user: userID, + count: buckets.length, + type: 'REPEAT 반복형', + buckets, + }); + } catch (error) { + console.error('반복형 버킷 조회 실패:', error); + return res.status(500).json({ + success: false, + error: { code: 500, message: '서버 내부 오류가 발생했습니다.' }, + }); + } }; export const getAchievementBuckets = async (req, res) => { - try { - const userID = req.user.userID; - - // 1) ACHIEVEMENT 타입 && 해당 userID의 버킷 조회 - const buckets = await prisma.bucket.findMany({ - where: { - userID, - type: 'ACHIEVEMENT', - }, - select: { - bucketID: true, - content: true, - isCompleted: true, - // isChallenging 필드는 달성형에 의미 없으므로 제외 - }, - orderBy: { createdAt: 'desc' }, - }); - - return res.status(200).json({ - success: true, - user: userID, - count: buckets.length, - type: 'ACHIEVEMENT 달성형', - buckets, - }); - } catch (error) { - console.error('달성형 버킷 조회 실패:', error); - return res.status(500).json({ - success: false, - error: { code: 500, message: '서버 내부 오류가 발생했습니다.' }, - }); - } + try { + const userID = req.user.userID; + + // 1) ACHIEVEMENT 타입 && 해당 userID의 버킷 조회 + const buckets = await prisma.bucket.findMany({ + where: { + userID, + type: 'ACHIEVEMENT', + }, + select: { + bucketID: true, + content: true, + isCompleted: true, + // isChallenging 필드는 달성형에 의미 없으므로 제외 + }, + orderBy: { createdAt: 'desc' }, + }); + + return res.status(200).json({ + success: true, + user: userID, + count: buckets.length, + type: 'ACHIEVEMENT 달성형', + buckets, + }); + } catch (error) { + console.error('달성형 버킷 조회 실패:', error); + return res.status(500).json({ + success: false, + error: { code: 500, message: '서버 내부 오류가 발생했습니다.' }, + }); + } }; export const deleteBucket = async (req, res) => { - try { - const userID = req.user.userID; // JWT 인증 후 주입된 값 - const { bucketID } = req.params; - - // 1) 버킷 존재 여부 확인 - const existingBucket = await prisma.bucket.findUnique({ - where: { bucketID }, - }); - if (!existingBucket) { - return res.status(404).json({ - success: false, - error: { code: 404, message: '해당 버킷을 찾을 수 없습니다.' }, - }); - } - - // 2) 소유자 체크 - if (existingBucket.userID !== userID) { - return res.status(403).json({ - success: false, - error: { code: 403, message: '버킷 삭제 권한이 없습니다.' }, - }); - } - - // 3) 연쇄 삭제 (모멘트 → 버킷) - await prisma.$transaction(async (tx) => { - // 3-1) 버킷에 속한 모멘트들 전부 삭제 - await tx.moment.deleteMany({ - where: { bucketID }, - }); - - // 3-2) 버킷 삭제 - await tx.bucket.delete({ - where: { bucketID }, - }); - }); - return res.status(200).json({ - success: true, - message: '버킷리스트가 성공적으로 삭제되었습니다.', - type: existingBucket.type, - bucketID, - }); - } catch (error) { - console.error('버킷 삭제 실패:', error); - return res.status(500).json({ - success: false, - error: { code: 500, message: '서버 내부 오류가 발생했습니다.' }, - }); + try { + const userID = req.user.userID; // JWT 인증 후 주입된 값 + const { bucketID } = req.params; + + // 1) 버킷 존재 여부 확인 + const existingBucket = await prisma.bucket.findUnique({ + where: { bucketID }, + }); + if (!existingBucket) { + return res.status(404).json({ + success: false, + error: { code: 404, message: '해당 버킷을 찾을 수 없습니다.' }, + }); + } + + // 2) 소유자 체크 + if (existingBucket.userID !== userID) { + return res.status(403).json({ + success: false, + error: { code: 403, message: '버킷 삭제 권한이 없습니다.' }, + }); } + + // 3) 연쇄 삭제 (모멘트 → 버킷) + await prisma.$transaction(async (tx) => { + // 3-1) 버킷에 속한 모멘트들 전부 삭제 + await tx.moment.deleteMany({ + where: { bucketID }, + }); + + // 3-2) 버킷 삭제 + await tx.bucket.delete({ + where: { bucketID }, + }); + }); + return res.status(200).json({ + success: true, + message: '버킷리스트가 성공적으로 삭제되었습니다.', + type: existingBucket.type, + bucketID, + }); + } catch (error) { + console.error('버킷 삭제 실패:', error); + return res.status(500).json({ + success: false, + error: { code: 500, message: '서버 내부 오류가 발생했습니다.' }, + }); + } }; diff --git a/src/controllers/feedControllers.js b/src/controllers/feedControllers.js index 5838c3c..89d8cba 100644 --- a/src/controllers/feedControllers.js +++ b/src/controllers/feedControllers.js @@ -7,14 +7,14 @@ const getKoreaNow = () => { return now; }; -const prisma = new PrismaClient(); +const prisma = new PrismaClient(); export const getFriendFeed = async (req, res) => { try { const userID = req.user.userID; const friendID = req.params.friendID; // 요청 URL에서 친구 ID 가져오기 const koreaNow = getKoreaNow(); - + // 1. 친구 관계 확인 const friendRelation = await prisma.friend.findMany({ where: { @@ -66,7 +66,7 @@ export const getFriendFeed = async (req, res) => { frequency: true, }, }); - + const repeatBuckets = buckets.filter( (bucket) => bucket.type !== "ACHIEVEMENT" ); @@ -74,8 +74,8 @@ export const getFriendFeed = async (req, res) => { // 반복형 버킷리스트가 없을 경우 if (repeatBuckets.length === 0) { return res.status(404).json({ - success: false, - message: "해당 친구의 반복형 버킷리스트가 존재하지 않습니다.", + success: false, + message: "해당 친구의 반복형 버킷리스트가 존재하지 않습니다.", }); } @@ -101,9 +101,9 @@ export const getFriendFeed = async (req, res) => { orderBy: { completedAt: "desc", }, - }); + }); - // ✅ 사용자가 해당 모멘트에 응원했는지 확인 + // ✅ 사용자가 해당 모멘트에 응원했는지 확인 for (const moment of bucketMoments) { const friendFeed = await prisma.friendFeed.findFirst({ where: { @@ -122,7 +122,7 @@ export const getFriendFeed = async (req, res) => { imageUrl: moment.photoUrl, frequency: bucket.frequency, date: moment.completedAt, - cheered: friendFeed ? friendFeed.cheer : false, + cheered: friendFeed ? friendFeed.cheer : false, }); } } diff --git a/src/controllers/friendControllers.js b/src/controllers/friendControllers.js index 94822e7..cd28c69 100644 --- a/src/controllers/friendControllers.js +++ b/src/controllers/friendControllers.js @@ -18,7 +18,7 @@ export const getFriends = async (req, res) => { // 친구 목록 조회 const friends = await prisma.friend.findMany({ where: { - userID: userID, // 사용자가 친구 관계의 주체인 경우 + userID: userID, // 사용자가 친구 관계의 주체인 경우 }, include: { friendUser: { @@ -149,18 +149,18 @@ export const addFriend = async (req, res) => { }, ], }); - + // 친구 요청한 유저 const user = await prisma.user.findUnique({ - where: { userID : requesterID } + where: { userID: requesterID } }); // 친구 요청 받은 유저 const friend = await prisma.user.findUnique({ - where: { userID : receiverID } + where: { userID: receiverID } }); - + // 친구 생성 알림 추가 await prisma.notification.createMany({ data: [ @@ -202,7 +202,7 @@ export const deleteFriend = async (req, res) => { if (!friendUserID) { return res.status(400).json({ message: '삭제할 친구의 사용자 ID가 필요합니다.' }); } - + try { // 친구 관계 확인 const friendRelation = await prisma.friend.findMany({ @@ -263,7 +263,7 @@ export const knockFriend = async (req, res) => { const koreaNow = getKoreaNow(); try { - + // 친구 관계 및 최근 피드(모멘트) 정보 검색 const friendRelation = await prisma.friend.findUnique({ where: { @@ -289,13 +289,13 @@ export const knockFriend = async (req, res) => { where: { userID: friendUserID } }); - + // 친구가 최근 7일 이내에 피드(모멘트)를 올렸는지 확인 -> 내부 확인용 const sevenDaysAgo = moment().subtract(7, 'days'); // 현재 날짜 기준 7일 전 - const hasRecentCompletedMoment = friendRelation.friendUser.moments.some(momentItem => + const hasRecentCompletedMoment = friendRelation.friendUser.moments.some(momentItem => momentItem.isCompleted === true && - momentItem.updatedAt && + momentItem.updatedAt && moment(new Date(momentItem.updatedAt)).isAfter(sevenDaysAgo) ); @@ -320,7 +320,7 @@ export const knockFriend = async (req, res) => { isKnock: true } }); - + // 노크한 유저 const user = await prisma.user.findUnique({ where: { userID } diff --git a/src/controllers/homeControllers.js b/src/controllers/homeControllers.js index 2835f9b..0ff1d97 100644 --- a/src/controllers/homeControllers.js +++ b/src/controllers/homeControllers.js @@ -21,25 +21,25 @@ export const getHome = async (req, res) => { where: { userID }, }); - if (!currentUser) { - return res.status(404).json({ + if (!currentUser) { + return res.status(404).json({ success: false, error: { code: 404, message: '현재 사용자를 찾을 수 없습니다.' } }); }; - + // 사용자의 moments 조회 const moments = await prisma.moment.findMany({ where: { userID, - startDate: { lte: koreaNow }, - endDate: { gte: koreaNow } + startDate: { lte: koreaNow }, + endDate: { gte: koreaNow } }, select: { momentID: true, content: true, startDate: true, - endDate: true, + endDate: true, isCompleted: true } }); @@ -57,7 +57,7 @@ export const getHome = async (req, res) => { }) } catch (err) { console.error('홈 조회 실패:', err.message); - res.status(500).json({ + res.status(500).json({ success: false, error: { code: 500, message: '홈 조회를 하는 데 실패하였습니다.' } }); @@ -71,7 +71,7 @@ export const getCompletedMomentsByWeek = async (req, res) => { const koreaNow = getKoreaNow(); const koreaNowMoment = moment(koreaNow); - const startOfWeek = koreaNowMoment.startOf('isoWeek'); + const startOfWeek = koreaNowMoment.startOf('isoWeek'); const weekDates = []; for (let i = 0; i < 7; i++) { weekDates.push(moment(startOfWeek).add(i, 'days').toDate()); @@ -102,7 +102,7 @@ export const getCompletedMomentsByWeek = async (req, res) => { const isComplete = momentsForDate.some(m => m.isCompleted && m.completedAt && - moment.tz(m.completedAt, "Asia/Seoul").tz(moment.tz.guess()).format("YYYY-MM-DD") === + moment.tz(m.completedAt, "Asia/Seoul").tz(moment.tz.guess()).format("YYYY-MM-DD") === moment.tz(day, "Asia/Seoul").tz(moment.tz.guess()).format("YYYY-MM-DD") ); @@ -146,7 +146,7 @@ export const getConsecutiveCompletedDays = async (req, res) => { error: { code: 404, message: '현재 사용자를 찾을 수 없습니다.' } }); } - + // 연속된 isCompleted === true인 날짜 개수를 계산 let consecutiveDays = 1; let checkDate = koreaNow; @@ -164,11 +164,11 @@ export const getConsecutiveCompletedDays = async (req, res) => { } }); - const isComplete = + const isComplete = moments.length > 0 && - moments.some(m => + moments.some(m => m.isCompleted && - moment.tz(m.completedAt, "Asia/Seoul").tz(moment.tz.guess()).format("YYYY-MM-DD") === + moment.tz(m.completedAt, "Asia/Seoul").tz(moment.tz.guess()).format("YYYY-MM-DD") === moment.tz(checkDate, "Asia/Seoul").tz(moment.tz.guess()).format("YYYY-MM-DD") ); @@ -201,12 +201,12 @@ export const getCompletedBucket = async (req, res) => { // 전체 버킷 수 const totalBuckets = await prisma.bucket.count({ - where: { userID }, + where: { userID }, }); console.log(totalBuckets); // 완료된 버킷 수 const completedBucketsCount = await prisma.bucket.count({ - where: { userID, isCompleted: true }, + where: { userID, isCompleted: true }, }); console.log(completedBucketsCount); diff --git a/src/controllers/momentControllers.js b/src/controllers/momentControllers.js index f7abd9e..fc04d47 100644 --- a/src/controllers/momentControllers.js +++ b/src/controllers/momentControllers.js @@ -3,479 +3,479 @@ import { PrismaClient } from '@prisma/client'; import { s3Client } from '../config/s3config.js'; const getKoreaNow = () => { - const now = new Date(); // 현재 UTC 기준 시간 - now.setHours(now.getHours() + 9); // 9시간 추가 (UTC → KST 변환) - return now; + const now = new Date(); // 현재 UTC 기준 시간 + now.setHours(now.getHours() + 9); // 9시간 추가 (UTC → KST 변환) + return now; }; const prisma = new PrismaClient(); // 모멘트 생성 API (예외처리 완료) export const createMoments = async (req, res) => { - try { - const userID = req.user.userID; - const { bucketID } = req.params; - const { startDate, endDate, moments, frequency } = req.body; - const koreaNow = getKoreaNow(); - - // 1) 요청 검증 - if (!Array.isArray(moments) || moments.length === 0) { - return res.status(400).json({ - success: false, - error: { code: 400, message: 'moments 배열이 비어 있습니다.' }, - }); - } - - // 2) 버킷 조회 - const bucket = await prisma.bucket.findUnique({ where: { bucketID } }); - if (!bucket) { - return res.status(404).json({ - success: false, - error: { code: 404, message: '해당 버킷을 찾을 수 없습니다.' }, - }); - } - // 소유자 체크 - if (bucket.userID !== userID) { - return res.status(403).json({ - success: false, - error: { code: 403, message: '버킷 소유자가 아닙니다.' }, - }); - } - - //버킷 상태 체크: 반복형 + 도전 중 - if (bucket.type !== 'REPEAT' || !bucket.isChallenging) { - return res.status(400).json({ - success: false, - error: { - code: 400, - message: '모멘트를 추가할 수 없는 버킷입니다. (반복형 + 도전 중이어야 함)', - }, - }); - } - const toStartOfDay = (dateStr) => { - // dateStr 예: "2024-01-20" - const d = new Date(`${dateStr}T00:00:00`); // 날짜를 0시 0분 0초로 - // 아래 setHours로 더 정확히 0,0,0,0 설정 - d.setHours(0, 0, 0, 0); - return d; - }; - const toEndOfDay = (dateStr) => { - const d = new Date(`${dateStr}T00:00:00`); - d.setDate(d.getDate() - 1); // 하루 전으로 조정 - d.setHours(23, 59, 59, 999); // 날짜를 23:59:59.999로 - return d; - }; - - // 버킷의 startDate, endDate 변환 - let bucketStart = bucket.startDate; // 기존 값 - let bucketEnd = bucket.endDate; // 기존 값 - - if (startDate) { - bucketStart = toStartOfDay(startDate); - } - if (endDate) { - bucketEnd = toEndOfDay(endDate); - } - - - // 3) 트랜잭션 - const result = await prisma.$transaction(async (tx) => { - - // 여러 모멘트 생성 - const createdMoments = []; - - for (const m of moments) { - // m.startDate, m.endDate (YYYY-MM-DD) → 실제 Date로 변환 - const mStart = toStartOfDay(m.startDate); - const mEnd = toEndOfDay(m.endDate); - - const momentCreated = await tx.moment.create({ - data: { - content: m.content || '', - photoUrl: null, - isCompleted: false, - startDate: mStart, - endDate: mEnd, - bucketID: bucketID, - userID: userID, - createdAt: koreaNow, - }, - }); - createdMoments.push(momentCreated); - } - // 버킷의 startDate, endDate 업데이트 - // (이미 값이 있다면 덮어쓰기, 없으면 새로 세팅) - const updatedBucket = await tx.bucket.update({ - where: { bucketID }, - data: { - startDate: startDate ? new Date(startDate) : bucket.startDate, - endDate: endDate ? new Date(endDate) : bucket.endDate, - updatedAt: koreaNow, - frequency, - }, - }); - - return { updatedBucket, createdMoments }; - }); - - return res.status(201).json({ - success: true, - message: '버킷 기간이 설정되고, 모멘트들이 생성되었습니다.', - bucket: result.updatedBucket, - moments: result.createdMoments, - }); - } catch (error) { - console.error('버킷 날짜 & 모멘트 벌크 생성 실패:', error); - return res.status(500).json({ - success: false, - error: { code: 500, message: '서버 내부 오류가 발생했습니다.' }, - }); - } + try { + const userID = req.user.userID; + const { bucketID } = req.params; + const { startDate, endDate, moments, frequency } = req.body; + const koreaNow = getKoreaNow(); + + // 1) 요청 검증 + if (!Array.isArray(moments) || moments.length === 0) { + return res.status(400).json({ + success: false, + error: { code: 400, message: 'moments 배열이 비어 있습니다.' }, + }); + } + + // 2) 버킷 조회 + const bucket = await prisma.bucket.findUnique({ where: { bucketID } }); + if (!bucket) { + return res.status(404).json({ + success: false, + error: { code: 404, message: '해당 버킷을 찾을 수 없습니다.' }, + }); + } + // 소유자 체크 + if (bucket.userID !== userID) { + return res.status(403).json({ + success: false, + error: { code: 403, message: '버킷 소유자가 아닙니다.' }, + }); + } + + //버킷 상태 체크: 반복형 + 도전 중 + if (bucket.type !== 'REPEAT' || !bucket.isChallenging) { + return res.status(400).json({ + success: false, + error: { + code: 400, + message: '모멘트를 추가할 수 없는 버킷입니다. (반복형 + 도전 중이어야 함)', + }, + }); + } + const toStartOfDay = (dateStr) => { + // dateStr 예: "2024-01-20" + const d = new Date(`${dateStr}T00:00:00`); // 날짜를 0시 0분 0초로 + // 아래 setHours로 더 정확히 0,0,0,0 설정 + d.setHours(0, 0, 0, 0); + return d; + }; + const toEndOfDay = (dateStr) => { + const d = new Date(`${dateStr}T00:00:00`); + d.setDate(d.getDate() - 1); // 하루 전으로 조정 + d.setHours(23, 59, 59, 999); // 날짜를 23:59:59.999로 + return d; + }; + + // 버킷의 startDate, endDate 변환 + let bucketStart = bucket.startDate; // 기존 값 + let bucketEnd = bucket.endDate; // 기존 값 + + if (startDate) { + bucketStart = toStartOfDay(startDate); + } + if (endDate) { + bucketEnd = toEndOfDay(endDate); + } + + + // 3) 트랜잭션 + const result = await prisma.$transaction(async (tx) => { + + // 여러 모멘트 생성 + const createdMoments = []; + + for (const m of moments) { + // m.startDate, m.endDate (YYYY-MM-DD) → 실제 Date로 변환 + const mStart = toStartOfDay(m.startDate); + const mEnd = toEndOfDay(m.endDate); + + const momentCreated = await tx.moment.create({ + data: { + content: m.content || '', + photoUrl: null, + isCompleted: false, + startDate: mStart, + endDate: mEnd, + bucketID: bucketID, + userID: userID, + createdAt: koreaNow, + }, + }); + createdMoments.push(momentCreated); + } + // 버킷의 startDate, endDate 업데이트 + // (이미 값이 있다면 덮어쓰기, 없으면 새로 세팅) + const updatedBucket = await tx.bucket.update({ + where: { bucketID }, + data: { + startDate: startDate ? new Date(startDate) : bucket.startDate, + endDate: endDate ? new Date(endDate) : bucket.endDate, + updatedAt: koreaNow, + frequency, + }, + }); + + return { updatedBucket, createdMoments }; + }); + + return res.status(201).json({ + success: true, + message: '버킷 기간이 설정되고, 모멘트들이 생성되었습니다.', + bucket: result.updatedBucket, + moments: result.createdMoments, + }); + } catch (error) { + console.error('버킷 날짜 & 모멘트 벌크 생성 실패:', error); + return res.status(500).json({ + success: false, + error: { code: 500, message: '서버 내부 오류가 발생했습니다.' }, + }); + } }; // 모멘트 조회 export const getMomentsByBucket = async (req, res) => { - try { - const userID = req.user.userID; - const { bucketID } = req.params; - - - // 버킷리스트 ENDDATE 확인(지났을 경우 FALSE 처리) - await prisma.bucket.updateMany({ - where: { - userID, - isChallenging: true, - endDate: { not: null, lt: now }, - }, - data: { - isChallenging: false, - }, - }); - //버킷 조회 - const bucket = await prisma.bucket.findUnique({ - where: { bucketID }, - }); - - if (!bucket) { - return res.status(404).json({ - success: false, - error: { code: 404, message: '해당 버킷을 찾을 수 없습니다.' }, - }); - } - - // 소유자 체크 - if (bucket.userID !== userID) { - return res.status(403).json({ - success: false, - error: { code: 403, message: '버킷 소유자가 아닙니다.' }, - }); - } - - // 버킷 타입 체크 (ACHIEVEMENT이면 조회 불가) - if (bucket.type === 'ACHIEVEMENT') { - return res.status(400).json({ - success: false, - error: { - code: 400, - message: '이 버킷은 반복형(REPEAT) 버킷만 조회할 수 있습니다.', - }, - }); - } - - //모멘트 목록 조회 - const moments = await prisma.moment.findMany({ - where: { bucketID }, - orderBy: { createdAt: 'asc' }, // 정렬 기준 - }); - const totalMomentCount = moments.length; - - return res.status(200).json({ - success: true, - moments, - momentsCount: totalMomentCount, - }); - } catch (error) { - console.error('모멘트 목록 조회 실패:', error); - return res.status(500).json({ - success: false, - error: { code: 500, message: '서버 내부 오류가 발생했습니다.' }, - }); - } + try { + const userID = req.user.userID; + const { bucketID } = req.params; + + + // 버킷리스트 ENDDATE 확인(지났을 경우 FALSE 처리) + await prisma.bucket.updateMany({ + where: { + userID, + isChallenging: true, + endDate: { not: null, lt: now }, + }, + data: { + isChallenging: false, + }, + }); + //버킷 조회 + const bucket = await prisma.bucket.findUnique({ + where: { bucketID }, + }); + + if (!bucket) { + return res.status(404).json({ + success: false, + error: { code: 404, message: '해당 버킷을 찾을 수 없습니다.' }, + }); + } + + // 소유자 체크 + if (bucket.userID !== userID) { + return res.status(403).json({ + success: false, + error: { code: 403, message: '버킷 소유자가 아닙니다.' }, + }); + } + + // 버킷 타입 체크 (ACHIEVEMENT이면 조회 불가) + if (bucket.type === 'ACHIEVEMENT') { + return res.status(400).json({ + success: false, + error: { + code: 400, + message: '이 버킷은 반복형(REPEAT) 버킷만 조회할 수 있습니다.', + }, + }); + } + + //모멘트 목록 조회 + const moments = await prisma.moment.findMany({ + where: { bucketID }, + orderBy: { createdAt: 'asc' }, // 정렬 기준 + }); + const totalMomentCount = moments.length; + + return res.status(200).json({ + success: true, + moments, + momentsCount: totalMomentCount, + }); + } catch (error) { + console.error('모멘트 목록 조회 실패:', error); + return res.status(500).json({ + success: false, + error: { code: 500, message: '서버 내부 오류가 발생했습니다.' }, + }); + } }; // 모멘트 달성 (예외처리 완료) export const updateMoment = async (req, res) => { - try { - const userID = req.user.userID; - const { momentID } = req.params; - const koreaNow = getKoreaNow(); - - // 모멘트+버킷 조회 - const existingMoment = await prisma.moment.findUnique({ - where: { momentID }, - include: { bucket: true }, - }); - if (!existingMoment) { - return res.status(404).json({ - success: false, - error: { code: 404, message: '모멘트를 찾을 수 없습니다.' }, - }); - } - if (existingMoment.bucket.userID !== userID) { - return res.status(403).json({ - success: false, - error: { code: 403, message: '모멘트 수정 권한이 없습니다.' }, - }); - } - - // 버킷이 도전 중인지 확인 - if (!existingMoment.bucket.isChallenging) { - return res.status(400).json({ - success: false, - error: { code: 400, message: '이미 도전이 끝난 버킷입니다. 수정할 수 없습니다.' }, - }); - } - - // S3 이미지 업로드 처리 - let newPhotoUrl = null; - if (req.file) { - try { - const bucketName = process.env.AWS_S3_BUCKET_NAME; - const key = `moment/${userID}/${momentID}/${Date.now()}`; - - const command = new PutObjectCommand({ - Bucket: bucketName, - Key: key, - Body: req.file.buffer, - ContentType: req.file.mimetype, - }); - - await s3Client.send(command); - newPhotoUrl = `https://${bucketName}.s3.${process.env.AWS_REGION}.amazonaws.com/${key}`; - console.log('S3 업로드 성공:', newPhotoUrl); - } catch (uploadErr) { - console.error('S3 업로드 실패:', uploadErr); - return res.status(500).json({ - success: false, - error: { code: 500, message: 'S3 업로드에 실패했습니다.' }, - }); - } - } - - // 트랜잭션 - const result = await prisma.$transaction(async (tx) => { - // 1) 모멘트 업데이트 - const updatedMoment = await tx.moment.update({ - where: { momentID }, - data: { - photoUrl: newPhotoUrl, - isCompleted: true, - completedAt: koreaNow, - updatedAt: koreaNow, - }, - }); - - - // 2) 버킷 아래 모든 모멘트 완료 여부 확인 - const bucketID = existingMoment.bucket.bucketID; - - // 전체 모멘트 개수 - const totalMoments = await tx.moment.count({ - where: { bucketID }, - }); - - // 완료된 모멘트 개수 - const completedMoments = await tx.moment.count({ - where: { bucketID, isCompleted: true }, - }); - - let updatedBucket = null; - // 모든 모멘트가 완료되었으면 버킷도 완료 상태로 업데이트 - if (totalMoments > 0 && totalMoments === completedMoments) { - updatedBucket = await tx.bucket.update({ - where: { bucketID }, - data: { isCompleted: true, isChallenging: false, updatedAt: koreaNow }, - }); - } - - return { updatedMoment, updatedBucket, totalMoments, completedMoments }; - }); - - return res.status(200).json({ - success: true, - message: '모멘트를 업데이트했습니다.', - result, - }); - } catch (error) { - console.error('모멘트 수정 실패:', error); - return res.status(500).json({ - success: false, - error: { code: 500, message: '서버 내부 오류가 발생했습니다.' }, - }); - } + try { + const userID = req.user.userID; + const { momentID } = req.params; + const koreaNow = getKoreaNow(); + + // 모멘트+버킷 조회 + const existingMoment = await prisma.moment.findUnique({ + where: { momentID }, + include: { bucket: true }, + }); + if (!existingMoment) { + return res.status(404).json({ + success: false, + error: { code: 404, message: '모멘트를 찾을 수 없습니다.' }, + }); + } + if (existingMoment.bucket.userID !== userID) { + return res.status(403).json({ + success: false, + error: { code: 403, message: '모멘트 수정 권한이 없습니다.' }, + }); + } + + // 버킷이 도전 중인지 확인 + if (!existingMoment.bucket.isChallenging) { + return res.status(400).json({ + success: false, + error: { code: 400, message: '이미 도전이 끝난 버킷입니다. 수정할 수 없습니다.' }, + }); + } + + // S3 이미지 업로드 처리 + let newPhotoUrl = null; + if (req.file) { + try { + const bucketName = process.env.AWS_S3_BUCKET_NAME; + const key = `moment/${userID}/${momentID}/${Date.now()}`; + + const command = new PutObjectCommand({ + Bucket: bucketName, + Key: key, + Body: req.file.buffer, + ContentType: req.file.mimetype, + }); + + await s3Client.send(command); + newPhotoUrl = `https://${bucketName}.s3.${process.env.AWS_REGION}.amazonaws.com/${key}`; + console.log('S3 업로드 성공:', newPhotoUrl); + } catch (uploadErr) { + console.error('S3 업로드 실패:', uploadErr); + return res.status(500).json({ + success: false, + error: { code: 500, message: 'S3 업로드에 실패했습니다.' }, + }); + } + } + + // 트랜잭션 + const result = await prisma.$transaction(async (tx) => { + // 1) 모멘트 업데이트 + const updatedMoment = await tx.moment.update({ + where: { momentID }, + data: { + photoUrl: newPhotoUrl, + isCompleted: true, + completedAt: koreaNow, + updatedAt: koreaNow, + }, + }); + + + // 2) 버킷 아래 모든 모멘트 완료 여부 확인 + const bucketID = existingMoment.bucket.bucketID; + + // 전체 모멘트 개수 + const totalMoments = await tx.moment.count({ + where: { bucketID }, + }); + + // 완료된 모멘트 개수 + const completedMoments = await tx.moment.count({ + where: { bucketID, isCompleted: true }, + }); + + let updatedBucket = null; + // 모든 모멘트가 완료되었으면 버킷도 완료 상태로 업데이트 + if (totalMoments > 0 && totalMoments === completedMoments) { + updatedBucket = await tx.bucket.update({ + where: { bucketID }, + data: { isCompleted: true, isChallenging: false, updatedAt: koreaNow }, + }); + } + + return { updatedMoment, updatedBucket, totalMoments, completedMoments }; + }); + + return res.status(200).json({ + success: true, + message: '모멘트를 업데이트했습니다.', + result, + }); + } catch (error) { + console.error('모멘트 수정 실패:', error); + return res.status(500).json({ + success: false, + error: { code: 500, message: '서버 내부 오류가 발생했습니다.' }, + }); + } }; export const getDetailMoment = async (req, res) => { - try { - const userID = req.user.userID; // 인증된 사용자 ID - const { momentID } = req.params; // 요청된 momentID - - // 모멘트 조회 - const moment = await prisma.moment.findUnique({ - where: { momentID }, - include: { bucket: true }, // 관련된 버킷 정보를 포함 - }); - - // 모멘트가 존재하지 않을 경우 처리 - if (!moment) { - return res.status(404).json({ - success: false, - error: { code: 404, message: '모멘트를 찾을 수 없습니다.' }, - }); - } - - // 소유자 확인 - if (moment.userID !== userID) { - return res.status(403).json({ - success: false, - error: { code: 403, message: '모멘트 상세 정보를 확인할 권한이 없습니다.' }, - }); - } - - return res.status(200).json({ - success: true, - moment, - }); - } catch (err) { - console.error('모멘트 상세 조회 실패:', error); - return res.status(500).json({ - success: false, - error: { code: 500, message: '서버 내부 오류가 발생했습니다.' }, - }); - } + try { + const userID = req.user.userID; // 인증된 사용자 ID + const { momentID } = req.params; // 요청된 momentID + + // 모멘트 조회 + const moment = await prisma.moment.findUnique({ + where: { momentID }, + include: { bucket: true }, // 관련된 버킷 정보를 포함 + }); + + // 모멘트가 존재하지 않을 경우 처리 + if (!moment) { + return res.status(404).json({ + success: false, + error: { code: 404, message: '모멘트를 찾을 수 없습니다.' }, + }); + } + + // 소유자 확인 + if (moment.userID !== userID) { + return res.status(403).json({ + success: false, + error: { code: 403, message: '모멘트 상세 정보를 확인할 권한이 없습니다.' }, + }); + } + + return res.status(200).json({ + success: true, + moment, + }); + } catch (err) { + console.error('모멘트 상세 조회 실패:', error); + return res.status(500).json({ + success: false, + error: { code: 500, message: '서버 내부 오류가 발생했습니다.' }, + }); + } } export const getChallengingBucketsAndMoments = async (req, res) => { - try { - const userID = req.user.userID; - const koreaNow = getKoreaNow(); - // 0) 만료된 버킷 처리 (버킷 ID 찾기) - const expiredBuckets = await prisma.bucket.findMany({ - where: { - userID, - isChallenging: true, - endDate: { not: null, lt: koreaNow }, - }, - select: { bucketID: true }, - }); - - const expiredBucketIDs = expiredBuckets.map(b => b.bucketID); - - if (expiredBucketIDs.length > 0) { - // (A) 만료된 버킷들의 모든 모멘트 삭제 - await prisma.moment.deleteMany({ - where: { - bucketID: { in: expiredBucketIDs } - } - }); - - // (B) 만료된 버킷들의 isChallenging 값 false로 변경 - await prisma.bucket.updateMany({ - where: { - bucketID: { in: expiredBucketIDs } - }, - data: { - isChallenging: false, - updatedAt: koreaNow, - }, - }); - } - // 1) 도전 중인(isChallenging=true) 버킷들 조회 - // 해당 유저의 버킷만 - const buckets = await prisma.bucket.findMany({ - where: { - userID, - isChallenging: true, - }, - include: { - moments: true, // 모멘트 전부 가져옴, 추후 필터링 - }, - }); - - // 2) 응답용 데이터 가공 - const responseData = buckets.map((bucket) => { - // (a) 실시간 계산 - const allMoments = bucket.moments; - const momentCount = allMoments.length; - const completedMomentsCount = allMoments.filter((m) => m.isCompleted).length; - - // (b) 현재시간(now)이 startDate ≤ now ≤ endDate 인 모멘트만 필터 - const inRangeMoments = allMoments.filter((m) => { - // startDate, endDate 가 null인 경우 처리 (기획에 따라) - if (!m.startDate || !m.endDate) return false; - - const start = new Date(m.startDate); - const end = new Date(m.endDate); - - return start <= koreaNow && koreaNow <= end; - }); - - // (c) 응답용 모멘트 필드만 추출 - const filteredMoments = inRangeMoments.map((m) => ({ - photoUrl: m.photoUrl || '', - content: m.content, - momentID: m.momentID, - isCompleted: m.isCompleted, - bucketID: bucket.bucketID, // or m.bucketID, same - })); - - return { - bucketID: bucket.bucketID, - content: bucket.content, - momentCount, - completedMomentsCount, - moments: filteredMoments, - }; - }); - - return res.status(200).json({ - success: true, - count: responseData.length, - data: responseData, - }); - } catch (error) { - console.error('도전 중 버킷 및 모멘트 조회 실패:', error); - return res.status(500).json({ - success: false, - error: { code: 500, message: '서버 내부 오류가 발생했습니다.' }, - }); - } + try { + const userID = req.user.userID; + const koreaNow = getKoreaNow(); + // 0) 만료된 버킷 처리 (버킷 ID 찾기) + const expiredBuckets = await prisma.bucket.findMany({ + where: { + userID, + isChallenging: true, + endDate: { not: null, lt: koreaNow }, + }, + select: { bucketID: true }, + }); + + const expiredBucketIDs = expiredBuckets.map(b => b.bucketID); + + if (expiredBucketIDs.length > 0) { + // (A) 만료된 버킷들의 모든 모멘트 삭제 + await prisma.moment.deleteMany({ + where: { + bucketID: { in: expiredBucketIDs } + } + }); + + // (B) 만료된 버킷들의 isChallenging 값 false로 변경 + await prisma.bucket.updateMany({ + where: { + bucketID: { in: expiredBucketIDs } + }, + data: { + isChallenging: false, + updatedAt: koreaNow, + }, + }); + } + // 1) 도전 중인(isChallenging=true) 버킷들 조회 + // 해당 유저의 버킷만 + const buckets = await prisma.bucket.findMany({ + where: { + userID, + isChallenging: true, + }, + include: { + moments: true, // 모멘트 전부 가져옴, 추후 필터링 + }, + }); + + // 2) 응답용 데이터 가공 + const responseData = buckets.map((bucket) => { + // (a) 실시간 계산 + const allMoments = bucket.moments; + const momentCount = allMoments.length; + const completedMomentsCount = allMoments.filter((m) => m.isCompleted).length; + + // (b) 현재시간(now)이 startDate ≤ now ≤ endDate 인 모멘트만 필터 + const inRangeMoments = allMoments.filter((m) => { + // startDate, endDate 가 null인 경우 처리 (기획에 따라) + if (!m.startDate || !m.endDate) return false; + + const start = new Date(m.startDate); + const end = new Date(m.endDate); + + return start <= koreaNow && koreaNow <= end; + }); + + // (c) 응답용 모멘트 필드만 추출 + const filteredMoments = inRangeMoments.map((m) => ({ + photoUrl: m.photoUrl || '', + content: m.content, + momentID: m.momentID, + isCompleted: m.isCompleted, + bucketID: bucket.bucketID, // or m.bucketID, same + })); + + return { + bucketID: bucket.bucketID, + content: bucket.content, + momentCount, + completedMomentsCount, + moments: filteredMoments, + }; + }); + + return res.status(200).json({ + success: true, + count: responseData.length, + data: responseData, + }); + } catch (error) { + console.error('도전 중 버킷 및 모멘트 조회 실패:', error); + return res.status(500).json({ + success: false, + error: { code: 500, message: '서버 내부 오류가 발생했습니다.' }, + }); + } }; export const getChallengingBucketCount = async (req, res) => { - try { - const userID = req.user.userID; // JWT 인증 후, userID 획득 - - // 1) 현재 유저의 도전 중 버킷 갯수 - const challengingCount = await prisma.bucket.count({ - where: { - userID, - isChallenging: true, - }, - }); - - return res.status(200).json({ - success: true, - challengingCount, // 도전 중 버킷 갯수 - }); - } catch (error) { - console.error('도전 중 버킷 개수 조회 실패:', error); - return res.status(500).json({ - success: false, - error: { code: 500, message: '서버 내부 오류가 발생했습니다.' }, - }); - } + try { + const userID = req.user.userID; // JWT 인증 후, userID 획득 + + // 1) 현재 유저의 도전 중 버킷 갯수 + const challengingCount = await prisma.bucket.count({ + where: { + userID, + isChallenging: true, + }, + }); + + return res.status(200).json({ + success: true, + challengingCount, // 도전 중 버킷 갯수 + }); + } catch (error) { + console.error('도전 중 버킷 개수 조회 실패:', error); + return res.status(500).json({ + success: false, + error: { code: 500, message: '서버 내부 오류가 발생했습니다.' }, + }); + } }; \ No newline at end of file diff --git a/src/controllers/notificationControllers.js b/src/controllers/notificationControllers.js index 0d942ef..fc92305 100644 --- a/src/controllers/notificationControllers.js +++ b/src/controllers/notificationControllers.js @@ -11,15 +11,15 @@ export const getUnreadNotificationsCount = async (req, res) => { where: { userID }, }); - if (!currentUser) { - return res.status(404).json({ + if (!currentUser) { + return res.status(404).json({ success: false, error: { code: 404, message: '현재 사용자를 찾을 수 없습니다.' } }); } const unreadNotificationsCount = await prisma.notification.count({ - where: { userID, isRead: false} + where: { userID, isRead: false } }); return res.status(200).json({ @@ -30,7 +30,7 @@ export const getUnreadNotificationsCount = async (req, res) => { }) } catch (err) { console.error('읽지 않은 알림 개수 조회 실패:', err.message); - res.status(500).json({ + res.status(500).json({ success: false, error: { code: 500, message: '읽지 않은 알림 개수 조회에 실패하였습니다.' } }); @@ -47,13 +47,13 @@ export const getAndMarkNotificationsAsRead = async (req, res) => { where: { userID }, }); - if (!currentUser) { - return res.status(404).json({ + if (!currentUser) { + return res.status(404).json({ success: false, error: { code: 404, message: '현재 사용자를 찾을 수 없습니다.' } }); } - + // 사용자의 알림 조회 (최대 8개) const notifications = await prisma.notification.findMany({ where: { userID }, @@ -69,19 +69,19 @@ export const getAndMarkNotificationsAsRead = async (req, res) => { }); if (notifications.length > 0) { - // 조회한 알림 중 아직 읽지 않은 항목만 읽음 처리 - const unreadNotificationIds = notifications - .filter(notification => !notification.isRead) - .map(notification => notification.notificationID); - - if (unreadNotificationIds.length > 0) { - await prisma.notification.updateMany({ - where: { - notificationID: { in: unreadNotificationIds } - }, - data: { isRead: true } - }); - } + // 조회한 알림 중 아직 읽지 않은 항목만 읽음 처리 + const unreadNotificationIds = notifications + .filter(notification => !notification.isRead) + .map(notification => notification.notificationID); + + if (unreadNotificationIds.length > 0) { + await prisma.notification.updateMany({ + where: { + notificationID: { in: unreadNotificationIds } + }, + data: { isRead: true } + }); + } } return res.status(200).json({ @@ -95,7 +95,7 @@ export const getAndMarkNotificationsAsRead = async (req, res) => { }) } catch (err) { console.error('알림 조회 및 읽음 처리 실패:', err.message); - res.status(500).json({ + res.status(500).json({ success: false, error: { code: 500, message: '알림 조회 및 읽음 처리에 실패하였습니다.' } }); @@ -113,9 +113,9 @@ export const deleteNotification = async (req, res) => { where: { userID }, }); - if (!currentUser) { + if (!currentUser) { console.error("사용자 찾을 수 없음") - return res.status(404).json({ + return res.status(404).json({ success: false, error: { code: 404, message: '현재 사용자를 찾을 수 없습니다.' } }); @@ -126,9 +126,9 @@ export const deleteNotification = async (req, res) => { where: { notificationID, userID }, }); - if (!currentNotification) { + if (!currentNotification) { console.error("현재 알람 찾을 수 없음") - return res.status(404).json({ + return res.status(404).json({ success: false, error: { code: 404, message: '현재 알람을 찾을 수 없습니다.' } }); diff --git a/src/controllers/userControllers.js b/src/controllers/userControllers.js index e87f50c..8dd5b04 100644 --- a/src/controllers/userControllers.js +++ b/src/controllers/userControllers.js @@ -19,7 +19,7 @@ export const getCurrentUser = async (req, res) => { // 사용자 정보 반환 res.status(200).json({ message: '사용자 프로필 접근 성공', - user: currentUser + user: currentUser }); } catch (err) { console.error('getCurrentUser 오류:', err.message); @@ -38,11 +38,11 @@ export const getUserFriendCode = async (req, res) => { if (!currentUser) { // 현재 사용자가 없는 경우 return res.status(404).json({ message: '현재 사용자를 찾을 수 없습니다.' }); } - + // 사용자 정보 반환 res.status(200).json({ message: '사용자의 친구 코드 접근 성공', - friendCode: currentUser.friendCode + friendCode: currentUser.friendCode }); } catch (err) { console.error('getUserFriendCode 오류:', err.message); @@ -199,7 +199,7 @@ export const deleteUser = async (req, res) => { try { // 현재 요청한 사용자 조회 const userID = req.user.userID; - + // 사용자 삭제 (연관된 데이터 Cascade로 삭제) await prisma.$transaction(async (tx) => { @@ -208,12 +208,12 @@ export const deleteUser = async (req, res) => { }); // 1️⃣ 친구 관계 삭제 (user가 포함된 모든 Friend 관계 삭제) await tx.friend.deleteMany({ - where: { - OR: [ - { userID: userID }, - { friendUserID: userID } - ] - } + where: { + OR: [ + { userID: userID }, + { friendUserID: userID } + ] + } }); await tx.friendFeed.deleteMany({ where: { userID: userID } @@ -221,19 +221,19 @@ export const deleteUser = async (req, res) => { // 2️⃣ 모멘트 삭제 (해당 사용자가 생성한 모든 모멘트 삭제) await tx.moment.deleteMany({ - where: { userID: userID } + where: { userID: userID } }); // 3️⃣ 버킷리스트 삭제 (해당 사용자가 생성한 모든 버킷 삭제) await tx.bucket.deleteMany({ - where: { userID: userID } + where: { userID: userID } }); // 4️⃣ 사용자 삭제 await tx.user.delete({ - where: { userID: userID } + where: { userID: userID } }); - }); + }); res.status(200).json({ diff --git a/src/middlewares/jwtMiddlewares.js b/src/middlewares/jwtMiddlewares.js index 2c32669..3f856c2 100644 --- a/src/middlewares/jwtMiddlewares.js +++ b/src/middlewares/jwtMiddlewares.js @@ -1,6 +1,6 @@ import jwt from 'jsonwebtoken'; import { PrismaClient } from '@prisma/client'; -import { refreshAccessToken } from '../controllers/jwtControllers.js'; +import { refreshAccessToken } from '../service/jwtService.js'; const prisma = new PrismaClient(); diff --git a/src/routes/homeRoutes.js b/src/routes/homeRoutes.js index 75373ea..acb2ae7 100644 --- a/src/routes/homeRoutes.js +++ b/src/routes/homeRoutes.js @@ -9,7 +9,7 @@ const router = express.Router(); router.use(jwtMiddleware); router.get('', getHome); // 홈 당일 모먼트 조회 -router.get('/consecutiveDays',getConsecutiveCompletedDays); // 연속적으로 인증한 날짜 조회 (작심 N일일) +router.get('/consecutiveDays', getConsecutiveCompletedDays); // 연속적으로 인증한 날짜 조회 (작심 N일) router.get('/momentsComplete/week', getCompletedMomentsByWeek); // 일주일 인증 확인 router.patch('/notifications', getAndMarkNotificationsAsRead); // 알림 조회 및 읽음 처리 diff --git a/src/server.js b/src/server.js index 16bee58..6ca9213 100644 --- a/src/server.js +++ b/src/server.js @@ -3,10 +3,9 @@ import app from './app.js'; import { checkS3Connection } from './config/s3config.js'; const PORT = process.env.PORT || 3000; -const ENV = process.env.NODE_ENV || 'development'; const server = http.createServer(app); server.listen(PORT, async () => { - console.log(`Server running on http://localhost:${PORT}`); - await checkS3Connection(); // S3 연결 확인 + console.log(`Server running on http://localhost:${PORT}`); + await checkS3Connection(); // S3 연결 확인 }); \ No newline at end of file diff --git a/src/controllers/jwtControllers.js b/src/service/jwtService.js similarity index 98% rename from src/controllers/jwtControllers.js rename to src/service/jwtService.js index 006541c..68cc9df 100644 --- a/src/controllers/jwtControllers.js +++ b/src/service/jwtService.js @@ -28,7 +28,7 @@ export const refreshAccessToken = async (req) => { } try { - const decoded = jwt.verify(refreshToken, process.env.JWT_REFRESH_SECRET); + const decoded = jwt.verify(refreshToken, process.env.JWT_REFRESH_SECRET); const user = await prisma.user.findUnique({ where: { id: decoded.userId }, });