Skip to content
Snippets Groups Projects
Commit 01e5bbc0 authored by YuJin's avatar YuJin
Browse files

Merge branch 'jin' into 'main'

Completed API development for crew, event, and region; user authentication...

See merge request !8
parents e0ffce7a 24d9d9dc
Branches
No related tags found
1 merge request!8Completed API development for crew, event, and region; user authentication...
Showing
with 1659 additions and 406 deletions
......@@ -3,7 +3,10 @@
.vscode/
# Downloaded dependencies.
**/*/node_modules/
**/node_modules/
# Environment variables.
.env
# Local database.
mysql
\ No newline at end of file
......@@ -6,6 +6,7 @@ down: down-frontend down-apiserver
.PHONY: run-frontend
run-frontend:
docker-compose build frontend
docker-compose up -d frontend
.PHONY: logs-frontend
......@@ -18,6 +19,7 @@ down-frontend:
.PHONY: run-apiserver
run-apiserver:
docker-compose build apiserver
docker-compose up -d apiserver mysql
.PHONY: logs-apiserver
......
......@@ -7,7 +7,7 @@ services:
MYSQL_USER: admin
MYSQL_PASSWORD: localadmin
ports:
- "3306:3306"
- "3307:3306"
volumes:
- ./webapp/backend/ddl:/docker-entrypoint-initdb.d/
- ./mysql:/var/lib/mysql:delegated
......@@ -18,7 +18,7 @@ services:
context: ./webapp/frontend
dockerfile: Dockerfile
ports:
- "3000:3000"
- "3001:3000"
volumes:
- ./webapp/frontend/src:/app/src
......@@ -34,6 +34,6 @@ services:
MYSQL_PASSWORD: localadmin
ports:
- "8180:8080"
# TODO(minjae): hot reload.
# TODO(minjae): add Redis, Object Storage, etc.
depends_on:
- mysql
command: sh -c "until nc -z mysql 3306; do echo Waiting for MySQL; sleep 2; done && node apiserver/app.js"
\ No newline at end of file
......@@ -4,16 +4,22 @@ FROM node:22-alpine
# Set the working directory
WORKDIR /app
# package.json 복사 및 의존성 설치
COPY ./apiserver/package.json .
COPY ./apiserver ./apiserver
# Install dependencies
RUN npm install
# Copy entire codebase
COPY ./apiserver .
COPY ./apiserver ./
# config 디렉토리 복사
COPY ./config ./config
# Expose the port on which the app will run
EXPOSE 8080
# Start the app
CMD ["node", "app.js"]
CMD ["node", "apiserver/app.js"]
var express = require('express');
var cors = require('cors');
var cookieParser = require('cookie-parser');
const express = require('express');
const cors = require('cors');
const cookieParser = require('cookie-parser');
const sequelize = require('../config/database'); // backend/config/database.js 파일 경로
const Crew = require('./models/Crew'); // Crew 모델 추가
const Region = require('./models/Region'); // Region 모델 추가
const SportType = require('./models/SportType'); // SportType 모델 추가
const port = 8080; // TODO(minjae): read from env var.
const port = process.env.PORT || 8080; // Docker Compose에서 PORT 주입
var indexRouter = require('./routes/index');
// 라우터 불러오기
const indexRouter = require('./routes/index');
const crewRouter = require('./routes/crew');
const eventRouter = require('./routes/event');
const regionRouter = require('./routes/region');
var app = express();
const app = express();
// TODO(minjae): add loagger.
app.use(express.json());
app.use(cookieParser());
app.use(cors());
// **미들웨어 설정**
app.use(express.json()); // JSON 형식의 요청 본문 파싱
app.use(cookieParser()); // 쿠키 파싱
app.use(cors()); // CORS 허용
app.use('/', indexRouter);
// **테이블 동기화**
(async () => {
try {
await sequelize.sync({ alter: true }); // alter를 사용하면 기존 테이블 구조를 업데이트
console.log('✅ Database synchronized successfully.');
} catch (err) {
console.error('❌ Error synchronizing database:', err);
process.exit(1); // 동기화 실패 시 프로세스 종료
}
})();
// **데이터베이스 연결 확인**
sequelize
.authenticate()
.then(() => {
console.log('✅ Database connected successfully.');
})
.catch((err) => {
console.error('❌ Unable to connect to the database:', err.message);
process.exit(1); // 데이터베이스 연결 실패 시 프로세스 종료
});
// **라우터 설정**
app.use('/', indexRouter); // 기본 라우터
app.use('/api/crews', crewRouter); // 크루 관련 API
app.use('/api/events', eventRouter); // 이벤트 관련 API
app.use('/api/regions', regionRouter); // 지역 관련 API
// **에러 핸들링**
app.use((err, req, res, next) => {
console.error(err.stack); // 에러 로그 기록
res.status(err.status || 500).json({ error: err.message || '서버 오류가 발생했습니다.' });
});
// **서버 실행**
app.listen(port, () => {
console.log(`Server is running on http://localhost:${port}`);
console.log(`✅ Server is running on http://localhost:${port}`);
});
// **프로세스 종료 핸들러 추가**
process.on('SIGINT', () => {
console.log('\n❌ Server shutting down...');
sequelize.close().then(() => {
console.log('✅ Database connection closed.');
process.exit(0);
});
});
const Crew = require('../models/Crew.js');
const Event = require('../models/Event.js');
const UserCrew = require('../models/UserCrew.js'); // UserCrew 모델 가져오기
// 한 페이지당 기본 아이템 수
const itemsPerPage = 10;
// 크루 생성 컨트롤러
exports.createCrew = async (req, res) => {
try {
const { regionID, name, sportTypeId, capacity, fee_krw, description, createdDate } = req.body;
// TODO: 클라이언트에게 누락된 필드 정보를 명시적으로 전달하지 못하는 패턴입니다.
// 추후 리팩터링 시 누락된 필드를 클라이언트에 명확히 알려주는 로직으로 개선할 필요가 있습니다.
if (!regionID || !name || !sportTypeId || !createdDate) {
return res.status(400).json({ error: '필수 필드가 누락되었습니다.' });
}
const newCrew = await Crew.create({
regionID,
name,
sportTypeId,
capacity,
fee_krw,
description,
createdDate,
});
res.status(201).json(newCrew);
} catch (error) {
console.error('크루 생성 중 오류:', error);
res.status(500).json({ error: '크루 생성 중 오류가 발생했습니다.' });
}
};
// 크루 가입 컨트롤러
exports.joinCrew = async (req, res) => {
try {
const { crewID } = req.params;
const { userID, role } = req.body;
if (!crewID || !userID) {
return res.status(400).json({ error: '필수 데이터가 누락되었습니다.' });
}
const crew = await Crew.findByPk(crewID);
if (!crew) {
return res.status(404).json({ error: '해당 크루가 존재하지 않습니다.' });
}
const userCrew = await UserCrew.create({
crewID,
userID,
role: role || 'General',
});
res.status(201).json({
crewID: userCrew.crewID,
userID: userCrew.userID,
role: userCrew.role,
});
} catch (error) {
console.error('크루 가입 중 오류:', error);
res.status(500).json({ error: '크루 가입 중 오류가 발생했습니다.' });
}
};
// 크루 탈퇴 컨트롤러
exports.leaveCrew = async (req, res) => {
try {
const { crewID } = req.params;
const { userID } = req.body;
if (!crewID || !userID) {
return res.status(400).json({ error: '필수 데이터가 누락되었습니다.' });
}
const crew = await Crew.findByPk(crewID);
if (!crew) {
return res.status(404).json({ error: '해당 크루가 존재하지 않습니다.' });
}
const userCrew = await UserCrew.findOne({ where: { crewID, userID } });
if (!userCrew) {
return res.status(404).json({ error: '해당 사용자가 크루에 가입되어 있지 않습니다.' });
}
await userCrew.destroy();
res.status(200).json({});
} catch (error) {
console.error('크루 탈퇴 중 오류:', error);
res.status(500).json({ error: '크루 탈퇴 중 오류가 발생했습니다.' });
}
};
// 크루 조회 컨트롤러
exports.getCrew = async (req, res) => {
try {
const { crewID } = req.params;
const crew = await Crew.findByPk(crewID);
if (!crew) {
return res.status(404).json({ error: '해당 크루가 존재하지 않습니다.' });
}
res.status(200).json(crew);
} catch (error) {
console.error('크루 조회 중 오류:', error);
res.status(500).json({ error: '크루 조회 중 오류가 발생했습니다.' });
}
};
// 크루 목록 조회 컨트롤러
exports.getCrews = async (req, res) => {
try {
const { regionID, sportTypeId, page = 1 } = req.query;
const offset = (page - 1) * itemsPerPage;
const where = {};
if (regionID) where.regionID = regionID;
if (sportTypeId) where.sportTypeId = sportTypeId;
const { rows: crews, count: total } = await Crew.findAndCountAll({
where,
limit: itemsPerPage,
offset,
});
res.status(200).json({
crews,
total,
itemsPerPage,
});
} catch (error) {
console.error('크루 조회 중 오류:', error);
res.status(500).json({ error: '크루 조회 중 오류가 발생했습니다.' });
}
};
// 크루 수정 컨트롤러
exports.updateCrew = async (req, res) => {
try {
const { crewID } = req.params;
const { name, capacity, fee_krw, description } = req.body;
if (!name || !capacity || !fee_krw || !description) {
return res.status(400).json({ error: '모든 필드가 필수입니다.' });
}
const crew = await Crew.findByPk(crewID);
if (!crew) {
return res.status(404).json({ error: '해당 크루를 찾을 수 없습니다.' });
}
await crew.update({
name,
capacity,
fee_krw,
description,
});
res.status(200).json({ updatedCrew: crew });
} catch (error) {
console.error('크루 수정 중 오류:', error);
res.status(500).json({ error: '크루 수정 중 오류가 발생했습니다.' });
}
};
// 크루 삭제 컨트롤러
exports.deleteCrew = async (req, res) => {
try {
const { crewID } = req.params;
const crew = await Crew.findByPk(crewID);
if (!crew) {
return res.status(404).json({ error: '해당 크루를 찾을 수 없습니다.' });
}
await crew.destroy();
res.status(200).json({});
} catch (error) {
console.error('크루 삭제 중 오류:', error);
res.status(500).json({ error: '크루 삭제 중 오류가 발생했습니다.' });
}
};
// 특정 크루의 이벤트 조회
exports.getEventsByCrew = async (req, res) => {
try {
const { crewID } = req.params;
const { page = 1 } = req.query;
const offset = (page - 1) * itemsPerPage;
const crew = await Crew.findByPk(crewID);
if (!crew) {
return res.status(404).json({ error: '해당 크루가 존재하지 않습니다.' });
}
const { rows: events, count: total } = await Event.findAndCountAll({
where: { crewID },
limit: itemsPerPage,
offset,
});
res.status(200).json({
events,
total,
itemsPerPage,
});
} catch (error) {
console.error('특정 크루의 이벤트 조회 중 오류:', error);
res.status(500).json({ error: '특정 크루의 이벤트 조회 중 오류가 발생했습니다.' });
}
};
const Event = require('../models/Event');
const EventParticipants = require('../models/EventParticipants');
const { Op } = require('sequelize');
const moment = require('moment'); // 날짜 포맷팅 라이브러리
// 한 페이지당 기본 아이템 수
const itemsPerPage = 10;
// 이벤트 목록 조회 (단일 엔드포인트)
exports.getEvents = async (req, res) => {
console.log('이벤트 목록 조회 컨트롤러 실행'); // 로그 확인용
try {
const { sportTypeId, regionID, eventDate, page = 1 } = req.query; // 쿼리 파라미터 가져오기
const offset = (page - 1) * itemsPerPage; // 시작 지점 계산
const where = {};
// 쿼리 파라미터 조건 추가
if (sportTypeId) where.sportTypeId = sportTypeId;
if (regionID) where.regionID = regionID;
if (eventDate) where.eventDate = eventDate;
// 이벤트 데이터 가져오기
const { rows: events, count: total } = await Event.findAndCountAll({
where,
limit: itemsPerPage,
offset,
});
// 응답 데이터 반환
res.status(200).json({
events,
total, // 조건에 맞는 총 이벤트 개수
itemsPerPage, // 페이지당 이벤트 수
});
} catch (error) {
console.error('이벤트 목록 조회 중 오류:', error);
res.status(500).json({ error: '이벤트 목록 조회 중 오류가 발생했습니다.' });
}
};
// 이벤트 생성 컨트롤러
exports.createEvent = async (req, res) => {
try {
const {
crewID,
regionID,
name,
sportTypeId,
eventDate,
capacity,
feeCondition,
userID,
createdDate,
} = req.body;
// TODO: 클라이언트에게 누락된 필드 정보를 명시적으로 전달하지 못하는 패턴입니다.
// 추후 리팩터링 시 누락된 필드를 클라이언트에 명확히 알려주는 로직으로 개선할 필요가 있습니다.
if (!regionID || !sportTypeId || !userID || !createdDate) {
return res.status(400).json({ error: '필수 필드가 누락되었습니다.' });
}
const formattedEventDate = eventDate ? moment(eventDate).format('YYYY-MM-DD') : null;
const formattedCreatedDate = moment(createdDate).format('YYYY-MM-DD');
const newEvent = await Event.create({
crewID,
regionID,
name,
sportTypeId,
eventDate: formattedEventDate,
capacity,
feeCondition,
userID,
createdDate: formattedCreatedDate,
});
res.status(201).json(newEvent);
} catch (error) {
console.error('이벤트 생성 중 오류:', error);
res.status(500).json({ error: '이벤트 생성 중 오류가 발생했습니다.' });
}
};
// 특정 이벤트 조회 컨트롤러
exports.getEventById = async (req, res) => {
try {
const { eventID } = req.params;
const event = await Event.findByPk(eventID);
if (!event) {
return res.status(404).json({ error: '해당 이벤트가 존재하지 않습니다.' });
}
res.status(200).json(event);
} catch (error) {
console.error('이벤트 조회 중 오류:', error);
res.status(500).json({ error: '이벤트 조회 중 오류가 발생했습니다.' });
}
};
// 이벤트 수정 컨트롤러
exports.updateEvent = async (req, res) => {
try {
const { eventID } = req.params;
const { userID } = req.body;
const updateData = req.body;
const event = await Event.findByPk(eventID);
if (!event) {
return res.status(404).json({ error: '해당 이벤트가 존재하지 않습니다.' });
}
if (event.userID !== userID) {
return res.status(403).json({ error: '이벤트 수정 권한이 없습니다.' });
}
await event.update(updateData);
res.status(200).json({ updatedEvent: event });
} catch (error) {
console.error('이벤트 수정 중 오류:', error);
res.status(500).json({ error: '이벤트 수정 중 오류가 발생했습니다.' });
}
};
// 이벤트 삭제 컨트롤러
exports.deleteEvent = async (req, res) => {
try {
const { eventID } = req.params;
const { userID } = req.body;
const event = await Event.findByPk(eventID);
if (!event) {
return res.status(404).json({ error: '해당 이벤트가 존재하지 않습니다.' });
}
if (event.userID !== userID) {
return res.status(403).json({ error: '이벤트 삭제 권한이 없습니다.' });
}
await event.destroy();
res.status(200).json({ message: '이벤트가 성공적으로 삭제되었습니다.' });
} catch (error) {
console.error('이벤트 삭제 중 오류:', error);
res.status(500).json({ error: '이벤트 삭제 중 오류가 발생했습니다.' });
}
};
// 이벤트 참여 컨트롤러
exports.participateEvent = async (req, res) => {
try {
const { eventID } = req.params;
const { userID } = req.body;
if (!eventID || !userID) {
return res.status(400).json({ error: '필수 데이터가 누락되었습니다.' });
}
const event = await Event.findByPk(eventID);
if (!event) {
return res.status(404).json({ error: '해당 이벤트가 존재하지 않습니다.' });
}
const existingParticipant = await EventParticipants.findOne({ where: { eventID, userID } });
if (existingParticipant) {
return res.status(409).json({ error: '이미 이벤트에 참여 중입니다.' });
}
const participantCount = await EventParticipants.count({ where: { eventID, status: 'attending' } });
if (event.capacity && participantCount >= event.capacity) {
return res.status(409).json({ error: '이벤트 정원이 초과되었습니다.' });
}
const newParticipant = await EventParticipants.create({
userID,
eventID,
participationDate: new Date(),
status: 'attending',
});
res.status(201).json(newParticipant);
} catch (error) {
console.error('이벤트 참여 중 오류:', error);
res.status(500).json({ error: '이벤트 참여 중 오류가 발생했습니다.' });
}
};
// 이벤트 참여 취소 컨트롤러
exports.cancelParticipation = async (req, res) => {
try {
const { eventID } = req.params; // URL에서 eventID 가져오기
const { userID } = req.body; // 요청 본문에서 userID 가져오기
// 필수 데이터 검증
if (!eventID || !userID) {
return res.status(400).json({ error: '필수 데이터가 누락되었습니다.' });
}
// 이벤트 존재 여부 확인
const event = await Event.findByPk(eventID);
if (!event) {
return res.status(404).json({ error: '해당 이벤트가 존재하지 않습니다.' });
}
// 참여 기록 확인
const participant = await EventParticipants.findOne({ where: { eventID, userID } });
if (!participant) {
return res.status(404).json({ error: '참여 기록이 존재하지 않습니다.' });
}
// 상태 변경 (참여 취소)
participant.status = 'canceled';
await participant.save();
res.status(200).json({
message: '이벤트 참여가 성공적으로 취소되었습니다.',
participant,
});
} catch (error) {
console.error('이벤트 참여 취소 중 오류:', error);
res.status(500).json({ error: '이벤트 참여 취소 중 오류가 발생했습니다.' });
}
};
const Region = require('../models/Region');
// // 모든 지역 목록 조회 컨트롤러
// exports.getAllRegions = async (req, res) => {
// try {
// // 모든 지역 데이터 조회
// const regions = await Region.findAll();
// // 응답 데이터 반환
// res.status(200).json(regions);
// } catch (error) {
// console.error('모든 지역 조회 중 오류:', error);
// res.status(500).json({ error: '지역 조회 중 오류가 발생했습니다.' });
// }
// };
// 특정 지역 조회 컨트롤러
exports.getRegionById = async (req, res) => {
try {
const { regionID } = req.params;
// 데이터베이스에서 해당 지역 조회
const region = await Region.findByPk(regionID);
// 지역이 없을 경우
if (!region) {
return res.status(404).json({ error: '해당 지역을 찾을 수 없습니다.' });
}
// 조회된 지역 정보 반환
res.status(200).json(region);
} catch (error) {
console.error('지역 조회 중 오류:', error);
res.status(500).json({ error: '지역 조회 중 오류가 발생했습니다.' });
}
};
// 특정 지역의 하위 Depth Region 목록 조회
exports.listChildrenRegions = async (req, res) => {
try {
const { regionID } = req.params; // URL 경로에서 regionID 가져오기
// 상위 지역 확인
const parentRegion = await Region.findByPk(regionID);
if (!parentRegion) {
return res.status(404).json({ error: '해당 상위 지역이 존재하지 않습니다.' });
}
// 하위 지역 목록 조회
const childrenRegions = await Region.findAll({
where: { parentRegionID: regionID },
});
// 응답 데이터 반환
res.status(200).json({
regions: childrenRegions, // 하위 지역 배열
});
} catch (error) {
console.error('하위 지역 조회 중 오류:', error);
res.status(500).json({ error: '하위 지역 조회 중 오류가 발생했습니다.' });
}
};
const { DataTypes } = require('sequelize');
const sequelize = require('../../config/database'); // config 폴더는 apiserver의 상위 폴더에 위치
const Region = require('./Region'); // Region 모델 가져오기
const SportType = require('./SportType'); // SportType 모델 가져오기
const Crew = sequelize.define('Crew', {
crewID: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true,
},
regionID: {
type: DataTypes.INTEGER,
allowNull: false,
references: {
model: Region, // 참조할 모델
key: 'regionID', // Region 모델의 primary key
},
onUpdate: 'CASCADE', // Region 업데이트 시 동기화
onDelete: 'RESTRICT', // Region 삭제 제한
},
name: {
type: DataTypes.STRING(100),
allowNull: false,
},
sportTypeId: {
type: DataTypes.INTEGER,
allowNull: false,
references: {
model: SportType, // 참조할 모델
key: 'sportTypeId', // SportType 모델의 primary key
},
onUpdate: 'CASCADE', // SportType 업데이트 시 동기화
onDelete: 'RESTRICT', // SportType 삭제 제한
},
capacity: {
type: DataTypes.INTEGER,
allowNull: true,
},
fee_krw: {
type: DataTypes.DECIMAL(10, 2),
allowNull: true,
},
description: {
type: DataTypes.STRING(255),
allowNull: true,
},
createdDate: {
type: DataTypes.DATE,
allowNull: false,
defaultValue: DataTypes.NOW,
},
}, {
timestamps: false, // Sequelize 자동 시간 관리 비활성화
tableName: 'Crew', // 테이블 이름 매핑
});
// Associations 설정
Region.hasMany(Crew, { foreignKey: 'regionID' }); // Region은 여러 Crew를 가질 수 있음
Crew.belongsTo(Region, { foreignKey: 'regionID' }); // Crew는 하나의 Region에 속함
SportType.hasMany(Crew, { foreignKey: 'sportTypeId' }); // SportType은 여러 Crew를 가질 수 있음
Crew.belongsTo(SportType, { foreignKey: 'sportTypeId' }); // Crew는 하나의 SportType에 속함
module.exports = Crew;
const { DataTypes } = require('sequelize');
const sequelize = require('../../config/database');
const Crew = require('./Crew');
const Region = require('./Region');
const User = require('./User'); // User 모델 가져오기
const SportType = require('./SportType'); // SportType 모델 가져오기
const Event = sequelize.define('Event', {
eventID: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true,
},
crewID: {
type: DataTypes.INTEGER,
allowNull: true,
references: {
model: Crew,
key: 'crewID',
},
onUpdate: 'CASCADE',
onDelete: 'SET NULL',
},
regionID: {
type: DataTypes.INTEGER,
allowNull: false,
references: {
model: Region,
key: 'regionID',
},
onUpdate: 'CASCADE',
onDelete: 'RESTRICT',
},
userID: {
type: DataTypes.INTEGER,
allowNull: false,
references: {
model: User, // User 테이블 참조
key: 'userID',
},
onUpdate: 'CASCADE',
onDelete: 'CASCADE', // 사용자가 삭제되면 관련 이벤트도 삭제
},
sportTypeId: {
type: DataTypes.INTEGER,
allowNull: false,
references: {
model: SportType, // SportType 테이블 참조
key: 'sportTypeId',
},
onUpdate: 'CASCADE',
onDelete: 'RESTRICT', // SportType 삭제 제한
},
name: {
type: DataTypes.STRING(100),
allowNull: true,
},
eventDate: {
type: DataTypes.DATE,
allowNull: true,
},
capacity: {
type: DataTypes.INTEGER,
allowNull: true,
},
feeCondition: {
type: DataTypes.STRING(50),
allowNull: true,
},
createdDate: {
type: DataTypes.DATE,
allowNull: false,
},
}, {
timestamps: false,
tableName: 'Event',
});
// Associations 설정
Crew.hasMany(Event, { foreignKey: 'crewID' });
Event.belongsTo(Crew, { foreignKey: 'crewID' });
Region.hasMany(Event, { foreignKey: 'regionID' });
Event.belongsTo(Region, { foreignKey: 'regionID' });
User.hasMany(Event, { foreignKey: 'userID' });
Event.belongsTo(User, { foreignKey: 'userID' });
SportType.hasMany(Event, { foreignKey: 'sportTypeId' });
Event.belongsTo(SportType, { foreignKey: 'sportTypeId' });
module.exports = Event;
const { DataTypes } = require('sequelize');
const sequelize = require('../../config/database'); // config 폴더는 apiserver의 상위 폴더에 위치
const EventParticipants = sequelize.define('EventParticipants', {
userID: {
type: DataTypes.INTEGER,
allowNull: false,
primaryKey: true,
references: {
model: 'User',
key: 'userID',
},
},
eventID: {
type: DataTypes.INTEGER,
allowNull: false,
primaryKey: true,
references: {
model: 'Event',
key: 'eventID',
},
},
participationDate: {
type: DataTypes.DATE,
allowNull: true,
comment: '참여 날짜',
},
status: {
type: DataTypes.ENUM('attending', 'canceled'),
allowNull: true,
comment: '참여 상태 (attending, canceled)',
},
}, {
tableName: 'EventParticipants',
timestamps: false, // createdAt, updatedAt 필드 사용 안 함
});
module.exports = EventParticipants;
const { DataTypes } = require('sequelize');
const sequelize = require('../../config/database'); // config 폴더는 apiserver의 상위 폴더에 위치
const Region = sequelize.define('Region', {
regionID: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true,
},
regionName: {
type: DataTypes.STRING(100),
allowNull: false,
},
parentRegionID: {
type: DataTypes.INTEGER,
allowNull: true,
references: {
model: 'Region', // 자기 자신을 참조
key: 'regionID', // 참조할 필드
},
onUpdate: 'CASCADE', // 부모 지역 ID 업데이트 시 동기화
onDelete: 'SET NULL', // 부모 지역 삭제 시 자식의 parentRegionID를 NULL로 설정
},
}, {
timestamps: false,
tableName: 'Region',
});
module.exports = Region;
const { DataTypes } = require('sequelize');
const sequelize = require('../../config/database'); // config 폴더는 apiserver의 상위 폴더에 위치
const SportType = sequelize.define('SportType', {
sportTypeId: {
type: DataTypes.INTEGER,
primaryKey: true,
allowNull: false,
},
sportName: {
type: DataTypes.STRING(100),
allowNull: false,
comment: '(예: 러닝, 헬스, 클라이밍)', // 필드 설명 추가
},
metadata: {
type: DataTypes.JSON,
allowNull: true,
comment: '(카테고리와 옵션을 JSON으로 저장)', // 필드 설명 추가
},
}, {
tableName: 'SportType', // 테이블 이름
timestamps: false, // createdAt, updatedAt 비활성화
});
module.exports = SportType;
const { DataTypes } = require('sequelize');
const sequelize = require('../../config/database'); // config 폴더는 apiserver의 상위 폴더에 위치
const User = sequelize.define('User', {
userID: {
type: DataTypes.INTEGER,
primaryKey: true,
allowNull: false,
autoIncrement: true, // 사용자 ID 자동 증가
},
name: {
type: DataTypes.STRING(100),
allowNull: false,
},
email: {
type: DataTypes.STRING(100),
allowNull: false,
unique: true, // 이메일 고유 제약 조건 추가
validate: {
isEmail: true, // 유효한 이메일 형식 검증
},
},
password: {
type: DataTypes.STRING(100),
allowNull: false,
},
}, {
tableName: 'User', // MySQL 테이블 이름 매핑
timestamps: false, // createdAt, updatedAt 비활성화
indexes: [
{
name: 'idx_user_email', // 인덱스 이름
unique: true,
fields: ['email'], // 이메일 컬럼에 고유 인덱스 추가
},
],
});
module.exports = User;
const { DataTypes } = require('sequelize');
const sequelize = require('../../config/database'); // config 폴더는 apiserver의 상위 폴더에 위치
// UserCrew 모델 정의
const UserCrew = sequelize.define('UserCrew', {
userID: { type: DataTypes.INTEGER, allowNull: false, primaryKey: true },
crewID: { type: DataTypes.INTEGER, allowNull: false, primaryKey: true },
role: { type: DataTypes.ENUM('Leader', 'Staff', 'General'), allowNull: true },
}, {
tableName: 'UserCrew',
timestamps: false, // createdAt, updatedAt 자동 생성 비활성화
});
module.exports = UserCrew;
This diff is collapsed.
......@@ -9,6 +9,14 @@
"cookie-parser": "~1.4.4",
"cors": "^2.8.5",
"debug": "~2.6.9",
"express": "~4.16.1"
"dotenv": "^16.4.5",
"express": "~4.16.1",
"moment": "^2.30.1",
"morgan": "^1.10.0",
"mysql2": "^3.11.4",
"sequelize": "^6.37.5"
},
"devDependencies": {
"nodemon": "^3.1.7"
}
}
var express = require('express');
var router = express.Router();
const crewController = require('../controllers/crewController');
// 크루 생성 API
router.post('/', crewController.createCrew);
// 크루 목록 조회 API (단일 엔드포인트)
router.get('/', crewController.getCrews);
// // 크루 조회 (스포츠 타입) API
// router.get('/sportType', crewController.getCrewsBySportType);
// // 크루 조회 (지역) API
// router.get('/region', crewController.getCrewsByRegion);
// // 크루 조회 (지역 & 스포츠 타입) API
// router.get('/regionAndSportType', crewController.getCrewsByRegionAndSportType);
// ** 위는 정적 아래는 동적 라우팅 ** //
// 특정 크루의 이벤트 조회 API
router.get('/:crewID/events', crewController.getEventsByCrew);
// 크루 멤버 추가 (가입) API
router.post('/:crewID/members', crewController.joinCrew);
// 크루 멤버 삭제 (탈퇴) API
router.delete('/:crewID/members', crewController.leaveCrew);
// 크루 단일 조회 API
router.get('/:crewID', crewController.getCrew);
// 크루 수정 API
router.put('/:crewID', crewController.updateCrew);
// 크루 삭제 API
router.delete('/:crewID', crewController.deleteCrew);
module.exports = router;
var express = require('express');
var router = express.Router();
const eventController = require('../controllers/eventController');
// **정적 라우팅**
// 이벤트 생성 API
router.post('/', eventController.createEvent);
// 이벤트 목록 조회 API (단일 엔드포인트)
router.get('/', eventController.getEvents);
// // 스포츠 타입별 이벤트 조회 API
// router.get('/sportType', eventController.getEventsBySportType);
// // 지역별 이벤트 조회 API
// router.get('/region', eventController.getEventsByRegion);
// // 날짜별 이벤트 조회 API
// router.get('/eventDate', eventController.getEventsByDate);
// // 스포츠 타입 & 지역으로 이벤트 조회 API
// router.get('/sportTypeAndRegion', eventController.getEventsBySportTypeAndRegion);
// // 스포츠 타입 & 날짜로 이벤트 조회 API
// router.get('/sportTypeAndEventDate', eventController.getEventsBySportTypeAndEventDate);
// // 지역 & 날짜로 이벤트 조회 API
// router.get('/regionAndEventDate', eventController.getEventsByRegionAndDate);
// // 스포츠 타입, 지역 및 날짜로 이벤트 조회 API
// router.get('/sportTypeRegionAndEventDate', eventController.getEventsBySportTypeRegionAndDate);
// **동적 라우팅**
// 특정 이벤트 조회 API
router.get('/:eventID', eventController.getEventById);
// 이벤트 삭제 API
router.delete('/:eventID', eventController.deleteEvent);
// 이벤트 참여 API
router.post('/:eventID/participants', eventController.participateEvent);
// 이벤트 참여 취소 API
router.delete('/:eventID/participants', eventController.cancelParticipation);
// 이벤트 수정 API
router.put('/:eventID', eventController.updateEvent);
module.exports = router;
var express = require('express');
var router = express.Router();
const regionController = require('../controllers/regionController');
// // 모든 지역 목록 조회 API
// router.get('/', regionController.getAllRegions);
// 특정 지역 조회 API
router.get('/:regionID', regionController.getRegionById);
// 특정 지역의 하위 Depth Region 목록 조회 API
router.get('/:regionID/children', regionController.listChildrenRegions);
module.exports = router;
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment