const argon2 = require('argon2'); const jwt = require('jsonwebtoken'); const AccountConfig = require('../../config/account'); const sequelize = require('../../config/database'); const User = require('../models/User'); const Profile = require('../models/Profile'); const Crew = require('../models/Crew'); const Event = require('../models/Event'); const UserCrew = require('../models/UserCrew'); const EventParticipants = require('../models/EventParticipants'); const {buildFullImageUrl, removeHostFromImageUrl} = require('./imageController'); const authTokenExpiryTime = '1h'; const listItemsPerPage = 10; async function hashPassword(password) { return await argon2.hash(password, { type: argon2.argon2id, }); } async function verifyPassword(hash, plainPassword) { return await argon2.verify(hash, plainPassword); } // JWT 생성 function generateToken(payload) { return jwt.sign( payload, AccountConfig.JWT_SECRET_KEY, { expiresIn: authTokenExpiryTime, }, ); } exports.signUp = async (req, res) => { const {name, email, password} = req.body; if (!name) { return res.status(400).json({error: '이름을 입력하세요.'}); } if (!email) { return res.status(400).json({error: '이메일을 입력하세요.'}); } if (!password) { return res.status(400).json({error: '비밀번호를 입력하세요.'}); } // Check if the user already exists const user = await User.findOne({ where: {email: email}, }); if (user) { return res.status(409).json({error: '이미 등록된 사용자입니다.'}); } const profile = req.body.profile || {}; if (!profile.regionID) { return res.status(400).json({error: '지역을 입력하세요.'}); } if (!profile.sportTypeID) { return res.status(400).json({error: '운동 종목을 입력하세요.'}); } try { const tx = await sequelize.transaction(); try { const user = await User.create({ name, email, password: await hashPassword(password), }, {transaction: tx}); const userProfile = await Profile.create({ profileImage: profile.profileImage, regionID: profile.regionID, job: profile.job || null, birthDate: profile.birthDate || null, experience: profile.experience || null, introduction: profile.introduction || null, sportTypeID: profile.sportTypeID, userID: user.userID, }, {transaction: tx}); await tx.commit(); // 생성된 사용자 정보 반환 res.status(201).json({ userID: user.userID, name: user.name, email: user.email, createdAt: user.createdAt, updatedAt: user.updatedAt, profile: { ...userProfile.toJSON(), profileImage: buildFullImageUrl(userProfile.profileImage), }, }); } catch (error) { await tx.rollback(); console.error('사용자 생성 중 오류:', error); res.status(500).json({error: '사용자 생성 중 오류가 발생했습니다.'}); } } catch (error) { console.error('사용자 생성 중 오류:', error); res.status(500).json({error: '사용자 생성 중 오류가 발생했습니다.'}); } } exports.verifyEmail = async (req, res) => { const {email} = req.body; if (!email) { return res.status(400).json({error: '이메일을 입력하세요.'}); } const user = await User.findOne({ where: {email: email}, }); if (user) { return res.status(409).json({ error: '이미 등록된 사용자입니다.' }); } res.status(200).json({ message: '사용 가능한 이메일입니다.' }); } exports.login = async (req, res) => { try { const {email, password} = req.body; if (!email) { return res.status(400).json({error: '이메일을 입력하세요.'}); } if (!password) { return res.status(400).json({error: '비밀번호를 입력하세요.'}); } // 이메일로 사용자 조회 const user = await User.findOne({ where: {email: email}, }); // 사용자가 없을 경우 if (!user) { return res.status(404).json({error: '사용자를 찾을 수 없습니다.'}); } // 비밀번호 검증 const isPasswordMatch = await verifyPassword(user.password, password); // 비밀번호가 일치하지 않을 경우 if (!isPasswordMatch) { return res.status(401).json({error: '비밀번호가 일치하지 않습니다.'}); } // JWT 토큰 생성 const token = await generateToken({ userID: user.userID, email: user.email, }); // 토큰 반환 res.status(200).json({ "auth_token": token, }); } catch (error) { console.error('로그인 중 오류:', error); res.status(500).json({error: '로그인 중 오류가 발생했습니다.'}); } } exports.getUserById = async (req, res) => { try { const userID = req.params.id; const user = await User.findByPk(userID, { include: [{model: Profile}], attributes: ['userID', 'name', 'email', 'createdAt', 'updatedAt'], }); if (!user) { return res.status(404).json({error: '사용자를 찾을 수 없습니다.'}); } res.status(200).json({ userID: user.userID, name: user.name, email: user.email, createdAt: user.createdAt, updatedAt: user.updatedAt, profile: { ...user.Profile.toJSON(), profileImage: buildFullImageUrl(user.Profile.profileImage), }, }); } catch (error) { console.error('사용자 조회 중 오류:', error); res.status(500).json({error: '사용자 조회 중 오류가 발생했습니다.'}); } } exports.updateUser = async (req, res) => { // TODO: verify the user by auth token with middleware. #14 const userID = req.user.userID; if (parseInt(req.params.id) !== parseInt(userID)) { return res.status(403).json({ error: '사용자 정보를 업데이트할 권한이 없습니다.' }); } const tx = await sequelize.transaction(); const {password, profile} = req.body; try { const user = await User.findByPk(userID, {transaction: tx}); if (!user) { await tx.rollback(); return res.status(404).json({error: '사용자를 찾을 수 없습니다.'}); } if (password) user.password = await hashPassword(password); await user.save({transaction: tx}); let userProfile; if (profile) { userProfile = await user.getProfile({transaction: tx}); if (userProfile) { // TODO: Save the image to storage and save the URL to the database if (profile.profileImage) userProfile.profileImage = removeHostFromImageUrl(profile.profileImage); if (profile.regionID) userProfile.regionID = profile.regionID; if (profile.job) userProfile.job = profile.job; if (profile.birthDate) userProfile.birthDate = profile.birthDate; if (profile.experience) userProfile.experience = profile.experience; if (profile.introduction) userProfile.introduction = profile.introduction; if (profile.sportTypeID) userProfile.sportTypeID = profile.sportTypeID; await userProfile.save({transaction: tx}); } } await tx.commit(); res.status(200).json({ userID: user.userID, name: user.name, email: user.email, createdAt: user.createdAt, updatedAt: user.updatedAt, profile: { ...userProfile.toJSON(), profileImage: buildFullImageUrl(userProfile.profileImage), }, }); } catch (error) { await tx.rollback(); console.error('사용자 업데이트 중 오류:', error); res.status(500).json({error: '사용자 업데이트 중 오류가 발생했습니다.'}); } } exports.deleteUser = async (req, res) => { const userID = req.user.userID; if (parseInt(req.params.id) !== parseInt(userID)) { return res.status(403).json({ error: '사용자 정보를 삭제할 권한이 없습니다.' }); } const tx = await sequelize.transaction(); try { const user = await User.findByPk(userID, {transaction: tx}); if (!user) { await tx.rollback(); return res.status(404).json({error: '사용자를 찾을 수 없습니다.'}); } const userProfile = await user.getProfile({transaction: tx}); if (userProfile) { await userProfile.destroy({transaction: tx}); } await user.destroy({transaction: tx}); await tx.commit(); res.status(204).json({message: '사용자가 삭제되었습니다.'}); } catch (error) { await tx.rollback(); console.error('사용자 삭제 중 오류:', error); res.status(500).json({error: '사용자 삭제 중 오류가 발생했습니다.'}); } } exports.listUserCrews = async (req, res) => { const userID = req.user.userID; const page = req.query.page || 0; try { const user = await User.findByPk(userID); if (!user) { return res.status(404).json({error: '사용자를 찾을 수 없습니다.'}); } const crews = await UserCrew.findAll({ where: { userID: userID }, include: [ { model: Crew, attributes: [ 'crewID', 'name', 'createdDate', 'regionID', 'sportTypeId', 'capacity', 'fee_krw', 'description', ], include: [ { model: UserCrew, attributes: [], }, ], }, ], attributes: { include: [ [ sequelize.literal(`( SELECT COUNT(*) FROM UserCrew AS uc WHERE uc.crewID = UserCrew.crewID )`), 'memberCount', ], ], }, limit: listItemsPerPage, offset: parseInt(page, 10) * listItemsPerPage, order: [[sequelize.literal('crewID'), 'DESC']], }); // NOTE: cache it. const joinedCrewCount = await UserCrew.count({ where: {userID: userID}, }); res.status(200).json({ 'crews': crews, 'total': joinedCrewCount, 'per_page': listItemsPerPage, }); } catch (error) { console.error('사용자 크루 조회 중 오류:', error); res.status(500).json({error: '사용자 크루 조회 중 오류가 발생했습니다.'}); } } exports.listUserEvents = async (req, res) => { const userID = req.user.userID; const page = req.query.page || 0; console.log(userID); try { const user = await User.findByPk(userID); if (!user) { return res.status(404).json({error: '사용자를 찾을 수 없습니다.'}); } const events = await EventParticipants.findAll({ where: { userID: userID }, include: [ { model: Event, attributes: [ 'eventID', 'name', 'eventDate', 'regionID', 'sportTypeId', 'capacity', 'feeCondition', 'createdDate', ], include: [ { model: EventParticipants, attributes: [], }, ], }, ], attributes: { include: [ [ sequelize.literal(`( SELECT COUNT(*) FROM EventParticipants AS ep WHERE ep.eventID = EventParticipants.eventID )`), 'participantCount', ], ], }, limit: listItemsPerPage, offset: parseInt(page, 10) * listItemsPerPage, order: [[sequelize.literal('eventID'), 'DESC']], }); const joinedEventCount = await EventParticipants.count({ where: {userID: userID}, }); res.status(200).json({ 'events': events, 'total': joinedEventCount, 'per_page': listItemsPerPage, }); } catch (error) { console.error('사용자 이벤트 조회 중 오류:', error); res.status(500).json({error: '사용자 이벤트 조회 중 오류가 발생했습니다.'}); } }