commit 5b719b67660690d620599c8efc330402854d2ae0 Author: Jaybe Date: Thu Apr 17 23:32:39 2025 +0900 init diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a206f04 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +.history +.env +.windsurfrules +config.js +backend/node_modules/* +frontend/node_modules/* diff --git a/README.md b/README.md new file mode 100644 index 0000000..e69de29 diff --git a/backend/app.js b/backend/app.js new file mode 100644 index 0000000..0593538 --- /dev/null +++ b/backend/app.js @@ -0,0 +1,86 @@ +const express = require('express'); +const bodyParser = require('body-parser'); +const cors = require('cors'); +const http = require('http'); +const socketIo = require('socket.io'); + +const adminRoutes = require('./routes/adminRoutes'); +const authRoutes = require('./routes/authRoutes'); +const eventRoutes = require('./routes/eventRoutes'); +const clubRoutes = require('./routes/clubRoutes'); +const userRoutes = require('./routes/userRoutes'); +const notificationRoutes = require('./routes/notificationRoutes'); +const menuRoutes = require('./routes/menuRoutes'); +const subscriptionRoutes = require('./routes/subscriptionRoutes'); +const featureRoutes = require('./routes/featureRoutes'); +const { sequelize, syncModels } = require('./models'); + +const app = express(); +const server = http.createServer(app); +const io = socketIo(server, { + cors: { + origin: '*', + methods: ['GET', 'POST'] + } +}); + +app.use(cors()); +app.use(bodyParser.json()); + +// Socket.IO 연결 처리 +io.on('connection', (socket) => { + console.log('새로운 클라이언트 연결:', socket.id); + + // 사용자 인증 및 알림 룸 참여 + socket.on('authenticate', (userData) => { + if (userData && userData.id) { + socket.join(`user:${userData.id}`); + console.log(`사용자 ${userData.id}가 알림 룸에 참여했습니다.`); + } + }); + + // 연결 해제 처리 + socket.on('disconnect', () => { + console.log('클라이언트 연결 해제:', socket.id); + }); +}); + +// 전역 Socket.IO 인스턴스 설정 +app.set('io', io); + +// 데이터베이스 연결 및 모델 동기화 실행 +sequelize.authenticate() + .then(() => { + console.log('데이터베이스에 연결되었습니다.'); + return syncModels(); + }) + .then(() => { + console.log('데이터베이스 모델이 동기화되었습니다.'); + }) + .catch(err => { + console.error('데이터베이스 연결 또는 모델 동기화 오류:', err); + }); + +// Routes 설정 +app.use('/api/auth', authRoutes); +app.use('/api/admin', adminRoutes); +app.use('/api/club', clubRoutes); +app.use('/api/club/events', eventRoutes); +app.use('/api/features', featureRoutes); +app.use('/api/subscriptions', subscriptionRoutes); +app.use('/api/users', userRoutes); +app.use('/api/notifications', notificationRoutes); +app.use('/api/menus', menuRoutes); + +// 루트 경로 핸들러 +app.get('/', (req, res) => { + res.json({ message: '볼링 클럽 매니저 API 서버에 오신 것을 환영합니다!' }); +}); + +// 서버 시작 +const PORT = process.env.PORT || 3030; +server.listen(PORT, () => { + console.log(`서버가 포트 ${PORT}에서 실행 중입니다.`); +}); + +module.exports = app; diff --git a/backend/config/database.js b/backend/config/database.js new file mode 100644 index 0000000..12aa3fa --- /dev/null +++ b/backend/config/database.js @@ -0,0 +1,40 @@ +const { Sequelize } = require('sequelize'); +require('dotenv').config(); + +const sequelize = new Sequelize( + process.env.DB_NAME, + process.env.DB_USER, + process.env.DB_PASSWORD, + { + host: process.env.DB_HOST, + port: process.env.DB_PORT, + dialect: 'mysql', + logging: true, // 개발 중에는 true로 설정하여 SQL 쿼리 로그 확인 가능 + pool: { + max: 5, + min: 0, + acquire: 30000, + idle: 10000 + }, + define: { + charset: 'utf8mb4', + collate: 'utf8mb4_unicode_ci', + timestamps: true + } + } +); + +// 데이터베이스 연결 테스트 +const testConnection = async () => { + try { + await sequelize.authenticate(); + console.log('데이터베이스 연결 성공!'); + } catch (error) { + console.error('데이터베이스 연결 실패:', error); + } +}; + +module.exports = { + sequelize, + testConnection +}; diff --git a/backend/controllers/authController.js b/backend/controllers/authController.js new file mode 100644 index 0000000..3a0b8bf --- /dev/null +++ b/backend/controllers/authController.js @@ -0,0 +1,221 @@ +/** + * 인증 컨트롤러 + * 사용자 인증 관련 기능을 처리하는 컨트롤러 + */ +const jwt = require('jsonwebtoken'); +const { User, Member } = require('../models'); +const { Op } = require('sequelize'); +require('dotenv').config(); + +// 로그인 +exports.login = async (req, res) => { + try { + const { email, password } = req.body; + + // 이메일 필수 확인 + if (!email) { + return res.status(400).json({ message: '이메일은 필수 항목입니다.' }); + } + + // 사용자 조회 + const user = await User.findOne({ where: { email } }); + + // 사용자가 존재하지 않는 경우 + if (!user) { + return res.status(401).json({ message: '이메일 또는 비밀번호가 올바르지 않습니다.' }); + } + + // 비밀번호 검증 + const isPasswordValid = await user.validatePassword(password); + + if (!isPasswordValid) { + return res.status(401).json({ message: '이메일 또는 비밀번호가 올바르지 않습니다.' }); + } + + // 마지막 로그인 시간 업데이트 + await user.update({ lastLogin: new Date() }); + + // JWT 토큰 생성 + const token = jwt.sign( + { + id: user.id, + email: user.email, + role: user.role, + name: user.name + }, + process.env.JWT_SECRET || 'your-secret-key', + { expiresIn: process.env.JWT_EXPIRES_IN || '1d' } + ); + + // 모임장/운영진으로 참여 중인 클럽 ID 조회 + const clubMembers = await Member.findAll({ + where: { + userId: user.id, + memberType: { + [Op.in]: ['모임장', '운영진'] + } + }, + attributes: ['clubId'] + }); + // console.log(`clubMembers: ${JSON.stringify(clubMembers)}`); + + // 모임장/운영진 클럽 ID 배열 생성 + const clubManagerIds = clubMembers.map(cm => cm.clubId); + let clubId = null; + if(clubManagerIds.length > 0) { + clubId = clubManagerIds[0]; + } + + // 사용자 정보에서 비밀번호 제외 + const userData = { + id: user.id, + name: user.name, + email: user.email, + role: user.role, + status: user.status, + clubId: clubId + }; + // console.log(`userData: ${JSON.stringify(userData)}`); + + res.json({ + token, + user: userData + }); + } catch (error) { + console.error('로그인 오류:', error); + res.status(500).json({ message: '로그인 중 오류가 발생했습니다.' }); + } +}; + +// 회원가입 +exports.register = async (req, res) => { + try { + const { name, email, password, role, status } = req.body; + + // 필수 필드 검증 + if (!name || !email || !password) { + return res.status(400).json({ message: '이름, 이메일, 비밀번호는 필수 항목입니다.' }); + } + + // 이메일 중복 확인 + const existingUser = await User.findOne({ where: { email } }); + + if (existingUser) { + return res.status(400).json({ message: '이미 등록된 이메일입니다.' }); + } + + // 사용자 생성 + const newUser = await User.create({ + name, + email, + password, + username: email, + role: role || 'user', + status: status || 'active' + }); + + // 민감한 정보 제외 + const userResponse = { + id: newUser.id, + name: newUser.name, + email: newUser.email, + role: newUser.role, + status: newUser.status, + createdAt: newUser.createdAt + }; + + res.status(201).json(userResponse); + } catch (error) { + console.error('회원가입 오류:', error); + res.status(500).json({ message: '회원가입 중 오류가 발생했습니다.' }); + } +}; + +// 현재 사용자 정보 조회 +exports.getCurrentUser = async (req, res) => { + try { + const userId = req.user.id; + + const user = await User.findByPk(userId, { + attributes: { exclude: ['password'] } + }); + + if (!user) { + return res.status(404).json({ message: '사용자를 찾을 수 없습니다.' }); + } + + res.json(user); + } catch (error) { + console.error('사용자 정보 조회 오류:', error); + res.status(500).json({ message: '사용자 정보를 조회하는 중 오류가 발생했습니다.' }); + } +}; + +// 프로필 업데이트 +exports.updateProfile = async (req, res) => { + try { + const userId = req.user.id; + const { name, email, password, currentPassword } = req.body; + + // 사용자 조회 + const user = await User.findByPk(userId); + + if (!user) { + return res.status(404).json({ message: '사용자를 찾을 수 없습니다.' }); + } + + // 비밀번호 변경이 요청된 경우 + if (password && currentPassword) { + // 현재 비밀번호 검증 + const isPasswordValid = await user.validatePassword(currentPassword); + if (!isPasswordValid) { + return res.status(401).json({ message: '현재 비밀번호가 올바르지 않습니다.' }); + } + } + + // 이메일이 변경되는 경우 + if (email && email !== user.email) { + // Users 테이블에서 이메일 중복 확인 + const existingUser = await User.findOne({ where: { email } }); + if (existingUser) { + return res.status(400).json({ message: '이미 사용 중인 이메일입니다.' }); + } + + // Members 테이블에서 이메일 중복 확인 + const existingMember = await Member.findOne({ where: { email } }); + if (existingMember) { + return res.status(400).json({ message: '이미 클럽 회원이 사용 중인 이메일입니다.' }); + } + + // 해당 사용자의 Members 테이블 레코드도 함께 업데이트 + await Member.update( + { email }, + { where: { userId } } + ); + } + + // 프로필 업데이트 + const updateData = { + name: name || user.name, + email: email || user.email, + username: email || user.email // username도 이메일과 동일하게 업데이트 + }; + + // 비밀번호가 제공된 경우에만 업데이트 + if (password && currentPassword) { + updateData.password = password; + } + + await user.update(updateData); + + // 업데이트된 사용자 정보 반환 (비밀번호 제외) + const updatedUser = await User.findByPk(userId, { + attributes: { exclude: ['password'] } + }); + + res.json(updatedUser); + } catch (error) { + console.error('프로필 업데이트 오류:', error); + res.status(500).json({ message: '프로필 업데이트 중 오류가 발생했습니다.' }); + } +}; diff --git a/backend/controllers/clubController.js b/backend/controllers/clubController.js new file mode 100644 index 0000000..08d7521 --- /dev/null +++ b/backend/controllers/clubController.js @@ -0,0 +1,925 @@ +/** + * 클럽 컨트롤러 + * 클럽 관련 기능을 처리하는 컨트롤러 + */ +const Club = require('../models/Club'); +const User = require('../models/User'); +const Member = require('../models/Member'); +const ClubFeature = require('../models/ClubFeature'); +const Event = require('../models/Event'); +const EventParticipant = require('../models/EventParticipant'); +const EventScore = require('../models/EventScore'); +const { Op } = require('sequelize'); + +// 관리자 권한 체크 함수 +const isAdmin = (user) => { + return user && (user.role === 'admin' || user.role === 'superadmin'); +}; + +// 모든 클럽 조회 +exports.getAllClubs = async (req, res) => { + try { + const clubs = await Club.findAll({ + where: { status: 'active' }, + include: [ + { + model: Member, + where: { memberType: '모임장' }, + required: false, + attributes: ['id', 'name', 'email', 'phone', 'memberType'] + } + ], + order: [['createdAt', 'DESC']] + }); + + const formattedClubs = clubs.map(club => { + const clubData = club.toJSON(); + // 모임장 정보 추가 + const ownerMember = clubData.Members && clubData.Members.length > 0 ? clubData.Members[0] : null; + + return { + ...clubData, + ownerName: ownerMember ? ownerMember.name : '알 수 없음', + ownerEmail: ownerMember ? ownerMember.email : '', + ownerPhone: ownerMember ? ownerMember.phone : '', + ownerMemberType: ownerMember ? ownerMember.memberType : '' + }; + }); + + res.json(formattedClubs); + } catch (error) { + console.error('클럽 목록 조회 오류:', error); + res.status(500).json({ message: '클럽 목록을 가져오는 중 오류가 발생했습니다.' }); + } +}; + +// 특정 클럽 조회 +exports.getClubById = async (req, res) => { + try { + const { clubId } = req.body; + console.log(`clubId: ${clubId}`); + if (!clubId) { + return res.status(400).json({ message: "유효하지 않은 클럽 ID입니다." }); + } + + const club = await Club.findByPk(clubId); + // console.log(`club: ${club}`); + if (club) { + // 클럽 소유자 정보 조회 + const owner = await Member.findOne({where: {userId: club.ownerId}}, { + attributes: ['name', 'email', 'memberType'] + }); + + const clubData = club.toJSON(); + const formattedClub = { + ...clubData, + ownerEmail: owner ? owner.email : '', + ownerName: owner ? owner.name : '', + ownerMemberType: owner ? owner.memberType : '' + }; + + res.json(formattedClub); + } else { + res.status(404).json({ message: '클럽을 찾을 수 없습니다.' }); + } + } catch (error) { + console.error('클럽 조회 오류:', error); + res.status(500).json({ message: '클럽 정보를 가져오는 중 오류가 발생했습니다.' }); + } +}; + +// 새 클럽 생성 +exports.createClub = async (req, res) => { + try { + const { name, location, description, ownerEmail, contact, features, sendInvite } = req.body; + + // 필수 필드 검증 + if (!name || !ownerEmail) { + return res.status(400).json({ message: '클럽 이름과 모임장 이메일은 필수 항목입니다.' }); + } + + // 이메일로 사용자 조회 + const owner = await User.findOne({ where: { email: ownerEmail } }); + + if (!owner) { + return res.status(404).json({ message: '해당 이메일로 등록된 사용자를 찾을 수 없습니다.' }); + } + + // 클럽 생성 + const newClub = await Club.create({ + name, + location, + description, + contact, + ownerId: owner.id, + memberCount: 1, // 모임장 포함 + status: 'active' + }); + + // 클럽 모임장을 클럽 멤버로 추가 + await Member.create({ + name: owner.name, + gender: '남성', + userId: owner.id, + clubId: newClub.id, + memberType: '모임장', + joinDate: new Date(), + status: 'active' + }); + + // 선택된 기능 추가 + if (features && features.length > 0) { + const featurePromises = features.map(featureId => + ClubFeature.create({ + clubId: newClub.id, + featureId, + enabled: true + }) + ); + + await Promise.all(featurePromises); + } + + // 초대 이메일 발송 로직 (sendInvite가 true인 경우) + if (sendInvite) { + // TODO: 초대 이메일 발송 로직 구현 + console.log(`${ownerEmail}에게 초대 이메일 발송 예정`); + } + + res.status(201).json(newClub); + } catch (error) { + console.error('클럽 생성 오류:', error); + res.status(500).json({ message: '클럽을 생성하는 중 오류가 발생했습니다.' }); + } +}; + +// 클럽 정보 업데이트 +exports.updateClub = async (req, res) => { + try { + const { clubId, name, location, description, contact, ownerEmail, status } = req.body; + + const club = await Club.findByPk(clubId); + + if (!club) { + return res.status(404).json({ message: '클럽을 찾을 수 없습니다.' }); + } + + // 이메일이 변경된 경우 새 모임장 확인 + let newOwnerId = club.ownerId; + let ownerChanged = false; + + if (ownerEmail && ownerEmail !== club.ownerEmail) { + const newOwner = await User.findOne({ where: { email: ownerEmail } }); + if (!newOwner) { + return res.status(404).json({ message: '해당 이메일로 등록된 사용자를 찾을 수 없습니다.' }); + } + newOwnerId = newOwner.id; + ownerChanged = true; + } + + // 클럽 정보 업데이트 + await club.update({ + name: name || club.name, + location: location || club.location, + description: description !== undefined ? description : club.description, + contact: contact !== undefined ? contact : club.contact, + status: status || club.status, + ownerId: newOwnerId + }); + + // 모임장이 변경된 경우 Members 테이블 업데이트 + if (ownerChanged) { + // 기존 모임장을 정회원으로 변경 + await Member.update( + { memberType: '정회원' }, + { + where: { + clubId: clubId, + userId: club.ownerId, + memberType: '모임장' + } + } + ); + + // 새 모임장 확인 및 업데이트 + const newOwnerMember = await Member.findOne({ + where: { + clubId: clubId, + userId: newOwnerId + } + }); + + if (newOwnerMember) { + // 기존 회원인 경우 모임장으로 변경 + await newOwnerMember.update({ memberType: '모임장' }); + } else { + // 새로운 사용자 정보 가져오기 + const newOwnerUser = await User.findByPk(newOwnerId); + if (!newOwnerUser) { + return res.status(404).json({ message: '새 모임장 사용자 정보를 찾을 수 없습니다.' }); + } + + // 새 회원으로 모임장 추가 + await Member.create({ + userId: newOwnerId, + clubId: clubId, + name: newOwnerUser.name || ownerEmail.split('@')[0], // 이름이 없는 경우 이메일 아이디 사용 + gender: '남성', // 성별 정보가 없는 경우 기본값 + memberType: '모임장', + joinDate: new Date(), + status: 'active' + }); + } + } + + res.json(club); + } catch (error) { + console.error('클럽 정보 업데이트 오류:', error); + res.status(500).json({ message: '클럽 정보를 업데이트하는 중 오류가 발생했습니다.' }); + } +}; + +// 클럽 삭제 +exports.deleteClub = async (req, res) => { + try { + const { clubId } = req.body; + + // 클럽 존재 여부 확인 + const club = await Club.findByPk(clubId); + + if (!club) { + return res.status(404).json({ message: '클럽을 찾을 수 없습니다.' }); + } + + // 소프트 삭제 (상태만 변경) + await club.update({ status: 'inactive' }); + + res.status(204).send(); + } catch (error) { + console.error('클럽 삭제 오류:', error); + res.status(500).json({ message: '클럽을 삭제하는 중 오류가 발생했습니다.' }); + } +}; + +// 클럽 기능 관리 +exports.manageClubFeatures = async (req, res) => { + try { + const { clubId, features } = req.body; + + // 클럽 존재 여부 확인 + const club = await Club.findByPk(clubId); + + if (!club) { + return res.status(404).json({ message: '클럽을 찾을 수 없습니다.' }); + } + + // 클럽 기능 업데이트 + await club.update({ features }); + + res.json({ + id: club.id, + features: club.features, + updatedAt: club.updatedAt + }); + } catch (error) { + console.error('클럽 기능 관리 오류:', error); + res.status(500).json({ message: '클럽 기능을 업데이트하는 중 오류가 발생했습니다.' }); + } +}; + +// 클럽 회원 목록 조회 +exports.getClubMembers = async (req, res) => { + try { + const { clubId } = req.body; + + if (!clubId) { + return res.status(400).json({ message: '클럽 ID가 필요합니다.' }); + } + + // 클럽 존재 여부 확인 + const club = await Club.findByPk(clubId); + if (!club) { + return res.status(404).json({ message: '클럽을 찾을 수 없습니다.' }); + } + + const members = await Member.findAll({ + where: { + clubId, + status: { + [Op.ne]: 'deleted' + } + }, + order: [ + ['memberType', 'ASC'], + ['name', 'ASC'] + ] + }); + + // 응답 데이터 형식화 + const formattedMembers = members.map(member => { + const memberData = member.toJSON(); + return { + id: memberData.id, + name: memberData.name, + gender: memberData.gender, + memberType: memberData.memberType, + handicap: memberData.handicap, + average: memberData.average, + games: memberData.games, + phone: memberData.phone, + email: memberData.user ? memberData.user.email : memberData.email, + joinDate: memberData.joinDate, + status: memberData.status + }; + }); + + res.json(formattedMembers); + } catch (error) { + console.error('클럽 회원 목록 조회 오류:', error); + res.status(500).json({ message: '클럽 회원 목록을 가져오는 중 오류가 발생했습니다.' }); + } +}; + +// 클럽 회원 추가 +exports.addClubMember = async (req, res) => { + try { + const { clubId, userId, name, gender, memberType, email, phone } = req.body; + + if (!clubId || !name || !gender) { + return res.status(400).json({ message: '필수 정보가 누락되었습니다.' }); + } + + const club = await Club.findByPk(clubId); + if (!club) { + return res.status(404).json({ message: '클럽을 찾을 수 없습니다.' }); + } + + // 회원 생성 + const newMember = await Member.create({ + clubId, + userId, + name, + gender, + memberType: memberType || '준회원', + email, + phone + }); + + // 클럽의 회원 수 업데이트 + const memberCount = await Member.count({ where: { clubId: clubId, status: 'active' } }); + await club.update({ memberCount }); + + res.status(201).json(newMember); + } catch (error) { + console.error('클럽 회원 추가 오류:', error); + res.status(500).json({ message: '회원을 추가하는 중 오류가 발생했습니다.' }); + } +}; + +// 클럽 회원 정보 수정 +exports.updateClubMember = async (req, res) => { + try { + const { clubId, memberId, name, gender, memberType, email, phone, status } = req.body; + + if (!clubId || !memberId) { + return res.status(400).json({ message: '잘못된 클럽 또는 회원 ID입니다.' }); + } + + const member = await Member.findOne({ + where: { + id: memberId, + clubId: clubId + } + }); + + if (!member) { + return res.status(404).json({ message: '회원을 찾을 수 없습니다.' }); + } + + // 모임장은 한 명만 존재할 수 있음 + if (memberType === '모임장') { + const existingOwner = await Member.findOne({ + where: { + clubId: clubId, + memberType: '모임장', + status: 'active', + id: { [Op.ne]: memberId } + } + }); + + if (existingOwner) { + return res.status(400).json({ message: '이미 다른 모임장이 존재합니다.' }); + } + } + + // 회원 정보 업데이트 + await member.update({ + name: name || member.name, + gender: gender || member.gender, + memberType: memberType || member.memberType, + email: email || member.email, + phone: phone || member.phone, + status: status || member.status + }); + + // 클럽의 회원 수 업데이트 + const club = await Club.findByPk(clubId); + if (club) { + const memberCount = await Member.count({ where: { clubId: clubId, status: 'active' } }); + await club.update({ memberCount }); + } + + res.json(member); + } catch (error) { + console.error('클럽 회원 정보 수정 오류:', error); + res.status(500).json({ message: '회원 정보를 수정하는 중 오류가 발생했습니다.' }); + } +}; + +// 클럽 회원 삭제 +exports.deleteClubMember = async (req, res) => { + try { + const { clubId, memberId } = req.body; + + if (!clubId || !memberId) { + return res.status(400).json({ message: '잘못된 클럽 또는 회원 ID입니다.' }); + } + + const member = await Member.findOne({ + where: { + id: memberId, + clubId: clubId + } + }); + + if (!member) { + return res.status(404).json({ message: '회원을 찾을 수 없습니다.' }); + } + + // 회원 상태를 deleted로 변경 + await member.update({ status: 'deleted' }); + + // 클럽의 회원 수 감소 + const club = await Club.findByPk(clubId); + if (club) { + const memberCount = await Member.count({ where: { clubId: clubId, status: 'active' } }); + await club.update({ memberCount }); + } + + res.json({ message: '회원이 성공적으로 삭제되었습니다.' }); + } catch (error) { + console.error('클럽 회원 삭제 오류:', error); + res.status(500).json({ message: '회원을 삭제하는 중 오류가 발생했습니다.' }); + } +}; + +// 회원 통계 조회 +exports.getMemberStats = async (req, res) => { + try { + const { clubId } = req.body; + + if (!clubId) { + return res.status(400).json({ message: '클럽 ID가 필요합니다.' }); + } + + const members = await Member.findAll({ + where: { + clubId, + status: 'active' + } + }); + + const stats = { + totalCount: members.length, + regularCount: members.filter(m => m.memberType === '정회원').length, + associateCount: members.filter(m => m.memberType === '준회원').length + }; + + res.json(stats); + } catch (error) { + console.error('회원 통계 조회 오류:', error); + res.status(500).json({ message: '회원 통계를 가져오는 중 오류가 발생했습니다.' }); + } +}; + +// 다음 모임 정보 조회 +exports.getNextMeeting = async (req, res) => { + try { + const { clubId } = req.body; + + if (!clubId) { + return res.status(400).json({ message: '클럽 ID가 필요합니다.' }); + } + + const nextMeeting = await Event.findOne({ + where: { + clubId, + startDate: { [Op.gt]: new Date() }, + status: 'active' + }, + include: [{ + model: EventParticipant, + as: 'participants', + where: { + status: { [Op.ne]: '취소' } + }, + required: false + }], + order: [['startDate', 'ASC']] + }); + + if (!nextMeeting) { + return res.json(null); + } + + const meetingData = { + date: nextMeeting.startDate, + participantsCount: nextMeeting.participants ? nextMeeting.participants.length : 0, + title: nextMeeting.title, + location: nextMeeting.location + }; + + res.json(meetingData); + } catch (error) { + console.error('다음 모임 정보 조회 오류:', error); + res.status(500).json({ message: '다음 모임 정보를 가져오는 중 오류가 발생했습니다.' }); + } +}; + +// 최근 모임 정보 조회 +exports.getLastMeeting = async (req, res) => { + try { + const { clubId } = req.body; + + if (!clubId) { + return res.status(400).json({ message: '클럽 ID가 필요합니다.' }); + } + + const lastMeeting = await Event.findOne({ + where: { + clubId, + startDate: { [Op.lt]: new Date() }, + status: 'active' + }, + include: [{ + model: EventParticipant, + as: 'participants', + where: { + status: { [Op.ne]: '취소' } + }, + required: false + }], + order: [['startDate', 'DESC']] + }); + + if (!lastMeeting) { + return res.json(null); + } + + const meetingData = { + date: lastMeeting.startDate, + participantsCount: lastMeeting.participants ? lastMeeting.participants.length : 0, + title: lastMeeting.title, + games: lastMeeting.gameCount || 0 + }; + + res.json(meetingData); + } catch (error) { + console.error('최근 모임 정보 조회 오류:', error); + res.status(500).json({ message: '최근 모임 정보를 가져오는 중 오류가 발생했습니다.' }); + } +}; + +// 점수 통계 조회 +exports.getScoreStats = async (req, res) => { + try { + const { clubId } = req.body; + + if (!clubId) { + return res.status(400).json({ message: '클럽 ID가 필요합니다.' }); + } + + // 최근 3개월 내의 이벤트 점수만 집계 + const threeMonthsAgo = new Date(); + threeMonthsAgo.setMonth(threeMonthsAgo.getMonth() - 3); + + const events = await Event.findAll({ + where: { + clubId, + startDate: { [Op.gte]: threeMonthsAgo }, + status: 'active' + }, + include: [{ + model: EventParticipant, + as: 'participants', + include: [{ + model: EventScore, + as: 'scores' + }] + }] + }); + + let allScores = []; + let totalGames = 0; + + events.forEach(event => { + event.participants.forEach(participant => { + if (participant.scores && participant.scores.length > 0) { + allScores = allScores.concat(participant.scores.map(score => score.score)); + totalGames += participant.scores.length; + } + }); + }); + + const stats = { + topGame: allScores.length > 0 ? Math.max(...allScores) : 0, + averageScore: allScores.length > 0 ? Math.round(allScores.reduce((a, b) => a + b, 0) / allScores.length) : 0, + totalGames: totalGames + }; + + res.json(stats); + } catch (error) { + console.error('점수 통계 조회 오류:', error); + res.status(500).json({ message: '점수 통계를 가져오는 중 오류가 발생했습니다.' }); + } +}; + +// 상위 회원 목록 조회 +exports.getTopMembers = async (req, res) => { + try { + const { clubId } = req.body; + + if (!clubId) { + return res.status(400).json({ message: '클럽 ID가 필요합니다.' }); + } + + const topMembers = await Member.findAll({ + where: { + clubId, + status: 'active', + games: { [Op.gt]: 0 } // 최소 1게임 이상 플레이한 회원 + }, + order: [ + ['average', 'DESC'] + ], + limit: 5, + attributes: ['id', 'name', 'average', 'games', 'handicap', 'memberType'] + }); + + res.json(topMembers); + } catch (error) { + console.error('상위 회원 목록 조회 오류:', error); + res.status(500).json({ message: '상위 회원 목록을 가져오는 중 오류가 발생했습니다.' }); + } +}; + +// 최근 모임 목록 조회 +exports.getRecentMeetings = async (req, res) => { + try { + const { clubId } = req.body; + + if (!clubId) { + return res.status(400).json({ message: '클럽 ID가 필요합니다.' }); + } + + const recentMeetings = await Event.findAll({ + where: { + clubId, + status: 'active', + startDate: { [Op.lt]: new Date() } + }, + include: [{ + model: EventParticipant, + as: 'participants', + where: { + status: { [Op.ne]: '취소' } + }, + required: false + }], + order: [['startDate', 'DESC']], + limit: 5 + }); + + const formattedMeetings = recentMeetings.map(meeting => ({ + date: meeting.startDate, + title: meeting.title, + participants: meeting.participants ? meeting.participants.length : 0, + games: meeting.gameCount || 0 + })); + + res.json(formattedMeetings); + } catch (error) { + console.error('최근 모임 목록 조회 오류:', error); + res.status(500).json({ message: '최근 모임 목록을 가져오는 중 오류가 발생했습니다.' }); + } +}; + +// 사용자 클럽 목록 조회 +exports.getUserClubs = async (req, res) => { + try { + // 사용자가 속한 클럽 목록 조회 (Member 테이블 조인) + // admin이면 모든 클럽을, 아니면 자신의 클럽만 조회 + const hasAdminAccess = isAdmin(req.user); + + const queryOptions = { + where: { status: 'active' } + }; + + // admin이 아닌 경우에만 Member 조건 추가 + if (!hasAdminAccess) { + queryOptions.include = [{ + model: Member, + where: { + userId: req.user.id + }, + attributes: ['memberType'] + }]; + } + + // order 옵션 추가 + queryOptions.order = [['createdAt', 'DESC']]; + const clubs = await Club.findAll(queryOptions); + + // 응답 데이터 가공 + const formattedClubs = clubs.map(club => { + const clubData = club.toJSON(); + let memberType = ''; + + // admin이 아닌 경우에만 Members가 있음 + if (!hasAdminAccess && clubData.Members && clubData.Members.length > 0) { + memberType = clubData.Members[0].memberType; + } else if (hasAdminAccess) { + memberType = 'admin'; + } + + return { + id: clubData.id, + name: clubData.name, + location: clubData.location, + memberType + }; + }); + + res.json(formattedClubs); + } catch (error) { + console.error('클럽 목록 조회 오류:', error); + res.status(500).json({ message: '클럽 목록을 가져오는 중 오류가 발생했습니다.' }); + } +}; + +// 클럽 통계 조회 +exports.getStatistics = async (req, res) => { + try { + const { clubId, days } = req.body; + + if (!clubId) { + return res.status(400).json({ message: '클럽 ID가 필요합니다.' }); + } + + const startDate = new Date(); + startDate.setDate(startDate.getDate() - (days || 90)); // 기본값: 90일 + + // 1. 회원 통계 + const members = await Member.findAll({ + where: { + clubId, + status: 'active' + } + }); + + // 2. 이벤트 및 참가자 통계 + const events = await Event.findAll({ + where: { + clubId, + startDate: { [Op.gte]: startDate }, + status: 'active' + }, + include: [{ + model: EventParticipant, + as: 'participants', + include: [{ + model: EventScore, + as: 'scores' + }] + }] + }); + + // 3. 점수 데이터 수집 + let allScores = []; + let totalParticipants = 0; + let totalGames = 0; + + events.forEach(event => { + if (event.participants) { + totalParticipants += event.participants.length; + event.participants.forEach(participant => { + if (participant.scores) { + allScores = allScores.concat(participant.scores.map(score => score.score)); + totalGames += participant.scores.length; + } + }); + } + }); + + // 4. 점수 분포 계산 + const scoreDistribution = Array(10).fill(0); + allScores.forEach(score => { + const index = Math.floor(score / 30); + if (index >= 0 && index < 10) { + scoreDistribution[index]++; + } + }); + + // 5. 참석률 추이 계산 + const attendanceTrend = events.map(event => ({ + date: event.startDate, + attendance: Math.round((event.participants ? event.participants.length : 0) / members.length * 100) + })); + + // 6. 회원 순위 계산 + const memberRankings = await Member.findAll({ + where: { + clubId, + status: 'active', + games: { [Op.gt]: 0 } + }, + order: [['average', 'DESC']], + limit: 10, + attributes: ['id', 'name', 'average', 'games', 'handicap', 'memberType'] + }); + + // 각 회원의 참석률 계산 + const memberAttendance = {}; + events.forEach(event => { + event.participants.forEach(participant => { + memberAttendance[participant.memberId] = memberAttendance[participant.memberId] || { attended: 0, total: 0 }; + memberAttendance[participant.memberId].attended++; + memberAttendance[participant.memberId].total = events.length; + }); + }); + + const rankingsWithAttendance = memberRankings.map(member => ({ + ...member.toJSON(), + attendance: memberAttendance[member.id] + ? Math.round(memberAttendance[member.id].attended / memberAttendance[member.id].total * 100) + : 0 + })); + + // 7. 모임별 통계 + const meetingStats = events.map(event => ({ + date: event.startDate, + title: event.title, + participants: event.participants ? event.participants.length : 0, + games: event.gameCount || 0, + averageScore: event.participants && event.participants.length > 0 + ? Math.round(event.participants.reduce((sum, p) => + sum + (p.scores ? p.scores.reduce((s, score) => s + score.score, 0) / p.scores.length : 0), + 0) / event.participants.length) + : 0 + })); + + // 8. 응답 데이터 구성 + const response = { + summary: { + totalMembers: members.length, + activeMembers: members.filter(m => m.games > 0).length, + averageAttendance: events.length > 0 + ? Math.round(totalParticipants / (events.length * members.length) * 100) + : 0, + highestGame: allScores.length > 0 ? Math.max(...allScores) : 0, + averageScore: allScores.length > 0 + ? Math.round(allScores.reduce((a, b) => a + b, 0) / allScores.length) + : 0, + totalGames: totalGames, + totalMeetings: events.length, + averageParticipants: events.length > 0 + ? Math.round(totalParticipants / events.length) + : 0, + averageGamesPerMeeting: events.length > 0 + ? Math.round(totalGames / events.length) + : 0, + averageHandicap: members.length > 0 + ? Math.round(members.reduce((sum, m) => sum + m.handicap, 0) / members.length) + : 0, + highestHandicap: members.length > 0 + ? Math.max(...members.map(m => m.handicap)) + : 0, + lowestHandicap: members.length > 0 + ? Math.min(...members.map(m => m.handicap)) + : 0 + }, + scoreDistribution: scoreDistribution.map((count, index) => ({ + range: `${index * 30}-${(index + 1) * 30 - 1}`, + count + })), + attendanceTrend, + memberRankings: rankingsWithAttendance, + meetingStats + }; + + res.json(response); + } catch (error) { + console.error('통계 데이터 조회 오류:', error); + res.status(500).json({ message: '통계 데이터를 가져오는 중 오류가 발생했습니다.' }); + } +}; diff --git a/backend/controllers/eventController.js b/backend/controllers/eventController.js new file mode 100644 index 0000000..e2ae7e2 --- /dev/null +++ b/backend/controllers/eventController.js @@ -0,0 +1,1232 @@ +/** + * 이벤트 컨트롤러 + * 이벤트 관련 기능을 처리하는 컨트롤러 + */ +const { Event, EventParticipant, Member, Club, EventScore, ClubMember } = require('../models'); +const { Op } = require('sequelize'); +const { sequelize } = require('../models'); + +// 이벤트 목록 조회 +exports.getAllEvents = async (req, res) => { + try { + const { clubId } = req.body; + + if (!clubId) { + return res.status(400).json({ message: '클럽 ID가 필요합니다.' }); + } + + const events = await Event.findAll({ + where: { + clubId, + status: { [Op.ne]: '삭제' } + }, + include: [ + { + model: Club, + attributes: ['id', 'name'] + }, + { + model: EventParticipant, + as: 'participants', + attributes: ['id', 'status', 'paymentStatus'], + where: { + status: { [Op.ne]: '취소' } + }, + include: [{ + model: Member, + attributes: ['id', 'name', 'memberType', 'gender'] + }] + } + ], + order: [['startDate', 'ASC']] + }); + + const formattedEvents = events.map(event => { + const eventData = event.toJSON(); + return { + ...eventData, + participantCount: eventData.participants ? eventData.participants.length : 0, + clubName: eventData.Club ? eventData.Club.name : '' + }; + }); + + res.json(formattedEvents); + } catch (error) { + console.error('이벤트 목록 조회 오류:', error); + res.status(500).json({ message: '이벤트 목록을 가져오는 중 오류가 발생했습니다.' }); + } +}; + +// 특정 이벤트 조회 +exports.getEventById = async (req, res) => { + try { + const eventId = req.params.id; + + if (!eventId) { + return res.status(400).json({ message: '이벤트 ID가 필요합니다.' }); + } + + const event = await Event.findOne({ + where: { + id: eventId, + status: { [Op.ne]: '삭제' } + }, + include: [ + { + model: Club, + attributes: ['id', 'name', 'location'] + }, + { + model: EventParticipant, + as: 'participants', + attributes: ['id', 'status', 'paymentStatus'], + where: { + status: { [Op.ne]: '취소' } + }, + include: [{ + model: Member, + attributes: ['id', 'name'] + }] + } + ] + }); + + if (!event) { + return res.status(404).json({ message: '이벤트를 찾을 수 없습니다.' }); + } + + const formattedEvent = { + ...event.toJSON(), + participantCount: event.participants ? event.participants.length : 0, + clubName: event.Club ? event.Club.name : '' + }; + + res.json(formattedEvent); + } catch (error) { + console.error('이벤트 조회 오류:', error); + res.status(500).json({ message: '이벤트 정보를 가져오는 중 오류가 발생했습니다.' }); + } +}; + +// 새 이벤트 생성 +exports.createEvent = async (req, res) => { + try { + const { + clubId, + title, + description, + startDate, + endDate, + location, + maxParticipants, + eventType, + entryFee, + gameCount, + laneCount, + status + } = req.body; + + if (!clubId) { + return res.status(400).json({ message: '클럽 ID가 필요합니다.' }); + } + + const club = await Club.findOne({ + where: { + id: clubId, + status: 'active' + } + }); + + if (!club) { + return res.status(404).json({ message: '클럽을 찾을 수 없습니다.' }); + } + const endDateVal = endDate ? new Date(endDate) : null; + const event = await Event.create({ + clubId, + title, + description, + startDate: new Date(startDate), + endDate: endDateVal, + location, + maxParticipants: maxParticipants || 0, + eventType: eventType || '일반', + entryFee: entryFee || 0, + gameCount: gameCount || 3, + laneCount: laneCount || 1, + status: status || '활성' + }); + + const createdEvent = await Event.findOne({ + where: { id: event.id }, + include: [ + { + model: Club, + attributes: ['id', 'name'] + } + ] + }); + + res.status(201).json({ + message: '이벤트가 생성되었습니다.', + event: { + ...createdEvent.toJSON(), + participantCount: 0, + clubName: createdEvent.Club ? createdEvent.Club.name : '' + } + }); + } catch (error) { + console.error('이벤트 생성 오류:', error); + res.status(500).json({ message: '이벤트 생성 중 오류가 발생했습니다.' }); + } +}; + +// 이벤트 정보 업데이트 +exports.updateEvent = async (req, res) => { + try { + const { + clubId, + title, + description, + startDate, + location, + maxParticipants, + eventType, + entryFee, + gameCount, + laneCount, + status + } = req.body; + const eventId = req.body.id; + let endDate = req.body.endDate; + if (!eventId || !clubId) { + return res.status(400).json({ message: '이벤트 ID와 클럽 ID가 필요합니다.' }); + } + + const event = await Event.findOne({ + where: { + id: eventId, + clubId, + status: { [Op.ne]: 'deleted' } + } + }); + + if (!event) { + return res.status(404).json({ message: '이벤트를 찾을 수 없습니다.' }); + } + + if(endDate == 'Invalid Date') { + endDate = null; + } + const updateData = { + title, + description, + startDate: startDate ? new Date(startDate) : event.startDate, + endDate: endDate ? new Date(endDate) : null, + location, + maxParticipants: maxParticipants || event.maxParticipants, + eventType: eventType || event.eventType, + entryFee: entryFee !== undefined ? entryFee : event.entryFee, + gameCount: gameCount || event.gameCount, + laneCount: laneCount || event.laneCount, + status: status || event.status + }; + + await event.update(updateData); + + const updatedEvent = await Event.findOne({ + where: { id: eventId }, + include: [ + { + model: Club, + attributes: ['id', 'name'] + }, + { + model: EventParticipant, + as: 'participants', + attributes: ['id', 'status'], + where: { + status: { [Op.ne]: '취소' } + } + } + ] + }); + + res.json({ + message: '이벤트가 수정되었습니다.', + event: { + ...updatedEvent.toJSON(), + participantCount: updatedEvent.participants ? updatedEvent.participants.length : 0, + clubName: updatedEvent.Club ? updatedEvent.Club.name : '' + } + }); + } catch (error) { + console.error('이벤트 수정 오류:', error); + res.status(500).json({ message: '이벤트 수정 중 오류가 발생했습니다.' }); + } +}; + +// 이벤트 삭제 (소프트 삭제) +exports.deleteEvent = async (req, res) => { + try { + const eventId = req.params.id; + const { clubId } = req.body; + + console.log(eventId, clubId, req.body, req.params); + if (!eventId || !clubId) { + return res.status(400).json({ message: '이벤트 ID와 클럽 ID가 필요합니다.' }); + } + + const event = await Event.findOne({ + where: { + id: eventId, + clubId, + status: { [Op.ne]: 'deleted' } + }, + include: [ + { + model: Club, + attributes: ['id', 'name'] + } + ] + }); + + if (!event) { + return res.status(404).json({ message: '이벤트를 찾을 수 없습니다.' }); + } + + // 참가자 정보도 함께 삭제 처리 + await EventParticipant.update( + { status: '취소' }, + { + where: { + eventId, + status: { [Op.ne]: '취소' } + } + } + ); + + await event.update({ status: 'deleted' }); + + res.json({ + message: '이벤트가 삭제되었습니다.', + event: { + id: event.id, + title: event.title, + clubId: event.clubId, + clubName: event.Club ? event.Club.name : '' + } + }); + } catch (error) { + console.error('이벤트 삭제 오류:', error); + res.status(500).json({ message: '이벤트 삭제 중 오류가 발생했습니다.' }); + } +}; + +// 이벤트 점수 조회 +exports.getEventScores = async (req, res) => { + try { + const eventId = req.params.id; + + if (!eventId) { + return res.status(400).json({ message: '이벤트 ID가 필요합니다.' }); + } + + // 이벤트 존재 여부 확인 + const event = await Event.findOne({ + where: { + id: eventId, + status: { [Op.ne]: 'deleted' } + } + }); + + if (!event) { + return res.status(404).json({ message: '이벤트를 찾을 수 없습니다.' }); + } + + const scores = await EventScore.findAll({ + where: { eventId }, + include: [ + { + model: EventParticipant, + as: 'participant', + attributes: ['id', 'status'], + include: [{ + model: Member, + attributes: ['id', 'name'] + }] + } + ], + order: [ + ['gameNumber', 'ASC'], + ['score', 'DESC'] + ] + }); + + return res.status(200).json(scores); + } catch (error) { + console.error('점수 조회 오류:', error); + res.status(500).json({ message: '점수 조회 중 오류가 발생했습니다.' }); + } +}; + +// 점수 등록 +exports.addEventScore = async (req, res) => { + try { + const eventId = req.params.id; + const { + participantId, + gameNumber, + score, + handicap = 0 + } = req.body; + + if (!eventId || !participantId || !gameNumber || score === undefined) { + return res.status(400).json({ + message: '이벤트 ID, 참가자 ID, 게임 번호, 점수가 필요합니다.' + }); + } + + // 이벤트 존재 여부 확인 + const event = await Event.findOne({ + where: { + id: eventId, + status: { [Op.ne]: 'deleted' } + } + }); + + if (!event) { + return res.status(404).json({ message: '이벤트를 찾을 수 없습니다.' }); + } + + // 참가자 확인 + const participant = await EventParticipant.findOne({ + where: { + id: participantId, + eventId, + status: { [Op.ne]: '취소' } + } + }); + + if (!participant) { + return res.status(400).json({ message: '이벤트 참가자가 아닙니다.' }); + } + + // 게임 번호 유효성 검사 + if (gameNumber < 1 || gameNumber > event.gameCount) { + return res.status(400).json({ + message: `게임 번호는 1부터 ${event.gameCount}까지만 입력 가능합니다.` + }); + } + + // 점수 유효성 검사 + if (score < 0 || score > 300) { + return res.status(400).json({ + message: '점수는 0에서 300 사이여야 합니다.' + }); + } + + // 동일한 게임의 점수가 이미 있는지 확인 + const existingScore = await EventScore.findOne({ + where: { + eventId, + participantId, + gameNumber + } + }); + + if (existingScore) { + return res.status(400).json({ + message: '해당 게임의 점수가 이미 등록되어 있습니다.' + }); + } + + // 점수 등록 + const totalScore = score + handicap; + const newScore = await EventScore.create({ + eventId, + participantId, + gameNumber, + score, + handicap, + totalScore + }); + + // 참가자 평균 점수 업데이트 + const allScores = await EventScore.findAll({ + where: { + eventId, + participantId + } + }); + + const totalGames = allScores.length; + const averageScore = Math.round(allScores.reduce((sum, s) => sum + s.score, 0) / totalGames); + + await participant.update({ + games: totalGames, + average: averageScore + }); + + res.status(201).json({ + message: '점수가 등록되었습니다.', + score: { + id: newScore.id, + eventId: newScore.eventId, + participantId: newScore.participantId, + gameNumber: newScore.gameNumber, + score: newScore.score, + handicap: newScore.handicap, + totalScore: newScore.totalScore, + createdAt: newScore.createdAt + } + }); + } catch (error) { + console.error('점수 등록 오류:', error); + res.status(500).json({ message: '점수 등록 중 오류가 발생했습니다.' }); + } +}; + +// 점수 수정 +exports.updateEventScore = async (req, res) => { + try { + const eventId = Number(req.params.id); + const scoreId = Number(req.params.scoreId); + const { + gameNumber, + score, + handicap = 0 + } = req.body; + + console.log('Updating score:', { + params: { eventId, scoreId }, + body: { gameNumber, score, handicap } + }); + + if (!eventId || !scoreId || !gameNumber || score === undefined || score === null) { + return res.status(400).json({ + message: '이벤트 ID, 점수 ID, 게임 번호, 점수가 필요합니다.', + received: { eventId, scoreId, gameNumber, score } + }); + } + + // 점수 레코드 확인 + const scoreRecord = await EventScore.findOne({ + where: { + id: scoreId, + eventId + } + }); + + // 점수 레코드가 없으면 참가자 ID를 가져와서 새로 생성 + if (!scoreRecord) { + // 기존 점수에서 참가자 ID 가져오기 + const existingScore = await EventScore.findOne({ + where: { eventId }, + order: [['createdAt', 'DESC']] + }); + + if (!existingScore) { + return res.status(404).json({ + message: '참가자 정보를 찾을 수 없습니다.' + }); + } + + // 새로운 게임 점수 생성 + const newScore = await EventScore.create({ + eventId, + participantId: existingScore.participantId, + teamNumber: existingScore.teamNumber, + gameNumber, + score, + handicap, + totalScore: score + handicap + }); + + return res.json({ + message: '새로운 게임 점수가 등록되었습니다.', + score: newScore + }); + } + + // 기존 레코드 업데이트 + await scoreRecord.update({ + gameNumber, + score, + handicap, + totalScore: score + handicap + }); + + // 업데이트된 레코드 조회 + const updatedScore = await EventScore.findOne({ + where: { + id: scoreId, + eventId + } + }); + + res.json({ + message: '점수가 수정되었습니다.', + score: updatedScore + }); + + } catch (error) { + console.error('점수 수정 오류:', error); + res.status(500).json({ + message: '점수 수정 중 오류가 발생했습니다.', + error: error.message + }); + } +}; + +// 점수 삭제 +exports.deleteEventScore = async (req, res) => { + try { + const { eventId, clubId, scoreId } = req.body; + + if (!eventId || !clubId || !scoreId) { + return res.status(400).json({ + message: '이벤트 ID, 클럽 ID, 점수 ID가 필요합니다.' + }); + } + + // 이벤트 존재 여부 확인 + const event = await Event.findOne({ + where: { + id: eventId, + clubId, + status: { [Op.ne]: 'deleted' } + } + }); + + if (!event) { + return res.status(404).json({ message: '이벤트를 찾을 수 없습니다.' }); + } + + // 점수 존재 여부 확인 + const score = await EventScore.findOne({ + where: { + id: scoreId, + eventId + }, + include: [ + { + model: EventParticipant, + as: 'participant', + attributes: ['id', 'status'], + include: [{ + model: Member, + attributes: ['id', 'name'] + }] + } + ] + }); + + if (!score) { + return res.status(404).json({ message: '점수를 찾을 수 없습니다.' }); + } + + await score.destroy(); + + res.json({ + message: '점수가 삭제되었습니다.', + score: { + id: score.id, + userId: score.userId, + userName: score.participant.Member ? score.participant.Member.name : '알 수 없음', + gameNumber: score.gameNumber, + frameNumber: score.frameNumber + } + }); + } catch (error) { + console.error('점수 삭제 오류:', error); + res.status(500).json({ message: '점수 삭제 중 오류가 발생했습니다.' }); + } +}; + +// 참가자의 모든 점수 삭제 +exports.deleteParticipantScores = async (req, res) => { + try { + const eventId = Number(req.params.id); + const participantId = Number(req.params.participantId); + + if (!eventId || !participantId) { + return res.status(400).json({ + message: '이벤트 ID와 참가자 ID가 필요합니다.' + }); + } + + // 점수 삭제 + await EventScore.destroy({ + where: { + eventId, + participantId + } + }); + + res.json({ + message: '점수가 삭제되었습니다.' + }); + } catch (error) { + console.error('점수 삭제 오류:', error); + res.status(500).json({ + message: '점수 삭제 중 오류가 발생했습니다.' + }); + } +}; + +// 이벤트 통계 조회 +exports.getEventStats = async (req, res) => { + try { + const eventId = parseInt(req.params.id); + const { clubId } = req.query; + + if (!clubId) { + return res.status(400).json({ message: '클럽 ID가 필요합니다.' }); + } + + const stats = await EventScore.findAll({ + where: { eventId }, + attributes: [ + 'userId', + [sequelize.fn('AVG', sequelize.col('score')), 'averageScore'], + [sequelize.fn('MAX', sequelize.col('score')), 'highScore'], + [sequelize.fn('COUNT', sequelize.literal('CASE WHEN "isStrike" = true THEN 1 END')), 'strikeCount'], + [sequelize.fn('COUNT', sequelize.literal('CASE WHEN "isSpare" = true THEN 1 END')), 'spareCount'] + ], + include: [{ + model: EventParticipant, + as: 'participant', + attributes: [], + include: [{ + model: Member, + attributes: ['name'] + }] + }], + group: ['userId', 'participant.id', 'participant.Member.id', 'participant.Member.name'] + }); + + res.json(stats); + } catch (error) { + console.error('통계 조회 오류:', error); + res.status(500).json({ message: '통계 조회 중 오류가 발생했습니다.' }); + } +}; + +// 캘린더용 이벤트 조회 +exports.getCalendarEvents = async (req, res) => { + try { + const { clubId } = req.query; + const { start, end } = req.query; + + if (!clubId) { + return res.status(400).json({ message: '클럽 ID가 필요합니다.' }); + } + + const where = { + clubId, + status: { [Op.ne]: 'deleted' } + }; + + if (start && end) { + where.startDate = { + [Op.between]: [new Date(start), new Date(end)] + }; + } + + const events = await Event.findAll({ + where, + attributes: [ + 'id', + 'title', + 'description', + 'startDate', + 'endDate', + 'location', + 'eventType', + 'status' + ], + order: [['startDate', 'ASC']] + }); + + const calendarEvents = events.map(event => ({ + id: event.id, + title: event.title, + start: event.startDate, + end: event.endDate, + description: event.description, + location: event.location, + eventType: event.eventType, + status: event.status + })); + + res.json(calendarEvents); + } catch (error) { + console.error('캘린더 이벤트 조회 오류:', error); + res.status(500).json({ message: '캘린더 이벤트 조회 중 오류가 발생했습니다.' }); + } +}; + +// 이벤트 참가 신청 +exports.registerForEvent = async (req, res) => { + try { + const eventId = parseInt(req.params.id); + const { clubId } = req.query; + const { userId, teamNumber } = req.body; + + if (!clubId) { + return res.status(400).json({ message: '클럽 ID가 필요합니다.' }); + } + + const event = await Event.findOne({ + where: { + id: eventId, + clubId, + status: { [Op.ne]: 'deleted' } + } + }); + + if (!event) { + return res.status(404).json({ message: '이벤트를 찾을 수 없습니다.' }); + } + + // 이미 등록된 참가자인지 확인 + const existingParticipant = await EventParticipant.findOne({ + where: { + eventId, + userId, + status: { [Op.ne]: '취소' } + } + }); + + if (existingParticipant) { + return res.status(400).json({ message: '이미 등록된 참가자입니다.' }); + } + + const participant = await EventParticipant.create({ + eventId, + userId, + teamNumber, + status: 'registered', + registeredAt: new Date() + }); + + res.status(201).json(participant); + } catch (error) { + console.error('참가 신청 오류:', error); + res.status(500).json({ message: '참가 신청 중 오류가 발생했습니다.' }); + } +}; + +// 참가자 제거 +exports.removeParticipant = async (req, res) => { + try { + const { id: eventId, userId } = req.params; + const { clubId } = req.query; + + if (!clubId) { + return res.status(400).json({ message: '클럽 ID가 필요합니다.' }); + } + + const participant = await EventParticipant.findOne({ + where: { + eventId, + userId, + status: { [Op.ne]: '취소' } + }, + include: [{ + model: Event, + where: { + clubId, + status: { [Op.ne]: 'deleted' } + } + }] + }); + + if (!participant) { + return res.status(404).json({ message: '참가자를 찾을 수 없습니다.' }); + } + + await participant.update({ status: '취소' }); + res.json({ message: '참가자가 제거되었습니다.' }); + } catch (error) { + console.error('참가자 제거 오류:', error); + res.status(500).json({ message: '참가자 제거 중 오류가 발생했습니다.' }); + } +}; + +// 참가자 상태 업데이트 +exports.updateParticipantStatus = async (req, res) => { + try { + const { id: eventId, userId } = req.params; + const { clubId } = req.query; + const { status, attendance, paymentStatus, paymentAmount } = req.body; + + if (!clubId) { + return res.status(400).json({ message: '클럽 ID가 필요합니다.' }); + } + + const participant = await EventParticipant.findOne({ + where: { + eventId, + userId, + status: { [Op.ne]: '취소' } + }, + include: [{ + model: Event, + where: { + clubId, + status: { [Op.ne]: 'deleted' } + } + }] + }); + + if (!participant) { + return res.status(404).json({ message: '참가자를 찾을 수 없습니다.' }); + } + + await participant.update({ + status, + attendance, + paymentStatus, + paymentAmount + }); + + res.json(participant); + } catch (error) { + console.error('참가자 상태 업데이트 오류:', error); + res.status(500).json({ message: '참가자 상태 업데이트 중 오류가 발생했습니다.' }); + } +}; + +// 클럽별 이벤트 조회 +exports.getEventsByClub = async (req, res) => { + try { + const clubId = parseInt(req.params.clubId); + + const events = await Event.findAll({ + where: { + clubId, + status: 'active' + }, + order: [['startDate', 'ASC']] + }); + + res.json(events); + } catch (error) { + console.error('클럽별 이벤트 조회 오류:', error); + res.status(500).json({ message: '클럽별 이벤트 목록을 가져오는 중 오류가 발생했습니다.' }); + } +}; + +// 사용자별 참가 이벤트 조회 +exports.getUserEvents = async (req, res) => { + try { + const userId = req.user.id; + + const events = await Event.findAll({ + include: [ + { + model: EventParticipant, + as: 'participants', + where: { userId }, + attributes: [] + }, + { + model: Club, + attributes: ['id', 'name'] + } + ], + order: [['startDate', 'ASC']] + }); + + res.json(events); + } catch (error) { + console.error('사용자별 이벤트 조회 오류:', error); + res.status(500).json({ message: '사용자별 이벤트 목록을 가져오는 중 오류가 발생했습니다.' }); + } +}; + +// 이벤트 참가자 목록 조회 +exports.getEventParticipants = async (req, res) => { + try { + const { eventId, clubId } = req.body; + + if (!eventId || !clubId) { + return res.status(400).json({ message: '이벤트 ID와 클럽 ID가 필요합니다.' }); + } + + // 이벤트 존재 여부 확인 + const event = await Event.findOne({ + where: { + id: eventId, + clubId, + status: { [Op.ne]: 'deleted' } + } + }); + + if (!event) { + return res.status(404).json({ message: '이벤트를 찾을 수 없습니다.' }); + } + + const participants = await EventParticipant.findAll({ + where: { + eventId, + status: { [Op.ne]: '취소' } + }, + include: [ + { + model: Member, + attributes: ['id', 'name', 'email', 'phone', 'memberType'] + } + ], + order: [ + ['createdAt', 'ASC'] + ] + }); + + const formattedParticipants = participants.map(participant => ({ + ...participant.toJSON(), + })); + + res.json({ + message: '참가자 목록 조회가 완료되었습니다.', + participants: formattedParticipants + }); + } catch (error) { + console.error('참가자 목록 조회 오류:', error); + res.status(500).json({ message: '참가자 목록을 가져오는 중 오류가 발생했습니다.' }); + } +}; + +// 이벤트 참가자 등록 +exports.addEventParticipant = async (req, res) => { + try { + const { + clubId, + memberId, + teamNumber, + paymentStatus, + paymentAmount + } = req.body; + const eventId = req.params.id; + if (!eventId || !clubId || !memberId) { + return res.status(400).json({ + message: '이벤트 ID, 클럽 ID, 사용자 ID가 필요합니다.' + }); + } + + // 이벤트 존재 여부 확인 + const event = await Event.findOne({ + where: { + id: eventId, + clubId, + status: { [Op.ne]: 'deleted' } + } + }); + + if (!event) { + return res.status(404).json({ message: '이벤트를 찾을 수 없습니다.' }); + } + + // 클럽 회원 확인 + const member = await Member.findOne({ + where: { + id: memberId, + clubId, + status: 'active' + } + }); + + if (!member) { + return res.status(400).json({ message: '클럽 회원이 아닙니다.' }); + } + + // 이미 참가 신청했는지 확인 + const existingParticipant = await EventParticipant.findOne({ + where: { + eventId, + memberId, + status: { [Op.ne]: '취소' } + } + }); + + if (existingParticipant) { + return res.status(400).json({ message: '이미 참가 신청한 사용자입니다.' }); + } + + // 참가자 수 제한 확인 + if (event.maxParticipants > 0) { + const currentParticipants = await EventParticipant.count({ + where: { + eventId, + status: { [Op.ne]: '취소' } + } + }); + + if (currentParticipants >= event.maxParticipants) { + return res.status(400).json({ message: '참가자 수가 초과되었습니다.' }); + } + } + + const participant = await EventParticipant.create({ + eventId, + memberId, + teamNumber: teamNumber || 1, + status: req.body.status || '참가예정', + attendance: false, + paymentStatus: req.body.paymentStatus || '미납', + paymentAmount: paymentAmount || event.entryFee || 0, + registeredAt: new Date() + }); + + // 참가자 정보 조회 + const participantWithDetails = await EventParticipant.findOne({ + where: { id: participant.id }, + include: [ + { + model: Member, + attributes: ['id', 'name'] + } + ] + }); + + res.status(201).json({ + message: '참가자가 등록되었습니다.', + participant: participantWithDetails + }); + } catch (error) { + console.error('참가자 등록 오류:', error); + res.status(500).json({ message: '참가자 등록 중 오류가 발생했습니다.' }); + } +}; + +// 참가자 정보 수정 +exports.updateEventParticipant = async (req, res) => { + try { + const eventId = req.params.id; + const { id, clubId, memberId, status, paymentStatus } = req.body; + + if (!eventId || !clubId || !memberId || !id) { + return res.status(400).json({ message: '이벤트 ID, 클럽 ID, 사용자 ID가 필요합니다.' }); + } + + // 이벤트 존재 여부 확인 + const event = await Event.findOne({ + where: { + id: eventId, + clubId, + status: { [Op.ne]: 'deleted' } + } + }); + + if (!event) { + return res.status(404).json({ message: '이벤트를 찾을 수 없습니다.' }); + } + + // 참가자 정보 수정 + const [updatedCount] = await EventParticipant.update( + { status, paymentStatus }, + { + where: { + id, + eventId, + memberId + } + } + ); + + if (updatedCount === 0) { + return res.status(404).json({ message: '수정할 참가자 정보를 찾을 수 없습니다.' }); + } + + // 수정된 참가자 정보 조회 + const updatedParticipant = await EventParticipant.findOne({ + where: { id }, + include: [ + { + model: Member, + attributes: ['id', 'name', 'email', 'phone', 'memberType'] + } + ] + }); + + res.json({ + message: '참가자 정보가 수정되었습니다.', + participant: { + ...updatedParticipant.toJSON(), + userName: updatedParticipant.Member ? updatedParticipant.Member.name : '알 수 없음', + userEmail: updatedParticipant.Member ? updatedParticipant.Member.email : '', + userPhone: updatedParticipant.Member ? updatedParticipant.Member.phone : '', + userType: updatedParticipant.Member ? updatedParticipant.Member.memberType : '' + } + }); + } catch (error) { + console.error('참가자 정보 수정 오류:', error); + res.status(500).json({ message: '참가자 정보 수정 중 오류가 발생했습니다.' }); + } +}; + +// 참가자 삭제 (소프트 삭제) +exports.deleteEventParticipant = async (req, res) => { + try { + const eventId = req.params.id; + const { id } = req.body; + + if (!eventId || !id) { + return res.status(400).json({ + message: '이벤트 ID, 참가자 ID가 필요합니다.' + }); + } + + // 이벤트 존재 여부 확인 + const event = await Event.findOne({ + where: { + id: eventId, + status: { [Op.ne]: 'deleted' } + } + }); + + if (!event) { + return res.status(404).json({ message: '이벤트를 찾을 수 없습니다.' }); + } + + // 참가자 존재 여부 확인 + const participant = await EventParticipant.findOne({ + where: { + eventId, + id, + status: { [Op.ne]: '취소' } + }, + include: [ + { + model: Member, + attributes: ['id', 'name', 'memberType'] + } + ] + }); + + if (!participant) { + return res.status(404).json({ message: '참가자를 찾을 수 없습니다.' }); + } + + // 참가자의 점수도 함께 삭제 + await EventScore.destroy({ + where: { + eventId, + participantId: id + } + }); + + await participant.update({ status: '취소' }); + + res.json({ + message: '참가자가 삭제되었습니다.', + participant: { + id: participant.id + } + }); + } catch (error) { + console.error('참가자 삭제 오류:', error); + res.status(500).json({ message: '참가자 삭제 중 오류가 발생했습니다.' }); + } +}; diff --git a/backend/controllers/featureController.js b/backend/controllers/featureController.js new file mode 100644 index 0000000..6adfd47 --- /dev/null +++ b/backend/controllers/featureController.js @@ -0,0 +1,203 @@ +const { Feature, ClubFeature, Club } = require('../models'); +const { Op } = require('sequelize'); + +// 전체 기능 목록 조회 +exports.getFeatures = async (req, res) => { + try { + const features = await Feature.findAll({ + where: { + status: 'active' + }, + attributes: ['id', 'name', 'description', 'price'] + }); + res.json(features); + } catch (error) { + console.error('기능 목록 조회 오류:', error); + res.status(500).json({ message: '기능 목록을 가져오는 중 오류가 발생했습니다.' }); + } +}; + +// 사용 가능한 기능 목록 조회 +exports.getAvailableFeatures = async (req, res) => { + try { + const { clubId } = req.query; + + if (!clubId) { + return res.status(400).json({ message: '클럽 ID가 필요합니다.' }); + } + + // 모든 기능 목록 조회 + const features = await Feature.findAll({ + where: { + status: 'active' + }, + attributes: ['id', 'name', 'description', 'price'] + }); + + // 클럽의 활성화된 기능 조회 + const activeFeatures = await ClubFeature.findAll({ + where: { + clubId, + expiryDate: { + [Op.gt]: new Date() + } + }, + attributes: ['featureId'] + }); + + const activeFeatureIds = activeFeatures.map(af => af.featureId); + + // 기능 목록에 활성화 상태 추가 + const formattedFeatures = features.map(feature => ({ + ...feature.toJSON(), + isActive: activeFeatureIds.includes(feature.id) + })); + + res.json(formattedFeatures); + } catch (error) { + console.error('사용 가능한 기능 목록 조회 오류:', error); + res.status(500).json({ message: '기능 목록을 가져오는 중 오류가 발생했습니다.' }); + } +}; + +// 활성화된 기능 목록 조회 +exports.getActiveFeatures = async (req, res) => { + try { + const { clubId } = req.query; + + if (!clubId) { + return res.status(400).json({ message: '클럽 ID가 필요합니다.' }); + } + + const activeFeatures = await ClubFeature.findAll({ + where: { + clubId, + expiryDate: { + [Op.gt]: new Date() + } + }, + include: [{ + model: Feature, + as: 'feature', + attributes: ['name', 'description'] + }], + attributes: ['id', 'featureId', 'purchaseDate', 'expiryDate'] + }); + + res.json(activeFeatures); + } catch (error) { + console.error('활성화된 기능 목록 조회 오류:', error); + res.status(500).json({ message: '활성화된 기능 목록을 가져오는 중 오류가 발생했습니다.' }); + } +}; + +// 기능 구매 내역 조회 +exports.getFeaturePayments = async (req, res) => { + try { + const { clubId } = req.query; + + if (!clubId) { + return res.status(400).json({ message: '클럽 ID가 필요합니다.' }); + } + + const payments = await ClubFeature.findAll({ + where: { + clubId + }, + include: [{ + model: Feature, + as: 'feature', + attributes: ['name'] + }], + attributes: [ + 'id', + 'featureId', + 'purchaseDate', + 'expiryDate', + 'amount', + 'duration', + 'status' + ], + order: [['purchaseDate', 'DESC']] + }); + + const formattedPayments = payments.map(payment => ({ + id: payment.id, + featureName: payment.feature.name, + purchaseDate: payment.purchaseDate, + expiryDate: payment.expiryDate, + amount: payment.amount, + duration: payment.duration, + status: payment.status + })); + + res.json(formattedPayments); + } catch (error) { + console.error('기능 구매 내역 조회 오류:', error); + res.status(500).json({ message: '구매 내역을 가져오는 중 오류가 발생했습니다.' }); + } +}; + +// 기능 구매 +exports.purchaseFeature = async (req, res) => { + try { + const { clubId, featureId, duration } = req.body; + + if (!clubId) { + return res.status(400).json({ message: '클럽 ID가 필요합니다.' }); + } + + // 기능 정보 조회 + const feature = await Feature.findByPk(featureId); + if (!feature) { + return res.status(404).json({ message: '존재하지 않는 기능입니다.' }); + } + + // 클럽 정보 조회 + const club = await Club.findByPk(clubId); + if (!club) { + return res.status(404).json({ message: '존재하지 않는 클럽입니다.' }); + } + + // 이미 활성화된 기능인지 확인 + const existingFeature = await ClubFeature.findOne({ + where: { + clubId, + featureId, + expiryDate: { + [Op.gt]: new Date() + } + } + }); + + if (existingFeature) { + return res.status(400).json({ message: '이미 활성화된 기능입니다.' }); + } + + // 구매 금액 계산 (duration에 따른 할인 적용 가능) + const amount = feature.price * duration; + + // 기능 구매 기록 생성 + const purchaseDate = new Date(); + const expiryDate = new Date(); + expiryDate.setMonth(expiryDate.getMonth() + duration); + + const clubFeature = await ClubFeature.create({ + clubId, + featureId, + purchaseDate, + expiryDate, + amount, + duration, + status: 'active' + }); + + res.json({ + message: '기능 구매가 완료되었습니다.', + purchaseInfo: clubFeature + }); + } catch (error) { + console.error('기능 구매 오류:', error); + res.status(500).json({ message: '기능 구매 중 오류가 발생했습니다.' }); + } +}; diff --git a/backend/controllers/menuController.js b/backend/controllers/menuController.js new file mode 100644 index 0000000..ead1d3e --- /dev/null +++ b/backend/controllers/menuController.js @@ -0,0 +1,166 @@ +const { Menu, Feature, ClubFeature, ClubSubscription, SubscriptionPlan, Member } = require('../models'); +const { Op } = require('sequelize'); + +/** + * 사용자의 접근 가능한 메뉴 목록을 반환합니다. + */ +exports.getUserMenus = async (req, res) => { + try { + const { clubId, role = 'user' } = req.body; + const userId = req.user.id; // JWT 토큰에서 사용자 ID 가져오기 + + if (!clubId) { + return res.status(400).json({ + success: false, + message: '클럽 ID가 필요합니다.' + }); + } + + // 멤버 정보 조회 + const member = await Member.findOne({ + where: { + clubId, + userId + } + }); + + const menus = await getAccessibleMenus(clubId, role, member); + + res.json({ + success: true, + data: menus + }); + } catch (error) { + console.error('메뉴 조회 중 오류 발생:', error); + res.status(500).json({ + success: false, + message: '메뉴 조회 중 오류가 발생했습니다.' + }); + } +} + +/** + * 사용자 권한과 클럽의 구독/기능 상태에 따라 접근 가능한 메뉴 목록을 조회합니다. + */ +async function getAccessibleMenus(clubId, userRole, member) { + try { + // 1. 클럽의 현재 구독 정보 조회 + const clubSubscription = await ClubSubscription.findOne({ + where: { + clubId, + status: 'active', + endDate: { + [Op.gt]: new Date() + } + }, + include: [{ + model: SubscriptionPlan, + attributes: ['name'] + }] + }); + + // 2. 클럽의 활성화된 기능 목록 조회 + const activeFeatures = await ClubFeature.findAll({ + where: { + clubId, + enabled: true, + [Op.or]: [ + { expiryDate: null }, + { expiryDate: { [Op.gt]: new Date() } } + ] + }, + include: [{ + model: Feature, + as: 'feature', + attributes: ['code'] + }] + }); + + const activeFeatureCodes = activeFeatures.map(f => f.feature.code); + const subscriptionTier = clubSubscription ? clubSubscription.SubscriptionPlan.name.toLowerCase() : 'basic'; + + // 3. 메뉴 조회 및 필터링 + const allMenus = await Menu.findAll({ + where: { + visible: true, + [Op.and]: [ + // 역할 기반 필터링 + { + [Op.or]: [ + { role: userRole }, + { role: 'user' } + ] + }, + // 구독 등급 기반 필터링 + { + [Op.or]: [ + { requiredSubscriptionTier: null }, + { requiredSubscriptionTier: subscriptionTier } + ] + } + ] + }, + include: [{ + model: Menu, + as: 'children', + required: false + }], + order: [ + ['order', 'ASC'], + [{ model: Menu, as: 'children' }, 'order', 'ASC'] + ] + }); + + // 4. 기능 코드 기반 필터링 및 메뉴 구조화 + const processMenu = (menu) => { + // 기능 코드가 있는 경우 접근 권한 확인 + if (menu.featureCode && !activeFeatureCodes.includes(menu.featureCode)) { + return null; + } + + const processedMenu = { + id: menu.id, + title: menu.title, + icon: menu.icon, + to: menu.to, + order: menu.order + }; + + // 하위 메뉴 처리 + if (menu.children && menu.children.length > 0) { + const children = menu.children + .map(processMenu) + .filter(child => child !== null); + + if (children.length > 0) { + processedMenu.children = children; + } + } + + return processedMenu; + }; + + // 최상위 메뉴만 필터링 (parentId가 null인 항목) + const rootMenus = allMenus + .filter(menu => !menu.parentId) + .map(processMenu) + .filter(menu => menu !== null); + + // 구독 관리 메뉴 추가 (모임장, 운영진만 접근 가능) + if (member && (member.memberType === '모임장' || member.memberType === '운영진')) { + rootMenus.push({ + id: 'subscription', + title: '구독/결제 관리', + icon: 'pi pi-credit-card', + to: '/club/subscription', + order: 90, + children: [] + }); + } + + return rootMenus; + } catch (error) { + console.error('메뉴 조회 중 오류 발생:', error); + throw error; + } +} \ No newline at end of file diff --git a/backend/controllers/notificationController.js b/backend/controllers/notificationController.js new file mode 100644 index 0000000..cb95fb2 --- /dev/null +++ b/backend/controllers/notificationController.js @@ -0,0 +1,217 @@ +/** + * 알림 컨트롤러 + * + * 볼링 클럽 매니저에서 지원하는 알림 유형: + * 1. 시스템 알림 + * - 시스템 업데이트, 유지보수 공지 등 + * - 아이콘: pi pi-info-circle + * - 타입: info + * + * 2. 클럽 관련 알림 + * - 새 회원 가입, 클럽 정보 변경 등 + * - 아이콘: pi pi-users + * - 타입: info 또는 success + * + * 3. 모임/대회 알림 + * - 모임 생성, 대회 일정, 참가 신청 등 + * - 아이콘: pi pi-calendar 또는 pi pi-flag + * - 타입: info 또는 warning + * + * 4. 점수 관련 알림 + * - 새 점수 등록, 평균 점수 갱신 등 + * - 아이콘: pi pi-chart-bar + * - 타입: success + * + * 5. 결제/구독 알림 + * - 결제 완료, 구독 만료 예정 등 + * - 아이콘: pi pi-credit-card 또는 pi pi-clock + * - 타입: success 또는 warning + * + * 6. 오류/경고 알림 + * - 시스템 오류, 보안 경고 등 + * - 아이콘: pi pi-exclamation-triangle + * - 타입: error 또는 warning + * + * 알림 생성 방법: + * 1. API 엔드포인트를 통한 생성: POST /api/notifications + * 2. 내부 함수를 통한 생성: createSystemNotification() + * + * 알림 필드: + * - user: 알림을 받을 사용자 ID (필수) + * - title: 알림 제목 (필수) + * - message: 알림 내용 (필수) + * - icon: 알림 아이콘 (PrimeIcons 사용) + * - type: 알림 타입 (info, success, warning, error) + * - link: 알림 클릭 시 이동할 링크 (옵션) + * - read: 읽음 상태 (기본값: false) + */ + +const User = require('../models/User'); +const Notification = require('../models/Notification'); +const { validationResult } = require('express-validator'); + +// 사용자의 알림 목록 조회 +exports.getUserNotifications = async (req, res) => { + try { + const userId = req.user.id; + + // 사용자의 알림 목록 조회 (최신순으로 정렬) + const notifications = await Notification.findAll({ + where: { userId }, + order: [['createdAt', 'DESC']], + limit: 10 + }); + + res.status(200).json(notifications); + } catch (error) { + console.error('알림 조회 오류:', error); + res.status(500).json({ message: '서버 오류가 발생했습니다.' }); + } +}; + +// 알림 읽음 표시 +exports.markAsRead = async (req, res) => { + try { + const { notificationId } = req.params; + const userId = req.user.id; + + const notification = await Notification.findOne({ + where: { + id: notificationId, + userId + } + }); + + if (!notification) { + return res.status(404).json({ message: '알림을 찾을 수 없습니다.' }); + } + + notification.read = true; + await notification.save(); + + res.status(200).json({ message: '알림이 읽음 처리되었습니다.' }); + } catch (error) { + console.error('알림 읽음 표시 오류:', error); + res.status(500).json({ message: '서버 오류가 발생했습니다.' }); + } +}; + +// 모든 알림 읽음 표시 +exports.markAllAsRead = async (req, res) => { + try { + const userId = req.user.id; + + await Notification.update( + { read: true }, + { where: { userId, read: false } } + ); + + res.status(200).json({ message: '모든 알림이 읽음 처리되었습니다.' }); + } catch (error) { + console.error('모든 알림 읽음 표시 오류:', error); + res.status(500).json({ message: '서버 오류가 발생했습니다.' }); + } +}; + +// 알림 생성 (관리자용) +exports.createNotification = async (req, res) => { + try { + const errors = validationResult(req); + if (!errors.isEmpty()) { + return res.status(400).json({ errors: errors.array() }); + } + + const { userId, title, message, icon, type, link } = req.body; + + const newNotification = await Notification.create({ + userId, + title, + message, + icon: icon || 'pi pi-info-circle', + type: type || 'info', + link, + read: false + }); + + // Socket.IO를 통해 실시간 알림 전송 + const io = req.app.get('io'); + if (io) { + io.to(`user:${userId}`).emit('notification', { + id: newNotification.id, + title: newNotification.title, + message: newNotification.message, + icon: newNotification.icon, + type: newNotification.type, + link: newNotification.link, + createdAt: newNotification.createdAt + }); + } + + res.status(201).json(newNotification); + } catch (error) { + console.error('알림 생성 오류:', error); + res.status(500).json({ message: '서버 오류가 발생했습니다.' }); + } +}; + +// 알림 삭제 +exports.deleteNotification = async (req, res) => { + try { + const { notificationId } = req.params; + const userId = req.user.id; + + const notification = await Notification.findOne({ + where: { + id: notificationId, + userId + } + }); + + if (!notification) { + return res.status(404).json({ message: '알림을 찾을 수 없습니다.' }); + } + + await notification.destroy(); + + res.status(200).json({ message: '알림이 삭제되었습니다.' }); + } catch (error) { + console.error('알림 삭제 오류:', error); + res.status(500).json({ message: '서버 오류가 발생했습니다.' }); + } +}; + +// 시스템 알림 생성 (내부 함수) +exports.createSystemNotification = async (userId, title, message, options = {}) => { + try { + const { icon, type, link } = options; + + const newNotification = await Notification.create({ + userId, + title, + message, + icon: icon || 'pi pi-info-circle', + type: type || 'info', + link, + read: false + }); + + // Socket.IO를 통해 실시간 알림 전송 + const io = global.io; + if (io) { + io.to(`user:${userId}`).emit('notification', { + id: newNotification.id, + title: newNotification.title, + message: newNotification.message, + icon: newNotification.icon, + type: newNotification.type, + link: newNotification.link, + createdAt: newNotification.createdAt + }); + } + + return newNotification; + } catch (error) { + console.error('시스템 알림 생성 오류:', error); + return null; + } +}; diff --git a/backend/controllers/subscriptionController.js b/backend/controllers/subscriptionController.js new file mode 100644 index 0000000..826550d --- /dev/null +++ b/backend/controllers/subscriptionController.js @@ -0,0 +1,237 @@ +const { SubscriptionPlan, SubscriptionPlanFeature, ClubSubscription, Feature, Club, User } = require('../models'); + +// 전체 구독 목록 조회 +exports.getClubSubscriptions = async (req, res) => { + try { + const subscriptions = await ClubSubscription.findAll({ + include: [ + { + model: SubscriptionPlan, + include: [{ + model: Feature, + through: { attributes: ['status'] } + }] + }, + { + model: Club, + include: [{ + model: User, + as: 'owner', + attributes: ['id', 'name', 'email'] + }] + } + ] + }); + res.json(subscriptions); + } catch (error) { + console.error('Error in getClubSubscriptions:', error); + res.status(500).json({ message: '구독 목록 조회 중 오류가 발생했습니다.' }); + } +}; + +// 구독 플랜 목록 조회 +exports.getSubscriptionPlans = async (req, res) => { + try { + const plans = await SubscriptionPlan.findAll({ + include: [{ + model: Feature, + through: { attributes: ['status'] } + }] + }); + res.json(plans); + } catch (error) { + console.error('Error in getSubscriptionPlans:', error); + res.status(500).json({ message: '구독 플랜 목록 조회 중 오류가 발생했습니다.' }); + } +}; + +// 구독 플랜 생성 +exports.createSubscriptionPlan = async (req, res) => { + try { + const { name, description, maxMembers, price, features } = req.body; + + const plan = await SubscriptionPlan.create({ + name, + description, + maxMembers, + price + }); + + if (features && features.length > 0) { + const featureAssociations = features.map(featureId => ({ + planId: plan.id, + featureId, + status: 'active' + })); + await SubscriptionPlanFeature.bulkCreate(featureAssociations); + } + + const createdPlan = await SubscriptionPlan.findByPk(plan.id, { + include: [{ + model: Feature, + through: { attributes: ['status'] } + }] + }); + + res.status(201).json(createdPlan); + } catch (error) { + console.error('Error in createSubscriptionPlan:', error); + res.status(500).json({ message: '구독 플랜 생성 중 오류가 발생했습니다.' }); + } +}; + +// 구독 플랜 수정 +exports.updateSubscriptionPlan = async (req, res) => { + try { + const { id } = req.params; + const { name, description, maxMembers, price, features } = req.body; + + const plan = await SubscriptionPlan.findByPk(id); + if (!plan) { + return res.status(404).json({ message: '구독 플랜을 찾을 수 없습니다.' }); + } + + await plan.update({ + name, + description, + maxMembers, + price + }); + + if (features) { + // 기존 기능 연결 삭제 + await SubscriptionPlanFeature.destroy({ + where: { planId: id } + }); + + // 새로운 기능 연결 생성 + if (features.length > 0) { + const featureAssociations = features.map(featureId => ({ + planId: id, + featureId, + status: 'active' + })); + await SubscriptionPlanFeature.bulkCreate(featureAssociations); + } + } + + const updatedPlan = await SubscriptionPlan.findByPk(id, { + include: [{ + model: Feature, + through: { attributes: ['status'] } + }] + }); + + res.json(updatedPlan); + } catch (error) { + console.error('Error in updateSubscriptionPlan:', error); + res.status(500).json({ message: '구독 플랜 수정 중 오류가 발생했습니다.' }); + } +}; + +// 구독 플랜 삭제 +exports.deleteSubscriptionPlan = async (req, res) => { + try { + const { id } = req.params; + + // 플랜이 존재하는지 확인 + const plan = await SubscriptionPlan.findByPk(id); + if (!plan) { + return res.status(404).json({ message: '구독 플랜을 찾을 수 없습니다.' }); + } + + // 플랜과 연결된 기능 관계 삭제 + await SubscriptionPlanFeature.destroy({ + where: { planId: id } + }); + + // 플랜 삭제 + await plan.destroy(); + + res.json({ message: '구독 플랜이 삭제되었습니다.' }); + } catch (error) { + console.error('Error in deleteSubscriptionPlan:', error); + res.status(500).json({ message: '구독 플랜 삭제 중 오류가 발생했습니다.' }); + } +}; + +// 클럽 구독 생성/수정 +exports.subscribeClub = async (req, res) => { + try { + const { clubId, planId, startDate, expiryDate } = req.body; + + // 기존 구독 확인 + let subscription = await ClubSubscription.findOne({ + where: { clubId, status: 'active' } + }); + + if (subscription) { + // 기존 구독 만료 처리 + await subscription.update({ + status: 'expired', + updatedAt: new Date() + }); + } + + // 새로운 구독 생성 + subscription = await ClubSubscription.create({ + clubId, + planId, + startDate: new Date(startDate), + expiryDate: new Date(expiryDate), + status: 'active' + }); + + res.status(201).json(subscription); + } catch (error) { + console.error('Error in subscribeClub:', error); + res.status(500).json({ message: '클럽 구독 처리 중 오류가 발생했습니다.' }); + } +}; + +// 클럽의 현재 구독 정보 조회 +exports.getClubSubscription = async (req, res) => { + try { + const { clubId } = req.params; + + const subscription = await ClubSubscription.findOne({ + where: { clubId, status: 'active' }, + include: [{ + model: SubscriptionPlan, + include: [{ + model: Feature, + through: { attributes: ['status'] } + }] + }] + }); + + if (!subscription) { + return res.status(404).json({ message: '활성화된 구독 정보가 없습니다.' }); + } + + res.json(subscription); + } catch (error) { + console.error('Error in getClubSubscription:', error); + res.status(500).json({ message: '구독 정보 조회 중 오류가 발생했습니다.' }); + } +}; + +// 플랜별 구독 목록 조회 +exports.getPlanSubscriptions = async (req, res) => { + try { + const { planId } = req.params; + + const subscriptions = await ClubSubscription.findAll({ + where: { planId }, + include: [{ + model: Club, + attributes: ['id', 'name'] + }] + }); + + res.json(subscriptions); + } catch (error) { + console.error('Error in getPlanSubscriptions:', error); + res.status(500).json({ message: '플랜별 구독 목록 조회 중 오류가 발생했습니다.' }); + } +}; diff --git a/backend/controllers/userController.js b/backend/controllers/userController.js new file mode 100644 index 0000000..5a6bdce --- /dev/null +++ b/backend/controllers/userController.js @@ -0,0 +1,91 @@ +const User = require('../models/User'); + +/** + * 전체 사용자 목록 조회 + */ +const getAllUsers = async (req, res) => { + try { + const users = await User.findAll({ + attributes: { + exclude: ['password'] + }, + order: [['createdAt', 'DESC']] + }); + res.json(users); + } catch (error) { + console.error('사용자 목록 조회 중 오류 발생:', error); + res.status(500).json({ message: '사용자 목록을 가져오는 중 오류가 발생했습니다.' }); + } +}; + +/** + * 단일 사용자 정보 조회 + */ +const getUser = async (req, res) => { + try { + const userId = req.params.id; + const user = await User.findByPk(userId, { + attributes: { exclude: ['password'] } + }); + + if (!user) { + return res.status(404).json({ message: '사용자를 찾을 수 없습니다.' }); + } + + res.json(user); + } catch (error) { + console.error('사용자 정보 조회 중 오류 발생:', error); + res.status(500).json({ message: '사용자 정보를 가져오는 중 오류가 발생했습니다.' }); + } +}; + +/** + * 사용자 정보 수정 + */ +const updateUser = async (req, res) => { + try { + const userId = req.params.id; + const { name, email, role, status } = req.body; + + const user = await User.findByPk(userId); + if (!user) { + return res.status(404).json({ message: '사용자를 찾을 수 없습니다.' }); + } + + // 이메일 중복 확인 (이메일이 변경된 경우에만) + if (email !== user.email) { + const existingUser = await User.findOne({ where: { email } }); + if (existingUser) { + return res.status(400).json({ message: '이미 사용 중인 이메일입니다.' }); + } + } + + // 필수 필드 확인 + if (!name || !email || !role || !status) { + return res.status(400).json({ message: '필수 정보가 누락되었습니다.' }); + } + + await user.update({ + name, + email, + role, + status, + updatedAt: new Date() + }); + + const updatedUser = await User.findByPk(userId, { + attributes: { exclude: ['password'] } + }); + + res.json(updatedUser); + } catch (error) { + console.error('사용자 정보 수정 중 오류 발생:', error); + res.status(500).json({ message: '사용자 정보를 수정하는 중 오류가 발생했습니다.' }); + } +}; + +module.exports = { + getAllUsers, + getUser, + updateUser +}; diff --git a/backend/database_schema.sql b/backend/database_schema.sql new file mode 100644 index 0000000..ab3876b --- /dev/null +++ b/backend/database_schema.sql @@ -0,0 +1,209 @@ +-- 볼링 매니저 데이터베이스 스키마 +-- 데이터베이스 생성 +CREATE DATABASE IF NOT EXISTS bowlingManager; +USE bowlingManager; + +-- 사용자 테이블 +CREATE TABLE IF NOT EXISTS Users ( + id INT AUTO_INCREMENT PRIMARY KEY, + username VARCHAR(255) NOT NULL UNIQUE, + password VARCHAR(255) NOT NULL, + name VARCHAR(255) NOT NULL, + email VARCHAR(255) NOT NULL UNIQUE, + role ENUM('admin', 'clubadmin', 'user') NOT NULL DEFAULT 'user', + status ENUM('active', 'inactive', 'suspended') NOT NULL DEFAULT 'active', + lastLoginAt DATETIME, + createdAt DATETIME NOT NULL, + updatedAt DATETIME NOT NULL +); + +-- 클럽 테이블 +CREATE TABLE IF NOT EXISTS Clubs ( + id INT AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(255) NOT NULL, + description TEXT, + ownerId INT NOT NULL, + location VARCHAR(255), + contact VARCHAR(255), + status ENUM('active', 'inactive', 'suspended') NOT NULL DEFAULT 'active', + memberCount INT NOT NULL DEFAULT 0, + createdAt DATETIME NOT NULL, + updatedAt DATETIME NOT NULL, + FOREIGN KEY (ownerId) REFERENCES Users(id) +); + +-- 기능 테이블 +CREATE TABLE IF NOT EXISTS Features ( + id INT AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(255) NOT NULL, + description TEXT, + code VARCHAR(255) NOT NULL UNIQUE, + price INT NOT NULL DEFAULT 0, + status ENUM('active', 'inactive', 'suspended') NOT NULL DEFAULT 'active', + createdAt DATETIME NOT NULL, + updatedAt DATETIME NOT NULL +); + +-- 구독 플랜 테이블 +CREATE TABLE IF NOT EXISTS SubscriptionPlans ( + id INT AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(255) NOT NULL, + description TEXT, + maxMembers INT NOT NULL, + price INT NOT NULL DEFAULT 0, + status ENUM('active', 'inactive') NOT NULL DEFAULT 'active', + createdAt DATETIME NOT NULL, + updatedAt DATETIME NOT NULL +); + +-- 클럽 구독 테이블 +CREATE TABLE IF NOT EXISTS ClubSubscriptions ( + id INT AUTO_INCREMENT PRIMARY KEY, + clubId INT NOT NULL, + planId INT NOT NULL, + status ENUM('active', 'expired', 'suspended') NOT NULL DEFAULT 'active', + startDate DATETIME NOT NULL, + endDate DATETIME NOT NULL, + price INT NOT NULL DEFAULT 0, + createdAt DATETIME NOT NULL, + updatedAt DATETIME NOT NULL, + FOREIGN KEY (clubId) REFERENCES Clubs(id), + FOREIGN KEY (planId) REFERENCES SubscriptionPlans(id) +); + +-- 구독 플랜 기능 테이블 +CREATE TABLE IF NOT EXISTS SubscriptionPlanFeatures ( + id INT AUTO_INCREMENT PRIMARY KEY, + planId INT NOT NULL, + featureId INT NOT NULL, + status ENUM('active', 'inactive') NOT NULL DEFAULT 'active', + createdAt DATETIME NOT NULL, + updatedAt DATETIME NOT NULL, + FOREIGN KEY (planId) REFERENCES SubscriptionPlans(id), + FOREIGN KEY (featureId) REFERENCES Features(id) +); + +-- 클럽 기능 테이블 (N:M 관계) +CREATE TABLE IF NOT EXISTS ClubFeatures ( + id INT AUTO_INCREMENT PRIMARY KEY, + clubId INT NOT NULL, + featureId INT NOT NULL, + enabled BOOLEAN NOT NULL DEFAULT TRUE, + expiryDate DATETIME, + createdAt DATETIME NOT NULL, + updatedAt DATETIME NOT NULL, + FOREIGN KEY (clubId) REFERENCES Clubs(id), + FOREIGN KEY (featureId) REFERENCES Features(id) +); + +-- 회원 테이블 +CREATE TABLE IF NOT EXISTS Members ( + id INT AUTO_INCREMENT PRIMARY KEY, + clubId INT NOT NULL, + userId INT, + name VARCHAR(255) NOT NULL, + gender ENUM('남성', '여성') NOT NULL, + memberType ENUM('정회원', '준회원', '운영진', '모임장') NOT NULL DEFAULT '준회원', + handicap INT NOT NULL DEFAULT 0, + average FLOAT NOT NULL DEFAULT 0, + games INT NOT NULL DEFAULT 0, + phone VARCHAR(255), + email VARCHAR(255), + joinDate DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + status ENUM('active', 'inactive', 'suspended') NOT NULL DEFAULT 'active', + createdAt DATETIME NOT NULL, + updatedAt DATETIME NOT NULL, + FOREIGN KEY (clubId) REFERENCES Clubs(id), + FOREIGN KEY (userId) REFERENCES Users(id) +); + +-- 이벤트 테이블 +CREATE TABLE IF NOT EXISTS `Events` ( + `id` int NOT NULL AUTO_INCREMENT, + `clubId` int NOT NULL, + `title` varchar(255) NOT NULL, + `description` text, + `eventType` enum('정기전', '번개', '연습', '교류전', '이벤트', '기타') NOT NULL DEFAULT '정기전', + `startDate` datetime NOT NULL, + `endDate` datetime, + `location` varchar(255), + `gameCount` int NOT NULL DEFAULT 3, + `entryFee` int NOT NULL DEFAULT 0, + `maxParticipants` int NOT NULL DEFAULT 0, + `registrationDeadline` datetime, + `status` enum('active', 'inactive', 'deleted') NOT NULL DEFAULT 'active', + `createdAt` datetime NOT NULL, + `updatedAt` datetime NOT NULL, + PRIMARY KEY (`id`), + KEY `clubId` (`clubId`), + CONSTRAINT `events_ibfk_1` FOREIGN KEY (`clubId`) REFERENCES `Clubs` (`id`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; + +-- 이벤트 참가자 테이블 +CREATE TABLE IF NOT EXISTS EventParticipants ( + id INT AUTO_INCREMENT PRIMARY KEY, + eventId INT NOT NULL, + memberId INT NOT NULL, + status ENUM('참가예정', '참가완료', '취소') NOT NULL DEFAULT '참가예정', + paymentStatus ENUM('미납', '납부완료') NOT NULL DEFAULT '미납', + createdAt DATETIME NOT NULL, + updatedAt DATETIME NOT NULL, + FOREIGN KEY (eventId) REFERENCES Events(id), + FOREIGN KEY (memberId) REFERENCES Members(id) +); + +-- 이벤트 점수 테이블 +CREATE TABLE IF NOT EXISTS EventScores ( + id INT AUTO_INCREMENT PRIMARY KEY, + eventId INT NOT NULL, + participantId INT NOT NULL, + gameNumber INT NOT NULL, + score INT NOT NULL, + handicap INT NOT NULL DEFAULT 0, + totalScore INT NOT NULL, + strikes INT NOT NULL DEFAULT 0, + spares INT NOT NULL DEFAULT 0, + openFrames INT NOT NULL DEFAULT 0, + createdAt DATETIME NOT NULL, + updatedAt DATETIME NOT NULL, + FOREIGN KEY (eventId) REFERENCES Events(id), + FOREIGN KEY (participantId) REFERENCES EventParticipants(id) +); + +-- 메뉴 테이블 +CREATE TABLE IF NOT EXISTS menus ( + id INT AUTO_INCREMENT PRIMARY KEY, + title VARCHAR(255) NOT NULL, + icon VARCHAR(255), + `to` VARCHAR(255) NOT NULL, + role VARCHAR(255) NOT NULL DEFAULT 'user', + `order` INT NOT NULL DEFAULT 0, + visible BOOLEAN NOT NULL DEFAULT TRUE, + createdAt DATETIME NOT NULL, + updatedAt DATETIME NOT NULL +); + +-- 알림 테이블 +CREATE TABLE IF NOT EXISTS Notifications ( + id INT AUTO_INCREMENT PRIMARY KEY, + userId INT NOT NULL, + title VARCHAR(255) NOT NULL, + message TEXT NOT NULL, + icon VARCHAR(255) DEFAULT 'pi pi-info-circle', + type ENUM('info', 'success', 'warning', 'error') NOT NULL DEFAULT 'info', + read BOOLEAN NOT NULL DEFAULT FALSE, + link VARCHAR(255) DEFAULT NULL, + createdAt DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + updatedAt DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + FOREIGN KEY (userId) REFERENCES Users(id) ON DELETE CASCADE +); + +-- 관리자 활동 로그 테이블 +CREATE TABLE IF NOT EXISTS ActivityLogs ( + id INT AUTO_INCREMENT PRIMARY KEY, + userId INT NOT NULL, + type VARCHAR(50) NOT NULL, + description TEXT, + createdAt DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (userId) REFERENCES Users(id) ON DELETE CASCADE +); diff --git a/backend/middleware/auth.js b/backend/middleware/auth.js new file mode 100644 index 0000000..9b529fa --- /dev/null +++ b/backend/middleware/auth.js @@ -0,0 +1,26 @@ +const jwt = require('jsonwebtoken'); +require('dotenv').config(); + +module.exports = (req, res, next) => { + try { + // 헤더에서 토큰 가져오기 + const authHeader = req.headers.authorization; + + if (!authHeader || !authHeader.startsWith('Bearer ')) { + return res.status(401).json({ message: '인증 토큰이 필요합니다.' }); + } + + const token = authHeader.split(' ')[1]; + + // 토큰 검증 + const decoded = jwt.verify(token, process.env.JWT_SECRET || 'your-secret-key'); + + // 요청 객체에 사용자 정보 추가 + req.user = decoded; + + next(); + } catch (error) { + console.error('인증 오류:', error); + res.status(401).json({ message: '유효하지 않은 토큰입니다.' }); + } +}; diff --git a/backend/middleware/authMiddleware.js b/backend/middleware/authMiddleware.js new file mode 100644 index 0000000..6835b3b --- /dev/null +++ b/backend/middleware/authMiddleware.js @@ -0,0 +1,48 @@ +const jwt = require('jsonwebtoken'); + +/** + * JWT 토큰 인증 미들웨어 + * 요청 헤더에서 토큰을 추출하고 검증 + */ +exports.authenticateJWT = (req, res, next) => { + try { + // 헤더에서 토큰 가져오기 + const authHeader = req.headers.authorization; + + if (!authHeader || !authHeader.startsWith('Bearer ')) { + return res.status(401).json({ message: '인증 토큰이 필요합니다.' }); + } + + const token = authHeader.split(' ')[1]; + + // 토큰 검증 + const decoded = jwt.verify(token, process.env.JWT_SECRET); + + // 요청 객체에 사용자 정보 추가 + req.user = decoded; + + next(); + } catch (error) { + console.error('인증 오류:', error); + res.status(401).json({ message: '유효하지 않은 토큰입니다.' }); + } +}; + +/** + * 역할 기반 권한 부여 미들웨어 + * 특정 역할을 가진 사용자만 접근 허용 + * @param {string[]} roles - 허용된 역할 목록 + */ +exports.authorizeRoles = (...roles) => { + return (req, res, next) => { + if (!req.user) { + return res.status(401).json({ message: '인증이 필요합니다.' }); + } + + if (!roles.includes(req.user.role)) { + return res.status(403).json({ message: '이 작업을 수행할 권한이 없습니다.' }); + } + + next(); + }; +}; diff --git a/backend/models/Club.js b/backend/models/Club.js new file mode 100644 index 0000000..ae4d759 --- /dev/null +++ b/backend/models/Club.js @@ -0,0 +1,57 @@ +const { DataTypes } = require('sequelize'); +const { sequelize } = require('../config/database'); + +const Club = sequelize.define('Club', { + id: { + type: DataTypes.INTEGER, + primaryKey: true, + autoIncrement: true + }, + name: { + type: DataTypes.STRING, + allowNull: false + }, + description: { + type: DataTypes.TEXT, + allowNull: true + }, + ownerId: { + type: DataTypes.INTEGER, + allowNull: false, + references: { + model: 'Users', + key: 'id' + } + }, + location: { + type: DataTypes.STRING, + allowNull: true + }, + contact: { + type: DataTypes.STRING, + allowNull: true + }, + status: { + type: DataTypes.ENUM('active', 'inactive', 'suspended'), + allowNull: false, + defaultValue: 'active' + }, + memberCount: { + type: DataTypes.INTEGER, + allowNull: false, + defaultValue: 0 + }, + createdAt: { + type: DataTypes.DATE, + allowNull: false + }, + updatedAt: { + type: DataTypes.DATE, + allowNull: false + } +}, { + tableName: 'Clubs', + timestamps: true +}); + +module.exports = Club; diff --git a/backend/models/ClubFeature.js b/backend/models/ClubFeature.js new file mode 100644 index 0000000..bf0a036 --- /dev/null +++ b/backend/models/ClubFeature.js @@ -0,0 +1,48 @@ +const { DataTypes } = require('sequelize'); +const { sequelize } = require('../config/database'); + +const ClubFeature = sequelize.define('ClubFeature', { + id: { + type: DataTypes.INTEGER, + primaryKey: true, + autoIncrement: true + }, + clubId: { + type: DataTypes.INTEGER, + allowNull: false, + references: { + model: 'Clubs', + key: 'id' + } + }, + featureId: { + type: DataTypes.INTEGER, + allowNull: false, + references: { + model: 'Features', + key: 'id' + } + }, + enabled: { + type: DataTypes.BOOLEAN, + allowNull: false, + defaultValue: true + }, + expiryDate: { + type: DataTypes.DATE, + allowNull: true + }, + createdAt: { + type: DataTypes.DATE, + allowNull: false + }, + updatedAt: { + type: DataTypes.DATE, + allowNull: false + } +}, { + tableName: 'ClubFeatures', + timestamps: true +}); + +module.exports = ClubFeature; diff --git a/backend/models/ClubSubscription.js b/backend/models/ClubSubscription.js new file mode 100644 index 0000000..5139cc9 --- /dev/null +++ b/backend/models/ClubSubscription.js @@ -0,0 +1,49 @@ +const { DataTypes } = require('sequelize'); +const { sequelize } = require('../config/database'); + +const ClubSubscription = sequelize.define('ClubSubscription', { + id: { + type: DataTypes.INTEGER, + primaryKey: true, + autoIncrement: true + }, + clubId: { + type: DataTypes.INTEGER, + allowNull: false, + references: { + model: 'Clubs', + key: 'id' + } + }, + planId: { + type: DataTypes.INTEGER, + allowNull: false, + references: { + model: 'SubscriptionPlans', + key: 'id' + } + }, + status: { + type: DataTypes.ENUM('active', 'expired', 'suspended'), + defaultValue: 'active', + allowNull: false + }, + startDate: { + type: DataTypes.DATE, + allowNull: false + }, + endDate: { + type: DataTypes.DATE, + allowNull: false + }, + price: { + type: DataTypes.INTEGER, + allowNull: false, + defaultValue: 0 + } +}, { + tableName: 'ClubSubscriptions', + timestamps: true +}); + +module.exports = ClubSubscription; diff --git a/backend/models/Event.js b/backend/models/Event.js new file mode 100644 index 0000000..97dcfbf --- /dev/null +++ b/backend/models/Event.js @@ -0,0 +1,103 @@ +const { DataTypes } = require('sequelize'); +const { sequelize } = require('../config/database'); + +const Event = sequelize.define('Event', { + id: { + type: DataTypes.INTEGER, + primaryKey: true, + autoIncrement: true + }, + clubId: { + type: DataTypes.INTEGER, + allowNull: false, + references: { + model: 'Clubs', + key: 'id' + } + }, + title: { + type: DataTypes.STRING, + allowNull: false + }, + description: { + type: DataTypes.TEXT, + allowNull: true + }, + eventType: { + type: DataTypes.ENUM('정기전', '번개', '연습', '교류전', '이벤트', '기타'), + allowNull: false, + defaultValue: '정기전' + }, + startDate: { + type: DataTypes.DATE, + allowNull: false + }, + endDate: { + type: DataTypes.DATE, + allowNull: true + }, + location: { + type: DataTypes.STRING, + allowNull: true + }, + gameCount: { + type: DataTypes.INTEGER, + allowNull: false, + defaultValue: 3 + }, + participantFee: { + type: DataTypes.DECIMAL(10, 2), + allowNull: false, + defaultValue: 0 + }, + maxParticipants: { + type: DataTypes.INTEGER, + allowNull: false, + defaultValue: 0 + }, + registrationDeadline: { + type: DataTypes.DATE, + allowNull: true + }, + participantCount: { + type: DataTypes.INTEGER, + allowNull: false, + defaultValue: 0 + }, + status: { + type: DataTypes.ENUM('준비', '활성', '완료', '취소', '삭제'), + allowNull: false, + defaultValue: '활성' + }, + createdAt: { + type: DataTypes.DATE, + allowNull: false + }, + updatedAt: { + type: DataTypes.DATE, + allowNull: false + } +}, { + tableName: 'Events', + timestamps: true +}); + +// 관계 설정 +Event.associate = (models) => { + Event.belongsTo(models.Club, { + foreignKey: 'clubId', + as: 'club' + }); + + Event.hasMany(models.EventParticipant, { + foreignKey: 'eventId', + as: 'participants' + }); + + Event.hasMany(models.EventScore, { + foreignKey: 'eventId', + as: 'scores' + }); +}; + +module.exports = Event; diff --git a/backend/models/EventParticipant.js b/backend/models/EventParticipant.js new file mode 100644 index 0000000..61ce63c --- /dev/null +++ b/backend/models/EventParticipant.js @@ -0,0 +1,59 @@ +const { DataTypes } = require('sequelize'); +const { sequelize } = require('../config/database'); + +const EventParticipant = sequelize.define('EventParticipant', { + id: { + type: DataTypes.INTEGER, + primaryKey: true, + autoIncrement: true + }, + eventId: { + type: DataTypes.INTEGER, + allowNull: false, + references: { + model: 'Events', + key: 'id' + } + }, + memberId: { + type: DataTypes.INTEGER, + allowNull: false, + references: { + model: 'Members', + key: 'id' + } + }, + status: { + type: DataTypes.ENUM('참가예정', '참가확정', '취소'), + allowNull: false, + defaultValue: '참가예정' + }, + paymentStatus: { + type: DataTypes.ENUM('미납', '납부완료'), + allowNull: false, + defaultValue: '미납' + } +}, { + tableName: 'EventParticipants', + timestamps: true +}); + +// 관계 설정 +EventParticipant.associate = (models) => { + EventParticipant.belongsTo(models.Event, { + foreignKey: 'eventId', + as: 'event' + }); + + EventParticipant.belongsTo(models.Member, { + foreignKey: 'memberId', + as: 'member' + }); + + EventParticipant.hasMany(models.EventScore, { + foreignKey: 'participantId', + as: 'scores' + }); +}; + +module.exports = EventParticipant; diff --git a/backend/models/EventScore.js b/backend/models/EventScore.js new file mode 100644 index 0000000..96d0d90 --- /dev/null +++ b/backend/models/EventScore.js @@ -0,0 +1,52 @@ +const { DataTypes } = require('sequelize'); +const { sequelize } = require('../config/database'); + +const EventScore = sequelize.define('EventScore', { + id: { + type: DataTypes.INTEGER, + primaryKey: true, + autoIncrement: true + }, + eventId: { + type: DataTypes.INTEGER, + allowNull: false, + references: { + model: 'Events', + key: 'id' + } + }, + participantId: { + type: DataTypes.INTEGER, + allowNull: false, + references: { + model: 'EventParticipants', + key: 'id' + } + }, + teamNumber: { + type: DataTypes.INTEGER, + allowNull: true, + }, + gameNumber: { + type: DataTypes.INTEGER, + allowNull: false + }, + score: { + type: DataTypes.INTEGER, + allowNull: false + }, + handicap: { + type: DataTypes.INTEGER, + allowNull: false, + defaultValue: 0 + }, + totalScore: { + type: DataTypes.INTEGER, + allowNull: false + } +}, { + tableName: 'EventScores', + timestamps: true +}); + +module.exports = EventScore; diff --git a/backend/models/Feature.js b/backend/models/Feature.js new file mode 100644 index 0000000..7c607b0 --- /dev/null +++ b/backend/models/Feature.js @@ -0,0 +1,59 @@ +const { DataTypes } = require('sequelize'); +const { sequelize } = require('../config/database'); + +const Feature = sequelize.define('Feature', { + id: { + type: DataTypes.INTEGER, + primaryKey: true, + autoIncrement: true + }, + name: { + type: DataTypes.STRING, + allowNull: false + }, + description: { + type: DataTypes.TEXT, + allowNull: true + }, + code: { + type: DataTypes.STRING, + allowNull: false, + unique: true + }, + purchaseType: { + type: DataTypes.ENUM('subscription_only', 'separate_only', 'both'), + allowNull: false, + defaultValue: 'both' + }, + price: { + type: DataTypes.INTEGER, + allowNull: false, + defaultValue: 0 + }, + oneTimePurchasePrice: { + type: DataTypes.INTEGER, + allowNull: true + }, + monthlySubscriptionPrice: { + type: DataTypes.INTEGER, + allowNull: true + }, + status: { + type: DataTypes.ENUM('active', 'inactive', 'suspended'), + allowNull: false, + defaultValue: 'active' + }, + createdAt: { + type: DataTypes.DATE, + allowNull: false + }, + updatedAt: { + type: DataTypes.DATE, + allowNull: false + } +}, { + tableName: 'Features', + timestamps: true +}); + +module.exports = Feature; diff --git a/backend/models/Member.js b/backend/models/Member.js new file mode 100644 index 0000000..d297d8c --- /dev/null +++ b/backend/models/Member.js @@ -0,0 +1,91 @@ +const { DataTypes } = require('sequelize'); +const { sequelize } = require('../config/database'); +const User = require('./User'); +const Club = require('./Club'); + +const Member = sequelize.define('Member', { + id: { + type: DataTypes.INTEGER, + primaryKey: true, + autoIncrement: true + }, + clubId: { + type: DataTypes.INTEGER, + allowNull: false, + references: { + model: 'Clubs', + key: 'id' + } + }, + userId: { + type: DataTypes.INTEGER, + allowNull: true, + references: { + model: 'Users', + key: 'id' + } + }, + name: { + type: DataTypes.STRING, + allowNull: false + }, + gender: { + type: DataTypes.ENUM('남성', '여성'), + allowNull: false + }, + memberType: { + type: DataTypes.ENUM('정회원', '준회원', '운영진', '모임장'), + allowNull: false, + defaultValue: '준회원' + }, + handicap: { + type: DataTypes.INTEGER, + allowNull: false, + defaultValue: 0 + }, + average: { + type: DataTypes.FLOAT, + allowNull: false, + defaultValue: 0 + }, + games: { + type: DataTypes.INTEGER, + allowNull: false, + defaultValue: 0 + }, + phone: { + type: DataTypes.STRING, + allowNull: true + }, + email: { + type: DataTypes.STRING, + allowNull: true + }, + joinDate: { + type: DataTypes.DATE, + allowNull: false, + defaultValue: DataTypes.NOW + }, + status: { + type: DataTypes.ENUM('active', 'inactive', 'suspended', 'deleted'), + allowNull: false, + defaultValue: 'active' + } +}, { + tableName: 'Members', + timestamps: true +}); + +// User와의 관계 설정 +Member.belongsTo(User, { + foreignKey: 'userId', + as: 'user' +}); + +// Club과의 관계 설정 +Member.belongsTo(Club, { + foreignKey: 'clubId', + as: 'club' +}); + +module.exports = Member; diff --git a/backend/models/Menu.js b/backend/models/Menu.js new file mode 100644 index 0000000..a747b52 --- /dev/null +++ b/backend/models/Menu.js @@ -0,0 +1,70 @@ +const { DataTypes } = require('sequelize'); +const { sequelize } = require('../config/database'); + +const Menu = sequelize.define('Menu', { + id: { + type: DataTypes.INTEGER, + primaryKey: true, + autoIncrement: true + }, + parentId: { + type: DataTypes.INTEGER, + allowNull: true, + references: { + model: 'Menus', // 변경 + key: 'id' + } + }, + title: { + type: DataTypes.STRING, + allowNull: false + }, + icon: { + type: DataTypes.STRING, + allowNull: true + }, + to: { + type: DataTypes.STRING, + allowNull: false + }, + featureCode: { + type: DataTypes.STRING, + allowNull: true, + references: { + model: 'Features', + key: 'code' + } + }, + requiredSubscriptionTier: { + type: DataTypes.ENUM('basic', 'standard', 'premium'), + allowNull: true + }, + role: { + type: DataTypes.STRING, + allowNull: false, + defaultValue: 'user' + }, + order: { + type: DataTypes.INTEGER, + allowNull: false, + defaultValue: 0 + }, + visible: { + type: DataTypes.BOOLEAN, + allowNull: false, + defaultValue: true + }, + createdAt: { + type: DataTypes.DATE, + allowNull: false + }, + updatedAt: { + type: DataTypes.DATE, + allowNull: false + } +}, { + tableName: 'Menus', + timestamps: true +}); + +module.exports = Menu; diff --git a/backend/models/Notification.js b/backend/models/Notification.js new file mode 100644 index 0000000..fc26db0 --- /dev/null +++ b/backend/models/Notification.js @@ -0,0 +1,59 @@ +const { DataTypes } = require('sequelize'); +const { sequelize } = require('../config/database'); + +const Notification = sequelize.define('Notification', { + id: { + type: DataTypes.INTEGER, + primaryKey: true, + autoIncrement: true + }, + userId: { + type: DataTypes.INTEGER, + allowNull: false, + references: { + model: 'Users', + key: 'id' + } + }, + title: { + type: DataTypes.STRING, + allowNull: false + }, + message: { + type: DataTypes.TEXT, + allowNull: false + }, + icon: { + type: DataTypes.STRING, + defaultValue: 'pi pi-info-circle' + }, + type: { + type: DataTypes.ENUM('info', 'success', 'warning', 'error'), + defaultValue: 'info' + }, + read: { + type: DataTypes.BOOLEAN, + allowNull: false, + defaultValue: false + }, + link: { + type: DataTypes.STRING, + allowNull: true, + defaultValue: null + }, + createdAt: { + type: DataTypes.DATE, + allowNull: false, + defaultValue: DataTypes.NOW + }, + updatedAt: { + type: DataTypes.DATE, + allowNull: false, + defaultValue: DataTypes.NOW + } +}, { + timestamps: true, + tableName: 'Notifications' +}); + +module.exports = Notification; diff --git a/backend/models/Participant.js b/backend/models/Participant.js new file mode 100644 index 0000000..2f6da9d --- /dev/null +++ b/backend/models/Participant.js @@ -0,0 +1,63 @@ +const { DataTypes } = require('sequelize'); +const { sequelize } = require('../config/database'); + +const Participant = sequelize.define('Participant', { + id: { + type: DataTypes.INTEGER, + primaryKey: true, + autoIncrement: true + }, + meetingId: { + type: DataTypes.INTEGER, + allowNull: false, + references: { + model: 'Meetings', + key: 'id' + } + }, + memberId: { + type: DataTypes.INTEGER, + allowNull: false, + references: { + model: 'Members', + key: 'id' + } + }, + teamNumber: { + type: DataTypes.INTEGER, + allowNull: false, + defaultValue: 1 + }, + status: { + type: DataTypes.ENUM('참가예정', '참가완료', '취소'), + allowNull: false, + defaultValue: '참가예정' + }, + attendance: { + type: DataTypes.BOOLEAN, + defaultValue: false + }, + paymentStatus: { + type: DataTypes.ENUM('미납', '납부완료'), + allowNull: false, + defaultValue: '미납' + }, + paymentAmount: { + type: DataTypes.INTEGER, + allowNull: false, + defaultValue: 0 + }, + createdAt: { + type: DataTypes.DATE, + allowNull: false + }, + updatedAt: { + type: DataTypes.DATE, + allowNull: false + } +}, { + tableName: 'EventParticipants', + timestamps: true +}); + +module.exports = Participant; diff --git a/backend/models/SubscriptionPlan.js b/backend/models/SubscriptionPlan.js new file mode 100644 index 0000000..ba030c9 --- /dev/null +++ b/backend/models/SubscriptionPlan.js @@ -0,0 +1,36 @@ +const { DataTypes } = require('sequelize'); +const { sequelize } = require('../config/database'); + +const SubscriptionPlan = sequelize.define('SubscriptionPlan', { + id: { + type: DataTypes.INTEGER, + primaryKey: true, + autoIncrement: true + }, + name: { + type: DataTypes.STRING, + allowNull: false + }, + description: { + type: DataTypes.TEXT + }, + maxMembers: { + type: DataTypes.INTEGER, + allowNull: false + }, + price: { + type: DataTypes.INTEGER, + allowNull: false, + defaultValue: 0 + }, + status: { + type: DataTypes.ENUM('active', 'inactive'), + defaultValue: 'active', + allowNull: false + } +}, { + tableName: 'SubscriptionPlan', + timestamps: true +}); + +module.exports = SubscriptionPlan; diff --git a/backend/models/SubscriptionPlanFeature.js b/backend/models/SubscriptionPlanFeature.js new file mode 100644 index 0000000..1a2ad6d --- /dev/null +++ b/backend/models/SubscriptionPlanFeature.js @@ -0,0 +1,36 @@ +const { DataTypes } = require('sequelize'); +const { sequelize } = require('../config/database'); + +const SubscriptionPlanFeature = sequelize.define('SubscriptionPlanFeature', { + id: { + type: DataTypes.INTEGER, + primaryKey: true, + autoIncrement: true + }, + planId: { + type: DataTypes.INTEGER, + allowNull: false, + references: { + model: 'SubscriptionPlans', + key: 'id' + } + }, + featureId: { + type: DataTypes.INTEGER, + allowNull: false, + references: { + model: 'Features', + key: 'id' + } + }, + status: { + type: DataTypes.ENUM('active', 'inactive'), + defaultValue: 'active', + allowNull: false + } +}, { + tableName: 'SubscriptionPlanFeatures', + timestamps: true +}); + +module.exports = SubscriptionPlanFeature; diff --git a/backend/models/User.js b/backend/models/User.js new file mode 100644 index 0000000..555e046 --- /dev/null +++ b/backend/models/User.js @@ -0,0 +1,78 @@ +const { DataTypes } = require('sequelize'); +const { sequelize } = require('../config/database'); +const bcrypt = require('bcrypt'); + +const User = sequelize.define('User', { + id: { + type: DataTypes.INTEGER, + primaryKey: true, + autoIncrement: true + }, + username: { + type: DataTypes.STRING, + allowNull: false, + unique: true + }, + password: { + type: DataTypes.STRING, + allowNull: false + }, + name: { + type: DataTypes.STRING, + allowNull: false + }, + email: { + type: DataTypes.STRING, + allowNull: false, + unique: true, + validate: { + isEmail: true + } + }, + role: { + type: DataTypes.ENUM('admin', 'clubadmin', 'user'), + allowNull: false, + defaultValue: 'user' + }, + status: { + type: DataTypes.ENUM('active', 'inactive', 'suspended'), + allowNull: false, + defaultValue: 'active' + }, + lastLoginAt: { + type: DataTypes.DATE, + allowNull: true + }, + createdAt: { + type: DataTypes.DATE, + allowNull: false + }, + updatedAt: { + type: DataTypes.DATE, + allowNull: false + } +}, { + tableName: 'Users', + timestamps: true, + hooks: { + beforeCreate: async (user) => { + if (user.password) { + const salt = await bcrypt.genSalt(10); + user.password = await bcrypt.hash(user.password, salt); + } + }, + beforeUpdate: async (user) => { + if (user.changed('password')) { + const salt = await bcrypt.genSalt(10); + user.password = await bcrypt.hash(user.password, salt); + } + } + } +}); + +// 비밀번호 검증 메소드 +User.prototype.validatePassword = async function(password) { + return await bcrypt.compare(password, this.password); +}; + +module.exports = User; diff --git a/backend/models/activityLog.js b/backend/models/activityLog.js new file mode 100644 index 0000000..b7acd48 --- /dev/null +++ b/backend/models/activityLog.js @@ -0,0 +1,40 @@ +const { Model, DataTypes } = require('sequelize'); +const { sequelize } = require('../config/database'); + +class ActivityLog extends Model {} + +ActivityLog.init({ + id: { + type: DataTypes.INTEGER, + primaryKey: true, + autoIncrement: true + }, + userId: { + type: DataTypes.INTEGER, + allowNull: false, + references: { + model: 'Users', + key: 'id' + } + }, + type: { + type: DataTypes.STRING(50), + allowNull: false + }, + description: { + type: DataTypes.TEXT, + allowNull: true + }, + createdAt: { + type: DataTypes.DATE, + defaultValue: DataTypes.NOW + } +}, { + sequelize, + modelName: 'ActivityLog', + tableName: 'ActivityLogs', + timestamps: true, + updatedAt: false +}); + +module.exports = ActivityLog; diff --git a/backend/models/index.js b/backend/models/index.js new file mode 100644 index 0000000..a853b71 --- /dev/null +++ b/backend/models/index.js @@ -0,0 +1,855 @@ +const { sequelize } = require('../config/database'); +const User = require('./User'); +const Club = require('./Club'); +const Event = require('./Event'); +const EventParticipant = require('./EventParticipant'); +const EventScore = require('./EventScore'); +const Member = require('./Member'); +const Notification = require('./Notification'); +const Feature = require('./Feature'); +const ClubFeature = require('./ClubFeature'); +const Menu = require('./Menu'); +const SubscriptionPlan = require('./SubscriptionPlan'); +const SubscriptionPlanFeature = require('./SubscriptionPlanFeature'); +const ClubSubscription = require('./ClubSubscription'); +const ActivityLog = require('./activityLog'); + +// 관계 설정 +// User와 Member 관계 +User.hasMany(Member, { foreignKey: 'userId' }); +Member.belongsTo(User, { foreignKey: 'userId' }); + +// Club과 Member 관계 +Club.hasMany(Member, { foreignKey: 'clubId' }); +Member.belongsTo(Club, { foreignKey: 'clubId' }); + +// Club과 User 사이의 다대다 관계 추가 +User.belongsToMany(Club, { through: Member, foreignKey: 'userId' }); +Club.belongsToMany(User, { through: Member, foreignKey: 'clubId' }); + +// Club과 User 사이의 소유자 관계 추가 +Club.belongsTo(User, { as: 'owner', foreignKey: 'ownerId' }); +User.hasMany(Club, { as: 'ownedClubs', foreignKey: 'ownerId' }); + +// Club과 Event 관계 +Club.hasMany(Event, { foreignKey: 'clubId' }); +Event.belongsTo(Club, { foreignKey: 'clubId' }); + +// Event와 EventParticipant 관계 +Event.hasMany(EventParticipant, { foreignKey: 'eventId', as: 'participants' }); +EventParticipant.belongsTo(Event, { foreignKey: 'eventId' }); + +// Member와 EventParticipant 관계 +Member.hasMany(EventParticipant, { foreignKey: 'memberId' }); +EventParticipant.belongsTo(Member, { foreignKey: 'memberId' }); + +// Event와 EventScore 관계 +Event.hasMany(EventScore, { foreignKey: 'eventId', as: 'scores' }); +EventScore.belongsTo(Event, { foreignKey: 'eventId', as: 'event' }); + +// EventParticipant와 EventScore 관계 +EventParticipant.hasMany(EventScore, { foreignKey: 'participantId', as: 'scores' }); +EventScore.belongsTo(EventParticipant, { foreignKey: 'participantId', as: 'participant' }); + +// Feature와 Menu 관계 +Feature.hasMany(Menu, { foreignKey: 'featureCode', sourceKey: 'code' }); +Menu.belongsTo(Feature, { foreignKey: 'featureCode', targetKey: 'code' }); + +// Menu 자기 참조 관계 +Menu.belongsTo(Menu, { as: 'parent', foreignKey: 'parentId' }); +Menu.hasMany(Menu, { as: 'children', foreignKey: 'parentId' }); + +// Club과 Feature의 관계 +Club.belongsToMany(Feature, { through: ClubFeature, foreignKey: 'clubId' }); +Feature.belongsToMany(Club, { through: ClubFeature, foreignKey: 'featureId' }); + +// Feature와 ClubFeature 관계 +Feature.hasMany(ClubFeature, { foreignKey: 'featureId', as: 'clubFeatures' }); +ClubFeature.belongsTo(Feature, { foreignKey: 'featureId', as: 'feature' }); + +// Club과 ClubFeature 관계 +Club.hasMany(ClubFeature, { foreignKey: 'clubId', as: 'features' }); +ClubFeature.belongsTo(Club, { foreignKey: 'clubId', as: 'club' }); + +// User와 Notification 관계 +User.hasMany(Notification, { foreignKey: 'userId' }); +Notification.belongsTo(User, { foreignKey: 'userId' }); + +// Club과 SubscriptionPlan의 관계 (through ClubSubscription) +Club.belongsToMany(SubscriptionPlan, { + through: ClubSubscription, + foreignKey: 'clubId', + otherKey: 'planId' +}); +SubscriptionPlan.belongsToMany(Club, { + through: ClubSubscription, + foreignKey: 'planId', + otherKey: 'clubId' +}); + +// SubscriptionPlan과 Feature의 관계 (through SubscriptionPlanFeature) +SubscriptionPlan.belongsToMany(Feature, { + through: SubscriptionPlanFeature, + foreignKey: 'planId', + otherKey: 'featureId' +}); +Feature.belongsToMany(SubscriptionPlan, { + through: SubscriptionPlanFeature, + foreignKey: 'featureId', + otherKey: 'planId' +}); + +// ClubSubscription과 SubscriptionPlan의 관계 +ClubSubscription.belongsTo(SubscriptionPlan, { foreignKey: 'planId' }); +SubscriptionPlan.hasMany(ClubSubscription, { foreignKey: 'planId' }); + +// ClubSubscription과 Club의 관계 +ClubSubscription.belongsTo(Club, { foreignKey: 'clubId' }); +Club.hasMany(ClubSubscription, { foreignKey: 'clubId' }); + +// 모델 동기화 함수 +const syncModels = async () => { + try { + // 외래 키 제약 조건을 비활성화하고 테이블 재생성 + // 개발 + // await sequelize.query('SET FOREIGN_KEY_CHECKS = 0'); + // await sequelize.sync({ force: true }); + // await sequelize.query('SET FOREIGN_KEY_CHECKS = 1'); + // await createAdminUser(); + // await createSampleData(); + + // 운영 alter: true - 구조변경만 alter처리 + await sequelize.sync({ alter: true }); +} catch (error) { + console.error('모델 동기화 중 오류 발생:', error); + } +}; + +// 초기 관리자 계정 생성 함수 +const createAdminUser = async () => { + try { + const adminExists = await User.findOne({ + where: { email: process.env.ADMIN_EMAIL } + }); + + if (!adminExists) { + await User.create({ + username: 'master', + name: '관리자', + email: process.env.ADMIN_EMAIL, + password: process.env.ADMIN_PASSWORD, + role: 'admin', + status: 'active', + createdAt: new Date(), + updatedAt: new Date() + }); + console.log('관리자 계정이 생성되었습니다.'); + } + } catch (error) { + console.error('관리자 계정 생성 중 오류 발생:', error); + } +}; + +// 초기 샘플 데이터 생성 함수 +const createSampleData = async () => { + try { + // 1. 기존 데이터 삭제 (순서 중요) + await ClubFeature.destroy({ where: {} }); + await ClubSubscription.destroy({ where: {} }); + await Member.destroy({ where: {} }); + await Club.destroy({ where: {} }); + await Feature.destroy({ where: {} }); + await SubscriptionPlan.destroy({ where: {} }); + await User.destroy({ where: {} }); + console.log('기존 데이터가 모두 삭제되었습니다.'); + + // 2. 샘플 사용자 데이터 생성 + const sampleUsers = await User.bulkCreate([ + { + username: 'hong', + name: '홍길동', + email: 'hong@test.com', + password: '1234', + role: 'user', + status: 'active', + createdAt: new Date(), + updatedAt: new Date() + }, + { + username: 'kim', + name: '김철수', + email: 'kim@test.com', + password: '1234', + role: 'user', + status: 'active', + createdAt: new Date(), + updatedAt: new Date() + }, + { + username: 'lee', + name: '이영희', + email: 'lee@test.com', + password: '1234', + role: 'user', + status: 'active', + createdAt: new Date(), + updatedAt: new Date() + } + ]); + console.log('샘플 사용자 데이터가 생성되었습니다:', sampleUsers.map(user => user.id)); + + // 3. 샘플 클럽 데이터 생성 + const clubs = await Club.bulkCreate([ + { + name: '서울 볼링 클럽', + description: '서울 지역 볼링 동호회입니다.', + location: '서울시 강남구', + ownerId: sampleUsers[0].id, + status: 'active', + memberCount: 3, + createdAt: new Date(), + updatedAt: new Date() + }, + { + name: '부산 볼링 클럽', + description: '부산 지역 볼링 동호회입니다.', + location: '부산시 해운대구', + ownerId: sampleUsers[1].id, + status: 'active', + memberCount: 3, + createdAt: new Date(), + updatedAt: new Date() + }, + { + name: '대전 볼링 클럽', + description: '대전 지역 볼링 동호회입니다.', + location: '대전시 유성구', + ownerId: sampleUsers[2].id, + status: 'active', + memberCount: 3, + createdAt: new Date(), + updatedAt: new Date() + } + ]); + console.log('샘플 클럽 데이터가 생성되었습니다:', clubs.map(club => club.id)); + + // 샘플 구독 플랜 데이터 생성 + const subscriptionPlans = [ + { + name: 'Basic', + description: '기본적인 클럽 관리 기능을 제공합니다.', + price: 30000, + duration: 1, + maxMembers: 30, + features: ['member_management', 'event_basic'], + status: 'active' + }, + { + name: 'Premium', + description: '고급 분석 기능과 함께 더 많은 회원을 관리할 수 있습니다.', + price: 50000, + duration: 1, + maxMembers: 100, + features: ['member_management', 'event_basic', 'analytics_basic', 'lane_booking'], + status: 'active' + }, + { + name: 'Enterprise', + description: '무제한 회원 관리와 모든 프리미엄 기능을 제공합니다.', + price: 100000, + duration: 1, + maxMembers: 999999, + features: ['member_management', 'event_basic', 'analytics_basic', 'lane_booking', 'tournament', 'analytics_advanced'], + status: 'active' + } + ]; + + await SubscriptionPlan.bulkCreate(subscriptionPlans); + console.log('샘플 구독 플랜 데이터가 생성되었습니다.'); + + // 샘플 클럽 구독 데이터 생성 + const clubSubscriptions = [ + { + clubId: 1, + planId: 3, // Enterprise 플랜 + startDate: new Date(), + endDate: new Date(new Date().setMonth(new Date().getMonth() + 12)), + amount: 1200000, // 연간 구독 + status: 'active', + paymentStatus: 'paid' + }, + { + clubId: 2, + planId: 2, // Premium 플랜 + startDate: new Date(), + endDate: new Date(new Date().setMonth(new Date().getMonth() + 6)), + amount: 300000, // 6개월 구독 + status: 'active', + paymentStatus: 'paid' + }, + { + clubId: 3, + planId: 1, // Basic 플랜 + startDate: new Date(), + endDate: new Date(new Date().setMonth(new Date().getMonth() + 3)), + amount: 90000, // 3개월 구독 + status: 'active', + paymentStatus: 'paid' + } + ]; + + await ClubSubscription.bulkCreate(clubSubscriptions); + console.log('샘플 클럽 구독 데이터가 생성되었습니다.'); + + // 샘플 기능 데이터 생성 + const features = [ + { + code: 'member_management', + name: '회원 관리', + description: '클럽 회원 관리 기능', + purchaseType: 'subscription_only', + monthlySubscriptionPrice: 0, + createdAt: new Date(), + updatedAt: new Date() + }, + { + code: 'event_management', + name: '이벤트 관리', + description: '클럽 이벤트 관리 기능', + purchaseType: 'subscription_only', + monthlySubscriptionPrice: 5000, + createdAt: new Date(), + updatedAt: new Date() + }, + { + code: 'event_calendar', + name: '이벤트 캘린더', + description: '클럽 이벤트 캘린더 기능', + purchaseType: 'subscription_only', + monthlySubscriptionPrice: 5000, + createdAt: new Date(), + updatedAt: new Date() + }, + { + code: 'statistics', + name: '통계', + description: '클럽 통계 기능', + purchaseType: 'subscription_only', + monthlySubscriptionPrice: 10000, + createdAt: new Date(), + updatedAt: new Date() + } + ]; + + // 기능 데이터 생성 + await Feature.bulkCreate(features); + console.log('샘플 기능 데이터가 생성되었습니다.'); + + // 샘플 클럽 회원 데이터 생성 + const members = [ + { + clubId: 1, + userId: 2, + name: '홍길동', + gender: '남성', + memberType: '모임장', + status: 'active', + joinDate: new Date(), + createdAt: new Date(), + updatedAt: new Date() + }, + { + clubId: 1, + userId: 3, + name: '김철수', + gender: '남성', + memberType: '정회원', + status: 'active', + joinDate: new Date(), + createdAt: new Date(), + updatedAt: new Date() + }, + { + clubId: 2, + userId: 3, + name: '김철수', + gender: '남성', + memberType: '모임장', + status: 'active', + joinDate: new Date(), + createdAt: new Date(), + updatedAt: new Date() + }, + { + clubId: 2, + userId: 4, + name: '이영희', + gender: '여성', + memberType: '정회원', + status: 'active', + joinDate: new Date(), + createdAt: new Date(), + updatedAt: new Date() + }, + { + clubId: 3, + userId: 4, + name: '이영희', + gender: '여성', + memberType: '모임장', + status: 'active', + joinDate: new Date(), + createdAt: new Date(), + updatedAt: new Date() + }, + { + clubId: 3, + userId: 2, + name: '홍길동', + gender: '남성', + memberType: '정회원', + status: 'active', + joinDate: new Date(), + createdAt: new Date(), + updatedAt: new Date() + }, + { + clubId: 1, + userId: null, + name: '정회원A', + gender: '여성', + memberType: '정회원', + status: 'active', + joinDate: new Date('2023-01-15'), + createdAt: new Date('2023-01-16'), + updatedAt: new Date('2023-01-17') + }, + { + clubId: 2, + userId: null, + name: '준회원B', + gender: '남성', + memberType: '준회원', + status: 'inactive', + joinDate: new Date('2023-02-20'), + createdAt: new Date('2023-02-21'), + updatedAt: new Date('2023-02-22') + }, + { + clubId: 3, + userId: null, + name: '정회원C', + gender: '여성', + memberType: '정회원', + status: 'suspended', + joinDate: new Date('2023-03-10'), + createdAt: new Date('2023-03-11'), + updatedAt: new Date('2023-03-12') + }, + { + clubId: 1, + userId: null, + name: '준회원D', + gender: '남성', + memberType: '준회원', + status: 'active', + joinDate: new Date('2023-04-05'), + createdAt: new Date('2023-04-06'), + updatedAt: new Date('2023-04-07') + }, + { + clubId: 2, + userId: null, + name: '정회원E', + gender: '여성', + memberType: '정회원', + status: 'suspended', + joinDate: new Date('2023-05-15'), + createdAt: new Date('2023-05-16'), + updatedAt: new Date('2023-05-17') + }, + { + clubId: 3, + userId: null, + name: '준회원F', + gender: '남성', + memberType: '준회원', + status: 'inactive', + joinDate: new Date('2023-06-25'), + createdAt: new Date('2023-06-26'), + updatedAt: new Date('2023-06-27') + }, + { + clubId: 1, + userId: null, + name: '정회원G', + gender: '여성', + memberType: '정회원', + status: 'active', + joinDate: new Date('2023-07-30'), + createdAt: new Date('2023-07-31'), + updatedAt: new Date('2023-08-01') + }, + { + clubId: 2, + userId: null, + name: '준회원H', + gender: '남성', + memberType: '준회원', + status: 'suspended', + joinDate: new Date('2023-08-15'), + createdAt: new Date('2023-08-16'), + updatedAt: new Date('2023-08-17') + } + ]; + + await Member.bulkCreate(members); + console.log('샘플 클럽 회원 데이터가 생성되었습니다.'); + + // 샘플 클럽 기능 데이터 생성 + // 각 클럽의 기능을 개별적으로 생성 + // 클럽 1 (Enterprise) + await ClubFeature.create({ + clubId: 1, + featureId: 1, + enabled: true, + expiryDate: new Date(new Date().setMonth(new Date().getMonth() + 12)), + createdAt: new Date(), + updatedAt: new Date() + }); + + await ClubFeature.create({ + clubId: 1, + featureId: 2, + enabled: true, + expiryDate: new Date(new Date().setMonth(new Date().getMonth() + 12)), + createdAt: new Date(), + updatedAt: new Date() + }); + + // 클럽 2 (Premium) + await ClubFeature.create({ + clubId: 2, + featureId: 1, + enabled: true, + expiryDate: new Date(new Date().setMonth(new Date().getMonth() + 6)), + createdAt: new Date(), + updatedAt: new Date() + }); + + await ClubFeature.create({ + clubId: 2, + featureId: 2, + enabled: true, + expiryDate: new Date(new Date().setMonth(new Date().getMonth() + 6)), + createdAt: new Date(), + updatedAt: new Date() + }); + + // 클럽 3 (Basic) + await ClubFeature.create({ + clubId: 3, + featureId: 1, + enabled: true, + expiryDate: new Date(new Date().setMonth(new Date().getMonth() + 3)), + createdAt: new Date(), + updatedAt: new Date() + }); + console.log('샘플 클럽 기능 데이터가 생성되었습니다.'); + + // 샘플 메뉴 데이터 생성 + const menus = [ + // 클럽 관리 메뉴 + { + title: '클럽 관리', + icon: 'pi pi-building', + to: '/club', + role: 'user', + order: 1, + visible: true, + children: [ + { + title: '대시보드', + icon: 'pi pi-home', + to: '/club', + role: 'user', + order: 1, + visible: true + }, + { + title: '회원 관리', + icon: 'pi pi-users', + to: '/club/members', + role: 'user', + order: 2, + visible: true, + featureCode: 'member_management', + requiredSubscriptionTier: 'basic' + }, + { + title: '이벤트 관리', + icon: 'pi pi-calendar', + to: '/club/events', + role: 'user', + order: 3, + visible: true, + featureCode: 'event_management', + requiredSubscriptionTier: 'standard' + }, + { + title: '이벤트 캘린더', + icon: 'pi pi-calendar-plus', + to: '/club/events/calendar', + role: 'user', + order: 4, + visible: true, + featureCode: 'event_calendar', + requiredSubscriptionTier: 'standard' + }, + { + title: '통계', + icon: 'pi pi-chart-line', + to: '/club/statistics', + role: 'user', + order: 5, + visible: true, + featureCode: 'statistics', + requiredSubscriptionTier: 'premium' + } + ] + } + ]; + + // 부모 메뉴 먼저 생성 + for (const menuData of menus) { + const { children, ...parentData } = menuData; + const parentMenu = await Menu.create(parentData); + + // 자식 메뉴 생성 및 부모와 연결 + if (children) { + for (const childData of children) { + await Menu.create({ + ...childData, + parentId: parentMenu.id + }); + } + } + } + + console.log('샘플 메뉴 데이터가 생성되었습니다.'); + + // 샘플 클럽 생성 + const clubsCount = await Club.count(); + if (clubsCount === 0) { + // 3. 샘플 클럽 데이터 생성 + const clubs = await Club.bulkCreate([ + { + name: '서울 볼링 클럽', + description: '서울 지역 볼링 동호회입니다.', + ownerId: sampleUsers[0].id, + location: '서울시 강남구', + status: 'active', + memberCount: 3, + createdAt: new Date(), + updatedAt: new Date() + }, + { + name: '부산 볼링 클럽', + description: '부산 지역 볼링 동호회입니다.', + ownerId: sampleUsers[1].id, + location: '부산시 해운대구', + status: 'active', + memberCount: 3, + createdAt: new Date(), + updatedAt: new Date() + }, + { + name: '대전 볼링 클럽', + description: '대전 지역 볼링 동호회입니다.', + ownerId: sampleUsers[2].id, + location: '대전시 유성구', + status: 'active', + memberCount: 3, + createdAt: new Date(), + updatedAt: new Date() + } + ]); + console.log('샘플 클럽 데이터가 생성되었습니다:', clubs.map(club => club.id)); + } + + // 샘플 구독 플랜 생성 + const subscriptionPlansCount = await SubscriptionPlan.count(); + if (subscriptionPlansCount === 0) { + const subscriptionPlans = [ + { + name: 'Basic', + description: '기본적인 클럽 관리 기능을 제공합니다.', + price: 30000, + duration: 1, + maxMembers: 30, + features: ['member_management', 'event_basic'], + status: 'active' + }, + { + name: 'Premium', + description: '고급 분석 기능과 함께 더 많은 회원을 관리할 수 있습니다.', + price: 50000, + duration: 1, + maxMembers: 100, + features: ['member_management', 'event_basic', 'analytics_basic', 'lane_booking'], + status: 'active' + }, + { + name: 'Enterprise', + description: '무제한 회원 관리와 모든 프리미엄 기능을 제공합니다.', + price: 100000, + duration: 1, + maxMembers: 999999, // 무제한을 표현하기 위해 큰 숫자 사용 + features: ['member_management', 'event_basic', 'analytics_basic', 'lane_booking', 'tournament', 'analytics_advanced'], + status: 'active' + } + ]; + + await SubscriptionPlan.bulkCreate(subscriptionPlans); + console.log('샘플 구독 플랜 데이터가 생성되었습니다.'); + } + + // 샘플 클럽 구독 데이터 생성 + const clubSubscriptionsCount = await ClubSubscription.count(); + if (clubSubscriptionsCount === 0) { + const clubSubscriptions = [ + { + clubId: 1, + planId: 3, // Enterprise 플랜 + startDate: new Date(), + endDate: new Date(new Date().setMonth(new Date().getMonth() + 12)), + amount: 1200000, // 연간 구독 + status: 'active', + paymentStatus: 'paid' + }, + { + clubId: 2, + planId: 2, // Premium 플랜 + startDate: new Date(), + endDate: new Date(new Date().setMonth(new Date().getMonth() + 6)), + amount: 300000, // 6개월 구독 + status: 'active', + paymentStatus: 'paid' + }, + { + clubId: 3, + planId: 1, // Basic 플랜 + startDate: new Date(), + endDate: new Date(new Date().setMonth(new Date().getMonth() + 3)), + amount: 90000, // 3개월 구독 + status: 'active', + paymentStatus: 'paid' + } + ]; + + await ClubSubscription.bulkCreate(clubSubscriptions); + console.log('샘플 클럽 구독 데이터가 생성되었습니다.'); + } + + // 5. 샘플 기능 데이터 생성 + const createdFeatures = await Feature.bulkCreate([ + { + name: '토너먼트 관리', + description: '클럽 내 토너먼트 대회를 개최하고 관리할 수 있습니다.', + code: 'tournament', + purchaseType: 'both', + price: 50000, + status: 'active', + createdAt: new Date(), + updatedAt: new Date() + }, + { + name: '레인 예약', + description: '볼링장 레인을 미리 예약하고 관리할 수 있습니다.', + code: 'lane_booking', + purchaseType: 'subscription_only', + price: 30000, + status: 'active', + createdAt: new Date(), + updatedAt: new Date() + }, + { + name: '점수 분석', + description: '회원들의 경기 점수를 분석하고 통계를 제공합니다.', + code: 'score_analysis', + purchaseType: 'separate_only', + price: 40000, + status: 'active', + createdAt: new Date(), + updatedAt: new Date() + } + ]); + console.log('샘플 기능 데이터가 생성되었습니다:', createdFeatures.map(f => f.id)); + + // 6. 샘플 클럽 기능 데이터 생성 + const sampleClubs = await Club.findAll(); + + if (createdFeatures.length > 0 && sampleClubs.length > 0) { + const clubFeatures = await ClubFeature.bulkCreate([ + { + clubId: sampleClubs[0].id, + featureId: createdFeatures[0].id, + enabled: true, + expiryDate: new Date(new Date().setMonth(new Date().getMonth() + 6)), + amount: 50000, + duration: 6, + status: 'active', + createdAt: new Date(), + updatedAt: new Date() + }, + { + clubId: sampleClubs[1].id, + featureId: createdFeatures[1].id, + enabled: true, + expiryDate: new Date(new Date().setMonth(new Date().getMonth() + 3)), + amount: 30000, + duration: 3, + status: 'active', + createdAt: new Date(), + updatedAt: new Date() + }, + { + clubId: sampleClubs[2].id, + featureId: createdFeatures[2].id, + enabled: true, + expiryDate: new Date(new Date().setMonth(new Date().getMonth() + 12)), + amount: 40000, + duration: 12, + status: 'active', + createdAt: new Date(), + updatedAt: new Date() + } + ]); + console.log('샘플 클럽 기능 데이터가 생성되었습니다:', clubFeatures.map(cf => `${cf.clubId}-${cf.featureId}`)); + } + + } catch (error) { + console.error('샘플 데이터 생성 중 오류 발생:', error); + } +}; + +module.exports = { + sequelize, + User, + Club, + Event, + EventParticipant, + EventScore, + Member, + Notification, + Feature, + ClubFeature, + Menu, + SubscriptionPlan, + SubscriptionPlanFeature, + ClubSubscription, + ActivityLog, + syncModels, + createAdminUser, + createSampleData +}; diff --git a/backend/package-lock.json b/backend/package-lock.json new file mode 100644 index 0000000..d9308ed --- /dev/null +++ b/backend/package-lock.json @@ -0,0 +1,3753 @@ +{ + "name": "backend", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "backend", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "bcrypt": "^5.1.1", + "body-parser": "^1.20.3", + "cookie-parser": "^1.4.7", + "cors": "^2.8.5", + "dotenv": "^16.4.7", + "express": "^4.21.2", + "express-validator": "^7.2.1", + "helmet": "^8.0.0", + "jsonwebtoken": "^9.0.2", + "mariadb": "^3.4.0", + "mongoose": "^8.12.1", + "morgan": "^1.10.0", + "mysql2": "^3.13.0", + "sequelize": "^6.37.6", + "socket.io": "^4.8.1", + "winston": "^3.17.0" + }, + "devDependencies": { + "sequelize-cli": "^6.6.2" + } + }, + "node_modules/@colors/colors": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.6.0.tgz", + "integrity": "sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==", + "license": "MIT", + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/@dabh/diagnostics": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.3.tgz", + "integrity": "sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA==", + "license": "MIT", + "dependencies": { + "colorspace": "1.1.x", + "enabled": "2.0.x", + "kuler": "^2.0.0" + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/@mapbox/node-pre-gyp": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz", + "integrity": "sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==", + "license": "BSD-3-Clause", + "dependencies": { + "detect-libc": "^2.0.0", + "https-proxy-agent": "^5.0.0", + "make-dir": "^3.1.0", + "node-fetch": "^2.6.7", + "nopt": "^5.0.0", + "npmlog": "^5.0.1", + "rimraf": "^3.0.2", + "semver": "^7.3.5", + "tar": "^6.1.11" + }, + "bin": { + "node-pre-gyp": "bin/node-pre-gyp" + } + }, + "node_modules/@mongodb-js/saslprep": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.2.0.tgz", + "integrity": "sha512-+ywrb0AqkfaYuhHs6LxKWgqbh3I72EpEgESCw37o+9qPx9WTCkgDm2B+eMrwehGtHBWHFU4GXvnSCNiFhhausg==", + "license": "MIT", + "dependencies": { + "sparse-bitfield": "^3.0.3" + } + }, + "node_modules/@one-ini/wasm": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@one-ini/wasm/-/wasm-0.1.1.tgz", + "integrity": "sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@socket.io/component-emitter": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", + "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==", + "license": "MIT" + }, + "node_modules/@types/cors": { + "version": "2.8.17", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.17.tgz", + "integrity": "sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/debug": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", + "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", + "license": "MIT", + "dependencies": { + "@types/ms": "*" + } + }, + "node_modules/@types/geojson": { + "version": "7946.0.16", + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz", + "integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==", + "license": "MIT" + }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "22.13.10", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.10.tgz", + "integrity": "sha512-I6LPUvlRH+O6VRUqYOcMudhaIdUVWfsjnZavnsraHvpBwaEyMN29ry+0UVJhImYL16xsscu0aske3yA+uPOWfw==", + "license": "MIT", + "dependencies": { + "undici-types": "~6.20.0" + } + }, + "node_modules/@types/triple-beam": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.5.tgz", + "integrity": "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==", + "license": "MIT" + }, + "node_modules/@types/validator": { + "version": "13.12.2", + "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.12.2.tgz", + "integrity": "sha512-6SlHBzUW8Jhf3liqrGGXyTJSIFe4nqlJ5A5KaMZ2l/vbM3Wh3KSybots/wfWVzNLK4D1NZluDlSQIbIEPx6oyA==", + "license": "MIT" + }, + "node_modules/@types/webidl-conversions": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz", + "integrity": "sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA==", + "license": "MIT" + }, + "node_modules/@types/whatwg-url": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-11.0.5.tgz", + "integrity": "sha512-coYR071JRaHa+xoEvvYqvnIHaVqaYrLPbsufM9BF63HkwI5Lgmy2QR8Q5K/lYDYo5AK82wOvSOS0UsLTpTG7uQ==", + "license": "MIT", + "dependencies": { + "@types/webidl-conversions": "*" + } + }, + "node_modules/abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "license": "ISC" + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "license": "MIT", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/agent-base/node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/agent-base/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/aproba": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", + "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==", + "license": "ISC" + }, + "node_modules/are-we-there-yet": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz", + "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==", + "deprecated": "This package is no longer supported.", + "license": "ISC", + "dependencies": { + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "license": "MIT" + }, + "node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "license": "MIT" + }, + "node_modules/at-least-node": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", + "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/aws-ssl-profiles": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/aws-ssl-profiles/-/aws-ssl-profiles-1.1.2.tgz", + "integrity": "sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g==", + "license": "MIT", + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "license": "MIT" + }, + "node_modules/base64id": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", + "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==", + "license": "MIT", + "engines": { + "node": "^4.5.0 || >= 5.9" + } + }, + "node_modules/basic-auth": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", + "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.1.2" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/basic-auth/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/bcrypt": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-5.1.1.tgz", + "integrity": "sha512-AGBHOG5hPYZ5Xl9KXzU5iKq9516yEmvCKDg3ecP5kX2aB6UqTeXZxk2ELnDgDm6BQSMlLt9rDB4LoSMx0rYwww==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "@mapbox/node-pre-gyp": "^1.0.11", + "node-addon-api": "^5.0.0" + }, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", + "dev": true, + "license": "MIT" + }, + "node_modules/body-parser": { + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.13.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/bson": { + "version": "6.10.3", + "resolved": "https://registry.npmjs.org/bson/-/bson-6.10.3.tgz", + "integrity": "sha512-MTxGsqgYTwfshYWTRdmZRC+M7FnG1b4y7RO7p2k3X24Wq0yv1m77Wsj0BzlPzd/IowgESfsruQCUToa7vbOpPQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=16.20.1" + } + }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "license": "BSD-3-Clause" + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/cli-color": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/cli-color/-/cli-color-2.0.4.tgz", + "integrity": "sha512-zlnpg0jNcibNrO7GG9IeHH7maWFeCz+Ja1wx/7tZNU5ASSSSZ+/qZciM0/LHCYxSdqv5h2sdbQ/PXYdOuetXvA==", + "dev": true, + "license": "ISC", + "dependencies": { + "d": "^1.0.1", + "es5-ext": "^0.10.64", + "es6-iterator": "^2.0.3", + "memoizee": "^0.4.15", + "timers-ext": "^0.1.7" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/color": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/color/-/color-3.2.1.tgz", + "integrity": "sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==", + "license": "MIT", + "dependencies": { + "color-convert": "^1.9.3", + "color-string": "^1.6.0" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/color-string": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", + "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", + "license": "MIT", + "dependencies": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, + "node_modules/color-support": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "license": "ISC", + "bin": { + "color-support": "bin.js" + } + }, + "node_modules/color/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "license": "MIT", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/color/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "license": "MIT" + }, + "node_modules/colorspace": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/colorspace/-/colorspace-1.1.4.tgz", + "integrity": "sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w==", + "license": "MIT", + "dependencies": { + "color": "^3.1.3", + "text-hex": "1.0.x" + } + }, + "node_modules/commander": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", + "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "license": "MIT" + }, + "node_modules/config-chain": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz", + "integrity": "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ini": "^1.3.4", + "proto-list": "~1.2.1" + } + }, + "node_modules/console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==", + "license": "ISC" + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-parser": { + "version": "1.4.7", + "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.7.tgz", + "integrity": "sha512-nGUvgXnotP3BsjiLX2ypbQnWoGUPIIfHQNZkkC668ntrzGWEZVW70HDEB1qnNGMicPje6EttlIgzo51YSwNQGw==", + "license": "MIT", + "dependencies": { + "cookie": "0.7.2", + "cookie-signature": "1.0.6" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/cookie-parser/node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "license": "MIT" + }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/d": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/d/-/d-1.0.2.tgz", + "integrity": "sha512-MOqHvMWF9/9MX6nza0KgvFH4HpMU0EF5uUDXqX/BtxtU8NfB0QzRtJ8Oe/6SuS4kbhyzVJwjd97EA4PKrzJ8bw==", + "dev": true, + "license": "ISC", + "dependencies": { + "es5-ext": "^0.10.64", + "type": "^2.7.2" + }, + "engines": { + "node": ">=0.12" + } + }, + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", + "license": "MIT" + }, + "node_modules/denque": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz", + "integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/detect-libc": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", + "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==", + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/dotenv": { + "version": "16.4.7", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz", + "integrity": "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dottie": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/dottie/-/dottie-2.0.6.tgz", + "integrity": "sha512-iGCHkfUc5kFekGiqhe8B/mdaurD+lakO9txNnTvKtA6PISrw86LgqHvRzWYPyoE2Ph5aMIrCw9/uko6XHTKCwA==", + "license": "MIT" + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, + "license": "MIT" + }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/editorconfig": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/editorconfig/-/editorconfig-1.0.4.tgz", + "integrity": "sha512-L9Qe08KWTlqYMVvMcTIvMAdl1cDUubzRNYL+WfA4bLDMHe4nemKkpmYzkznE1FwLKu0EEmy6obgQKzMJrg4x9Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@one-ini/wasm": "0.1.1", + "commander": "^10.0.0", + "minimatch": "9.0.1", + "semver": "^7.5.3" + }, + "bin": { + "editorconfig": "bin/editorconfig" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/editorconfig/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/editorconfig/node_modules/minimatch": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.1.tgz", + "integrity": "sha512-0jWhJpD/MdhPXwPuiRkCbfYfSKp2qnn2eOc279qI7f+osl/l+prKSrvhg157zSYvx/1nmgn2NqdT6k2Z7zSH9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/enabled": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz", + "integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==", + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/engine.io": { + "version": "6.6.4", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.4.tgz", + "integrity": "sha512-ZCkIjSYNDyGn0R6ewHDtXgns/Zre/NT6Agvq1/WobF7JXgFff4SeDroKiCO3fNJreU9YG429Sc81o4w5ok/W5g==", + "license": "MIT", + "dependencies": { + "@types/cors": "^2.8.12", + "@types/node": ">=10.0.0", + "accepts": "~1.3.4", + "base64id": "2.0.0", + "cookie": "~0.7.2", + "cors": "~2.8.5", + "debug": "~4.3.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.17.1" + }, + "engines": { + "node": ">=10.2.0" + } + }, + "node_modules/engine.io-parser": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz", + "integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/engine.io/node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/engine.io/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/engine.io/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es5-ext": { + "version": "0.10.64", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.64.tgz", + "integrity": "sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg==", + "dev": true, + "hasInstallScript": true, + "license": "ISC", + "dependencies": { + "es6-iterator": "^2.0.3", + "es6-symbol": "^3.1.3", + "esniff": "^2.0.1", + "next-tick": "^1.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/es6-iterator": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", + "integrity": "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==", + "dev": true, + "license": "MIT", + "dependencies": { + "d": "1", + "es5-ext": "^0.10.35", + "es6-symbol": "^3.1.1" + } + }, + "node_modules/es6-symbol": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.4.tgz", + "integrity": "sha512-U9bFFjX8tFiATgtkJ1zg25+KviIXpgRvRHS8sau3GfhVzThRQrOeksPeT0BWW2MNZs1OEWJ1DPXOQMn0KKRkvg==", + "dev": true, + "license": "ISC", + "dependencies": { + "d": "^1.0.2", + "ext": "^1.7.0" + }, + "engines": { + "node": ">=0.12" + } + }, + "node_modules/es6-weak-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.3.tgz", + "integrity": "sha512-p5um32HOTO1kP+w7PRnB+5lQ43Z6muuMuIMffvDN8ZB4GcnjLBV6zGStpbASIMk4DCAvEaamhe2zhyCb/QXXsA==", + "dev": true, + "license": "ISC", + "dependencies": { + "d": "1", + "es5-ext": "^0.10.46", + "es6-iterator": "^2.0.3", + "es6-symbol": "^3.1.1" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/esniff": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/esniff/-/esniff-2.0.1.tgz", + "integrity": "sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg==", + "dev": true, + "license": "ISC", + "dependencies": { + "d": "^1.0.1", + "es5-ext": "^0.10.62", + "event-emitter": "^0.3.5", + "type": "^2.7.2" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/event-emitter": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", + "integrity": "sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "d": "1", + "es5-ext": "~0.10.14" + } + }, + "node_modules/express": { + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", + "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", + "license": "MIT", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.3", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.7.1", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.3.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.12", + "proxy-addr": "~2.0.7", + "qs": "6.13.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.19.0", + "serve-static": "1.16.2", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express-validator": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/express-validator/-/express-validator-7.2.1.tgz", + "integrity": "sha512-CjNE6aakfpuwGaHQZ3m8ltCG2Qvivd7RHtVMS/6nVxOM7xVGqr4bhflsm4+N5FP5zI7Zxp+Hae+9RE+o8e3ZOQ==", + "license": "MIT", + "dependencies": { + "lodash": "^4.17.21", + "validator": "~13.12.0" + }, + "engines": { + "node": ">= 8.0.0" + } + }, + "node_modules/ext": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/ext/-/ext-1.7.0.tgz", + "integrity": "sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==", + "dev": true, + "license": "ISC", + "dependencies": { + "type": "^2.7.2" + } + }, + "node_modules/fecha": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz", + "integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==", + "license": "MIT" + }, + "node_modules/finalhandler": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/fn.name": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz", + "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==", + "license": "MIT" + }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/foreground-child/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/fs-minipass/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "license": "ISC" + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gauge": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz", + "integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==", + "deprecated": "This package is no longer supported.", + "license": "ISC", + "dependencies": { + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.2", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.1", + "object-assign": "^4.1.1", + "signal-exit": "^3.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/generate-function": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz", + "integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==", + "license": "MIT", + "dependencies": { + "is-property": "^1.0.2" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==", + "license": "ISC" + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/helmet": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/helmet/-/helmet-8.0.0.tgz", + "integrity": "sha512-VyusHLEIIO5mjQPUI1wpOAEu+wl6Q0998jzTxqUYGE45xCIcAxy3MsbEK/yyJUJ3ADeMoB6MornPH6GMWAf+Pw==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "license": "MIT", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "license": "MIT", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/https-proxy-agent/node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/https-proxy-agent/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/inflection": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/inflection/-/inflection-1.13.4.tgz", + "integrity": "sha512-6I/HUDeYFfuNCVS3td055BaXBwKYuzw7K3ExVMStBowKo9oOAMJIXIHvdyR3iboTCp1b+1i5DSkIZTcwIktuDw==", + "engines": [ + "node >= 0.4.0" + ], + "license": "MIT" + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true, + "license": "ISC" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-arrayish": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", + "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==", + "license": "MIT" + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-promise": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz", + "integrity": "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-property": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", + "integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==", + "license": "MIT" + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/js-beautify": { + "version": "1.15.4", + "resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.15.4.tgz", + "integrity": "sha512-9/KXeZUKKJwqCXUdBxFJ3vPh467OCckSBmYDwSK/EtV090K+iMJ7zx2S3HLVDIWFQdqMIsZWbnaGiba18aWhaA==", + "dev": true, + "license": "MIT", + "dependencies": { + "config-chain": "^1.1.13", + "editorconfig": "^1.0.4", + "glob": "^10.4.2", + "js-cookie": "^3.0.5", + "nopt": "^7.2.1" + }, + "bin": { + "css-beautify": "js/bin/css-beautify.js", + "html-beautify": "js/bin/html-beautify.js", + "js-beautify": "js/bin/js-beautify.js" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/js-beautify/node_modules/abbrev": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-2.0.0.tgz", + "integrity": "sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/js-beautify/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/js-beautify/node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/js-beautify/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/js-beautify/node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/js-beautify/node_modules/nopt": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-7.2.1.tgz", + "integrity": "sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w==", + "dev": true, + "license": "ISC", + "dependencies": { + "abbrev": "^2.0.0" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/js-cookie": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz", + "integrity": "sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + } + }, + "node_modules/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/jsonwebtoken": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", + "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==", + "license": "MIT", + "dependencies": { + "jws": "^3.2.2", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, + "node_modules/jsonwebtoken/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/jwa": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", + "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "license": "MIT", + "dependencies": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/kareem": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.6.3.tgz", + "integrity": "sha512-C3iHfuGUXK2u8/ipq9LfjFfXFxAZMQJJq7vLS45r3D9Y2xQ/m4S8zaR4zMLFWh9AsNPXmcFfUDhTEO8UIC/V6Q==", + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/kuler": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz", + "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==", + "license": "MIT" + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "license": "MIT" + }, + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", + "license": "MIT" + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", + "license": "MIT" + }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", + "license": "MIT" + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", + "license": "MIT" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "license": "MIT" + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", + "license": "MIT" + }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", + "license": "MIT" + }, + "node_modules/logform": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/logform/-/logform-2.7.0.tgz", + "integrity": "sha512-TFYA4jnP7PVbmlBIfhlSe+WKxs9dklXMTEGcBCIvLhE/Tn3H6Gk1norupVW7m5Cnd4bLcr08AytbyV/xj7f/kQ==", + "license": "MIT", + "dependencies": { + "@colors/colors": "1.6.0", + "@types/triple-beam": "^1.3.2", + "fecha": "^4.2.0", + "ms": "^2.1.1", + "safe-stable-stringify": "^2.3.1", + "triple-beam": "^1.3.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/logform/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/long": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/long/-/long-5.3.1.tgz", + "integrity": "sha512-ka87Jz3gcx/I7Hal94xaN2tZEOPoUOEVftkQqZx2EeQRN7LGdfLlI3FvZ+7WDplm+vK2Urx9ULrvSowtdCieng==", + "license": "Apache-2.0" + }, + "node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "license": "ISC" + }, + "node_modules/lru-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/lru-queue/-/lru-queue-0.1.0.tgz", + "integrity": "sha512-BpdYkt9EvGl8OfWHDQPISVpcl5xZthb+XPsbELj5AQXxIC8IriDZIQYjBJPEm5rS420sjZ0TLEzRcq5KdBhYrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es5-ext": "~0.10.2" + } + }, + "node_modules/lru.min": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/lru.min/-/lru.min-1.1.2.tgz", + "integrity": "sha512-Nv9KddBcQSlQopmBHXSsZVY5xsdlZkdH/Iey0BlcBYggMd4two7cZnKOK9vmy3nY0O5RGH99z1PCeTpPqszUYg==", + "license": "MIT", + "engines": { + "bun": ">=1.0.0", + "deno": ">=1.30.0", + "node": ">=8.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wellwelwel" + } + }, + "node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "license": "MIT", + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/mariadb": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/mariadb/-/mariadb-3.4.0.tgz", + "integrity": "sha512-hdRPcAzs+MTxK5VG1thBW18gGTlw6yWBe9YnLB65GLo7q0fO5DWsgomIevV/pXSaWRmD3qi6ka4oSFRTExRiEQ==", + "license": "LGPL-2.1-or-later", + "dependencies": { + "@types/geojson": "^7946.0.14", + "@types/node": "^22.5.4", + "denque": "^2.1.0", + "iconv-lite": "^0.6.3", + "lru-cache": "^10.3.0" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/mariadb/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/memoizee": { + "version": "0.4.17", + "resolved": "https://registry.npmjs.org/memoizee/-/memoizee-0.4.17.tgz", + "integrity": "sha512-DGqD7Hjpi/1or4F/aYAspXKNm5Yili0QDAFAY4QYvpqpgiY6+1jOfqpmByzjxbWd/T9mChbCArXAbDAsTm5oXA==", + "dev": true, + "license": "ISC", + "dependencies": { + "d": "^1.0.2", + "es5-ext": "^0.10.64", + "es6-weak-map": "^2.0.3", + "event-emitter": "^0.3.5", + "is-promise": "^2.2.2", + "lru-queue": "^0.1.0", + "next-tick": "^1.1.0", + "timers-ext": "^0.1.7" + }, + "engines": { + "node": ">=0.12" + } + }, + "node_modules/memory-pager": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz", + "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==", + "license": "MIT" + }, + "node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "license": "ISC", + "engines": { + "node": ">=8" + } + }, + "node_modules/minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "license": "MIT", + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minizlib/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "license": "MIT", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/moment": { + "version": "2.30.1", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz", + "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/moment-timezone": { + "version": "0.5.47", + "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.47.tgz", + "integrity": "sha512-UbNt/JAWS0m/NJOebR0QMRHBk0hu03r5dx9GK8Cs0AS3I81yDcOc9k+DytPItgVvBP7J6Mf6U2n3BPAacAV9oA==", + "license": "MIT", + "dependencies": { + "moment": "^2.29.4" + }, + "engines": { + "node": "*" + } + }, + "node_modules/mongodb": { + "version": "6.14.2", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.14.2.tgz", + "integrity": "sha512-kMEHNo0F3P6QKDq17zcDuPeaywK/YaJVCEQRzPF3TOM/Bl9MFg64YE5Tu7ifj37qZJMhwU1tl2Ioivws5gRG5Q==", + "license": "Apache-2.0", + "dependencies": { + "@mongodb-js/saslprep": "^1.1.9", + "bson": "^6.10.3", + "mongodb-connection-string-url": "^3.0.0" + }, + "engines": { + "node": ">=16.20.1" + }, + "peerDependencies": { + "@aws-sdk/credential-providers": "^3.188.0", + "@mongodb-js/zstd": "^1.1.0 || ^2.0.0", + "gcp-metadata": "^5.2.0", + "kerberos": "^2.0.1", + "mongodb-client-encryption": ">=6.0.0 <7", + "snappy": "^7.2.2", + "socks": "^2.7.1" + }, + "peerDependenciesMeta": { + "@aws-sdk/credential-providers": { + "optional": true + }, + "@mongodb-js/zstd": { + "optional": true + }, + "gcp-metadata": { + "optional": true + }, + "kerberos": { + "optional": true + }, + "mongodb-client-encryption": { + "optional": true + }, + "snappy": { + "optional": true + }, + "socks": { + "optional": true + } + } + }, + "node_modules/mongodb-connection-string-url": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-3.0.2.tgz", + "integrity": "sha512-rMO7CGo/9BFwyZABcKAWL8UJwH/Kc2x0g72uhDWzG48URRax5TCIcJ7Rc3RZqffZzO/Gwff/jyKwCU9TN8gehA==", + "license": "Apache-2.0", + "dependencies": { + "@types/whatwg-url": "^11.0.2", + "whatwg-url": "^14.1.0 || ^13.0.0" + } + }, + "node_modules/mongodb-connection-string-url/node_modules/tr46": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.0.0.tgz", + "integrity": "sha512-tk2G5R2KRwBd+ZN0zaEXpmzdKyOYksXwywulIX95MBODjSzMIuQnQ3m8JxgbhnL1LeVo7lqQKsYa1O3Htl7K5g==", + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/mongodb-connection-string-url/node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/mongodb-connection-string-url/node_modules/whatwg-url": { + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.1.1.tgz", + "integrity": "sha512-mDGf9diDad/giZ/Sm9Xi2YcyzaFpbdLpJPr+E9fSkyQ7KpQD4SdFcugkRQYzhmfI4KeV4Qpnn2sKPdo+kmsgRQ==", + "license": "MIT", + "dependencies": { + "tr46": "^5.0.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/mongoose": { + "version": "8.12.1", + "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.12.1.tgz", + "integrity": "sha512-UW22y8QFVYmrb36hm8cGncfn4ARc/XsYWQwRTaj0gxtQk1rDuhzDO1eBantS+hTTatfAIS96LlRCJrcNHvW5+Q==", + "license": "MIT", + "dependencies": { + "bson": "^6.10.3", + "kareem": "2.6.3", + "mongodb": "~6.14.0", + "mpath": "0.9.0", + "mquery": "5.0.0", + "ms": "2.1.3", + "sift": "17.1.3" + }, + "engines": { + "node": ">=16.20.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mongoose" + } + }, + "node_modules/mongoose/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/morgan": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.0.tgz", + "integrity": "sha512-AbegBVI4sh6El+1gNwvD5YIck7nSA36weD7xvIxG4in80j/UoK8AEGaWnnz8v1GxonMCltmlNs5ZKbGvl9b1XQ==", + "license": "MIT", + "dependencies": { + "basic-auth": "~2.0.1", + "debug": "2.6.9", + "depd": "~2.0.0", + "on-finished": "~2.3.0", + "on-headers": "~1.0.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/morgan/node_modules/on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/mpath": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.9.0.tgz", + "integrity": "sha512-ikJRQTk8hw5DEoFVxHG1Gn9T/xcjtdnOKIU1JTmGjZZlg9LST2mBLmcX3/ICIbgJydT2GOc15RnNy5mHmzfSew==", + "license": "MIT", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/mquery": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/mquery/-/mquery-5.0.0.tgz", + "integrity": "sha512-iQMncpmEK8R8ncT8HJGsGc9Dsp8xcgYMVSbs5jgnm1lFHTZqMJTUWTDx1LBO8+mK3tPNZWFLBghQEIOULSTHZg==", + "license": "MIT", + "dependencies": { + "debug": "4.x" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/mquery/node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/mquery/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/mysql2": { + "version": "3.13.0", + "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.13.0.tgz", + "integrity": "sha512-M6DIQjTqKeqXH5HLbLMxwcK5XfXHw30u5ap6EZmu7QVmcF/gnh2wS/EOiQ4MTbXz/vQeoXrmycPlVRM00WSslg==", + "license": "MIT", + "dependencies": { + "aws-ssl-profiles": "^1.1.1", + "denque": "^2.1.0", + "generate-function": "^2.3.1", + "iconv-lite": "^0.6.3", + "long": "^5.2.1", + "lru.min": "^1.0.0", + "named-placeholders": "^1.1.3", + "seq-queue": "^0.0.5", + "sqlstring": "^2.3.2" + }, + "engines": { + "node": ">= 8.0" + } + }, + "node_modules/mysql2/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/named-placeholders": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/named-placeholders/-/named-placeholders-1.1.3.tgz", + "integrity": "sha512-eLoBxg6wE/rZkJPhU/xRX1WTpkFEwDJEN96oxFrTsqBdbT5ec295Q+CoHrL9IT0DipqKhmGcaZmwOt8OON5x1w==", + "license": "MIT", + "dependencies": { + "lru-cache": "^7.14.1" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/named-placeholders/node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/next-tick": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz", + "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/node-addon-api": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz", + "integrity": "sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==", + "license": "MIT" + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/nopt": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", + "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", + "license": "ISC", + "dependencies": { + "abbrev": "1" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/npmlog": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz", + "integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==", + "deprecated": "This package is no longer supported.", + "license": "ISC", + "dependencies": { + "are-we-there-yet": "^2.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^3.0.0", + "set-blocking": "^2.0.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/one-time": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/one-time/-/one-time-1.0.0.tgz", + "integrity": "sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==", + "license": "MIT", + "dependencies": { + "fn.name": "1.x.x" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true, + "license": "BlueOak-1.0.0" + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-to-regexp": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", + "license": "MIT" + }, + "node_modules/pg-connection-string": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.7.0.tgz", + "integrity": "sha512-PI2W9mv53rXJQEOb8xNR8lH7Hr+EKa6oJa38zsK0S/ky2er16ios1wLKhZyxzD7jUReiWokc9WK5nxSnC7W1TA==", + "license": "MIT" + }, + "node_modules/proto-list": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", + "integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==", + "dev": true, + "license": "ISC" + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/qs": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.10", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", + "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/retry-as-promised": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/retry-as-promised/-/retry-as-promised-7.1.1.tgz", + "integrity": "sha512-hMD7odLOt3LkTjcif8aRZqi/hybjpLNgSk5oF5FCowfCjok6LukpN2bDX7R5wDmbgBQFn7YoBxSagmtXHaJYJw==", + "license": "MIT" + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safe-stable-stringify": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz", + "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/send": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/seq-queue": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/seq-queue/-/seq-queue-0.0.5.tgz", + "integrity": "sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q==" + }, + "node_modules/sequelize": { + "version": "6.37.6", + "resolved": "https://registry.npmjs.org/sequelize/-/sequelize-6.37.6.tgz", + "integrity": "sha512-4Slqjqpktofs7AVqWviFOInzP9w8ZRQDhF+DnRtm4WKIdIATpyzGgedyseP3xbgpBxapvfQcJv6CeIdZe4ZL2A==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/sequelize" + } + ], + "license": "MIT", + "dependencies": { + "@types/debug": "^4.1.8", + "@types/validator": "^13.7.17", + "debug": "^4.3.4", + "dottie": "^2.0.6", + "inflection": "^1.13.4", + "lodash": "^4.17.21", + "moment": "^2.29.4", + "moment-timezone": "^0.5.43", + "pg-connection-string": "^2.6.1", + "retry-as-promised": "^7.0.4", + "semver": "^7.5.4", + "sequelize-pool": "^7.1.0", + "toposort-class": "^1.0.1", + "uuid": "^8.3.2", + "validator": "^13.9.0", + "wkx": "^0.5.0" + }, + "engines": { + "node": ">=10.0.0" + }, + "peerDependenciesMeta": { + "ibm_db": { + "optional": true + }, + "mariadb": { + "optional": true + }, + "mysql2": { + "optional": true + }, + "oracledb": { + "optional": true + }, + "pg": { + "optional": true + }, + "pg-hstore": { + "optional": true + }, + "snowflake-sdk": { + "optional": true + }, + "sqlite3": { + "optional": true + }, + "tedious": { + "optional": true + } + } + }, + "node_modules/sequelize-cli": { + "version": "6.6.2", + "resolved": "https://registry.npmjs.org/sequelize-cli/-/sequelize-cli-6.6.2.tgz", + "integrity": "sha512-V8Oh+XMz2+uquLZltZES6MVAD+yEnmMfwfn+gpXcDiwE3jyQygLt4xoI0zG8gKt6cRcs84hsKnXAKDQjG/JAgg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cli-color": "^2.0.3", + "fs-extra": "^9.1.0", + "js-beautify": "^1.14.5", + "lodash": "^4.17.21", + "resolve": "^1.22.1", + "umzug": "^2.3.0", + "yargs": "^16.2.0" + }, + "bin": { + "sequelize": "lib/sequelize", + "sequelize-cli": "lib/sequelize" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/sequelize-pool": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/sequelize-pool/-/sequelize-pool-7.1.0.tgz", + "integrity": "sha512-G9c0qlIWQSK29pR/5U2JF5dDQeqqHRragoyahj/Nx4KOOQ3CPPfzxnfqFPCSB7x5UgjOgnZ61nSxz+fjDpRlJg==", + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/sequelize/node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/sequelize/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/serve-static": { + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", + "license": "MIT", + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.19.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", + "license": "ISC" + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/sift": { + "version": "17.1.3", + "resolved": "https://registry.npmjs.org/sift/-/sift-17.1.3.tgz", + "integrity": "sha512-Rtlj66/b0ICeFzYTuNvX/EF1igRbbnGSvEyT79McoZa/DeGhMyC5pWKOEsZKnpkqtSeovd5FL/bjHWC3CIIvCQ==", + "license": "MIT" + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "license": "ISC" + }, + "node_modules/simple-swizzle": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", + "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.3.1" + } + }, + "node_modules/socket.io": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.8.1.tgz", + "integrity": "sha512-oZ7iUCxph8WYRHHcjBEc9unw3adt5CmSNlppj/5Q4k2RIrhl8Z5yY2Xr4j9zj0+wzVZ0bxmYoGSzKJnRl6A4yg==", + "license": "MIT", + "dependencies": { + "accepts": "~1.3.4", + "base64id": "~2.0.0", + "cors": "~2.8.5", + "debug": "~4.3.2", + "engine.io": "~6.6.0", + "socket.io-adapter": "~2.5.2", + "socket.io-parser": "~4.2.4" + }, + "engines": { + "node": ">=10.2.0" + } + }, + "node_modules/socket.io-adapter": { + "version": "2.5.5", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.5.tgz", + "integrity": "sha512-eLDQas5dzPgOWCk9GuuJC2lBqItuhKI4uxGgo9aIV7MYbk2h9Q6uULEh8WBzThoI7l+qU9Ast9fVUmkqPP9wYg==", + "license": "MIT", + "dependencies": { + "debug": "~4.3.4", + "ws": "~8.17.1" + } + }, + "node_modules/socket.io-adapter/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/socket.io-adapter/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/socket.io-parser": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz", + "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==", + "license": "MIT", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/socket.io-parser/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/socket.io-parser/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/socket.io/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/socket.io/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/sparse-bitfield": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", + "integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==", + "license": "MIT", + "dependencies": { + "memory-pager": "^1.0.2" + } + }, + "node_modules/sqlstring": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.3.tgz", + "integrity": "sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/stack-trace": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", + "integrity": "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tar": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", + "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", + "license": "ISC", + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^5.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/text-hex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz", + "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==", + "license": "MIT" + }, + "node_modules/timers-ext": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/timers-ext/-/timers-ext-0.1.8.tgz", + "integrity": "sha512-wFH7+SEAcKfJpfLPkrgMPvvwnEtj8W4IurvEyrKsDleXnKLCDw71w8jltvfLa8Rm4qQxxT4jmDBYbJG/z7qoww==", + "dev": true, + "license": "ISC", + "dependencies": { + "es5-ext": "^0.10.64", + "next-tick": "^1.1.0" + }, + "engines": { + "node": ">=0.12" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/toposort-class": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toposort-class/-/toposort-class-1.0.1.tgz", + "integrity": "sha512-OsLcGGbYF3rMjPUf8oKktyvCiUxSbqMMS39m33MAjLTC1DVIH6x3WSt63/M77ihI09+Sdfk1AXvfhCEeUmC7mg==", + "license": "MIT" + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" + }, + "node_modules/triple-beam": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.4.1.tgz", + "integrity": "sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==", + "license": "MIT", + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/type": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/type/-/type-2.7.3.tgz", + "integrity": "sha512-8j+1QmAbPvLZow5Qpi6NCaN8FB60p/6x8/vfNqOk/hC+HuvFZhL4+WfekuhQLiqFZXOgQdrs3B+XxEmCc6b3FQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/umzug": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/umzug/-/umzug-2.3.0.tgz", + "integrity": "sha512-Z274K+e8goZK8QJxmbRPhl89HPO1K+ORFtm6rySPhFKfKc5GHhqdzD0SGhSWHkzoXasqJuItdhorSvY7/Cgflw==", + "dev": true, + "license": "MIT", + "dependencies": { + "bluebird": "^3.7.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/undici-types": { + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", + "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", + "license": "MIT" + }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/validator": { + "version": "13.12.0", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.12.0.tgz", + "integrity": "sha512-c1Q0mCiPlgdTVVVIJIrBuxNicYE+t/7oKeI9MWLj3fh/uq2Pxh/3eeWbVZ4OcGW1TUf53At0njHw5SMdA3tmMg==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wide-align": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", + "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", + "license": "ISC", + "dependencies": { + "string-width": "^1.0.2 || 2 || 3 || 4" + } + }, + "node_modules/winston": { + "version": "3.17.0", + "resolved": "https://registry.npmjs.org/winston/-/winston-3.17.0.tgz", + "integrity": "sha512-DLiFIXYC5fMPxaRg832S6F5mJYvePtmO5G9v9IgUFPhXm9/GkXarH/TUrBAVzhTCzAj9anE/+GjrgXp/54nOgw==", + "license": "MIT", + "dependencies": { + "@colors/colors": "^1.6.0", + "@dabh/diagnostics": "^2.0.2", + "async": "^3.2.3", + "is-stream": "^2.0.0", + "logform": "^2.7.0", + "one-time": "^1.0.0", + "readable-stream": "^3.4.0", + "safe-stable-stringify": "^2.3.1", + "stack-trace": "0.0.x", + "triple-beam": "^1.3.0", + "winston-transport": "^4.9.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/winston-transport": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.9.0.tgz", + "integrity": "sha512-8drMJ4rkgaPo1Me4zD/3WLfI/zPdA9o2IipKODunnGDcuqbHwjsbB79ylv04LCGGzU0xQ6vTznOMpQGaLhhm6A==", + "license": "MIT", + "dependencies": { + "logform": "^2.7.0", + "readable-stream": "^3.6.2", + "triple-beam": "^1.3.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/wkx": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/wkx/-/wkx-0.5.0.tgz", + "integrity": "sha512-Xng/d4Ichh8uN4l0FToV/258EjMGU9MGcA0HV2d9B/ZpZB3lqQm7nkOdZdm5GhKtLLhAE7PiVQwN4eN+2YJJUg==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, + "node_modules/ws": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "license": "ISC" + }, + "node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + } + } +} diff --git a/backend/package.json b/backend/package.json new file mode 100644 index 0000000..f9a589e --- /dev/null +++ b/backend/package.json @@ -0,0 +1,33 @@ +{ + "name": "backend", + "version": "1.0.0", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC", + "description": "", + "dependencies": { + "bcrypt": "^5.1.1", + "body-parser": "^1.20.3", + "cookie-parser": "^1.4.7", + "cors": "^2.8.5", + "dotenv": "^16.4.7", + "express": "^4.21.2", + "express-validator": "^7.2.1", + "helmet": "^8.0.0", + "jsonwebtoken": "^9.0.2", + "mariadb": "^3.4.0", + "mongoose": "^8.12.1", + "morgan": "^1.10.0", + "mysql2": "^3.13.0", + "sequelize": "^6.37.6", + "socket.io": "^4.8.1", + "winston": "^3.17.0" + }, + "devDependencies": { + "sequelize-cli": "^6.6.2" + } +} diff --git a/backend/routes/adminRoutes.js b/backend/routes/adminRoutes.js new file mode 100644 index 0000000..e9aa25b --- /dev/null +++ b/backend/routes/adminRoutes.js @@ -0,0 +1,1443 @@ +const express = require('express'); +const router = express.Router(); +const { Club, Member, User, Feature, ClubFeature, ActivityLog, SubscriptionPlan, Meeting, MeetingParticipant, ClubSubscription } = require('../models'); +const { sequelize } = require('../models/index'); +const { Op } = require('sequelize'); +const { authenticateJWT, authorizeRoles } = require('../middleware/authMiddleware'); + +// 관리자 인증 미들웨어 +const adminMiddleware = (req, res, next) => { + if (req.user && (req.user.role === 'admin' || req.user.role === 'superadmin')) { + next(); + } else { + res.status(403).json({ message: '관리자 권한이 필요합니다.' }); + } +}; + +// 활동 로그 기록 함수 +const logActivity = async (userId, type, description) => { + try { + await ActivityLog.create({ + userId, + type, + description + }); + } catch (error) { + console.error('활동 로그 기록 중 오류:', error); + } +}; + +// 모든 관리자 라우트에 인증과 admin 권한 검사 미들웨어 적용 +router.use(authenticateJWT); +router.use(authorizeRoles('admin', 'superadmin')); + +// 대시보드 데이터 조회 +router.get('/dashboard', async (req, res) => { + try { + const [ + totalUsers, + totalClubs, + activeSubscriptions, + recentUsers, + recentClubs, + recentSubscriptions + ] = await Promise.all([ + // 전체 사용자 수 + User.count(), + + // 전체 클럽 수 + Club.count(), + + // 활성 구독 수 + ClubSubscription.count({ + where: { + status: 'active' + } + }), + + // 최근 가입한 사용자 + User.findAll({ + limit: 5, + order: [['createdAt', 'DESC']], + attributes: ['id', 'username', 'name', 'email', 'role', 'createdAt'] + }), + + // 최근 생성된 클럽 + Club.findAll({ + limit: 5, + order: [['createdAt', 'DESC']], + attributes: ['id', 'name', 'memberCount', 'createdAt'], + include: [{ + model: User, + as: 'owner', + attributes: ['id', 'name'] + }] + }), + + // 최근 구독 내역 + ClubSubscription.findAll({ + limit: 5, + order: [['createdAt', 'DESC']], + include: [ + { + model: Club, + attributes: ['id', 'name'] + }, + { + model: SubscriptionPlan, + attributes: ['id', 'name', 'price'] + } + ] + }) + ]); + + // 월별 통계 데이터 (최근 6개월) + const sixMonthsAgo = new Date(); + sixMonthsAgo.setMonth(sixMonthsAgo.getMonth() - 5); + + const [userStats, clubStats, subscriptionStats] = await Promise.all([ + // 월별 사용자 가입 통계 + User.findAll({ + where: { + createdAt: { + [Op.gte]: sixMonthsAgo + } + }, + attributes: [ + [sequelize.fn('DATE_FORMAT', sequelize.col('createdAt'), '%Y-%m'), 'month'], + [sequelize.fn('COUNT', sequelize.col('id')), 'count'] + ], + group: [sequelize.fn('DATE_FORMAT', sequelize.col('createdAt'), '%Y-%m')], + order: [[sequelize.fn('DATE_FORMAT', sequelize.col('createdAt'), '%Y-%m'), 'ASC']] + }), + + // 월별 클럽 생성 통계 + Club.findAll({ + where: { + createdAt: { + [Op.gte]: sixMonthsAgo + } + }, + attributes: [ + [sequelize.fn('DATE_FORMAT', sequelize.col('createdAt'), '%Y-%m'), 'month'], + [sequelize.fn('COUNT', sequelize.col('id')), 'count'] + ], + group: [sequelize.fn('DATE_FORMAT', sequelize.col('createdAt'), '%Y-%m')], + order: [[sequelize.fn('DATE_FORMAT', sequelize.col('createdAt'), '%Y-%m'), 'ASC']] + }), + + // 월별 구독 통계 + ClubSubscription.findAll({ + where: { + createdAt: { + [Op.gte]: sixMonthsAgo + } + }, + attributes: [ + [sequelize.fn('DATE_FORMAT', sequelize.col('createdAt'), '%Y-%m'), 'month'], + [sequelize.fn('COUNT', sequelize.col('id')), 'count'] + ], + group: [sequelize.fn('DATE_FORMAT', sequelize.col('createdAt'), '%Y-%m')], + order: [[sequelize.fn('DATE_FORMAT', sequelize.col('createdAt'), '%Y-%m'), 'ASC']] + }) + ]); + + res.json({ + summary: { + totalUsers, + totalClubs, + activeSubscriptions + }, + recent: { + users: recentUsers, + clubs: recentClubs, + subscriptions: recentSubscriptions.map(sub => ({ + ...sub.toJSON(), + expiryDate: sub.endDate + })) + }, + stats: { + users: userStats, + clubs: clubStats, + subscriptions: subscriptionStats + } + }); + + await logActivity(req.user.id, 'VIEW_DASHBOARD', '관리자 대시보드를 조회했습니다.'); + } catch (error) { + console.error('대시보드 데이터 조회 중 오류:', error); + res.status(500).json({ message: '서버 오류가 발생했습니다.' }); + } +}); + +// 클럽 통계 정보 가져오기 +router.get('/clubs/stats', async (req, res) => { + try { + // 전체 클럽 수 조회 + const totalCount = await Club.count(); + + // 활성 클럽 수 조회 (status가 'active'인 클럽) + const activeCount = await Club.count({ + where: { status: 'active' } + }); + + // 구독 상태별 클럽 수 조회 + const subscriptionStats = await ClubSubscription.findAll({ + attributes: ['status', [sequelize.fn('COUNT', sequelize.col('id')), 'count']], + group: ['status'] + }); + + // 최근 생성된 클럽 5개 조회 + const recentClubs = await Club.findAll({ + attributes: ['id', 'name', 'memberCount', 'createdAt', 'status'], + order: [['createdAt', 'DESC']], + limit: 5, + include: [{ + model: User, + as: 'owner', + attributes: ['name', 'email'] + }] + }); + + const subscriptions = { + active: 0, + expired: 0, + suspended: 0 + }; + + subscriptionStats.forEach(stat => { + subscriptions[stat.status] = parseInt(stat.getDataValue('count')); + }); + + res.json({ + totalCount, + activeCount, + subscriptions, + recentClubs: recentClubs.map(club => ({ + ...club.toJSON(), + isActive: club.status === 'active' + })) + }); + + // 활동 로그 기록 + await logActivity(req.user.id, 'VIEW_STATS', '클럽 통계 정보를 조회했습니다.'); + } catch (error) { + console.error('클럽 통계 정보 조회 중 오류:', error); + res.status(500).json({ message: '서버 오류가 발생했습니다.' }); + } +}); + +// 클럽 통계 가져오기 +router.get('/clubs/stats', async (req, res) => { + try { + // 전체 클럽 수 + const totalCount = await Club.count(); + + // 활성 클럽 수 (status가 active인 클럽) + const activeCount = await Club.count({ + where: { status: 'active' } + }); + + // 최근 가입한 클럽 수 (최근 7일) + const newCount = await Club.count({ + where: { + createdAt: { + [Op.gte]: new Date(new Date() - 7 * 24 * 60 * 60 * 1000) + } + } + }); + + // 평균 회원 수 + const avgMemberCount = await Club.findOne({ + attributes: [ + [sequelize.fn('AVG', sequelize.col('memberCount')), 'avgMembers'] + ], + raw: true + }); + + res.json({ + totalCount, + activeCount, + newCount, + averageMemberCount: Math.round(avgMemberCount.avgMembers || 0) + }); + + await logActivity(req.user.id, 'VIEW_CLUB_STATS', '클럽 통계를 조회했습니다.'); + } catch (error) { + console.error('클럽 통계 조회 중 오류:', error); + res.status(500).json({ message: '서버 오류가 발생했습니다.' }); + } +}); + +// 기능 통계 가져오기 +router.get('/features/stats', async (req, res) => { + try { + // 전체 기능 수 + const totalCount = await Feature.count(); + + // 활성화된 기능 수 (status가 active인 기능) + const activeCount = await Feature.count({ + where: { status: 'active' } + }); + + // 클럽별 기능 사용 현황 + const usageStats = await ClubFeature.findAll({ + attributes: [ + 'featureId', + [sequelize.fn('COUNT', sequelize.col('ClubFeature.id')), 'usageCount'] + ], + include: [{ + model: Feature, + attributes: ['name'] + }], + group: ['featureId', 'Feature.id', 'Feature.name'], + raw: true + }); + + // 최근 추가된 기능 (최근 30일) + const newCount = await Feature.count({ + where: { + createdAt: { + [Op.gte]: new Date(new Date() - 30 * 24 * 60 * 60 * 1000) + } + } + }); + + res.json({ + totalCount, + activeCount, + newCount, + usageStats: usageStats.map(stat => ({ + featureName: stat['Feature.name'], + usageCount: parseInt(stat.usageCount) + })) + }); + + await logActivity(req.user.id, 'VIEW_FEATURE_STATS', '기능 통계를 조회했습니다.'); + } catch (error) { + console.error('기능 통계 조회 중 오류:', error); + res.status(500).json({ message: '서버 오류가 발생했습니다.' }); + } +}); + +// 사용자 목록 가져오기 +router.get('/users', async (req, res) => { + try { + const users = await User.findAll({ + attributes: ['id', 'name', 'email', 'role', 'createdAt'], + order: [['createdAt', 'DESC']] + }); + + res.json(users); + + // 활동 로그 기록 + await logActivity(req.user.id, 'VIEW_USERS', '사용자 목록을 조회했습니다.'); + } catch (error) { + console.error('사용자 목록 조회 중 오류:', error); + res.status(500).json({ message: '서버 오류가 발생했습니다.' }); + } +}); + +// 특정 사용자 조회 +router.get('/users/:id', async (req, res) => { + try { + const { id } = req.params; + const user = await User.findByPk(id, { + attributes: ['id', 'username', 'name', 'email', 'role', 'status', 'createdAt'] + }); + + if (!user) { + return res.status(404).json({ message: '사용자를 찾을 수 없습니다.' }); + } + + res.json(user); + await logActivity(req.user.id, 'VIEW_USER', `사용자 ${id}의 정보를 조회했습니다.`); + } catch (error) { + console.error('사용자 조회 중 오류:', error); + res.status(500).json({ message: '서버 오류가 발생했습니다.' }); + } +}); + +// 사용자 정보 수정 +router.put('/users/:id', async (req, res) => { + try { + const { id } = req.params; + const { username, name, email, role, status } = req.body; + + const user = await User.findByPk(id); + if (!user) { + return res.status(404).json({ message: '사용자를 찾을 수 없습니다.' }); + } + + // 관리자는 자신의 role을 변경할 수 없음 + if (req.user.id === parseInt(id) && role && role !== user.role) { + return res.status(403).json({ message: '자신의 권한을 변경할 수 없습니다.' }); + } + + await user.update({ + username, + name, + email, + role, + status + }); + + const updatedUser = await User.findByPk(id, { + attributes: ['id', 'username', 'name', 'email', 'role', 'status', 'createdAt'] + }); + + res.json(updatedUser); + await logActivity(req.user.id, 'UPDATE_USER', `사용자 ${id}의 정보를 수정했습니다.`); + } catch (error) { + console.error('사용자 수정 중 오류:', error); + if (error.name === 'SequelizeUniqueConstraintError') { + return res.status(400).json({ message: '이미 사용 중인 이메일 또는 사용자명입니다.' }); + } + res.status(500).json({ message: '서버 오류가 발생했습니다.' }); + } +}); + +// 사용자 삭제 +router.delete('/users/:id', async (req, res) => { + try { + const { id } = req.params; + + // 자기 자신은 삭제할 수 없음 + if (req.user.id === parseInt(id)) { + return res.status(403).json({ message: '자신의 계정은 삭제할 수 없습니다.' }); + } + + const user = await User.findByPk(id); + if (!user) { + return res.status(404).json({ message: '사용자를 찾을 수 없습니다.' }); + } + + await user.destroy(); + + res.json({ message: '사용자가 삭제되었습니다.' }); + await logActivity(req.user.id, 'DELETE_USER', `사용자 ${id}를 삭제했습니다.`); + } catch (error) { + console.error('사용자 삭제 중 오류:', error); + res.status(500).json({ message: '서버 오류가 발생했습니다.' }); + } +}); + +// 클럽 목록 가져오기 +router.get('/clubs', async (req, res) => { + try { + const clubs = await Club.findAll({ + attributes: [ + 'id', + 'name', + 'location', + 'createdAt', + [sequelize.literal('(SELECT COUNT(*) FROM Members WHERE Members.clubId = Club.id)'), 'memberCount'] + ], + include: [{ + model: User, + as: 'owner', + attributes: ['id', 'name', 'email'] + }], + order: [['createdAt', 'DESC']] + }); + + res.json(clubs); + + // 활동 로그 기록 + await logActivity(req.user.id, 'VIEW_CLUBS', '클럽 목록을 조회했습니다.'); + } catch (error) { + console.error('클럽 목록 조회 중 오류:', error); + res.status(500).json({ message: '서버 오류가 발생했습니다.' }); + } +}); + +// 구독 목록 가져오기 +router.get('/subscriptions', async (req, res) => { + try { + const subscriptions = await ClubSubscription.findAll({ + include: [ + { + model: Club, + attributes: ['id', 'name'] + }, + { + model: SubscriptionPlan, + include: [Feature] + } + ], + order: [['createdAt', 'DESC']] + }); + + const formattedSubscriptions = subscriptions.map(subscription => { + const data = subscription.toJSON(); + data.expiryDate = data.endDate; + delete data.endDate; + return data; + }); + + res.json(formattedSubscriptions); + + await logActivity(req.user.id, 'VIEW_SUBSCRIPTIONS', '구독 목록을 조회했습니다.'); + } catch (error) { + console.error('구독 목록 조회 중 오류:', error); + res.status(500).json({ message: '서버 오류가 발생했습니다.' }); + } +}); + +// 구독 통계 가져오기 +router.get('/subscriptions/stats', async (req, res) => { + try { + // 전체 구독 수 + const totalSubscriptions = await ClubSubscription.count(); + + // 활성 구독 수 + const activeSubscriptions = await ClubSubscription.count({ + where: { status: 'active' } + }); + + // 만료된 구독 수 + const expiredSubscriptions = await ClubSubscription.count({ + where: { status: 'expired' } + }); + + // 플랜별 구독 통계 + const planStats = await ClubSubscription.findAll({ + attributes: [ + 'planId', + [sequelize.fn('COUNT', sequelize.col('ClubSubscription.id')), 'count'] + ], + include: [{ + model: SubscriptionPlan, + attributes: ['name', 'price'] + }], + group: ['planId', 'SubscriptionPlan.id', 'SubscriptionPlan.name', 'SubscriptionPlan.price'], + raw: true + }); + + // 월별 구독 통계 (최근 6개월) + const sixMonthsAgo = new Date(); + sixMonthsAgo.setMonth(sixMonthsAgo.getMonth() - 5); + + const monthlyStats = await ClubSubscription.findAll({ + attributes: [ + [sequelize.fn('DATE_FORMAT', sequelize.col('ClubSubscription.createdAt'), '%Y-%m'), 'month'], + [sequelize.fn('COUNT', sequelize.col('ClubSubscription.id')), 'count'] + ], + where: { + createdAt: { + [Op.gte]: sixMonthsAgo + } + }, + group: [sequelize.fn('DATE_FORMAT', sequelize.col('ClubSubscription.createdAt'), '%Y-%m')], + order: [[sequelize.fn('DATE_FORMAT', sequelize.col('ClubSubscription.createdAt'), '%Y-%m'), 'ASC']], + raw: true + }); + + // 총 매출 계산 + const totalRevenue = planStats.reduce((sum, stat) => { + return sum + (stat['SubscriptionPlan.price'] * parseInt(stat.count)); + }, 0); + + res.json({ + summary: { + totalSubscriptions, + activeSubscriptions, + expiredSubscriptions, + totalRevenue + }, + planStats: planStats.map(stat => ({ + planName: stat['SubscriptionPlan.name'], + planPrice: stat['SubscriptionPlan.price'], + count: parseInt(stat.count), + revenue: stat['SubscriptionPlan.price'] * parseInt(stat.count) + })), + monthlyStats: monthlyStats.map(stat => ({ + month: stat.month, + count: parseInt(stat.count) + })) + }); + + await logActivity(req.user.id, 'VIEW_SUBSCRIPTION_STATS', '구독 통계를 조회했습니다.'); + } catch (error) { + console.error('구독 통계 조회 중 오류:', error); + res.status(500).json({ message: '서버 오류가 발생했습니다.' }); + } +}); + +// 이벤트 목록 가져오기 +router.get('/events', async (req, res) => { + try { + const events = await Meeting.findAll({ + include: [Club], + order: [['date', 'DESC']] + }); + + const formattedEvents = events.map(event => ({ + id: event.id, + title: event.title, + date: event.date, + status: event.status, + teamCount: event.teamCount, + gameCount: event.gameCount, + participantCount: event.participantCount, + clubName: event.Club.name, + location: event.location + })); + + res.json(formattedEvents); + + await logActivity(req.user.id, 'VIEW_EVENTS', '이벤트 목록을 조회했습니다.'); + } catch (error) { + console.error('이벤트 목록 조회 중 오류:', error); + res.status(500).json({ message: '서버 오류가 발생했습니다.' }); + } +}); + +// 이벤트 참가자 목록 가져오기 +router.get('/events/:id/participants', async (req, res) => { + try { + const { id } = req.params; + + const event = await Meeting.findByPk(id, { + include: [{ + model: MeetingParticipant, + include: [Member] + }] + }); + + if (!event) { + return res.status(404).json({ message: '이벤트를 찾을 수 없습니다.' }); + } + + const participants = event.MeetingParticipants.map(p => ({ + id: p.id, + memberId: p.memberId, + memberName: p.Member.name, + teamNumber: p.teamNumber, + status: p.status, + attendance: p.attendance, + paymentStatus: p.paymentStatus, + registeredAt: p.createdAt + })); + + res.json(participants); + + await logActivity(req.user.id, 'VIEW_EVENT_PARTICIPANTS', `이벤트 참가자 목록을 조회했습니다.`); + } catch (error) { + console.error('이벤트 참가자 목록 조회 중 오류:', error); + res.status(500).json({ message: '서버 오류가 발생했습니다.' }); + } +}); + +// 클럽 회원 목록 조회 +router.get('/clubs/:id/members', async (req, res) => { + try { + const { id } = req.params; + + const members = await Member.findAll({ + where: { clubId: id }, + include: [{ + model: User, + attributes: ['id', 'name', 'email'] + }], + order: [['createdAt', 'DESC']] + }); + + res.json(members); + + // 활동 로그 기록 + await logActivity(req.user.id, 'VIEW_CLUB_MEMBERS', `클럽 ${id}의 회원 목록을 조회했습니다.`); + } catch (error) { + console.error('클럽 회원 목록 조회 중 오류:', error); + res.status(500).json({ message: '서버 오류가 발생했습니다.' }); + } +}); + +// 클럽 회원 추가 +router.post('/clubs/:id/members', async (req, res) => { + try { + const { id } = req.params; + const { name, gender, memberType, userId } = req.body; + + const member = await Member.create({ + clubId: id, + userId, + name, + gender, + memberType, + joinDate: new Date(), + status: 'active' + }); + + res.status(201).json(member); + + // 활동 로그 기록 + await logActivity(req.user.id, 'ADD_CLUB_MEMBER', `클럽 ${id}에 새 회원을 추가했습니다.`); + } catch (error) { + console.error('클럽 회원 추가 중 오류:', error); + res.status(500).json({ message: '서버 오류가 발생했습니다.' }); + } +}); + +// 클럽 회원 정보 수정 +router.put('/clubs/:id/members/:memberId', async (req, res) => { + try { + const { id, memberId } = req.params; + const updateData = req.body; + + const member = await Member.findOne({ + where: { + id: memberId, + clubId: id + } + }); + + if (!member) { + return res.status(404).json({ message: '회원을 찾을 수 없습니다.' }); + } + + await member.update(updateData); + + res.json(member); + + // 활동 로그 기록 + await logActivity(req.user.id, 'UPDATE_CLUB_MEMBER', `클럽 ${id}의 회원 정보를 수정했습니다.`); + } catch (error) { + console.error('클럽 회원 정보 수정 중 오류:', error); + res.status(500).json({ message: '서버 오류가 발생했습니다.' }); + } +}); + +// 클럽 회원 삭제 +router.delete('/clubs/:id/members/:memberId', async (req, res) => { + try { + const { id, memberId } = req.params; + + const member = await Member.findOne({ + where: { + id: memberId, + clubId: id + } + }); + + if (!member) { + return res.status(404).json({ message: '회원을 찾을 수 없습니다.' }); + } + + await member.destroy(); + + res.json({ message: '회원이 삭제되었습니다.' }); + + // 활동 로그 기록 + await logActivity(req.user.id, 'DELETE_CLUB_MEMBER', `클럽 ${id}의 회원을 삭제했습니다.`); + } catch (error) { + console.error('클럽 회원 삭제 중 오류:', error); + res.status(500).json({ message: '서버 오류가 발생했습니다.' }); + } +}); + +// 활동 로그 목록 조회 +router.get('/activity-logs', async (req, res) => { + try { + const page = parseInt(req.query.page) || 1; + const limit = parseInt(req.query.limit) || 20; + const offset = (page - 1) * limit; + + const logs = await ActivityLog.findAndCountAll({ + include: [{ + model: User, + attributes: ['username', 'name'] + }], + order: [['createdAt', 'DESC']], + limit, + offset + }); + + res.json({ + logs: logs.rows.map(log => ({ + id: log.id, + type: log.type, + description: log.description, + createdAt: log.createdAt, + user: { + username: log.User.username, + name: log.User.name + } + })), + totalPages: Math.ceil(logs.count / limit), + currentPage: page, + totalItems: logs.count + }); + + await logActivity(req.user.id, 'VIEW_ACTIVITY_LOGS', '활동 로그를 조회했습니다.'); + } catch (error) { + console.error('활동 로그 조회 중 오류:', error); + res.status(500).json({ message: '서버 오류가 발생했습니다.' }); + } +}); + +// 구독 플랜 목록 조회 +router.get('/subscriptions/plans', async (req, res) => { + try { + const plans = await SubscriptionPlan.findAll({ + include: [{ + model: Feature, + through: { attributes: [] } + }], + order: [['price', 'ASC']] + }); + + res.json(plans); + + await logActivity(req.user.id, 'VIEW_SUBSCRIPTION_PLANS', '구독 플랜 목록을 조회했습니다.'); + } catch (error) { + console.error('구독 플랜 목록 조회 중 오류:', error); + res.status(500).json({ message: '서버 오류가 발생했습니다.' }); + } +}); + +// 구독 플랜 생성 +router.post('/subscriptions/plans', async (req, res) => { + try { + const { name, description, price, duration, features } = req.body; + + const plan = await SubscriptionPlan.create({ + name, + description, + price, + duration + }); + + if (features && features.length > 0) { + await plan.setFeatures(features); + } + + res.status(201).json(plan); + + await logActivity(req.user.id, 'CREATE_SUBSCRIPTION_PLAN', `새 구독 플랜 "${name}"을(를) 생성했습니다.`); + } catch (error) { + console.error('구독 플랜 생성 중 오류:', error); + res.status(500).json({ message: '서버 오류가 발생했습니다.' }); + } +}); + +// 구독 플랜 수정 +router.put('/subscriptions/plans/:id', async (req, res) => { + try { + const { id } = req.params; + const { name, description, price, duration, features } = req.body; + + const plan = await SubscriptionPlan.findByPk(id); + if (!plan) { + return res.status(404).json({ message: '구독 플랜을 찾을 수 없습니다.' }); + } + + await plan.update({ + name, + description, + price, + duration + }); + + if (features) { + await plan.setFeatures(features); + } + + res.json(plan); + + await logActivity(req.user.id, 'UPDATE_SUBSCRIPTION_PLAN', `구독 플랜 "${name}"을(를) 수정했습니다.`); + } catch (error) { + console.error('구독 플랜 수정 중 오류:', error); + res.status(500).json({ message: '서버 오류가 발생했습니다.' }); + } +}); + +// 구독 플랜 삭제 +router.delete('/subscriptions/plans/:id', async (req, res) => { + try { + const { id } = req.params; + + const plan = await SubscriptionPlan.findByPk(id); + if (!plan) { + return res.status(404).json({ message: '구독 플랜을 찾을 수 없습니다.' }); + } + + await plan.destroy(); + + res.json({ message: '구독 플랜이 삭제되었습니다.' }); + + await logActivity(req.user.id, 'DELETE_SUBSCRIPTION_PLAN', `구독 플랜 "${plan.name}"을(를) 삭제했습니다.`); + } catch (error) { + console.error('구독 플랜 삭제 중 오류:', error); + res.status(500).json({ message: '서버 오류가 발생했습니다.' }); + } +}); + +// 기능 목록 조회 +router.get('/features', async (req, res) => { + try { + const features = await Feature.findAll({ + order: [['name', 'ASC']] + }); + + res.json(features); + + await logActivity(req.user.id, 'VIEW_FEATURES', '기능 목록을 조회했습니다.'); + } catch (error) { + console.error('기능 목록 조회 중 오류:', error); + res.status(500).json({ message: '서버 오류가 발생했습니다.' }); + } +}); + +// 기능 생성 +router.post('/features', async (req, res) => { + try { + const { name, description, category } = req.body; + + const feature = await Feature.create({ + name, + description, + category + }); + + res.status(201).json(feature); + + await logActivity(req.user.id, 'CREATE_FEATURE', `새 기능 "${name}"을(를) 생성했습니다.`); + } catch (error) { + console.error('기능 생성 중 오류:', error); + res.status(500).json({ message: '서버 오류가 발생했습니다.' }); + } +}); + +// 기능 수정 +router.put('/features/:id', async (req, res) => { + try { + const { id } = req.params; + const { name, description, category } = req.body; + + const feature = await Feature.findByPk(id); + if (!feature) { + return res.status(404).json({ message: '기능을 찾을 수 없습니다.' }); + } + + await feature.update({ + name, + description, + category + }); + + res.json(feature); + + await logActivity(req.user.id, 'UPDATE_FEATURE', `기능 "${name}"을(를) 수정했습니다.`); + } catch (error) { + console.error('기능 수정 중 오류:', error); + res.status(500).json({ message: '서버 오류가 발생했습니다.' }); + } +}); + +// 기능 삭제 +router.delete('/features/:id', async (req, res) => { + try { + const { id } = req.params; + + const feature = await Feature.findByPk(id); + if (!feature) { + return res.status(404).json({ message: '기능을 찾을 수 없습니다.' }); + } + + await feature.destroy(); + + res.json({ message: '기능이 삭제되었습니다.' }); + + await logActivity(req.user.id, 'DELETE_FEATURE', `기능 "${feature.name}"을(를) 삭제했습니다.`); + } catch (error) { + console.error('기능 삭제 중 오류:', error); + res.status(500).json({ message: '서버 오류가 발생했습니다.' }); + } +}); + +// 구독 생성 +router.post('/subscriptions', async (req, res) => { + try { + const { clubId, planId, startDate, expiryDate, status } = req.body; + console.log('구독 생성 요청:', { clubId, planId, startDate, expiryDate, status }); + + // 트랜잭션 시작 + const result = await sequelize.transaction(async (t) => { + // 클럽과 플랜이 존재하는지 확인 + const [club, plan] = await Promise.all([ + Club.findByPk(clubId, { transaction: t }), + SubscriptionPlan.findByPk(planId, { + include: [Feature], + transaction: t + }) + ]); + + if (!club) { + throw new Error('클럽을 찾을 수 없습니다.'); + } + + if (!plan) { + throw new Error('구독 플랜을 찾을 수 없습니다.'); + } + + // 기존 활성 구독이 있는지 확인 + const existingSubscription = await ClubSubscription.findOne({ + where: { + clubId, + status: 'active' + }, + transaction: t + }); + + if (existingSubscription) { + throw new Error('이미 활성화된 구독이 있습니다.'); + } + + // 구독 생성 + const subscription = await ClubSubscription.create({ + clubId, + planId, + startDate: startDate || new Date(), + endDate: expiryDate, + status: status || 'active', + price: plan.price + }, { transaction: t }); + + // 플랜의 기능들을 클럽에 연결 + if (plan.Features && plan.Features.length > 0) { + const features = plan.Features.map(feature => ({ + clubId, + featureId: feature.id, + enabled: true, + expiryDate, + createdAt: new Date(), + updatedAt: new Date() + })); + + await ClubFeature.bulkCreate(features, { + transaction: t, + fields: ['clubId', 'featureId', 'enabled', 'expiryDate', 'createdAt', 'updatedAt'] + }); + } + + return { subscription, features: plan.Features }; + }); + + // 응답 데이터 구성 + const response = result.subscription.toJSON(); + response.expiryDate = response.endDate; + delete response.endDate; + response.features = result.features; + + res.status(201).json(response); + + await logActivity(req.user.id, 'CREATE_SUBSCRIPTION', `클럽 ${clubId}의 새 구독을 생성했습니다.`); + } catch (error) { + console.error('구독 생성 중 오류 발생:', error); + console.error('오류 상세:', error.stack); + res.status(error.message.includes('찾을 수 없습니다') ? 404 : + error.message.includes('이미 활성화된') ? 400 : 500) + .json({ message: error.message || '서버 오류가 발생했습니다.' }); + } +}); + +// 구독 수정 +router.put('/subscriptions/:id', async (req, res) => { + try { + const { id } = req.params; + const { planId, startDate, expiryDate, status } = req.body; + + const subscription = await ClubSubscription.findByPk(id, { + include: [Club, SubscriptionPlan] + }); + + if (!subscription) { + return res.status(404).json({ message: '구독을 찾을 수 없습니다.' }); + } + + // 플랜이 변경되는 경우 새 플랜 확인 + let newPlan = null; + if (planId && planId !== subscription.planId) { + newPlan = await SubscriptionPlan.findByPk(planId); + if (!newPlan) { + return res.status(404).json({ message: '구독 플랜을 찾을 수 없습니다.' }); + } + } + + // 구독 정보 업데이트 + await subscription.update({ + planId: planId || subscription.planId, + startDate: startDate || subscription.startDate, + endDate: expiryDate || subscription.endDate, + status: status || subscription.status, + price: newPlan ? newPlan.price : subscription.price + }); + + // 플랜이 변경된 경우 기능도 업데이트 + if (newPlan) { + const planFeatures = await newPlan.getFeatures(); + await subscription.Club.setFeatures(planFeatures); + } + + // 업데이트된 구독 정보 조회 + const updatedSubscription = await ClubSubscription.findByPk(id, { + include: [{ + model: SubscriptionPlan, + include: [Feature] + }] + }); + + // endDate를 expiryDate로 변환 + const result = updatedSubscription.toJSON(); + result.expiryDate = result.endDate; + delete result.endDate; + + res.json(result); + + await logActivity(req.user.id, 'UPDATE_SUBSCRIPTION', `구독 ${id}를 수정했습니다.`); + } catch (error) { + console.error('구독 수정 중 오류:', error); + res.status(500).json({ message: '서버 오류가 발생했습니다.' }); + } +}); + +// 구독 삭제 +router.delete('/subscriptions/:id', async (req, res) => { + try { + const { id } = req.params; + + const subscription = await ClubSubscription.findByPk(id); + if (!subscription) { + return res.status(404).json({ message: '구독을 찾을 수 없습니다.' }); + } + + // 구독과 관련된 기능들을 제거 + const club = await Club.findByPk(subscription.clubId); + if (club) { + await club.setFeatures([]); + } + + await subscription.destroy(); + + res.json({ message: '구독이 삭제되었습니다.' }); + + await logActivity(req.user.id, 'DELETE_SUBSCRIPTION', `구독 ${id}를 삭제했습니다.`); + } catch (error) { + console.error('구독 삭제 중 오류:', error); + res.status(500).json({ message: '서버 오류가 발생했습니다.' }); + } +}); + +// 구독 목록 조회 +router.get('/subscriptions', async (req, res) => { + try { + const subscriptions = await ClubSubscription.findAll({ + include: [{ + model: Club, + attributes: ['id', 'name'] + }, { + model: SubscriptionPlan, + include: [Feature] + }], + order: [['createdAt', 'DESC']] + }); + + const formattedSubscriptions = subscriptions.map(subscription => { + const data = subscription.toJSON(); + data.expiryDate = data.endDate; + delete data.endDate; + return data; + }); + + res.json(formattedSubscriptions); + + await logActivity(req.user.id, 'VIEW_SUBSCRIPTIONS', '구독 목록을 조회했습니다.'); + } catch (error) { + console.error('구독 목록 조회 중 오류:', error); + res.status(500).json({ message: '서버 오류가 발생했습니다.' }); + } +}); + +// 특정 구독 조회 +router.get('/subscriptions/:id', async (req, res) => { + try { + const { id } = req.params; + + const subscription = await ClubSubscription.findByPk(id, { + include: [{ + model: Club, + attributes: ['id', 'name'] + }, { + model: SubscriptionPlan, + include: [Feature] + }] + }); + + if (!subscription) { + return res.status(404).json({ message: '구독을 찾을 수 없습니다.' }); + } + + res.json(subscription); + + await logActivity(req.user.id, 'VIEW_SUBSCRIPTION', `구독 ${id}를 조회했습니다.`); + } catch (error) { + console.error('구독 조회 중 오류:', error); + res.status(500).json({ message: '서버 오류가 발생했습니다.' }); + } +}); + +// 특정 플랜의 구독 목록 조회 +router.get('/subscriptions/plans/:planId/subscriptions', async (req, res) => { + try { + const { planId } = req.params; + + const subscriptions = await ClubSubscription.findAll({ + where: { + planId + }, + include: [ + { + model: Club, + attributes: ['id', 'name'] + }, + { + model: SubscriptionPlan, + include: [Feature] + } + ], + order: [['createdAt', 'DESC']] + }); + + const formattedSubscriptions = subscriptions.map(subscription => { + const data = subscription.toJSON(); + data.expiryDate = data.endDate; + delete data.endDate; + return data; + }); + + res.json(formattedSubscriptions); + + await logActivity(req.user.id, 'VIEW_PLAN_SUBSCRIPTIONS', `플랜 ${planId}의 구독 목록을 조회했습니다.`); + } catch (error) { + console.error('플랜별 구독 목록 조회 중 오류:', error); + res.status(500).json({ message: '서버 오류가 발생했습니다.' }); + } +}); + +// 클럽 상세 조회 +router.get('/clubs/:id', async (req, res) => { + try { + const { id } = req.params; + const club = await Club.findByPk(id, { + include: [ + { + model: User, + as: 'owner', + attributes: ['id', 'name', 'email'] + }, + { + model: ClubFeature, + as: 'features', + include: [{ + model: Feature, + as: 'feature', + attributes: ['id', 'name', 'code', 'description'] + }] + }, + { + model: ClubSubscription, + where: { + status: 'active' + }, + required: false, + include: [{ + model: SubscriptionPlan, + include: [Feature] + }] + } + ] + }); + + if (!club) { + return res.status(404).json({ message: '클럽을 찾을 수 없습니다.' }); + } + + // 응답 데이터 구성 + const response = club.toJSON(); + + // 활성 구독이 있는 경우 구독 정보 포맷팅 + if (response.ClubSubscriptions && response.ClubSubscriptions.length > 0) { + const subscription = response.ClubSubscriptions[0]; + response.activeSubscription = { + id: subscription.id, + startDate: subscription.startDate, + expiryDate: subscription.endDate, + plan: subscription.SubscriptionPlan + }; + } + delete response.ClubSubscriptions; + + // 기능 정보 포맷팅 + const formattedFeatures = response.features.map(feature => ({ + ...feature.feature, + enabled: feature.enabled, + expiryDate: feature.expiryDate + })); + response.features = formattedFeatures; + + res.json(response); + await logActivity(req.user.id, 'VIEW_CLUB', `클럽 ${id}의 정보를 조회했습니다.`); + } catch (error) { + console.error('클럽 조회 중 오류:', error); + res.status(500).json({ message: '서버 오류가 발생했습니다.' }); + } +}); + +// 클럽 수정 +router.put('/clubs/:id', async (req, res) => { + try { + const { id } = req.params; + const { name, description, ownerEmail, location } = req.body; + + // 클럽 존재 여부 확인 + const club = await Club.findByPk(id); + if (!club) { + return res.status(404).json({ message: '클럽을 찾을 수 없습니다.' }); + } + + // 새 모임장 정보 확인 + const newOwner = await User.findOne({ where: { email: ownerEmail } }); + if (!newOwner) { + return res.status(404).json({ message: '모임장으로 지정할 사용자를 찾을 수 없습니다.' }); + } + + // 트랜잭션 시작 + const t = await sequelize.transaction(); + + try { + // 클럽 정보 업데이트 + await club.update({ + name, + description, + location, + ownerId: newOwner.id + }, { transaction: t }); + + // 활동 로그 기록 + await logActivity(req.user.id, 'UPDATE_CLUB', `클럽 "${name}"의 정보를 수정했습니다.`); + + await t.commit(); + + // 업데이트된 클럽 정보 조회 + const updatedClub = await Club.findByPk(id, { + include: [{ + model: User, + as: 'owner', + attributes: ['id', 'name', 'email'] + }] + }); + + res.json(updatedClub); + } catch (error) { + await t.rollback(); + throw error; + } + } catch (error) { + console.error('클럽 수정 중 오류:', error); + res.status(500).json({ message: '클럽 수정 중 오류가 발생했습니다.' }); + } +}); + +// 클럽 기능 관리 +router.put('/clubs/:id/features', async (req, res) => { + const t = await sequelize.transaction(); + + try { + const { id } = req.params; + const { features } = req.body; + + const club = await Club.findByPk(id, { transaction: t }); + if (!club) { + await t.rollback(); + return res.status(404).json({ message: '클럽을 찾을 수 없습니다.' }); + } + + // 기존 기능 목록 조회 + const existingFeatures = await ClubFeature.findAll({ + where: { clubId: id }, + transaction: t + }); + + // 기능 업데이트 또는 생성 + for (const feature of features) { + const existingFeature = existingFeatures.find(ef => ef.featureId === feature.id); + + if (existingFeature) { + await existingFeature.update({ + enabled: feature.enabled, + expiryDate: feature.expiryDate + }, { transaction: t }); + } else { + await ClubFeature.create({ + clubId: id, + featureId: feature.id, + enabled: feature.enabled, + expiryDate: feature.expiryDate + }, { transaction: t }); + } + } + + // 제거된 기능 처리 + const featureIds = features.map(f => f.id); + await ClubFeature.destroy({ + where: { + clubId: id, + featureId: { + [Op.notIn]: featureIds + } + }, + transaction: t + }); + + await t.commit(); + + // 업데이트된 클럽 정보 조회 + const updatedClub = await Club.findByPk(id, { + include: [{ + model: Feature, + through: { + attributes: ['enabled', 'expiryDate'] + } + }] + }); + + const response = updatedClub.toJSON(); + response.features = response.Features.map(feature => ({ + ...feature, + enabled: feature.ClubFeature.enabled, + expiryDate: feature.ClubFeature.expiryDate + })); + delete response.Features; + + res.json(response); + await logActivity(req.user.id, 'UPDATE_CLUB_FEATURES', `클럽 ${id}의 기능을 수정했습니다.`); + } catch (error) { + await t.rollback(); + console.error('클럽 기능 수정 중 오류:', error); + res.status(500).json({ message: '서버 오류가 발생했습니다.' }); + } +}); + +// 에러 처리 미들웨어 +const errorHandler = (err, req, res, next) => { + console.error('에러 발생:', err); + res.status(500).json({ + message: '서버 오류가 발생했습니다.', + error: process.env.NODE_ENV === 'development' ? err.message : undefined + }); +}; + +router.use(errorHandler); + +module.exports = router; diff --git a/backend/routes/authRoutes.js b/backend/routes/authRoutes.js new file mode 100644 index 0000000..0c156c5 --- /dev/null +++ b/backend/routes/authRoutes.js @@ -0,0 +1,20 @@ +const express = require('express'); +const router = express.Router(); +const authController = require('../controllers/authController'); + +// 인증 미들웨어 +const authMiddleware = require('../middleware/auth'); + +// 로그인 +router.post('/login', authController.login); + +// 현재 사용자 정보 가져오기 +router.get('/me', authMiddleware, authController.getCurrentUser); + +// 회원가입 +router.post('/register', authController.register); + +// 프로필 업데이트 +router.put('/profile', authMiddleware, authController.updateProfile); + +module.exports = router; diff --git a/backend/routes/clubRoutes.js b/backend/routes/clubRoutes.js new file mode 100644 index 0000000..5544c12 --- /dev/null +++ b/backend/routes/clubRoutes.js @@ -0,0 +1,61 @@ +const express = require('express'); +const router = express.Router(); +const jwt = require('jsonwebtoken'); +const clubController = require('../controllers/clubController'); +const { authenticateJWT } = require('../middleware/authMiddleware'); + +// 모든 클럽 조회 +router.get('/', clubController.getAllClubs); + +// 특정 클럽 조회 +router.post('/', authenticateJWT, clubController.getClubById); + +// 클럽 정보 업데이트 +router.put('/', authenticateJWT, clubController.updateClub); + +// 클럽 삭제 +router.delete('/', authenticateJWT, clubController.deleteClub); + +// 사용자 클럽 목록 조회 +router.post('/user', authenticateJWT, clubController.getUserClubs); + +// 클럽 회원 통계 조회 +router.post('/members/stats', authenticateJWT, clubController.getMemberStats); + +// 클럽 회원 목록 조회 +router.post('/members', authenticateJWT, clubController.getClubMembers); + +// 클럽 회원 추가 +router.post('/members/add', authenticateJWT, clubController.addClubMember); + +// 클럽 회원 수정 +router.post('/members/update', authenticateJWT, clubController.updateClubMember); + +// 클럽 회원 삭제 +router.post('/members/delete', authenticateJWT, clubController.deleteClubMember); + +// 다음 모임 정보 조회 +router.post('/meetings/next', authenticateJWT, clubController.getNextMeeting); + +// 최근 모임 정보 조회 +router.post('/meetings/last', authenticateJWT, clubController.getLastMeeting); + +// 최근 정기 모임 목록 조회 +router.post('/meetings/recent', authenticateJWT, clubController.getRecentMeetings); + +// 점수 통계 조회 +router.post('/scores/stats', authenticateJWT, clubController.getScoreStats); + +// 상위 회원 목록 조회 +router.post('/members/top', authenticateJWT, clubController.getTopMembers); + +// 새 클럽 생성 +router.post('/create', authenticateJWT, clubController.createClub); + +// 클럽 통계 조회 +router.post('/statistics', authenticateJWT, clubController.getStatistics); + +// 클럽 기능 관리 +router.put('/features', authenticateJWT, clubController.manageClubFeatures); + +module.exports = router; diff --git a/backend/routes/eventRoutes.js b/backend/routes/eventRoutes.js new file mode 100644 index 0000000..fd2e496 --- /dev/null +++ b/backend/routes/eventRoutes.js @@ -0,0 +1,29 @@ +const express = require('express'); +const router = express.Router(); +const eventController = require('../controllers/eventController'); +const { authenticateJWT } = require('../middleware/authMiddleware'); + +// 이벤트 기본 CRUD +router.post('/', authenticateJWT, eventController.getAllEvents); +router.get('/:id', authenticateJWT, eventController.getEventById); +router.post('/create', authenticateJWT, eventController.createEvent); +router.put('/:id', authenticateJWT, eventController.updateEvent); +router.delete('/:id', authenticateJWT, eventController.deleteEvent); + +// 이벤트 참가자 관리 +router.post('/:id/participants/list', authenticateJWT, eventController.getEventParticipants); +router.post('/:id/participants', authenticateJWT, eventController.addEventParticipant); +router.put('/:id/participants', authenticateJWT, eventController.updateEventParticipant); +router.delete('/:id/participants', authenticateJWT, eventController.deleteEventParticipant); + +// 이벤트 점수 관리 +router.get('/:id/scores', authenticateJWT, eventController.getEventScores); +router.post('/:id/scores', authenticateJWT, eventController.addEventScore); +router.put('/:id/scores/:scoreId', authenticateJWT, eventController.updateEventScore); +router.delete('/:id/scores/:scoreId', authenticateJWT, eventController.deleteEventScore); +router.delete('/:id/participants/:participantId/scores', authenticateJWT, eventController.deleteParticipantScores); + +// 사용자별 이벤트 관리 +router.get('/user/events', authenticateJWT, eventController.getUserEvents); + +module.exports = router; \ No newline at end of file diff --git a/backend/routes/featureRoutes.js b/backend/routes/featureRoutes.js new file mode 100644 index 0000000..9d49c94 --- /dev/null +++ b/backend/routes/featureRoutes.js @@ -0,0 +1,15 @@ +const express = require('express'); +const router = express.Router(); +const featureController = require('../controllers/featureController'); +const { authenticateJWT } = require('../middleware/authMiddleware'); + +// 기본 기능 관리 +router.get('/', authenticateJWT, featureController.getFeatures); + +// 기능 구매 관련 API +router.get('/available', authenticateJWT, featureController.getAvailableFeatures); +router.get('/active', authenticateJWT, featureController.getActiveFeatures); +router.get('/payments', authenticateJWT, featureController.getFeaturePayments); +router.post('/purchase', authenticateJWT, featureController.purchaseFeature); + +module.exports = router; diff --git a/backend/routes/menuRoutes.js b/backend/routes/menuRoutes.js new file mode 100644 index 0000000..e9deef4 --- /dev/null +++ b/backend/routes/menuRoutes.js @@ -0,0 +1,13 @@ +/** + * 메뉴 라우트 + * 사용자 역할과 클럽의 구독/기능 상태에 따른 메뉴 아이템을 제공하는 API + */ +const express = require('express'); +const router = express.Router(); +const { authenticateJWT } = require('../middleware/authMiddleware'); +const menuController = require('../controllers/menuController'); + +// 사용자의 접근 가능한 메뉴 목록 조회 +router.post('/', authenticateJWT, menuController.getUserMenus); + +module.exports = router; diff --git a/backend/routes/notificationRoutes.js b/backend/routes/notificationRoutes.js new file mode 100644 index 0000000..2d75229 --- /dev/null +++ b/backend/routes/notificationRoutes.js @@ -0,0 +1,34 @@ +/** + * 알림 라우트 + * 사용자 알림 관련 API + */ +const express = require('express'); +const router = express.Router(); +const notificationController = require('../controllers/notificationController'); +const { check } = require('express-validator'); +const { authenticateJWT } = require('../middleware/authMiddleware'); + +// 사용자 알림 목록 가져오기 +router.get('/', authenticateJWT, notificationController.getUserNotifications); + +// 알림 읽음 표시 +router.put('/:notificationId/read', authenticateJWT, notificationController.markAsRead); + +// 모든 알림 읽음 표시 +router.put('/read-all', authenticateJWT, notificationController.markAllAsRead); + +// 알림 삭제 +router.delete('/:notificationId', authenticateJWT, notificationController.deleteNotification); + +// 알림 생성 (관리자 전용) +router.post('/', + authenticateJWT, + [ + check('userId', '사용자 ID가 필요합니다.').not().isEmpty(), + check('title', '알림 제목이 필요합니다.').not().isEmpty(), + check('message', '알림 내용이 필요합니다.').not().isEmpty() + ], + notificationController.createNotification +); + +module.exports = router; diff --git a/backend/routes/subscriptionRoutes.js b/backend/routes/subscriptionRoutes.js new file mode 100644 index 0000000..835cc27 --- /dev/null +++ b/backend/routes/subscriptionRoutes.js @@ -0,0 +1,19 @@ +const express = require('express'); +const router = express.Router(); +const subscriptionController = require('../controllers/subscriptionController'); +const authMiddleware = require('../middleware/auth'); +// 구독 목록 조회 +router.get('/', authMiddleware, subscriptionController.getClubSubscriptions); + +// 구독 플랜 관리 (관리자 전용) +router.get('/plans', authMiddleware, subscriptionController.getSubscriptionPlans); +router.post('/plans', authMiddleware, subscriptionController.createSubscriptionPlan); +router.put('/plans/:id', authMiddleware, subscriptionController.updateSubscriptionPlan); +router.delete('/plans/:id', authMiddleware, subscriptionController.deleteSubscriptionPlan); +router.get('/plans/:planId/subscriptions', authMiddleware, subscriptionController.getPlanSubscriptions); + +// 클럽 구독 관리 +router.post('/subscribe', authMiddleware, subscriptionController.subscribeClub); +router.get('/clubs/:clubId', authMiddleware, subscriptionController.getClubSubscription); + +module.exports = router; \ No newline at end of file diff --git a/backend/routes/userRoutes.js b/backend/routes/userRoutes.js new file mode 100644 index 0000000..c371cfe --- /dev/null +++ b/backend/routes/userRoutes.js @@ -0,0 +1,23 @@ +/** + * 사용자 라우트 + * 사용자 관련 API 엔드포인트 정의 + */ +const express = require('express'); +const router = express.Router(); +const { getCurrentUser } = require('../controllers/authController'); +const { getAllUsers, getUser, updateUser } = require('../controllers/userController'); +const { authenticateJWT } = require('../middleware/authMiddleware'); + +// 전체 사용자 목록 조회 +router.get('/all', authenticateJWT, getAllUsers); + +// 사용자 프로필 조회 +router.get('/profile', authenticateJWT, getCurrentUser); + +// 단일 사용자 정보 조회 +router.get('/:id', authenticateJWT, getUser); + +// 사용자 정보 수정 +router.put('/:id', authenticateJWT, updateUser); + +module.exports = router; diff --git a/backend/scripts/check-db-tables.js b/backend/scripts/check-db-tables.js new file mode 100644 index 0000000..0ed275e --- /dev/null +++ b/backend/scripts/check-db-tables.js @@ -0,0 +1,38 @@ +const { sequelize } = require('../config/database'); + +async function checkDatabaseTables() { + try { + // 데이터베이스 연결 + await sequelize.authenticate(); + console.log('데이터베이스 연결 성공'); + + // 모든 테이블 목록 조회 + const [tables] = await sequelize.query('SHOW TABLES'); + + console.log('데이터베이스 테이블 목록:'); + tables.forEach(table => { + const tableName = Object.values(table)[0]; + console.log(`- ${tableName}`); + }); + + // 각 테이블의 구조 확인 + for (const table of tables) { + const tableName = Object.values(table)[0]; + const [columns] = await sequelize.query(`DESCRIBE \`${tableName}\``); + + console.log(`\n테이블: ${tableName}`); + columns.forEach(column => { + console.log(` - ${column.Field}: ${column.Type} ${column.Null === 'NO' ? 'NOT NULL' : 'NULL'} ${column.Key === 'PRI' ? 'PRIMARY KEY' : ''}`); + }); + } + + } catch (error) { + console.error('오류 발생:', error); + } finally { + // 연결 종료 + await sequelize.close(); + } +} + +// 스크립트 실행 +checkDatabaseTables(); diff --git a/backend/scripts/create-default-club.js b/backend/scripts/create-default-club.js new file mode 100644 index 0000000..e285606 --- /dev/null +++ b/backend/scripts/create-default-club.js @@ -0,0 +1,94 @@ +/** + * 기본 클럽 생성 스크립트 + * 데이터베이스에 기본 클럽을 생성하고 사용자에게 할당합니다. + */ +const { sequelize } = require('../src/config/database'); +const Club = require('../src/models/Club'); +const User = require('../src/models/User'); +const UserClub = require('../src/models/UserClub'); +require('dotenv').config(); + +async function createDefaultClub() { + try { + // 데이터베이스 연결 확인 + await sequelize.authenticate(); + console.log('데이터베이스 연결 성공'); + + // 관리자 사용자 찾기 + const adminUser = await User.findOne({ + where: { + role: 'admin' + } + }); + + if (!adminUser) { + console.log('관리자 사용자를 찾을 수 없습니다. 기본 클럽을 생성할 수 없습니다.'); + return; + } + + console.log('관리자 사용자 찾음:', adminUser.email); + + // 기존 클럽 확인 + const existingClubs = await Club.findAll(); + + let defaultClub; + + if (existingClubs.length === 0) { + // 기본 클럽 생성 + defaultClub = await Club.create({ + name: '볼링 마스터 클럽', + description: '볼링 클럽 매니저 기본 클럽입니다.', + ownerId: adminUser.id, + active: true, + subscriptionStatus: '활성', + subscriptionType: '기본 구독', + subscriptionExpiry: new Date(Date.now() + 365 * 24 * 60 * 60 * 1000), // 1년 후 + memberCount: 1 + }); + console.log('기본 클럽 생성 완료:', defaultClub.name); + } else { + defaultClub = existingClubs[0]; + console.log('기존 클럽 사용:', defaultClub.name); + } + + // 모든 사용자 가져오기 + const users = await User.findAll(); + + // 각 사용자에게 클럽 할당 + for (const user of users) { + // 이미 클럽에 속해 있는지 확인 + const existingUserClub = await UserClub.findOne({ + where: { + userId: user.id, + clubId: defaultClub.id + } + }); + + if (!existingUserClub) { + // 사용자-클럽 관계 생성 + await UserClub.create({ + userId: user.id, + clubId: defaultClub.id, + role: user.role === 'admin' || user.role === 'superadmin' ? 'admin' : 'member', + joinDate: new Date(), + status: 'active' + }); + console.log(`사용자 ${user.email}에게 클럽 할당 완료`); + } else { + console.log(`사용자 ${user.email}는 이미 클럽에 속해 있습니다.`); + } + } + + console.log('기본 클럽 생성 및 사용자 할당이 완료되었습니다.'); + console.log('기본 클럽 ID:', defaultClub.id); + + return defaultClub.id; + } catch (error) { + console.error('기본 클럽 생성 중 오류 발생:', error); + } finally { + process.exit(); + } +} + +// 스크립트 실행 +createDefaultClub(); diff --git a/backend/scripts/create-menus.js b/backend/scripts/create-menus.js new file mode 100644 index 0000000..662fc9d --- /dev/null +++ b/backend/scripts/create-menus.js @@ -0,0 +1,153 @@ +/** + * 메뉴 생성 스크립트 + * 데이터베이스에 기본 메뉴 항목을 생성합니다. + */ +const { Menu } = require('../src/models'); +const { sequelize } = require('../src/config/database'); +require('dotenv').config(); + +// 관리자용 메뉴 항목 +const adminMenus = [ + { + title: '대시보드', + icon: 'pi pi-home', + to: '/admin', + role: 'admin', + order: 1, + visible: true + }, + { + title: '클럽 관리', + icon: 'pi pi-users', + to: '/admin/clubs', + role: 'admin', + order: 2, + visible: true + }, + { + title: '사용자 관리', + icon: 'pi pi-user', + to: '/admin/users', + role: 'admin', + order: 3, + visible: true + }, + { + title: '구독 관리', + icon: 'pi pi-credit-card', + to: '/admin/subscriptions', + role: 'admin', + order: 4, + visible: true + } +]; + +// 일반 사용자용 메뉴 항목 +const userMenus = [ + { + title: '대시보드', + icon: 'pi pi-home', + to: '/clubs', + role: 'user', + order: 1, + visible: true + }, + { + title: '회원 관리', + icon: 'pi pi-users', + to: '/clubs/members', + role: 'user', + order: 2, + visible: true + }, + { + title: '모임 관리', + icon: 'pi pi-calendar', + to: '/clubs/meetings', + role: 'user', + order: 3, + visible: true + }, + { + title: '점수 관리', + icon: 'pi pi-chart-bar', + to: '/clubs/meetings/scores', + role: 'user', + order: 4, + visible: true + }, + { + title: '통계', + icon: 'pi pi-chart-line', + to: '/statistics', + role: 'user', + order: 5, + visible: true + }, + { + title: '예약', + icon: 'pi pi-bookmark', + to: '/clubs/reservations', + role: 'user', + order: 6, + visible: true + }, + { + title: '이벤트 관리', + icon: 'pi pi-list', + to: '/clubs/events', + role: 'user', + order: 7, + visible: true + }, + { + title: '이벤트 캘린더', + icon: 'pi pi-calendar', + to: '/clubs/events/calendar', + role: 'user', + order: 8, + visible: true + } +]; + +// 메뉴 생성 함수 +async function createMenus() { + try { + // 데이터베이스 연결 확인 + await sequelize.authenticate(); + console.log('데이터베이스 연결 성공'); + + // 메뉴 테이블 동기화 + await Menu.sync({ alter: true }); + console.log('메뉴 테이블 동기화 완료'); + + // 기존 메뉴 항목 확인 + const existingMenus = await Menu.findAll(); + + if (existingMenus.length > 0) { + console.log('메뉴 항목이 이미 존재합니다.'); + return; + } + + // 관리자용 메뉴 생성 + for (const menu of adminMenus) { + await Menu.create(menu); + } + console.log('관리자용 메뉴 생성 완료'); + + // 일반 사용자용 메뉴 생성 + for (const menu of userMenus) { + await Menu.create(menu); + } + console.log('일반 사용자용 메뉴 생성 완료'); + + console.log('모든 메뉴 항목이 성공적으로 생성되었습니다.'); + } catch (error) { + console.error('메뉴 생성 중 오류 발생:', error); + } finally { + process.exit(); + } +} + +// 스크립트 실행 +createMenus(); diff --git a/backend/scripts/create-notifications.js b/backend/scripts/create-notifications.js new file mode 100644 index 0000000..f3d9d8b --- /dev/null +++ b/backend/scripts/create-notifications.js @@ -0,0 +1,109 @@ +/** + * 초기 알림 데이터 생성 스크립트 + */ +require('dotenv').config(); +const mongoose = require('mongoose'); +const User = require('../models/User'); +const Notification = require('../models/Notification'); + +// MongoDB 연결 +mongoose.connect(process.env.MONGODB_URI || 'mongodb://localhost:27017/bowlingManager', { + useNewUrlParser: true, + useUnifiedTopology: true +}) +.then(() => console.log('MongoDB 연결 성공')) +.catch(err => { + console.error('MongoDB 연결 오류:', err); + process.exit(1); +}); + +// 초기 알림 데이터 생성 +const createNotifications = async () => { + try { + // 모든 사용자 가져오기 + const users = await User.find(); + + if (users.length === 0) { + console.log('사용자가 없습니다. 먼저 사용자를 생성해주세요.'); + process.exit(0); + } + + // 기존 알림 삭제 + await Notification.deleteMany({}); + console.log('기존 알림 데이터 삭제 완료'); + + // 각 사용자에 대한 알림 생성 + const notificationPromises = users.map(async (user) => { + // 알림 타입 및 메시지 배열 + const notificationTemplates = [ + { + title: '시스템 알림', + message: '볼링 클럽 매니저 시스템에 오신 것을 환영합니다.', + icon: 'pi pi-info-circle', + type: 'info', + read: false + }, + { + title: '클럽 알림', + message: '새로운 클럽 회원이 가입했습니다.', + icon: 'pi pi-users', + type: 'success', + read: false + }, + { + title: '예약 알림', + message: '레인 예약이 확정되었습니다.', + icon: 'pi pi-calendar', + type: 'info', + read: true + }, + { + title: '대회 알림', + message: '다음 주 토요일 클럽 대회가 예정되어 있습니다.', + icon: 'pi pi-flag', + type: 'warning', + read: false + }, + { + title: '시스템 업데이트', + message: '시스템이 업데이트 되었습니다. 새로운 기능을 확인해보세요.', + icon: 'pi pi-refresh', + type: 'info', + read: true + } + ]; + + // 사용자별 알림 생성 (시간 차이를 두고) + const notifications = notificationTemplates.map((template, index) => { + const hoursAgo = (index + 1) * 12; // 12시간, 24시간, 36시간... 간격 + const createdAt = new Date(Date.now() - (hoursAgo * 3600000)); + + return new Notification({ + user: user._id, + title: template.title, + message: template.message, + icon: template.icon, + type: template.type, + read: template.read, + createdAt + }); + }); + + return Notification.insertMany(notifications); + }); + + await Promise.all(notificationPromises); + console.log('알림 데이터 생성 완료'); + + // 연결 종료 + mongoose.connection.close(); + console.log('MongoDB 연결 종료'); + process.exit(0); + } catch (error) { + console.error('알림 데이터 생성 오류:', error); + mongoose.connection.close(); + process.exit(1); + } +}; + +createNotifications(); diff --git a/backend/scripts/create-superadmin.js b/backend/scripts/create-superadmin.js new file mode 100644 index 0000000..6017b41 --- /dev/null +++ b/backend/scripts/create-superadmin.js @@ -0,0 +1,46 @@ +require('dotenv').config(); +const { User, syncModels, testConnection } = require('../src/models'); + +async function createSuperAdmin() { + try { + // 데이터베이스 연결 테스트 + console.log('데이터베이스 연결 테스트 중...'); + await testConnection(); + + // 모델 동기화 (필요한 경우) + // console.log('데이터베이스 모델 동기화 중...'); + // await syncModels(); + + // 기존 superadmin 계정 확인 + const existingAdmin = await User.findOne({ + where: { email: process.env.ADMIN_EMAIL } + }); + + if (existingAdmin) { + console.log('이미 superadmin 계정이 존재합니다:', existingAdmin.email); + process.exit(0); + } + + // superadmin 계정 생성 + const superAdmin = await User.create({ + username: 'master', + password: process.env.ADMIN_PASSWORD, + name: '최고관리자', + email: process.env.ADMIN_EMAIL, + role: 'superadmin', + active: true + }); + + console.log('superadmin 계정이 성공적으로 생성되었습니다:'); + console.log('- 이메일:', superAdmin.email); + console.log('- 역할:', superAdmin.role); + + process.exit(0); + } catch (error) { + console.error('superadmin 계정 생성 중 오류 발생:', error); + process.exit(1); + } +} + +// 스크립트 실행 +createSuperAdmin(); diff --git a/backend/seeders/20250313-admin-user.js b/backend/seeders/20250313-admin-user.js new file mode 100644 index 0000000..d93e8b5 --- /dev/null +++ b/backend/seeders/20250313-admin-user.js @@ -0,0 +1,27 @@ +'use strict'; +const bcrypt = require('bcrypt'); + +/** @type {import('sequelize-cli').Migration} */ +module.exports = { + async up (queryInterface, Sequelize) { + // 관리자 계정 비밀번호 해시 생성 + const salt = await bcrypt.genSalt(10); + const hashedPassword = await bcrypt.hash('Dlrwns0304#', salt); + + // 관리자 계정 생성 + await queryInterface.bulkInsert('users', [{ + name: '관리자', + email: 'master@jaybe.dev', + password: hashedPassword, + role: 'admin', + status: 'active', + createdAt: new Date(), + updatedAt: new Date() + }], {}); + }, + + async down (queryInterface, Sequelize) { + // 관리자 계정 삭제 + await queryInterface.bulkDelete('users', { email: 'master@jaybe.dev' }, {}); + } +}; diff --git a/backend/sync-database.js b/backend/sync-database.js new file mode 100644 index 0000000..66c3689 --- /dev/null +++ b/backend/sync-database.js @@ -0,0 +1,23 @@ +// 데이터베이스 모델 동기화 스크립트 +const { sequelize, syncModels, testConnection } = require('./src/models'); + +async function initializeDatabase() { + try { + // 데이터베이스 연결 테스트 + console.log('데이터베이스 연결 테스트 중...'); + await testConnection(); + + // 모델 동기화 (테이블 생성) + console.log('데이터베이스 모델 동기화 중...'); + await syncModels(); + + console.log('데이터베이스 초기화가 완료되었습니다.'); + process.exit(0); + } catch (error) { + console.error('데이터베이스 초기화 중 오류 발생:', error); + process.exit(1); + } +} + +// 스크립트 실행 +initializeDatabase(); diff --git a/backend/utils/notificationExamples.js b/backend/utils/notificationExamples.js new file mode 100644 index 0000000..ddebb87 --- /dev/null +++ b/backend/utils/notificationExamples.js @@ -0,0 +1,160 @@ +/** + * 알림 생성 예제 유틸리티 + * 다양한 상황에서 알림을 생성하는 예제 함수들을 제공합니다. + */ + +const notificationController = require('../controllers/notificationController'); + +/** + * 새 회원 가입 알림 생성 + * @param {Object} clubOwner 클럽 소유자 정보 + * @param {Object} newMember 새로 가입한 회원 정보 + */ +exports.newMemberJoinNotification = async (clubOwner, newMember) => { + await notificationController.createSystemNotification( + clubOwner.id, + '새 회원 가입', + `${newMember.name}님이 클럽에 가입했습니다.`, + { + icon: 'pi pi-users', + type: 'success', + link: `/club/members/${newMember.id}` + } + ); +}; + +/** + * 모임 생성 알림 생성 + * @param {Array} members 알림을 받을 회원 목록 + * @param {Object} meeting 생성된 모임 정보 + */ +exports.newMeetingNotification = async (members, meeting) => { + for (const member of members) { + await notificationController.createSystemNotification( + member.id, + '새 모임 생성', + `새로운 모임 "${meeting.title}"이(가) 생성되었습니다. (${meeting.date})`, + { + icon: 'pi pi-calendar', + type: 'info', + link: `/meetings/${meeting.id}` + } + ); + } +}; + +/** + * 점수 등록 알림 생성 + * @param {Object} member 점수를 등록한 회원 + * @param {Object} score 등록된 점수 정보 + */ +exports.scoreRegisteredNotification = async (member, score) => { + await notificationController.createSystemNotification( + member.id, + '점수 등록 완료', + `게임 점수 ${score.score}점이 등록되었습니다.`, + { + icon: 'pi pi-chart-bar', + type: 'success', + link: `/scores/${score.id}` + } + ); +}; + +/** + * 구독 만료 예정 알림 생성 + * @param {Object} clubOwner 클럽 소유자 정보 + * @param {Object} subscription 구독 정보 + * @param {Number} daysLeft 만료까지 남은 일수 + */ +exports.subscriptionExpiryNotification = async (clubOwner, subscription, daysLeft) => { + await notificationController.createSystemNotification( + clubOwner.id, + '구독 만료 예정', + `클럽 구독이 ${daysLeft}일 후에 만료됩니다. 갱신하시려면 결제를 진행해주세요.`, + { + icon: 'pi pi-clock', + type: 'warning', + link: '/subscription' + } + ); +}; + +/** + * 시스템 유지보수 알림 생성 + * @param {Array} users 알림을 받을 사용자 목록 + * @param {String} maintenanceDate 유지보수 일자 + * @param {String} maintenanceTime 유지보수 시간 + */ +exports.systemMaintenanceNotification = async (users, maintenanceDate, maintenanceTime) => { + for (const user of users) { + await notificationController.createSystemNotification( + user.id, + '시스템 유지보수 안내', + `${maintenanceDate} ${maintenanceTime}에 시스템 유지보수가 예정되어 있습니다. 서비스 이용에 참고해주세요.`, + { + icon: 'pi pi-info-circle', + type: 'info' + } + ); + } +}; + +/** + * 결제 완료 알림 생성 + * @param {Object} user 결제한 사용자 정보 + * @param {Object} payment 결제 정보 + */ +exports.paymentCompletedNotification = async (user, payment) => { + await notificationController.createSystemNotification( + user.id, + '결제 완료', + `${payment.amount.toLocaleString()}원 결제가 완료되었습니다.`, + { + icon: 'pi pi-credit-card', + type: 'success', + link: '/payments/history' + } + ); +}; + +/** + * 대회 일정 알림 생성 + * @param {Array} participants 대회 참가자 목록 + * @param {Object} tournament 대회 정보 + */ +exports.tournamentReminderNotification = async (participants, tournament) => { + for (const participant of participants) { + await notificationController.createSystemNotification( + participant.id, + '대회 일정 안내', + `내일 "${tournament.title}" 대회가 ${tournament.location}에서 개최됩니다.`, + { + icon: 'pi pi-flag', + type: 'warning', + link: `/tournaments/${tournament.id}` + } + ); + } +}; + +/** + * 오류 발생 알림 생성 (관리자용) + * @param {Array} admins 알림을 받을 관리자 목록 + * @param {String} errorMessage 오류 메시지 + * @param {String} errorLocation 오류 발생 위치 + */ +exports.systemErrorNotification = async (admins, errorMessage, errorLocation) => { + for (const admin of admins) { + await notificationController.createSystemNotification( + admin.id, + '시스템 오류 발생', + `위치: ${errorLocation}\n오류: ${errorMessage}`, + { + icon: 'pi pi-exclamation-triangle', + type: 'error', + link: '/admin/logs' + } + ); + } +}; diff --git a/frontend/.vscode/extensions.json b/frontend/.vscode/extensions.json new file mode 100644 index 0000000..85b465b --- /dev/null +++ b/frontend/.vscode/extensions.json @@ -0,0 +1,3 @@ +{ + "recommendations": ["Vue.volar"] +} diff --git a/frontend/README.md b/frontend/README.md new file mode 100644 index 0000000..a544c07 --- /dev/null +++ b/frontend/README.md @@ -0,0 +1,29 @@ +# frontend + +This template should help get you started developing with Vue 3 in Vite. + +## Recommended IDE Setup + +[VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur). + +## Customize configuration + +See [Vite Configuration Reference](https://vite.dev/config/). + +## Project Setup + +```sh +npm install +``` + +### Compile and Hot-Reload for Development + +```sh +npm run dev +``` + +### Compile and Minify for Production + +```sh +npm run build +``` diff --git a/frontend/index.html b/frontend/index.html new file mode 100644 index 0000000..457388a --- /dev/null +++ b/frontend/index.html @@ -0,0 +1,13 @@ + + + + + + + Vite App + + +
+ + + diff --git a/frontend/jsconfig.json b/frontend/jsconfig.json new file mode 100644 index 0000000..45ec4f7 --- /dev/null +++ b/frontend/jsconfig.json @@ -0,0 +1,8 @@ +{ + "compilerOptions": { + "paths": { + "@/*": ["./src/*"] + } + }, + "exclude": ["node_modules", "dist"] +} diff --git a/frontend/package-lock.json b/frontend/package-lock.json new file mode 100644 index 0000000..ddb03c4 --- /dev/null +++ b/frontend/package-lock.json @@ -0,0 +1,3528 @@ +{ + "name": "frontend", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "frontend", + "version": "0.0.0", + "dependencies": { + "@fullcalendar/core": "^6.1.15", + "@fullcalendar/daygrid": "^6.1.15", + "@fullcalendar/interaction": "^6.1.15", + "@fullcalendar/timegrid": "^6.1.15", + "@fullcalendar/vue3": "^6.1.15", + "@vuelidate/core": "^2.0.3", + "@vuelidate/validators": "^2.0.4", + "axios": "^1.8.2", + "chart.js": "^4.4.8", + "dayjs": "^1.11.10", + "jwt-decode": "^4.0.0", + "pinia": "^2.1.7", + "primeflex": "^3.3.1", + "primeicons": "^6.0.1", + "primevue": "^3.49.1", + "socket.io-client": "^4.8.1", + "vee-validate": "^4.12.5", + "vue": "^3.5.13", + "vue-chartjs": "^5.3.2", + "vue-router": "^4.5.0", + "yup": "^1.3.3" + }, + "devDependencies": { + "@vitejs/plugin-vue": "^5.2.1", + "vite": "^6.2.1", + "vite-plugin-vue-devtools": "^7.7.2" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@antfu/utils": { + "version": "0.7.10", + "resolved": "https://registry.npmjs.org/@antfu/utils/-/utils-0.7.10.tgz", + "integrity": "sha512-+562v9k4aI80m1+VuMHehNJWLOFjBnXn3tdOitzD0il5b7smkSBal4+a3oKiQTbrwMmN/TBUMDvbdoWDehgOww==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.26.2", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", + "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.25.9", + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.26.8", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.26.8.tgz", + "integrity": "sha512-oH5UPLMWR3L2wEFLnFJ1TZXqHufiTKAiLfqw5zkhS4dKXLJ10yVztfil/twG8EDTA4F/tvVNw9nOl4ZMslB8rQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.26.9", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.9.tgz", + "integrity": "sha512-lWBYIrF7qK5+GjY5Uy+/hEgp8OJWOD/rpy74GplYRhEauvbHDeFB8t5hPOZxCZ0Oxf4Cc36tK51/l3ymJysrKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.26.2", + "@babel/generator": "^7.26.9", + "@babel/helper-compilation-targets": "^7.26.5", + "@babel/helper-module-transforms": "^7.26.0", + "@babel/helpers": "^7.26.9", + "@babel/parser": "^7.26.9", + "@babel/template": "^7.26.9", + "@babel/traverse": "^7.26.9", + "@babel/types": "^7.26.9", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.26.9", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.26.9.tgz", + "integrity": "sha512-kEWdzjOAUMW4hAyrzJ0ZaTOu9OmpyDIQicIh0zg0EEcEkYXZb2TjtBhnHi2ViX7PKwZqF4xwqfAm299/QMP3lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.26.9", + "@babel/types": "^7.26.9", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-annotate-as-pure": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.25.9.tgz", + "integrity": "sha512-gv7320KBUFJz1RnylIg5WWYPRXKZ884AGkYpgpWW02TH66Dl+HaC1t1CKd0z3R4b6hdYEcmrNZHUmfCP+1u3/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.26.5", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.26.5.tgz", + "integrity": "sha512-IXuyn5EkouFJscIDuFF5EsiSolseme1s0CZB+QxVugqJLYmKdxI1VfIBOst0SUu4rnk2Z7kqTwmoO1lp3HIfnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.26.5", + "@babel/helper-validator-option": "^7.25.9", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-create-class-features-plugin": { + "version": "7.26.9", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.26.9.tgz", + "integrity": "sha512-ubbUqCofvxPRurw5L8WTsCLSkQiVpov4Qx0WMA+jUN+nXBK8ADPlJO1grkFw5CWKC5+sZSOfuGMdX1aI1iT9Sg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.25.9", + "@babel/helper-member-expression-to-functions": "^7.25.9", + "@babel/helper-optimise-call-expression": "^7.25.9", + "@babel/helper-replace-supers": "^7.26.5", + "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9", + "@babel/traverse": "^7.26.9", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.25.9.tgz", + "integrity": "sha512-wbfdZ9w5vk0C0oyHqAJbc62+vet5prjj01jjJ8sKn3j9h3MQQlflEdXYvuqRWjHnM12coDEqiC1IRCi0U/EKwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz", + "integrity": "sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.26.0.tgz", + "integrity": "sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9", + "@babel/traverse": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-optimise-call-expression": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.25.9.tgz", + "integrity": "sha512-FIpuNaz5ow8VyrYcnXQTDRGvV6tTjkNtCK/RYNDXGSLlUD6cBuQTSw43CShGxjvfBTfcUA/r6UhUCbtYqkhcuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.26.5", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.26.5.tgz", + "integrity": "sha512-RS+jZcRdZdRFzMyr+wcsaqOmld1/EqTghfaBGQQd/WnRdzdlvSZ//kF7U8VQTxf1ynZ4cjUcYgjVGx13ewNPMg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-replace-supers": { + "version": "7.26.5", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.26.5.tgz", + "integrity": "sha512-bJ6iIVdYX1YooY2X7w1q6VITt+LnUILtNk7zT78ykuwStx8BauCzxvFqFaHjOpW1bVnSUM1PN1f0p5P21wHxvg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-member-expression-to-functions": "^7.25.9", + "@babel/helper-optimise-call-expression": "^7.25.9", + "@babel/traverse": "^7.26.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.25.9.tgz", + "integrity": "sha512-K4Du3BFa3gvyhzgPcntrkDgZzQaq6uozzcpGbOO1OEJaI+EJdqWIMTLgFgQf6lrfiDFo5FU+BxKepI9RmZqahA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", + "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", + "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.25.9.tgz", + "integrity": "sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.26.9", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.26.9.tgz", + "integrity": "sha512-Mz/4+y8udxBKdmzt/UjPACs4G3j5SshJJEFFKxlCGPydG4JAHXxjWjAwjd09tf6oINvl1VfMJo+nB7H2YKQ0dA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.26.9", + "@babel/types": "^7.26.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.26.9", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.9.tgz", + "integrity": "sha512-81NWa1njQblgZbQHxWHpxxCzNsa3ZwvFqpUg7P+NNUU6f3UU2jBEg4OlF/J6rl8+PQGh1q6/zWScd001YwcA5A==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.26.9" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-proposal-decorators": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.25.9.tgz", + "integrity": "sha512-smkNLL/O1ezy9Nhy4CNosc4Va+1wo5w4gzSZeLe6y6dM4mmHfYOCPolXQPHQxonZCF+ZyebxN9vqOolkYrSn5g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/plugin-syntax-decorators": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-decorators": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.25.9.tgz", + "integrity": "sha512-ryzI0McXUPJnRCvMo4lumIKZUzhYUO/ScI+Mz4YVaTLt04DHNSjEUjKVvbzQjZFLuod/cYEc07mJWhzl6v4DPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.26.0.tgz", + "integrity": "sha512-e2dttdsJ1ZTpi3B9UYGLw41hifAubg19AtCu/2I/F1QNVclOBr1dYpTdmdyZ84Xiz43BS/tCUkMAZNLv12Pi+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.25.9.tgz", + "integrity": "sha512-ld6oezHQMZsZfp6pWtbjaNDF2tiiCYYDqQszHt5VV437lewP9aSi2Of99CK0D0XB21k7FLgnLcmQKyKzynfeAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.25.9.tgz", + "integrity": "sha512-hjMgRy5hb8uJJjUcdWunWVcoi9bGpJp8p5Ol1229PoN6aytsLwNMgmdftO23wnCLMfVmTwZDWMPNq/D1SY60JQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typescript": { + "version": "7.26.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.26.8.tgz", + "integrity": "sha512-bME5J9AC8ChwA7aEPJ6zym3w7aObZULHhbNLU0bKUhKsAkylkzUdq+0kdymh9rzi8nlNFl2bmldFBCKNJBUpuw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.25.9", + "@babel/helper-create-class-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.26.5", + "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9", + "@babel/plugin-syntax-typescript": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.26.9", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.26.9.tgz", + "integrity": "sha512-qyRplbeIpNZhmzOysF/wFMuP9sctmh2cFzRAZOn1YapxBsE1i9bJIY586R/WBLfLcmcBlM8ROBiQURnnNy+zfA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.26.2", + "@babel/parser": "^7.26.9", + "@babel/types": "^7.26.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.26.9", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.26.9.tgz", + "integrity": "sha512-ZYW7L+pL8ahU5fXmNbPF+iZFHCv5scFak7MZ9bwaRPLUhHh7QQEMjZUg0HevihoqCM5iSYHN61EyCoZvqC+bxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.26.2", + "@babel/generator": "^7.26.9", + "@babel/parser": "^7.26.9", + "@babel/template": "^7.26.9", + "@babel/types": "^7.26.9", + "debug": "^4.3.1", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.26.9", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.9.tgz", + "integrity": "sha512-Y3IR1cRnOxOCDvMmNiym7XpXQ93iGDDPHx+Zj+NM+rg0fBaShfQLkg+hKPaZCEvg5N/LeCo4+Rj/i3FuJsIQaw==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.1.tgz", + "integrity": "sha512-kfYGy8IdzTGy+z0vFGvExZtxkFlA4zAxgKEahG9KE1ScBjpQnFsNOX8KTU5ojNru5ed5CVoJYXFtoxaq5nFbjQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.1.tgz", + "integrity": "sha512-dp+MshLYux6j/JjdqVLnMglQlFu+MuVeNrmT5nk6q07wNhCdSnB7QZj+7G8VMUGh1q+vj2Bq8kRsuyA00I/k+Q==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.1.tgz", + "integrity": "sha512-50tM0zCJW5kGqgG7fQ7IHvQOcAn9TKiVRuQ/lN0xR+T2lzEFvAi1ZcS8DiksFcEpf1t/GYOeOfCAgDHFpkiSmA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.1.tgz", + "integrity": "sha512-GCj6WfUtNldqUzYkN/ITtlhwQqGWu9S45vUXs7EIYf+7rCiiqH9bCloatO9VhxsL0Pji+PF4Lz2XXCES+Q8hDw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.1.tgz", + "integrity": "sha512-5hEZKPf+nQjYoSr/elb62U19/l1mZDdqidGfmFutVUjjUZrOazAtwK+Kr+3y0C/oeJfLlxo9fXb1w7L+P7E4FQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.1.tgz", + "integrity": "sha512-hxVnwL2Dqs3fM1IWq8Iezh0cX7ZGdVhbTfnOy5uURtao5OIVCEyj9xIzemDi7sRvKsuSdtCAhMKarxqtlyVyfA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.1.tgz", + "integrity": "sha512-1MrCZs0fZa2g8E+FUo2ipw6jw5qqQiH+tERoS5fAfKnRx6NXH31tXBKI3VpmLijLH6yriMZsxJtaXUyFt/8Y4A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.1.tgz", + "integrity": "sha512-0IZWLiTyz7nm0xuIs0q1Y3QWJC52R8aSXxe40VUxm6BB1RNmkODtW6LHvWRrGiICulcX7ZvyH6h5fqdLu4gkww==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.1.tgz", + "integrity": "sha512-NdKOhS4u7JhDKw9G3cY6sWqFcnLITn6SqivVArbzIaf3cemShqfLGHYMx8Xlm/lBit3/5d7kXvriTUGa5YViuQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.1.tgz", + "integrity": "sha512-jaN3dHi0/DDPelk0nLcXRm1q7DNJpjXy7yWaWvbfkPvI+7XNSc/lDOnCLN7gzsyzgu6qSAmgSvP9oXAhP973uQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.1.tgz", + "integrity": "sha512-OJykPaF4v8JidKNGz8c/q1lBO44sQNUQtq1KktJXdBLn1hPod5rE/Hko5ugKKZd+D2+o1a9MFGUEIUwO2YfgkQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.1.tgz", + "integrity": "sha512-nGfornQj4dzcq5Vp835oM/o21UMlXzn79KobKlcs3Wz9smwiifknLy4xDCLUU0BWp7b/houtdrgUz7nOGnfIYg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.1.tgz", + "integrity": "sha512-1osBbPEFYwIE5IVB/0g2X6i1qInZa1aIoj1TdL4AaAb55xIIgbg8Doq6a5BzYWgr+tEcDzYH67XVnTmUzL+nXg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.1.tgz", + "integrity": "sha512-/6VBJOwUf3TdTvJZ82qF3tbLuWsscd7/1w+D9LH0W/SqUgM5/JJD0lrJ1fVIfZsqB6RFmLCe0Xz3fmZc3WtyVg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.1.tgz", + "integrity": "sha512-nSut/Mx5gnilhcq2yIMLMe3Wl4FK5wx/o0QuuCLMtmJn+WeWYoEGDN1ipcN72g1WHsnIbxGXd4i/MF0gTcuAjQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.1.tgz", + "integrity": "sha512-cEECeLlJNfT8kZHqLarDBQso9a27o2Zd2AQ8USAEoGtejOrCYHNtKP8XQhMDJMtthdF4GBmjR2au3x1udADQQQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.1.tgz", + "integrity": "sha512-xbfUhu/gnvSEg+EGovRc+kjBAkrvtk38RlerAzQxvMzlB4fXpCFCeUAYzJvrnhFtdeyVCDANSjJvOvGYoeKzFA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.1.tgz", + "integrity": "sha512-O96poM2XGhLtpTh+s4+nP7YCCAfb4tJNRVZHfIE7dgmax+yMP2WgMd2OecBuaATHKTHsLWHQeuaxMRnCsH8+5g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.1.tgz", + "integrity": "sha512-X53z6uXip6KFXBQ+Krbx25XHV/NCbzryM6ehOAeAil7X7oa4XIq+394PWGnwaSQ2WRA0KI6PUO6hTO5zeF5ijA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.1.tgz", + "integrity": "sha512-Na9T3szbXezdzM/Kfs3GcRQNjHzM6GzFBeU1/6IV/npKP5ORtp9zbQjvkDJ47s6BCgaAZnnnu/cY1x342+MvZg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.1.tgz", + "integrity": "sha512-T3H78X2h1tszfRSf+txbt5aOp/e7TAz3ptVKu9Oyir3IAOFPGV6O9c2naym5TOriy1l0nNf6a4X5UXRZSGX/dw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.1.tgz", + "integrity": "sha512-2H3RUvcmULO7dIE5EWJH8eubZAI4xw54H1ilJnRNZdeo8dTADEZ21w6J22XBkXqGJbe0+wnNJtw3UXRoLJnFEg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.1.tgz", + "integrity": "sha512-GE7XvrdOzrb+yVKB9KsRMq+7a2U/K5Cf/8grVFRAGJmfADr/e/ODQ134RK2/eeHqYV5eQRFxb1hY7Nr15fv1NQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.1.tgz", + "integrity": "sha512-uOxSJCIcavSiT6UnBhBzE8wy3n0hOkJsBOzy7HDAuTDE++1DJMRRVCPGisULScHL+a/ZwdXPpXD3IyFKjA7K8A==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.1.tgz", + "integrity": "sha512-Y1EQdcfwMSeQN/ujR5VayLOJ1BHaK+ssyk0AEzPjC+t1lITgsnccPqFjb6V+LsTp/9Iov4ysfjxLaGJ9RPtkVg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@fullcalendar/core": { + "version": "6.1.15", + "resolved": "https://registry.npmjs.org/@fullcalendar/core/-/core-6.1.15.tgz", + "integrity": "sha512-BuX7o6ALpLb84cMw1FCB9/cSgF4JbVO894cjJZ6kP74jzbUZNjtwffwRdA+Id8rrLjT30d/7TrkW90k4zbXB5Q==", + "license": "MIT", + "dependencies": { + "preact": "~10.12.1" + } + }, + "node_modules/@fullcalendar/daygrid": { + "version": "6.1.15", + "resolved": "https://registry.npmjs.org/@fullcalendar/daygrid/-/daygrid-6.1.15.tgz", + "integrity": "sha512-j8tL0HhfiVsdtOCLfzK2J0RtSkiad3BYYemwQKq512cx6btz6ZZ2RNc/hVnIxluuWFyvx5sXZwoeTJsFSFTEFA==", + "license": "MIT", + "peerDependencies": { + "@fullcalendar/core": "~6.1.15" + } + }, + "node_modules/@fullcalendar/interaction": { + "version": "6.1.15", + "resolved": "https://registry.npmjs.org/@fullcalendar/interaction/-/interaction-6.1.15.tgz", + "integrity": "sha512-DOTSkofizM7QItjgu7W68TvKKvN9PSEEvDJceyMbQDvlXHa7pm/WAVtAc6xSDZ9xmB1QramYoWGLHkCYbTW1rQ==", + "license": "MIT", + "peerDependencies": { + "@fullcalendar/core": "~6.1.15" + } + }, + "node_modules/@fullcalendar/timegrid": { + "version": "6.1.15", + "resolved": "https://registry.npmjs.org/@fullcalendar/timegrid/-/timegrid-6.1.15.tgz", + "integrity": "sha512-61ORr3A148RtxQ2FNG7JKvacyA/TEVZ7z6I+3E9Oeu3dqTf6M928bFcpehRTIK6zIA6Yifs7BeWHgOE9dFnpbw==", + "license": "MIT", + "dependencies": { + "@fullcalendar/daygrid": "~6.1.15" + }, + "peerDependencies": { + "@fullcalendar/core": "~6.1.15" + } + }, + "node_modules/@fullcalendar/vue3": { + "version": "6.1.15", + "resolved": "https://registry.npmjs.org/@fullcalendar/vue3/-/vue3-6.1.15.tgz", + "integrity": "sha512-ctfTICGrNEIj7gmLHQcUYe0WzDTSW5Vd9hyOnVChxPU75AZU9WqdDMkHwJYnfNxNhT6QQuiMHq/qsRRd5zQwOw==", + "license": "MIT", + "peerDependencies": { + "@fullcalendar/core": "~6.1.15", + "vue": "^3.0.11" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", + "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@kurkle/color": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.4.tgz", + "integrity": "sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==", + "license": "MIT" + }, + "node_modules/@polka/url": { + "version": "1.0.0-next.28", + "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.28.tgz", + "integrity": "sha512-8LduaNlMZGwdZ6qWrKlfa+2M4gahzFkprZiAt2TF8uS0qQgBizKXpXURqvTJ4WtmupWxaLqjRb2UCTe72mu+Aw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/pluginutils": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.4.tgz", + "integrity": "sha512-USm05zrsFxYLPdWWq+K3STlWiT/3ELn3RcV5hJMghpeAIhxfsUIg6mt12CBJBInWMV4VneoV7SfGv8xIwo2qNQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.35.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.35.0.tgz", + "integrity": "sha512-uYQ2WfPaqz5QtVgMxfN6NpLD+no0MYHDBywl7itPYd3K5TjjSghNKmX8ic9S8NU8w81NVhJv/XojcHptRly7qQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.35.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.35.0.tgz", + "integrity": "sha512-FtKddj9XZudurLhdJnBl9fl6BwCJ3ky8riCXjEw3/UIbjmIY58ppWwPEvU3fNu+W7FUsAsB1CdH+7EQE6CXAPA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.35.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.35.0.tgz", + "integrity": "sha512-Uk+GjOJR6CY844/q6r5DR/6lkPFOw0hjfOIzVx22THJXMxktXG6CbejseJFznU8vHcEBLpiXKY3/6xc+cBm65Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.35.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.35.0.tgz", + "integrity": "sha512-3IrHjfAS6Vkp+5bISNQnPogRAW5GAV1n+bNCrDwXmfMHbPl5EhTmWtfmwlJxFRUCBZ+tZ/OxDyU08aF6NI/N5Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.35.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.35.0.tgz", + "integrity": "sha512-sxjoD/6F9cDLSELuLNnY0fOrM9WA0KrM0vWm57XhrIMf5FGiN8D0l7fn+bpUeBSU7dCgPV2oX4zHAsAXyHFGcQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.35.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.35.0.tgz", + "integrity": "sha512-2mpHCeRuD1u/2kruUiHSsnjWtHjqVbzhBkNVQ1aVD63CcexKVcQGwJ2g5VphOd84GvxfSvnnlEyBtQCE5hxVVw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.35.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.35.0.tgz", + "integrity": "sha512-mrA0v3QMy6ZSvEuLs0dMxcO2LnaCONs1Z73GUDBHWbY8tFFocM6yl7YyMu7rz4zS81NDSqhrUuolyZXGi8TEqg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.35.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.35.0.tgz", + "integrity": "sha512-DnYhhzcvTAKNexIql8pFajr0PiDGrIsBYPRvCKlA5ixSS3uwo/CWNZxB09jhIapEIg945KOzcYEAGGSmTSpk7A==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.35.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.35.0.tgz", + "integrity": "sha512-uagpnH2M2g2b5iLsCTZ35CL1FgyuzzJQ8L9VtlJ+FckBXroTwNOaD0z0/UF+k5K3aNQjbm8LIVpxykUOQt1m/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.35.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.35.0.tgz", + "integrity": "sha512-XQxVOCd6VJeHQA/7YcqyV0/88N6ysSVzRjJ9I9UA/xXpEsjvAgDTgH3wQYz5bmr7SPtVK2TsP2fQ2N9L4ukoUg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loongarch64-gnu": { + "version": "4.35.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.35.0.tgz", + "integrity": "sha512-5pMT5PzfgwcXEwOaSrqVsz/LvjDZt+vQ8RT/70yhPU06PTuq8WaHhfT1LW+cdD7mW6i/J5/XIkX/1tCAkh1W6g==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.35.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.35.0.tgz", + "integrity": "sha512-c+zkcvbhbXF98f4CtEIP1EBA/lCic5xB0lToneZYvMeKu5Kamq3O8gqrxiYYLzlZH6E3Aq+TSW86E4ay8iD8EA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.35.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.35.0.tgz", + "integrity": "sha512-s91fuAHdOwH/Tad2tzTtPX7UZyytHIRR6V4+2IGlV0Cej5rkG0R61SX4l4y9sh0JBibMiploZx3oHKPnQBKe4g==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.35.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.35.0.tgz", + "integrity": "sha512-hQRkPQPLYJZYGP+Hj4fR9dDBMIM7zrzJDWFEMPdTnTy95Ljnv0/4w/ixFw3pTBMEuuEuoqtBINYND4M7ujcuQw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.35.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.35.0.tgz", + "integrity": "sha512-Pim1T8rXOri+0HmV4CdKSGrqcBWX0d1HoPnQ0uw0bdp1aP5SdQVNBy8LjYncvnLgu3fnnCt17xjWGd4cqh8/hA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.35.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.35.0.tgz", + "integrity": "sha512-QysqXzYiDvQWfUiTm8XmJNO2zm9yC9P/2Gkrwg2dH9cxotQzunBHYr6jk4SujCTqnfGxduOmQcI7c2ryuW8XVg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.35.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.35.0.tgz", + "integrity": "sha512-OUOlGqPkVJCdJETKOCEf1mw848ZyJ5w50/rZ/3IBQVdLfR5jk/6Sr5m3iO2tdPgwo0x7VcncYuOvMhBWZq8ayg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.35.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.35.0.tgz", + "integrity": "sha512-2/lsgejMrtwQe44glq7AFFHLfJBPafpsTa6JvP2NGef/ifOa4KBoglVf7AKN7EV9o32evBPRqfg96fEHzWo5kw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.35.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.35.0.tgz", + "integrity": "sha512-PIQeY5XDkrOysbQblSW7v3l1MDZzkTEzAfTPkj5VAu3FW8fS4ynyLg2sINp0fp3SjZ8xkRYpLqoKcYqAkhU1dw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@sec-ant/readable-stream": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@sec-ant/readable-stream/-/readable-stream-0.4.1.tgz", + "integrity": "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@sindresorhus/merge-streams": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-4.0.0.tgz", + "integrity": "sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@socket.io/component-emitter": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", + "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==", + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", + "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@vitejs/plugin-vue": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.2.1.tgz", + "integrity": "sha512-cxh314tzaWwOLqVes2gnnCtvBDcM1UMdn+iFR+UjAn411dPT3tOmqrJjbMd7koZpMAmBM/GqeV4n9ge7JSiJJQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "peerDependencies": { + "vite": "^5.0.0 || ^6.0.0", + "vue": "^3.2.25" + } + }, + "node_modules/@vue/babel-helper-vue-transform-on": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@vue/babel-helper-vue-transform-on/-/babel-helper-vue-transform-on-1.4.0.tgz", + "integrity": "sha512-mCokbouEQ/ocRce/FpKCRItGo+013tHg7tixg3DUNS+6bmIchPt66012kBMm476vyEIJPafrvOf4E5OYj3shSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@vue/babel-plugin-jsx": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@vue/babel-plugin-jsx/-/babel-plugin-jsx-1.4.0.tgz", + "integrity": "sha512-9zAHmwgMWlaN6qRKdrg1uKsBKHvnUU+Py+MOCTuYZBoZsopa90Di10QRjB+YPnVss0BZbG/H5XFwJY1fTxJWhA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.25.9", + "@babel/helper-plugin-utils": "^7.26.5", + "@babel/plugin-syntax-jsx": "^7.25.9", + "@babel/template": "^7.26.9", + "@babel/traverse": "^7.26.9", + "@babel/types": "^7.26.9", + "@vue/babel-helper-vue-transform-on": "1.4.0", + "@vue/babel-plugin-resolve-type": "1.4.0", + "@vue/shared": "^3.5.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + } + } + }, + "node_modules/@vue/babel-plugin-resolve-type": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@vue/babel-plugin-resolve-type/-/babel-plugin-resolve-type-1.4.0.tgz", + "integrity": "sha512-4xqDRRbQQEWHQyjlYSgZsWj44KfiF6D+ktCuXyZ8EnVDYV3pztmXJDf1HveAjUAXxAnR8daCQT51RneWWxtTyQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.26.2", + "@babel/helper-module-imports": "^7.25.9", + "@babel/helper-plugin-utils": "^7.26.5", + "@babel/parser": "^7.26.9", + "@vue/compiler-sfc": "^3.5.13" + }, + "funding": { + "url": "https://github.com/sponsors/sxzz" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@vue/compiler-core": { + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.13.tgz", + "integrity": "sha512-oOdAkwqUfW1WqpwSYJce06wvt6HljgY3fGeM9NcVA1HaYOij3mZG9Rkysn0OHuyUAGMbEbARIpsG+LPVlBJ5/Q==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.25.3", + "@vue/shared": "3.5.13", + "entities": "^4.5.0", + "estree-walker": "^2.0.2", + "source-map-js": "^1.2.0" + } + }, + "node_modules/@vue/compiler-dom": { + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.13.tgz", + "integrity": "sha512-ZOJ46sMOKUjO3e94wPdCzQ6P1Lx/vhp2RSvfaab88Ajexs0AHeV0uasYhi99WPaogmBlRHNRuly8xV75cNTMDA==", + "license": "MIT", + "dependencies": { + "@vue/compiler-core": "3.5.13", + "@vue/shared": "3.5.13" + } + }, + "node_modules/@vue/compiler-sfc": { + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.13.tgz", + "integrity": "sha512-6VdaljMpD82w6c2749Zhf5T9u5uLBWKnVue6XWxprDobftnletJ8+oel7sexFfM3qIxNmVE7LSFGTpv6obNyaQ==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.25.3", + "@vue/compiler-core": "3.5.13", + "@vue/compiler-dom": "3.5.13", + "@vue/compiler-ssr": "3.5.13", + "@vue/shared": "3.5.13", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.11", + "postcss": "^8.4.48", + "source-map-js": "^1.2.0" + } + }, + "node_modules/@vue/compiler-ssr": { + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.13.tgz", + "integrity": "sha512-wMH6vrYHxQl/IybKJagqbquvxpWCuVYpoUJfCqFZwa/JY1GdATAQ+TgVtgrwwMZ0D07QhA99rs/EAAWfvG6KpA==", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.13", + "@vue/shared": "3.5.13" + } + }, + "node_modules/@vue/devtools-api": { + "version": "6.6.4", + "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.6.4.tgz", + "integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==", + "license": "MIT" + }, + "node_modules/@vue/devtools-core": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/@vue/devtools-core/-/devtools-core-7.7.2.tgz", + "integrity": "sha512-lexREWj1lKi91Tblr38ntSsy6CvI8ba7u+jmwh2yruib/ltLUcsIzEjCnrkh1yYGGIKXbAuYV2tOG10fGDB9OQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/devtools-kit": "^7.7.2", + "@vue/devtools-shared": "^7.7.2", + "mitt": "^3.0.1", + "nanoid": "^5.0.9", + "pathe": "^2.0.2", + "vite-hot-client": "^0.2.4" + }, + "peerDependencies": { + "vue": "^3.0.0" + } + }, + "node_modules/@vue/devtools-core/node_modules/nanoid": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.3.tgz", + "integrity": "sha512-zAbEOEr7u2CbxwoMRlz/pNSpRP0FdAU4pRaYunCdEezWohXFs+a0Xw7RfkKaezMsmSM1vttcLthJtwRnVtOfHQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.js" + }, + "engines": { + "node": "^18 || >=20" + } + }, + "node_modules/@vue/devtools-kit": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/@vue/devtools-kit/-/devtools-kit-7.7.2.tgz", + "integrity": "sha512-CY0I1JH3Z8PECbn6k3TqM1Bk9ASWxeMtTCvZr7vb+CHi+X/QwQm5F1/fPagraamKMAHVfuuCbdcnNg1A4CYVWQ==", + "license": "MIT", + "dependencies": { + "@vue/devtools-shared": "^7.7.2", + "birpc": "^0.2.19", + "hookable": "^5.5.3", + "mitt": "^3.0.1", + "perfect-debounce": "^1.0.0", + "speakingurl": "^14.0.1", + "superjson": "^2.2.1" + } + }, + "node_modules/@vue/devtools-shared": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/@vue/devtools-shared/-/devtools-shared-7.7.2.tgz", + "integrity": "sha512-uBFxnp8gwW2vD6FrJB8JZLUzVb6PNRG0B0jBnHsOH8uKyva2qINY8PTF5Te4QlTbMDqU5K6qtJDr6cNsKWhbOA==", + "license": "MIT", + "dependencies": { + "rfdc": "^1.4.1" + } + }, + "node_modules/@vue/reactivity": { + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.13.tgz", + "integrity": "sha512-NaCwtw8o48B9I6L1zl2p41OHo/2Z4wqYGGIK1Khu5T7yxrn+ATOixn/Udn2m+6kZKB/J7cuT9DbWWhRxqixACg==", + "license": "MIT", + "dependencies": { + "@vue/shared": "3.5.13" + } + }, + "node_modules/@vue/runtime-core": { + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.13.tgz", + "integrity": "sha512-Fj4YRQ3Az0WTZw1sFe+QDb0aXCerigEpw418pw1HBUKFtnQHWzwojaukAs2X/c9DQz4MQ4bsXTGlcpGxU/RCIw==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.13", + "@vue/shared": "3.5.13" + } + }, + "node_modules/@vue/runtime-dom": { + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.13.tgz", + "integrity": "sha512-dLaj94s93NYLqjLiyFzVs9X6dWhTdAlEAciC3Moq7gzAc13VJUdCnjjRurNM6uTLFATRHexHCTu/Xp3eW6yoog==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.13", + "@vue/runtime-core": "3.5.13", + "@vue/shared": "3.5.13", + "csstype": "^3.1.3" + } + }, + "node_modules/@vue/server-renderer": { + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.13.tgz", + "integrity": "sha512-wAi4IRJV/2SAW3htkTlB+dHeRmpTiVIK1OGLWV1yeStVSebSQQOwGwIq0D3ZIoBj2C2qpgz5+vX9iEBkTdk5YA==", + "license": "MIT", + "dependencies": { + "@vue/compiler-ssr": "3.5.13", + "@vue/shared": "3.5.13" + }, + "peerDependencies": { + "vue": "3.5.13" + } + }, + "node_modules/@vue/shared": { + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.13.tgz", + "integrity": "sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ==", + "license": "MIT" + }, + "node_modules/@vuelidate/core": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@vuelidate/core/-/core-2.0.3.tgz", + "integrity": "sha512-AN6l7KF7+mEfyWG0doT96z+47ljwPpZfi9/JrNMkOGLFv27XVZvKzRLXlmDPQjPl/wOB1GNnHuc54jlCLRNqGA==", + "license": "MIT", + "dependencies": { + "vue-demi": "^0.13.11" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^2.0.0 || >=3.0.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, + "node_modules/@vuelidate/core/node_modules/vue-demi": { + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.13.11.tgz", + "integrity": "sha512-IR8HoEEGM65YY3ZJYAjMlKygDQn25D5ajNFNoKh9RSDMQtlzCxtfQjdQgv9jjK+m3377SsJXY8ysq8kLCZL25A==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "vue-demi-fix": "bin/vue-demi-fix.js", + "vue-demi-switch": "bin/vue-demi-switch.js" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^3.0.0-0 || ^2.6.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, + "node_modules/@vuelidate/validators": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@vuelidate/validators/-/validators-2.0.4.tgz", + "integrity": "sha512-odTxtUZ2JpwwiQ10t0QWYJkkYrfd0SyFYhdHH44QQ1jDatlZgTh/KRzrWVmn/ib9Gq7H4hFD4e8ahoo5YlUlDw==", + "license": "MIT", + "dependencies": { + "vue-demi": "^0.13.11" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^2.0.0 || >=3.0.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, + "node_modules/@vuelidate/validators/node_modules/vue-demi": { + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.13.11.tgz", + "integrity": "sha512-IR8HoEEGM65YY3ZJYAjMlKygDQn25D5ajNFNoKh9RSDMQtlzCxtfQjdQgv9jjK+m3377SsJXY8ysq8kLCZL25A==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "vue-demi-fix": "bin/vue-demi-fix.js", + "vue-demi-switch": "bin/vue-demi-switch.js" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^3.0.0-0 || ^2.6.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/axios": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.8.2.tgz", + "integrity": "sha512-ls4GYBm5aig9vWx8AWDSGLpnpDQRtWAfrjU+EuytuODrFBkqesN2RkOQCBzrA1RQNHw1SmRMSDDDSwzNAYQ6Rg==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/birpc": { + "version": "0.2.19", + "resolved": "https://registry.npmjs.org/birpc/-/birpc-0.2.19.tgz", + "integrity": "sha512-5WeXXAvTmitV1RqJFppT5QtUiz2p1mRSYU000Jkft5ZUCLJIk4uQriYNO50HknxKwM6jd8utNc66K1qGIwwWBQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/browserslist": { + "version": "4.24.4", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.4.tgz", + "integrity": "sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "caniuse-lite": "^1.0.30001688", + "electron-to-chromium": "^1.5.73", + "node-releases": "^2.0.19", + "update-browserslist-db": "^1.1.1" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/bundle-name": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz", + "integrity": "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "run-applescript": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001703", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001703.tgz", + "integrity": "sha512-kRlAGTRWgPsOj7oARC9m1okJEXdL/8fekFVcxA8Hl7GH4r/sN4OJn/i6Flde373T50KS7Y37oFbMwlE8+F42kQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chart.js": { + "version": "4.4.8", + "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.8.tgz", + "integrity": "sha512-IkGZlVpXP+83QpMm4uxEiGqSI7jFizwVtF3+n5Pc3k7sMO+tkd0qxh2OzLhenM0K80xtmAONWGBn082EiBQSDA==", + "license": "MIT", + "dependencies": { + "@kurkle/color": "^0.3.0" + }, + "engines": { + "pnpm": ">=8" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/copy-anything": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-3.0.5.tgz", + "integrity": "sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w==", + "license": "MIT", + "dependencies": { + "is-what": "^4.1.8" + }, + "engines": { + "node": ">=12.13" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "license": "MIT" + }, + "node_modules/dayjs": { + "version": "1.11.13", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz", + "integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==", + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/default-browser": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.2.1.tgz", + "integrity": "sha512-WY/3TUME0x3KPYdRRxEJJvXRHV4PyPoUsxtZa78lwItwRQRHhd2U9xOscaT/YTf8uCXIAjeJOFBVEh/7FtD8Xg==", + "dev": true, + "license": "MIT", + "dependencies": { + "bundle-name": "^4.1.0", + "default-browser-id": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-browser-id": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.0.tgz", + "integrity": "sha512-A6p/pu/6fyBcA1TRz/GqWYPViplrftcW2gZC9q79ngNCKAeR/X3gcEdXQHl4KNXV+3wgIJ1CPkJQ3IHM6lcsyA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/define-lazy-prop": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", + "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.114", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.114.tgz", + "integrity": "sha512-DFptFef3iktoKlFQK/afbo274/XNWD00Am0xa7M8FZUepHlHT8PEuiNBoRfFHbH1okqN58AlhbJ4QTkcnXorjA==", + "dev": true, + "license": "ISC" + }, + "node_modules/engine.io-client": { + "version": "6.6.3", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.6.3.tgz", + "integrity": "sha512-T0iLjnyNWahNyv/lcjS2y4oE358tVS/SYQNxYXGAJ9/GLgH4VCvOQ/mhTjqU88mLZCQgiG8RIegFHYCdVC+j5w==", + "license": "MIT", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.17.1", + "xmlhttprequest-ssl": "~2.1.1" + } + }, + "node_modules/engine.io-client/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/engine.io-parser": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz", + "integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/error-stack-parser-es": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/error-stack-parser-es/-/error-stack-parser-es-0.1.5.tgz", + "integrity": "sha512-xHku1X40RO+fO8yJ8Wh2f2rZWVjqyhb1zgq1yZ8aZRQkv6OOKhKWRUaht3eSCUbAOBaKIgM+ykwFLE+QUxgGeg==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.1.tgz", + "integrity": "sha512-BGO5LtrGC7vxnqucAe/rmvKdJllfGaYWdyABvyMoXQlfYMb2bbRuReWR5tEGE//4LcNJj9XrkovTqNYRFZHAMQ==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.1", + "@esbuild/android-arm": "0.25.1", + "@esbuild/android-arm64": "0.25.1", + "@esbuild/android-x64": "0.25.1", + "@esbuild/darwin-arm64": "0.25.1", + "@esbuild/darwin-x64": "0.25.1", + "@esbuild/freebsd-arm64": "0.25.1", + "@esbuild/freebsd-x64": "0.25.1", + "@esbuild/linux-arm": "0.25.1", + "@esbuild/linux-arm64": "0.25.1", + "@esbuild/linux-ia32": "0.25.1", + "@esbuild/linux-loong64": "0.25.1", + "@esbuild/linux-mips64el": "0.25.1", + "@esbuild/linux-ppc64": "0.25.1", + "@esbuild/linux-riscv64": "0.25.1", + "@esbuild/linux-s390x": "0.25.1", + "@esbuild/linux-x64": "0.25.1", + "@esbuild/netbsd-arm64": "0.25.1", + "@esbuild/netbsd-x64": "0.25.1", + "@esbuild/openbsd-arm64": "0.25.1", + "@esbuild/openbsd-x64": "0.25.1", + "@esbuild/sunos-x64": "0.25.1", + "@esbuild/win32-arm64": "0.25.1", + "@esbuild/win32-ia32": "0.25.1", + "@esbuild/win32-x64": "0.25.1" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "license": "MIT" + }, + "node_modules/execa": { + "version": "9.5.2", + "resolved": "https://registry.npmjs.org/execa/-/execa-9.5.2.tgz", + "integrity": "sha512-EHlpxMCpHWSAh1dgS6bVeoLAXGnJNdR93aabr4QCGbzOM73o5XmRfM/e5FUqsw3aagP8S8XEWUWFAxnRBnAF0Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sindresorhus/merge-streams": "^4.0.0", + "cross-spawn": "^7.0.3", + "figures": "^6.1.0", + "get-stream": "^9.0.0", + "human-signals": "^8.0.0", + "is-plain-obj": "^4.1.0", + "is-stream": "^4.0.1", + "npm-run-path": "^6.0.0", + "pretty-ms": "^9.0.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^4.0.0", + "yoctocolors": "^2.0.0" + }, + "engines": { + "node": "^18.19.0 || >=20.5.0" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/figures": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-6.1.0.tgz", + "integrity": "sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-unicode-supported": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/follow-redirects": { + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz", + "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fs-extra": { + "version": "11.3.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.0.tgz", + "integrity": "sha512-Z4XaCL6dUDHfP/jT25jJKMmtxvuwbkrD1vNSMFlo9lNLY2c5FHYSQgHPRZUjAB26TpDEoW9HCOgplrdbaPV/ew==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-stream": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-9.0.1.tgz", + "integrity": "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sec-ant/readable-stream": "^0.4.1", + "is-stream": "^4.0.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hookable": { + "version": "5.5.3", + "resolved": "https://registry.npmjs.org/hookable/-/hookable-5.5.3.tgz", + "integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==", + "license": "MIT" + }, + "node_modules/human-signals": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-8.0.0.tgz", + "integrity": "sha512-/1/GPCpDUCCYwlERiYjxoczfP0zfvZMU/OWgQPMya9AbAE24vseigFdhAMObpc8Q4lc/kjutPfUddDYyAmejnA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/is-docker": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", + "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", + "dev": true, + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-inside-container": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", + "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-docker": "^3.0.0" + }, + "bin": { + "is-inside-container": "cli.js" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-stream": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-4.0.1.tgz", + "integrity": "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-unicode-supported": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz", + "integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-what": { + "version": "4.1.16", + "resolved": "https://registry.npmjs.org/is-what/-/is-what-4.1.16.tgz", + "integrity": "sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A==", + "license": "MIT", + "engines": { + "node": ">=12.13" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, + "node_modules/is-wsl": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.0.tgz", + "integrity": "sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-inside-container": "^1.0.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/jwt-decode": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-4.0.0.tgz", + "integrity": "sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/kolorist": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/kolorist/-/kolorist-1.8.0.tgz", + "integrity": "sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/magic-string": { + "version": "0.30.17", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", + "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mitt": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", + "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", + "license": "MIT" + }, + "node_modules/mrmime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", + "integrity": "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.9", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.9.tgz", + "integrity": "sha512-SppoicMGpZvbF1l3z4x7No3OlIjP7QJvC9XR7AhZr1kL133KHnKPztkKDc+Ir4aJ/1VhTySrtKhrsycmrMQfvg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/node-releases": { + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", + "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", + "dev": true, + "license": "MIT" + }, + "node_modules/npm-run-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-6.0.0.tgz", + "integrity": "sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^4.0.0", + "unicorn-magic": "^0.3.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm-run-path/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/open": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/open/-/open-10.1.0.tgz", + "integrity": "sha512-mnkeQ1qP5Ue2wd+aivTD3NHd/lZ96Lu0jgf0pwktLPtx6cTZiH7tyeGRRHs0zX0rbrahXPnXlUnbeXyaBBuIaw==", + "dev": true, + "license": "MIT", + "dependencies": { + "default-browser": "^5.2.1", + "define-lazy-prop": "^3.0.0", + "is-inside-container": "^1.0.0", + "is-wsl": "^3.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parse-ms": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-4.0.0.tgz", + "integrity": "sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/perfect-debounce": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz", + "integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==", + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pinia": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/pinia/-/pinia-2.3.1.tgz", + "integrity": "sha512-khUlZSwt9xXCaTbbxFYBKDc/bWAGWJjOgvxETwkTN7KRm66EeT1ZdZj6i2ceh9sP2Pzqsbc704r2yngBrxBVug==", + "license": "MIT", + "dependencies": { + "@vue/devtools-api": "^6.6.3", + "vue-demi": "^0.14.10" + }, + "funding": { + "url": "https://github.com/sponsors/posva" + }, + "peerDependencies": { + "typescript": ">=4.4.4", + "vue": "^2.7.0 || ^3.5.11" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/postcss": { + "version": "8.5.3", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz", + "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.8", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/preact": { + "version": "10.12.1", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.12.1.tgz", + "integrity": "sha512-l8386ixSsBdbreOAkqtrwqHwdvR35ID8c3rKPa8lCWuO86dBi32QWHV4vfsZK1utLLFMvw+Z5Ad4XLkZzchscg==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/preact" + } + }, + "node_modules/pretty-ms": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-9.2.0.tgz", + "integrity": "sha512-4yf0QO/sllf/1zbZWYnvWw3NxCQwLXKzIj0G849LSufP15BXKM0rbD2Z3wVnkMfjdn/CB0Dpp444gYAACdsplg==", + "dev": true, + "license": "MIT", + "dependencies": { + "parse-ms": "^4.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/primeflex": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/primeflex/-/primeflex-3.3.1.tgz", + "integrity": "sha512-zaOq3YvcOYytbAmKv3zYc+0VNS9Wg5d37dfxZnveKBFPr7vEIwfV5ydrpiouTft8MVW6qNjfkaQphHSnvgQbpQ==", + "license": "MIT" + }, + "node_modules/primeicons": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/primeicons/-/primeicons-6.0.1.tgz", + "integrity": "sha512-KDeO94CbWI4pKsPnYpA1FPjo79EsY9I+M8ywoPBSf9XMXoe/0crjbUK7jcQEDHuc0ZMRIZsxH3TYLv4TUtHmAA==", + "license": "MIT" + }, + "node_modules/primevue": { + "version": "3.53.1", + "resolved": "https://registry.npmjs.org/primevue/-/primevue-3.53.1.tgz", + "integrity": "sha512-Bp4peZPdhfKYXwvtsOGGh5dfgmTelm+LZEZKGs/c5mOHhsUJ6xi3EcOZoQVI6oklS946ayMQvgD5L0S7itGO0g==", + "license": "MIT", + "peerDependencies": { + "vue": "^3.0.0" + } + }, + "node_modules/property-expr": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/property-expr/-/property-expr-2.0.6.tgz", + "integrity": "sha512-SVtmxhRE/CGkn3eZY1T6pC8Nln6Fr/lu1mKSgRud0eC73whjGfoAogbn78LkD8aFL0zz3bAFerKSnOl7NlErBA==", + "license": "MIT" + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, + "node_modules/rfdc": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", + "license": "MIT" + }, + "node_modules/rollup": { + "version": "4.35.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.35.0.tgz", + "integrity": "sha512-kg6oI4g+vc41vePJyO6dHt/yl0Rz3Thv0kJeVQ3D1kS3E5XSuKbPc29G4IpT/Kv1KQwgHVcN+HtyS+HYLNSvQg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.6" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.35.0", + "@rollup/rollup-android-arm64": "4.35.0", + "@rollup/rollup-darwin-arm64": "4.35.0", + "@rollup/rollup-darwin-x64": "4.35.0", + "@rollup/rollup-freebsd-arm64": "4.35.0", + "@rollup/rollup-freebsd-x64": "4.35.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.35.0", + "@rollup/rollup-linux-arm-musleabihf": "4.35.0", + "@rollup/rollup-linux-arm64-gnu": "4.35.0", + "@rollup/rollup-linux-arm64-musl": "4.35.0", + "@rollup/rollup-linux-loongarch64-gnu": "4.35.0", + "@rollup/rollup-linux-powerpc64le-gnu": "4.35.0", + "@rollup/rollup-linux-riscv64-gnu": "4.35.0", + "@rollup/rollup-linux-s390x-gnu": "4.35.0", + "@rollup/rollup-linux-x64-gnu": "4.35.0", + "@rollup/rollup-linux-x64-musl": "4.35.0", + "@rollup/rollup-win32-arm64-msvc": "4.35.0", + "@rollup/rollup-win32-ia32-msvc": "4.35.0", + "@rollup/rollup-win32-x64-msvc": "4.35.0", + "fsevents": "~2.3.2" + } + }, + "node_modules/run-applescript": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.0.0.tgz", + "integrity": "sha512-9by4Ij99JUr/MCFBUkDKLWK3G9HVXmabKz9U5MlIAIuvuzkiOicRYs8XJLxX+xahD+mLiiCYDqF9dKAgtzKP1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/sirv": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/sirv/-/sirv-3.0.1.tgz", + "integrity": "sha512-FoqMu0NCGBLCcAkS1qA+XJIQTR6/JHfQXl+uGteNCQ76T91DMUjPa9xfmeqMY3z80nLSg9yQmNjK0Px6RWsH/A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@polka/url": "^1.0.0-next.24", + "mrmime": "^2.0.0", + "totalist": "^3.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/socket.io-client": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.8.1.tgz", + "integrity": "sha512-hJVXfu3E28NmzGk8o1sHhN3om52tRvwYeidbj7xKy2eIIse5IoKX3USlS6Tqt3BHAtflLIkCQBkzVrEEfWUyYQ==", + "license": "MIT", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.2", + "engine.io-client": "~6.6.1", + "socket.io-parser": "~4.2.4" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/socket.io-client/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/socket.io-parser": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz", + "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==", + "license": "MIT", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/socket.io-parser/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/speakingurl": { + "version": "14.0.1", + "resolved": "https://registry.npmjs.org/speakingurl/-/speakingurl-14.0.1.tgz", + "integrity": "sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strip-final-newline": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-4.0.0.tgz", + "integrity": "sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/superjson": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/superjson/-/superjson-2.2.2.tgz", + "integrity": "sha512-5JRxVqC8I8NuOUjzBbvVJAKNM8qoVuH0O77h4WInc/qC2q5IreqKxYwgkga3PfA22OayK2ikceb/B26dztPl+Q==", + "license": "MIT", + "dependencies": { + "copy-anything": "^3.0.2" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/tiny-case": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-case/-/tiny-case-1.0.3.tgz", + "integrity": "sha512-Eet/eeMhkO6TX8mnUteS9zgPbUMQa4I6Kkp5ORiBD5476/m+PIRiumP5tmh5ioJpH7k51Kehawy2UDfsnxxY8Q==", + "license": "MIT" + }, + "node_modules/toposort": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/toposort/-/toposort-2.0.2.tgz", + "integrity": "sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg==", + "license": "MIT" + }, + "node_modules/totalist": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", + "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/type-fest": { + "version": "4.37.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.37.0.tgz", + "integrity": "sha512-S/5/0kFftkq27FPNye0XM1e2NsnoD/3FS+pBmbjmmtLT6I+i344KoOf7pvXreaFsDamWeaJX55nczA1m5PsBDg==", + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/unicorn-magic": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.3.0.tgz", + "integrity": "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", + "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/vee-validate": { + "version": "4.15.0", + "resolved": "https://registry.npmjs.org/vee-validate/-/vee-validate-4.15.0.tgz", + "integrity": "sha512-PGJh1QCFwCBjbHu5aN6vB8macYVWrajbDvgo1Y/8fz9n/RVIkLmZCJDpUgu7+mUmCOPMxeyq7vXUOhbwAqdXcA==", + "license": "MIT", + "dependencies": { + "@vue/devtools-api": "^7.5.2", + "type-fest": "^4.8.3" + }, + "peerDependencies": { + "vue": "^3.4.26" + } + }, + "node_modules/vee-validate/node_modules/@vue/devtools-api": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-7.7.2.tgz", + "integrity": "sha512-1syn558KhyN+chO5SjlZIwJ8bV/bQ1nOVTG66t2RbG66ZGekyiYNmRO7X9BJCXQqPsFHlnksqvPhce2qpzxFnA==", + "license": "MIT", + "dependencies": { + "@vue/devtools-kit": "^7.7.2" + } + }, + "node_modules/vite": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.2.1.tgz", + "integrity": "sha512-n2GnqDb6XPhlt9B8olZPrgMD/es/Nd1RdChF6CBD/fHW6pUyUTt2sQW2fPRX5GiD9XEa6+8A6A4f2vT6pSsE7Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "postcss": "^8.5.3", + "rollup": "^4.30.1" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vite-hot-client": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/vite-hot-client/-/vite-hot-client-0.2.4.tgz", + "integrity": "sha512-a1nzURqO7DDmnXqabFOliz908FRmIppkBKsJthS8rbe8hBEXwEwe4C3Pp33Z1JoFCYfVL4kTOMLKk0ZZxREIeA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "vite": "^2.6.0 || ^3.0.0 || ^4.0.0 || ^5.0.0-0 || ^6.0.0-0" + } + }, + "node_modules/vite-plugin-inspect": { + "version": "0.8.9", + "resolved": "https://registry.npmjs.org/vite-plugin-inspect/-/vite-plugin-inspect-0.8.9.tgz", + "integrity": "sha512-22/8qn+LYonzibb1VeFZmISdVao5kC22jmEKm24vfFE8siEn47EpVcCLYMv6iKOYMJfjSvSJfueOwcFCkUnV3A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@antfu/utils": "^0.7.10", + "@rollup/pluginutils": "^5.1.3", + "debug": "^4.3.7", + "error-stack-parser-es": "^0.1.5", + "fs-extra": "^11.2.0", + "open": "^10.1.0", + "perfect-debounce": "^1.0.0", + "picocolors": "^1.1.1", + "sirv": "^3.0.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "vite": "^3.1.0 || ^4.0.0 || ^5.0.0-0 || ^6.0.1" + }, + "peerDependenciesMeta": { + "@nuxt/kit": { + "optional": true + } + } + }, + "node_modules/vite-plugin-vue-devtools": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/vite-plugin-vue-devtools/-/vite-plugin-vue-devtools-7.7.2.tgz", + "integrity": "sha512-5V0UijQWiSBj32blkyPEqIbzc6HO9c1bwnBhx+ay2dzU0FakH+qMdNUT8nF9BvDE+i6I1U8CqCuJiO20vKEdQw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/devtools-core": "^7.7.2", + "@vue/devtools-kit": "^7.7.2", + "@vue/devtools-shared": "^7.7.2", + "execa": "^9.5.1", + "sirv": "^3.0.0", + "vite-plugin-inspect": "0.8.9", + "vite-plugin-vue-inspector": "^5.3.1" + }, + "engines": { + "node": ">=v14.21.3" + }, + "peerDependencies": { + "vite": "^3.1.0 || ^4.0.0-0 || ^5.0.0-0 || ^6.0.0-0" + } + }, + "node_modules/vite-plugin-vue-inspector": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/vite-plugin-vue-inspector/-/vite-plugin-vue-inspector-5.3.1.tgz", + "integrity": "sha512-cBk172kZKTdvGpJuzCCLg8lJ909wopwsu3Ve9FsL1XsnLBiRT9U3MePcqrgGHgCX2ZgkqZmAGR8taxw+TV6s7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.23.0", + "@babel/plugin-proposal-decorators": "^7.23.0", + "@babel/plugin-syntax-import-attributes": "^7.22.5", + "@babel/plugin-syntax-import-meta": "^7.10.4", + "@babel/plugin-transform-typescript": "^7.22.15", + "@vue/babel-plugin-jsx": "^1.1.5", + "@vue/compiler-dom": "^3.3.4", + "kolorist": "^1.8.0", + "magic-string": "^0.30.4" + }, + "peerDependencies": { + "vite": "^3.0.0-0 || ^4.0.0-0 || ^5.0.0-0 || ^6.0.0-0" + } + }, + "node_modules/vue": { + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.13.tgz", + "integrity": "sha512-wmeiSMxkZCSc+PM2w2VRsOYAZC8GdipNFRTsLSfodVqI9mbejKeXEGr8SckuLnrQPGe3oJN5c3K0vpoU9q/wCQ==", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.13", + "@vue/compiler-sfc": "3.5.13", + "@vue/runtime-dom": "3.5.13", + "@vue/server-renderer": "3.5.13", + "@vue/shared": "3.5.13" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/vue-chartjs": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/vue-chartjs/-/vue-chartjs-5.3.2.tgz", + "integrity": "sha512-NrkbRRoYshbXbWqJkTN6InoDVwVb90C0R7eAVgMWcB9dPikbruaOoTFjFYHE/+tNPdIe6qdLCDjfjPHQ0fw4jw==", + "license": "MIT", + "peerDependencies": { + "chart.js": "^4.1.1", + "vue": "^3.0.0-0 || ^2.7.0" + } + }, + "node_modules/vue-demi": { + "version": "0.14.10", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz", + "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "vue-demi-fix": "bin/vue-demi-fix.js", + "vue-demi-switch": "bin/vue-demi-switch.js" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^3.0.0-0 || ^2.6.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, + "node_modules/vue-router": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.5.0.tgz", + "integrity": "sha512-HDuk+PuH5monfNuY+ct49mNmkCRK4xJAV9Ts4z9UFc4rzdDnxQLyCMGGc8pKhZhHTVzfanpNwB/lwqevcBwI4w==", + "license": "MIT", + "dependencies": { + "@vue/devtools-api": "^6.6.4" + }, + "funding": { + "url": "https://github.com/sponsors/posva" + }, + "peerDependencies": { + "vue": "^3.2.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/ws": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xmlhttprequest-ssl": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.1.2.tgz", + "integrity": "sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/yoctocolors": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/yoctocolors/-/yoctocolors-2.1.1.tgz", + "integrity": "sha512-GQHQqAopRhwU8Kt1DDM8NjibDXHC8eoh1erhGAJPEyveY9qqVeXvVikNKrDz69sHowPMorbPUrH/mx8c50eiBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yup": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/yup/-/yup-1.6.1.tgz", + "integrity": "sha512-JED8pB50qbA4FOkDol0bYF/p60qSEDQqBD0/qeIrUCG1KbPBIQ776fCUNb9ldbPcSTxA69g/47XTo4TqWiuXOA==", + "license": "MIT", + "dependencies": { + "property-expr": "^2.0.5", + "tiny-case": "^1.0.3", + "toposort": "^2.0.2", + "type-fest": "^2.19.0" + } + }, + "node_modules/yup/node_modules/type-fest": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", + "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/frontend/package.json b/frontend/package.json new file mode 100644 index 0000000..baae4d2 --- /dev/null +++ b/frontend/package.json @@ -0,0 +1,39 @@ +{ + "name": "frontend", + "version": "0.0.0", + "private": true, + "type": "module", + "scripts": { + "dev": "vite --host 0.0.0.0 --port 5173", + "build": "vite build", + "preview": "vite preview" + }, + "dependencies": { + "@fullcalendar/core": "^6.1.15", + "@fullcalendar/daygrid": "^6.1.15", + "@fullcalendar/interaction": "^6.1.15", + "@fullcalendar/timegrid": "^6.1.15", + "@fullcalendar/vue3": "^6.1.15", + "@vuelidate/core": "^2.0.3", + "@vuelidate/validators": "^2.0.4", + "axios": "^1.8.2", + "chart.js": "^4.4.8", + "dayjs": "^1.11.10", + "jwt-decode": "^4.0.0", + "pinia": "^2.1.7", + "primeflex": "^3.3.1", + "primeicons": "^6.0.1", + "primevue": "^3.49.1", + "socket.io-client": "^4.8.1", + "vee-validate": "^4.12.5", + "vue": "^3.5.13", + "vue-chartjs": "^5.3.2", + "vue-router": "^4.5.0", + "yup": "^1.3.3" + }, + "devDependencies": { + "@vitejs/plugin-vue": "^5.2.1", + "vite": "^6.2.1", + "vite-plugin-vue-devtools": "^7.7.2" + } +} diff --git a/frontend/public/favicon.ico b/frontend/public/favicon.ico new file mode 100644 index 0000000..df36fcf Binary files /dev/null and b/frontend/public/favicon.ico differ diff --git a/frontend/src/App.vue b/frontend/src/App.vue new file mode 100644 index 0000000..fab6147 --- /dev/null +++ b/frontend/src/App.vue @@ -0,0 +1,533 @@ + + + + + diff --git a/frontend/src/assets/base.css b/frontend/src/assets/base.css new file mode 100644 index 0000000..db28f14 --- /dev/null +++ b/frontend/src/assets/base.css @@ -0,0 +1,86 @@ +/* color palette from */ +:root { + --vt-c-white: #ffffff; + --vt-c-white-soft: #f8f8f8; + --vt-c-white-mute: #f2f2f2; + + --vt-c-black: #181818; + --vt-c-black-soft: #222222; + --vt-c-black-mute: #282828; + + --vt-c-indigo: #2c3e50; + + --vt-c-divider-light-1: rgba(60, 60, 60, 0.29); + --vt-c-divider-light-2: rgba(60, 60, 60, 0.12); + --vt-c-divider-dark-1: rgba(84, 84, 84, 0.65); + --vt-c-divider-dark-2: rgba(84, 84, 84, 0.48); + + --vt-c-text-light-1: var(--vt-c-indigo); + --vt-c-text-light-2: rgba(60, 60, 60, 0.66); + --vt-c-text-dark-1: var(--vt-c-white); + --vt-c-text-dark-2: rgba(235, 235, 235, 0.64); +} + +/* semantic color variables for this project */ +:root { + --color-background: var(--vt-c-white); + --color-background-soft: var(--vt-c-white-soft); + --color-background-mute: var(--vt-c-white-mute); + + --color-border: var(--vt-c-divider-light-2); + --color-border-hover: var(--vt-c-divider-light-1); + + --color-heading: var(--vt-c-text-light-1); + --color-text: var(--vt-c-text-light-1); + + --section-gap: 160px; +} + +@media (prefers-color-scheme: dark) { + :root { + --color-background: var(--vt-c-black); + --color-background-soft: var(--vt-c-black-soft); + --color-background-mute: var(--vt-c-black-mute); + + --color-border: var(--vt-c-divider-dark-2); + --color-border-hover: var(--vt-c-divider-dark-1); + + --color-heading: var(--vt-c-text-dark-1); + --color-text: var(--vt-c-text-dark-2); + } +} + +*, +*::before, +*::after { + box-sizing: border-box; + margin: 0; + font-weight: normal; +} + +body { + min-height: 100vh; + color: var(--color-text); + background: var(--color-background); + transition: + color 0.5s, + background-color 0.5s; + line-height: 1.6; + font-family: + Inter, + -apple-system, + BlinkMacSystemFont, + 'Segoe UI', + Roboto, + Oxygen, + Ubuntu, + Cantarell, + 'Fira Sans', + 'Droid Sans', + 'Helvetica Neue', + sans-serif; + font-size: 15px; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} diff --git a/frontend/src/assets/logo.svg b/frontend/src/assets/logo.svg new file mode 100644 index 0000000..b87fee4 --- /dev/null +++ b/frontend/src/assets/logo.svg @@ -0,0 +1 @@ + diff --git a/frontend/src/assets/main.css b/frontend/src/assets/main.css new file mode 100644 index 0000000..e6c1b08 --- /dev/null +++ b/frontend/src/assets/main.css @@ -0,0 +1,35 @@ +@import './base.css'; + +#app { + max-width: 1280px; + margin: 0 auto; + padding: 2rem; + font-weight: normal; +} + +a, +.green { + text-decoration: none; + color: hsla(160, 100%, 37%, 1); + transition: 0.4s; + padding: 3px; +} + +@media (hover: hover) { + a:hover { + background-color: hsla(160, 100%, 37%, 0.2); + } +} + +@media (min-width: 1024px) { + body { + display: flex; + place-items: center; + } + + #app { + display: grid; + grid-template-columns: 1fr 1fr; + padding: 0 2rem; + } +} diff --git a/frontend/src/components/AppHeader.vue b/frontend/src/components/AppHeader.vue new file mode 100644 index 0000000..c4665a6 --- /dev/null +++ b/frontend/src/components/AppHeader.vue @@ -0,0 +1,489 @@ + + + + + diff --git a/frontend/src/components/HelloWorld.vue b/frontend/src/components/HelloWorld.vue new file mode 100644 index 0000000..a7252aa --- /dev/null +++ b/frontend/src/components/HelloWorld.vue @@ -0,0 +1,44 @@ + + + + + diff --git a/frontend/src/components/NotificationSystem.vue b/frontend/src/components/NotificationSystem.vue new file mode 100644 index 0000000..e2ad737 --- /dev/null +++ b/frontend/src/components/NotificationSystem.vue @@ -0,0 +1,365 @@ + + + + + diff --git a/frontend/src/components/TheWelcome.vue b/frontend/src/components/TheWelcome.vue new file mode 100644 index 0000000..4c35b7f --- /dev/null +++ b/frontend/src/components/TheWelcome.vue @@ -0,0 +1,94 @@ + + + diff --git a/frontend/src/components/WelcomeItem.vue b/frontend/src/components/WelcomeItem.vue new file mode 100644 index 0000000..d3bc3a3 --- /dev/null +++ b/frontend/src/components/WelcomeItem.vue @@ -0,0 +1,87 @@ + + + diff --git a/frontend/src/components/event/EventDetailsDialog.vue b/frontend/src/components/event/EventDetailsDialog.vue new file mode 100644 index 0000000..0741974 --- /dev/null +++ b/frontend/src/components/event/EventDetailsDialog.vue @@ -0,0 +1,158 @@ + + + + + diff --git a/frontend/src/components/event/EventDialog.vue b/frontend/src/components/event/EventDialog.vue new file mode 100644 index 0000000..dbd6dca --- /dev/null +++ b/frontend/src/components/event/EventDialog.vue @@ -0,0 +1,233 @@ +