Skip to content
Snippets Groups Projects
Commit 3019d3cb authored by 세현 임's avatar 세현 임
Browse files

[#] 미팅방 서비스 관련 검증 로직 보강 및 트랜잭션 적용

parents 7ea23de0 b3bea64d
No related branches found
No related tags found
2 merge requests!31Develop,!19미팅방 서비스 관련 검증 로직 보강 및 트랜잭션 적용
Showing
with 11599 additions and 6694 deletions
...@@ -4,7 +4,7 @@ require('dotenv').config(); ...@@ -4,7 +4,7 @@ require('dotenv').config();
const express = require('express'); const express = require('express');
const session = require('express-session'); const session = require('express-session');
const passport = require('./passport'); // 변경된 경로 const passport = require('./passport');
const flash = require('connect-flash'); const flash = require('connect-flash');
const { initScheduleCleaner } = require('./utils/scheduler'); const { initScheduleCleaner } = require('./utils/scheduler');
const connectMongoDB = require('./config/mongoose'); // MongoDB 연결 const connectMongoDB = require('./config/mongoose'); // MongoDB 연결
...@@ -16,10 +16,10 @@ const app = express(); ...@@ -16,10 +16,10 @@ const app = express();
// CORS 설정 // CORS 설정
app.use( app.use(
cors({ cors({
origin: 'http://localhost:3000', // 허용할 도메인 설정 (예: 프론트엔드 주소) origin: 'http://localhost:3000',
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'], methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
allowedHeaders: ['Content-Type', 'Authorization'], allowedHeaders: ['Content-Type', 'Authorization'],
credentials: true, // 쿠키와 인증 정보를 허용하려면 true로 설정 credentials: true,
}) })
); );
...@@ -41,12 +41,10 @@ app.use( ...@@ -41,12 +41,10 @@ app.use(
app.use(passport.initialize()); app.use(passport.initialize());
app.use(passport.session()); app.use(passport.session());
// 플래시 메시지 (선택 사항)
app.use(flash()); app.use(flash());
/** //라우터 등록
* 라우터 등록
*/
const authRoutes = require('./routes/auth'); const authRoutes = require('./routes/auth');
app.use('/auth', authRoutes); app.use('/auth', authRoutes);
...@@ -59,7 +57,7 @@ app.use('/api/friend', friendRoutes); ...@@ -59,7 +57,7 @@ app.use('/api/friend', friendRoutes);
const meetingRoutes = require('./routes/meetingRoute'); const meetingRoutes = require('./routes/meetingRoute');
app.use('/api/meeting', meetingRoutes); app.use('/api/meeting', meetingRoutes);
const chatRoutes = require('./routes/chatRoute'); //const chatRoutes = require('./routes/chatRoute');
app.use('/api/chat', chatRoutes); app.use('/api/chat', chatRoutes);
// 스케줄 클리너 초기화 // 스케줄 클리너 초기화
...@@ -72,11 +70,11 @@ const PORT = process.env.PORT || 3000; ...@@ -72,11 +70,11 @@ const PORT = process.env.PORT || 3000;
try { try {
// MongoDB 연결 // MongoDB 연결
await connectMongoDB(); await connectMongoDB();
console.log('✅ MongoDB 연결 성공'); //console.log('✅ MongoDB 연결 성공');
// MySQL 연결 확인 // MySQL 연결 확인
await sequelize.authenticate(); await sequelize.authenticate();
console.log('✅ MySQL 연결 성공'); //console.log('✅ MySQL 연결 성공');
// 서버 시작 // 서버 시작
app.listen(PORT, () => { app.listen(PORT, () => {
......
// config/sequelize.js // src/config/sequelize.js
const { Sequelize } = require('sequelize'); const { Sequelize } = require('sequelize');
require('dotenv').config();
const sequelize = new Sequelize(process.env.DB_NAME, process.env.DB_USER, process.env.DB_PASSWORD, { const isTest = process.env.NODE_ENV === 'test';
const sequelize = isTest
? new Sequelize('sqlite::memory:', { logging: false }) // 테스트 환경용 인메모리 DB
: new Sequelize(process.env.DB_NAME, process.env.DB_USER, process.env.DB_PASSWORD, {
host: process.env.DB_HOST, host: process.env.DB_HOST,
dialect: 'mysql', // 사용하려는 DBMS에 맞게 변경 dialect: 'mysql',
logging: false, logging: false,
define: {
//timestamps: true, // createdAt, updatedAt 자동 생성
underscored: true, // created_at 형식의 필드명 사용
},
}); });
module.exports = sequelize; module.exports = sequelize;
\ No newline at end of file
// controllers/meetingController.js
const MeetingService = require('../services/meetingService'); const MeetingService = require('../services/meetingService');
const CreateMeetingRequestDTO = require('../dtos/CreateMeetingRequestDTO');
class MeetingController { class MeetingController {
/**
* 번개 모임 생성
* POST /api/meetings
*/
async createMeeting(req, res) { async createMeeting(req, res) {
try { try {
const result = await MeetingService.createMeeting(req.body); const userId = req.userId; // 인증 미들웨어를 통해 설정된 사용자 ID
const meetingData = { ...req.body, created_by: userId };
// CreateMeetingRequestDTO를 사용하여 요청 데이터 검증
const createMeetingDTO = new CreateMeetingRequestDTO(meetingData);
createMeetingDTO.validate();
const result = await MeetingService.createMeeting(meetingData);
res.status(201).json(result); res.status(201).json(result);
} catch (err) { } catch (err) {
console.error('번개 모임 생성 오류:', err); console.error('번개 모임 생성 오류:', err);
...@@ -11,14 +25,14 @@ class MeetingController { ...@@ -11,14 +25,14 @@ class MeetingController {
} }
} }
/**
* 번개 모임 목록 조회
* GET /api/meetings
*/
async getMeetings(req, res) { async getMeetings(req, res) {
const { userId } = req.query;
if (!userId) {
return res.status(400).json({ error: '사용자 ID가 필요합니다.' });
}
try { try {
const userId = req.userId; // 인증 미들웨어를 통해 설정된 사용자 ID
const meetings = await MeetingService.getMeetings(userId); const meetings = await MeetingService.getMeetings(userId);
res.status(200).json(meetings); res.status(200).json(meetings);
} catch (err) { } catch (err) {
...@@ -27,6 +41,10 @@ class MeetingController { ...@@ -27,6 +41,10 @@ class MeetingController {
} }
} }
/**
* 번개 모임 마감
* PATCH /api/meetings/:meetingId/close
*/
async closeMeeting(req, res) { async closeMeeting(req, res) {
const { meetingId } = req.params; const { meetingId } = req.params;
...@@ -39,12 +57,16 @@ class MeetingController { ...@@ -39,12 +57,16 @@ class MeetingController {
} }
} }
/**
* 번개 모임 참가
* POST /api/meetings/:meetingId/join
*/
async joinMeeting(req, res) { async joinMeeting(req, res) {
try {
const { meetingId } = req.params; const { meetingId } = req.params;
const { user_id } = req.body; const userId = req.userId; // 인증 미들웨어를 통해 설정된 사용자 ID
try { await MeetingService.joinMeeting(meetingId, userId);
await MeetingService.joinMeeting(meetingId, user_id);
res.status(200).json({ message: '모임 및 채팅방 참가 완료' }); res.status(200).json({ message: '모임 및 채팅방 참가 완료' });
} catch (err) { } catch (err) {
console.error('모임 참가 오류:', err); console.error('모임 참가 오류:', err);
...@@ -52,6 +74,10 @@ class MeetingController { ...@@ -52,6 +74,10 @@ class MeetingController {
} }
} }
/**
* 번개 모임 상세 조회
* GET /api/meetings/:meetingId
*/
async getMeetingDetail(req, res) { async getMeetingDetail(req, res) {
const { meetingId } = req.params; const { meetingId } = req.params;
......
// controllers/scheduleController.js
const ScheduleService = require('../services/scheduleService'); const ScheduleService = require('../services/scheduleService');
const ScheduleRequestDTO = require('../dtos/ScheduleRequestDTO');
class scheduleController { class scheduleController {
/** /**
* 스케줄 생성 * 스케줄 생성
* POST /api/schedule * POST /api/schedule
* 해당 사용자 id는 auth 미들웨어에서 설정된 사용자 정보 이용
* req.user = User 모델의 인스턴스
*/ */
async createSchedule(req, res) { async createSchedule(req, res) {
try { try {
const userId = req.user.id; const userId = req.user.id;
const { title, start_time, end_time, is_fixed } = req.body; const scheduleRequestDTO = new ScheduleRequestDTO(req.body);
const validatedData = scheduleRequestDTO.validate('create');
const schedule = await ScheduleService.createSchedule({ const scheduleDTO = await ScheduleService.createSchedule({
userId, userId,
title, ...validatedData
start_time,
end_time,
is_fixed
}); });
return res.status(201).json({ return res.status(201).json({
success: true, success: true,
data: { data: scheduleDTO
schedule
}
}); });
} catch (error) { } catch (error) {
return res.status(400).json({ return res.status(400).json({
success: false, success: false,
error: { error: {
message: error.message, message: error.message.includes('Validation error') ? error.message : 'SCHEDULE_CREATE_ERROR',
code: 'SCHEDULE_CREATE_ERROR' code: error.message.includes('Validation error') ? 'VALIDATION_ERROR' : 'SCHEDULE_CREATE_ERROR'
} }
}); });
} }
...@@ -44,19 +40,15 @@ class scheduleController { ...@@ -44,19 +40,15 @@ class scheduleController {
async updateSchedule(req, res) { async updateSchedule(req, res) {
try { try {
const { id } = req.params; const { id } = req.params;
const { title, start_time, end_time } = req.body;
const userId = req.user.id; const userId = req.user.id;
const schedule = await ScheduleService.updateSchedule(id, userId, const scheduleRequestDTO = new ScheduleRequestDTO(req.body);
{ const validatedData = scheduleRequestDTO.validate('update');
title,
start_time, const scheduleDTO = await ScheduleService.updateSchedule(id, userId, validatedData);
end_time
});
return res.status(200).json({ return res.status(200).json({
success: true, success: true,
data: schedule data: scheduleDTO
}); });
} catch (error) { } catch (error) {
if (error.message === 'Schedule not found') { if (error.message === 'Schedule not found') {
...@@ -67,6 +59,14 @@ class scheduleController { ...@@ -67,6 +59,14 @@ class scheduleController {
code: 'SCHEDULE_NOT_FOUND' code: 'SCHEDULE_NOT_FOUND'
} }
}); });
} else if (error.message.includes('Validation error')) {
return res.status(400).json({
success: false,
error: {
message: error.message,
code: 'VALIDATION_ERROR'
}
});
} }
return res.status(400).json({ return res.status(400).json({
success: false, success: false,
...@@ -87,15 +87,14 @@ class scheduleController { ...@@ -87,15 +87,14 @@ class scheduleController {
const { id } = req.params; const { id } = req.params;
const userId = req.user.id; const userId = req.user.id;
await ScheduleService.deleteSchedule(id, userId); const deleteResult = await ScheduleService.deleteSchedule(id, userId);
return res.status(200).json({ return res.status(200).json({
success: true, success: true,
data: { data: deleteResult
message: 'Schedule successfully deleted'
}
}); });
} catch (error) { } catch (error) {
if (error.message === 'Schedule not found') {
return res.status(404).json({ return res.status(404).json({
success: false, success: false,
error: { error: {
...@@ -104,6 +103,14 @@ class scheduleController { ...@@ -104,6 +103,14 @@ class scheduleController {
} }
}); });
} }
return res.status(500).json({
success: false,
error: {
message: 'Failed to delete schedule',
code: 'DELETE_ERROR'
}
});
}
} }
/** /**
...@@ -113,11 +120,11 @@ class scheduleController { ...@@ -113,11 +120,11 @@ class scheduleController {
async getAllSchedules(req, res) { async getAllSchedules(req, res) {
try { try {
const userId = req.user.id; const userId = req.user.id;
const schedules = await ScheduleService.getAllSchedules(userId); const schedulesDTO = await ScheduleService.getAllSchedules(userId);
return res.status(200).json({ return res.status(200).json({
success: true, success: true,
data: schedules data: schedulesDTO
}); });
} catch (error) { } catch (error) {
return res.status(500).json({ return res.status(500).json({
...@@ -138,11 +145,11 @@ class scheduleController { ...@@ -138,11 +145,11 @@ class scheduleController {
try { try {
const { id } = req.params; const { id } = req.params;
const userId = req.user.id; const userId = req.user.id;
const schedule = await ScheduleService.getScheduleById(id, userId); const scheduleDTO = await ScheduleService.getScheduleById(id, userId);
return res.status(200).json({ return res.status(200).json({
success: true, success: true,
data: schedule data: scheduleDTO
}); });
} catch (error) { } catch (error) {
if (error.message === 'Schedule not found') { if (error.message === 'Schedule not found') {
......
// dtos/CreateMeetingRequestDTO.js
const Joi = require('joi');
class CreateMeetingRequestDTO {
constructor({ title, description, start_time, end_time, location, deadline, type, created_by }) {
this.title = title;
this.description = description;
this.start_time = start_time;
this.end_time = end_time;
this.location = location;
this.deadline = deadline;
this.type = type;
this.created_by = created_by;
}
validate() {
const schema = Joi.object({
title: Joi.string().min(1).max(255).required(),
description: Joi.string().allow('', null).optional(),
start_time: Joi.date().iso().required(),
end_time: Joi.date().iso().greater(Joi.ref('start_time')).required(),
location: Joi.string().allow('', null).optional(),
deadline: Joi.date().iso().greater(Joi.ref('start_time')).optional(),
type: Joi.string().valid('OPEN', 'CLOSE').required(),
created_by: Joi.number().integer().positive().required()
});
const { error } = schema.validate(this, { abortEarly: false });
if (error) {
const errorMessages = error.details.map(detail => detail.message).join(', ');
throw new Error(`Validation error: ${errorMessages}`);
}
return true;
}
}
module.exports = CreateMeetingRequestDTO;
// dto/FriendListDTO.js
class FriendListDTO {
/**
* @param {object} friend - Friend relationship object retrieved from the database.
* @param {number} userId - The ID of the user whose friend list is being retrieved.
*/
constructor(friend, userId) {
this.id = friend.id;
this.status = friend.status;
this.createdAt = friend.createdAt;
this.updatedAt = friend.updatedAt;
this.friendInfo = friend.requester_id === userId ? {
id: friend.receiver.id,
name: friend.receiver.name,
email: friend.receiver.email
} : {
id: friend.requester.id,
name: friend.requester.name,
email: friend.requester.email
};
this.relationshipType = friend.requester_id === userId ? 'sent' : 'received';
}
}
module.exports = FriendListDTO;
// dto/FriendRequestDTO.js
class FriendRequestDTO {
/**
* @param {object} friendRequest - Friend request object retrieved from the database.
*/
constructor(friendRequest) {
this.id = friendRequest.id;
this.requester = {
id: friendRequest.requester.id,
name: friendRequest.requester.name,
email: friendRequest.requester.email
};
this.receiver = {
id: friendRequest.receiver.id,
name: friendRequest.receiver.name,
email: friendRequest.receiver.email
};
this.status = friendRequest.status;
this.createdAt = friendRequest.createdAt;
this.updatedAt = friendRequest.updatedAt;
}
}
module.exports = FriendRequestDTO;
class MeetingDetailResponse {
constructor(meeting) {
this.id = meeting.id;
this.title = meeting.title;
this.description = meeting.description;
this.startTime = meeting.start_time;
this.endTime = meeting.end_time;
this.location = meeting.location;
this.deadline = meeting.deadline;
this.type = meeting.type;
this.creatorName = meeting.creator ? meeting.creator.name : 'Unknown';
this.participants = meeting.participants.map(participant => ({
userId: participant.user_id,
name: participant.participantUser ? participant.participantUser.name : 'Unknown',
email: participant.participantUser ? participant.participantUser.email : 'Unknown'
}));
}
}
module.exports = MeetingDetailResponse;
\ No newline at end of file
// dtos/MeetingDetailResponseDTO.js
class MeetingDetailResponseDTO {
constructor(meeting) {
this.id = meeting.id;
this.title = meeting.title;
this.description = meeting.description;
this.startTime = meeting.start_time;
this.endTime = meeting.end_time;
this.location = meeting.location;
this.deadline = meeting.deadline;
this.type = meeting.type;
this.creatorName = meeting.creator ? meeting.creator.name : 'Unknown';
this.participants = meeting.participants.map(participant => ({
userId: participant.user_id,
name: participant.participantUser ? participant.participantUser.name : 'Unknown',
email: participant.participantUser ? participant.participantUser.email : 'Unknown'
}));
}
}
module.exports = MeetingDetailResponseDTO;
// dtos/MeetingResponse.js
class MeetingResponse {
constructor(meeting, isParticipant, isScheduleConflict, creatorName) {
this.id = meeting.id;
this.title = meeting.title;
this.description = meeting.description;
this.startTime = meeting.start_time;
this.endTime = meeting.end_time;
this.location = meeting.location;
this.deadline = meeting.deadline;
this.type = meeting.type;
this.creatorName = creatorName;
this.isParticipant = isParticipant;
this.isScheduleConflict = isScheduleConflict;
}
}
module.exports = MeetingResponse;
\ No newline at end of file
// dtos/MeetingResponseDTO.js
class MeetingResponseDTO {
constructor(meeting, isParticipant, isScheduleConflict, creatorName) {
this.id = meeting.id;
this.title = meeting.title;
this.description = meeting.description;
this.startTime = meeting.start_time;
this.endTime = meeting.end_time;
this.location = meeting.location;
this.deadline = meeting.deadline;
this.type = meeting.type;
this.creatorName = creatorName;
this.isParticipant = isParticipant;
this.isScheduleConflict = isScheduleConflict;
}
}
module.exports = MeetingResponseDTO;
// dtos/ScheduleRequestDTO.js
const Joi = require('joi');
class ScheduleRequestDTO {
constructor(data) {
this.data = data;
}
validate(type = 'create') {
// 기본 스키마 정의
let schema = Joi.object({
title: Joi.string().min(1).max(255).required(),
start_time: Joi.date().iso().required(),
end_time: Joi.date().iso().required(),
is_fixed: Joi.boolean().required()
});
// 'update' 타입의 경우 모든 필드를 필수로 하지 않을 수 있음
if (type === 'update') {
schema = Joi.object({
title: Joi.string().min(1).max(255).optional(),
start_time: Joi.date().iso().optional(),
end_time: Joi.date().iso().optional(),
is_fixed: Joi.boolean().optional()
}).or('title', 'start_time', 'end_time', 'is_fixed'); // 최소 한 개 이상의 필드가 필요
}
const { error, value } = schema.validate(this.data, { abortEarly: false });
if (error) {
// 모든 에러 메시지를 하나의 문자열로 결합
const errorMessages = error.details.map(detail => detail.message).join(', ');
throw new Error(`Validation error: ${errorMessages}`);
}
// 검증된 데이터를 반환
return value;
}
}
module.exports = ScheduleRequestDTO;
// dtos/ScheduleResponseDTO.js
class ScheduleResponseDTO {
constructor(schedule) {
this.id = schedule.id;
this.user_id = schedule.user_id;
this.title = schedule.title;
this.start_time = schedule.start_time;
this.end_time = schedule.end_time;
this.is_fixed = schedule.is_fixed;
this.expiry_date = schedule.expiry_date;
this.createdAt = schedule.createdAt;
this.updatedAt = schedule.updatedAt;
}
}
module.exports = ScheduleResponseDTO;
...@@ -8,11 +8,12 @@ const Friend = sequelize.define('Friend', { ...@@ -8,11 +8,12 @@ const Friend = sequelize.define('Friend', {
status: { status: {
type: DataTypes.ENUM('PENDING', 'ACCEPTED'), type: DataTypes.ENUM('PENDING', 'ACCEPTED'),
allowNull: false, allowNull: false,
defaultValue: 'PENDING' defaultValue: 'PENDING',
} }
}, { }, {
tableName: 'Friends', tableName: 'Friends',
timestamps: true, timestamps: true,
underscored: true,
indexes: [ indexes: [
{ {
unique: true, unique: true,
......
// models/Meeting.js // models/Meeting.js
const { DataTypes } = require('sequelize'); const { DataTypes } = require('sequelize');
const { sequelize } = require('../config/sequelize'); const sequelize = require('../config/sequelize');
const User = require('./User'); const User = require('./User');
const Meeting = sequelize.define('Meeting', { const Meeting = sequelize.define('Meeting', {
id: {
type: DataTypes.BIGINT,
primaryKey: true,
autoIncrement: true,
},
title: { title: {
type: DataTypes.STRING, type: DataTypes.STRING,
allowNull: false, allowNull: false,
...@@ -36,12 +31,8 @@ const Meeting = sequelize.define('Meeting', { ...@@ -36,12 +31,8 @@ const Meeting = sequelize.define('Meeting', {
allowNull: false, allowNull: false,
}, },
created_by: { created_by: {
type: DataTypes.BIGINT, type: DataTypes.INTEGER,
allowNull: false, allowNull: false,
references: {
model: 'Users',
key: 'id',
},
}, },
chatRoomId: { // 새로운 필드 추가 chatRoomId: { // 새로운 필드 추가
type: DataTypes.STRING, type: DataTypes.STRING,
...@@ -54,15 +45,8 @@ const Meeting = sequelize.define('Meeting', { ...@@ -54,15 +45,8 @@ const Meeting = sequelize.define('Meeting', {
// 연관 관계 설정 // 연관 관계 설정
Meeting.associate = (models) => { Meeting.belongsTo(User, { foreignKey: 'created_by', as: 'creator' });
Meeting.belongsTo(models.User, { User.hasMany(Meeting, { foreignKey: 'created_by', as: 'meetings' });
foreignKey: 'created_by', // FK 설정
as: 'creator', // 별칭
});
Meeting.hasMany(models.MeetingParticipant, {
foreignKey: 'meeting_id',
as: 'participants',
});
};
module.exports = Meeting; module.exports = Meeting;
// models/MeetingParticipant.js // models/MeetingParticipant.js
const { DataTypes } = require('sequelize'); const { DataTypes } = require('sequelize');
const { sequelize } = require('../config/sequelize'); const sequelize = require('../config/sequelize');
const Meeting =require('./Meeting');
const User = require('./User');
const MeetingParticipant = sequelize.define('MeetingParticipant', { const MeetingParticipant = sequelize.define('MeetingParticipant', {
meeting_id: { meeting_id: {
type: DataTypes.BIGINT, type: DataTypes.INTEGER,
allowNull: false allowNull: false
}, },
user_id: { user_id: {
type: DataTypes.BIGINT, type: DataTypes.INTEGER,
allowNull: false allowNull: false
} }
}, { }, {
...@@ -17,16 +20,11 @@ const MeetingParticipant = sequelize.define('MeetingParticipant', { ...@@ -17,16 +20,11 @@ const MeetingParticipant = sequelize.define('MeetingParticipant', {
timestamps: false, timestamps: false,
}); });
MeetingParticipant.associate = (models) => { MeetingParticipant.belongsTo(Meeting, { foreignKey: 'meeting_id', as: 'meeting' });
MeetingParticipant.belongsTo(models.Meeting, { Meeting.hasMany(MeetingParticipant, { foreignKey: 'meeting_id', as: 'participants' });
foreignKey: 'meeting_id',
as: 'meeting' MeetingParticipant.belongsTo(User, { foreignKey: 'user_id', as: 'user' });
}); User.hasMany(MeetingParticipant, { foreignKey: 'user_id', as: 'meetingParticipations' });
MeetingParticipant.belongsTo(models.User, {
foreignKey: 'user_id',
as: 'participantUser'
});
};
module.exports = MeetingParticipant; module.exports = MeetingParticipant;
// models/User.js // models/User.js
const { DataTypes } = require('sequelize'); const { DataTypes } = require('sequelize');
const { sequelize } = require('../config/sequelize'); const sequelize = require('../config/sequelize');
const User = sequelize.define('User', { const User = sequelize.define('User', {
id: {
type: DataTypes.BIGINT, // 수정: id 필드를 BIGINT로 설정
autoIncrement: true,
primaryKey: true,
},
name: { name: {
type: DataTypes.STRING, // VARCHAR type: DataTypes.STRING, // VARCHAR
allowNull: false, allowNull: false,
...@@ -26,17 +21,4 @@ const User = sequelize.define('User', { ...@@ -26,17 +21,4 @@ const User = sequelize.define('User', {
timestamps: true, // createdAt과 updatedAt 자동 관리 timestamps: true, // createdAt과 updatedAt 자동 관리
}); });
User.associate = (models) => {
User.hasMany(models.Meeting, {
foreignKey: 'created_by',
as: 'createdMeetings',
});
User.hasMany(models.MeetingParticipant, {
foreignKey: 'user_id',
as: 'userMeetingParticipations',
});
};
module.exports = User; module.exports = User;
This diff is collapsed.
...@@ -3,8 +3,7 @@ ...@@ -3,8 +3,7 @@
"version": "1.0.0", "version": "1.0.0",
"main": "app.js", "main": "app.js",
"scripts": { "scripts": {
"start": "nodemon app", "test": "cross-env NODE_ENV=test jest"
"test": "echo \"Error: no test specified\" && exit 1"
}, },
"repository": { "repository": {
"type": "git", "type": "git",
...@@ -19,6 +18,8 @@ ...@@ -19,6 +18,8 @@
"dotenv": "^16.4.5", "dotenv": "^16.4.5",
"express": "^4.21.1", "express": "^4.21.1",
"express-session": "^1.18.1", "express-session": "^1.18.1",
"jest": "^29.7.0",
"joi": "^17.13.3",
"mongoose": "^8.8.1", "mongoose": "^8.8.1",
"morgan": "^1.10.0", "morgan": "^1.10.0",
"multer": "^1.4.5-lts.1", "multer": "^1.4.5-lts.1",
...@@ -27,10 +28,12 @@ ...@@ -27,10 +28,12 @@
"passport": "^0.7.0", "passport": "^0.7.0",
"passport-google-oauth20": "^2.0.0", "passport-google-oauth20": "^2.0.0",
"sequelize": "^6.37.5", "sequelize": "^6.37.5",
"sequelize-cli": "^6.6.2" "sequelize-cli": "^6.6.2",
"sqlite3": "^5.1.7"
}, },
"devDependencies": { "devDependencies": {
"artillery": "^2.0.21", "artillery": "^2.0.21",
"cross-env": "^7.0.3",
"nodemon": "^3.1.7" "nodemon": "^3.1.7"
} }
} }
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment