diff --git a/back/config/config.js b/back/config/config.js index 05df285..5cc2066 100644 --- a/back/config/config.js +++ b/back/config/config.js @@ -9,6 +9,7 @@ const config = { host: process.env.DB_HOST, port: process.env.DB_PORT, dialect: "mysql", + logging: false, define: { timestamps: false, }, diff --git a/back/package-lock.json b/back/package-lock.json index 862f5b0..7e3a2d4 100644 --- a/back/package-lock.json +++ b/back/package-lock.json @@ -25,6 +25,7 @@ "multer": "^1.4.5-lts.1", "multer-s3": "^2.10.0", "mysql2": "^3.6.2", + "node-schedule": "^2.1.1", "redis": "^4.6.10", "sequelize": "^6.33.0", "sequelize-auto": "^0.8.8", @@ -1967,6 +1968,17 @@ "node": ">= 0.10" } }, + "node_modules/cron-parser": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/cron-parser/-/cron-parser-4.9.0.tgz", + "integrity": "sha512-p0SaNjrHOnQeR8/VnfGbmg9te2kfyYSQ7Sc/j/6DtPL3JQvKxmjO9TSjNFpujqV3vEYYBvNNvXSxzyksBWAx1Q==", + "dependencies": { + "luxon": "^3.2.1" + }, + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/css-select": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz", @@ -2948,6 +2960,11 @@ "resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz", "integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==" }, + "node_modules/long-timeout": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/long-timeout/-/long-timeout-0.1.1.tgz", + "integrity": "sha512-BFRuQUqc7x2NWxfJBCyUrN8iYUYznzL9JROmRz1gZ6KlOIgmoD+njPVbb+VNn2nGMKggMsK79iUNErillsrx7w==" + }, "node_modules/lru-cache": { "version": "8.0.5", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-8.0.5.tgz", @@ -2964,6 +2981,14 @@ "es5-ext": "~0.10.2" } }, + "node_modules/luxon": { + "version": "3.4.4", + "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.4.4.tgz", + "integrity": "sha512-zobTr7akeGHnv7eBOXcRgMeCP6+uyYsczwmeRCauvpvaAltgNyTbLH/+VaEAPUeWBT+1GuNmz4wC/6jtQzbbVA==", + "engines": { + "node": ">=12" + } + }, "node_modules/media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -3180,6 +3205,19 @@ "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz", "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==" }, + "node_modules/node-schedule": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/node-schedule/-/node-schedule-2.1.1.tgz", + "integrity": "sha512-OXdegQq03OmXEjt2hZP33W2YPs/E5BcFQks46+G2gAxs4gHOIVD1u7EqlYLYSKsaIpyKCK9Gbk0ta1/gjRSMRQ==", + "dependencies": { + "cron-parser": "^4.2.0", + "long-timeout": "0.1.1", + "sorted-array-functions": "^1.3.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/nopt": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/nopt/-/nopt-6.0.0.tgz", @@ -3824,6 +3862,11 @@ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" }, + "node_modules/sorted-array-functions": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/sorted-array-functions/-/sorted-array-functions-1.3.0.tgz", + "integrity": "sha512-2sqgzeFlid6N4Z2fUQ1cvFmTOLRi/sEDzSQ0OKYchqgoPmQBVyM3959qYx3fpS6Esef80KjmpgPeEr028dP3OA==" + }, "node_modules/sqlstring": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.3.tgz", diff --git a/back/package.json b/back/package.json index c1c8aca..21264ea 100644 --- a/back/package.json +++ b/back/package.json @@ -28,6 +28,7 @@ "multer": "^1.4.5-lts.1", "multer-s3": "^2.10.0", "mysql2": "^3.6.2", + "node-schedule": "^2.1.1", "redis": "^4.6.10", "sequelize": "^6.33.0", "sequelize-auto": "^0.8.8", diff --git a/back/routes/activity.js b/back/routes/activity.js index e44f659..16edd05 100644 --- a/back/routes/activity.js +++ b/back/routes/activity.js @@ -17,6 +17,9 @@ const { ActivityEvidence, ActivityMember, ActivitySign, + ActivityEvidence_init, + Activity_init, + ActivityMember_init, } = require("../models"); const checkPermission = require("../utils/permission"); const checkReportDuration = require("../utils/duration"); @@ -108,7 +111,7 @@ router.get("/advisor_sign", async (req, res) => { order: [["sign_time", "DESC"]], }); - const latestEdit = await Activity.findOne({ + const latestEdit = await Activity_init.findOne({ where: { club_id, recent_edit: { @@ -136,15 +139,12 @@ router.post("/deleteActivity/:activityId", async (req, res) => { } const { activityId } = req.params; - console.log(activityId); - const transaction = await sequelize.transaction(); try { const activity = await Activity.findByPk(activityId); const authorized = await checkPermission(req, res, [ { club_rep: 4, club_id: activity.club_id }, - { advisor: activity.club_id }, ]); if (!authorized) { return; @@ -169,7 +169,6 @@ router.post("/deleteActivity/:activityId", async (req, res) => { }); await transaction.commit(); - console.log("terminated"); res .status(200) .send({ message: "Activity and related data deleted successfully" }); @@ -204,7 +203,6 @@ router.post("/addActivity", async (req, res) => { const authorized = await checkPermission(req, res, [ { club_rep: 4, club_id: clubId }, - { advisor: clubId }, ]); if (!authorized) { return; @@ -307,8 +305,6 @@ router.post("/editActivity", async (req, res) => { activity = await Activity.findByPk(activityId); const authorized = await checkPermission(req, res, [ { club_rep: 4, club_id: activity.club_id }, - { advisor: activity.club_id }, - // { executive: 4 }, ]); if (!authorized) { return; @@ -397,16 +393,24 @@ router.post("/editActivity", async (req, res) => { }); router.get("/getActivity/:activityId", async (req, res) => { + const durationCheck = await checkReportDuration(); + // if (!durationCheck.found || durationCheck.reportStatus !== 1) { + // return res.status(400).send({ message: "활동 추가 기한이 지났습니다." }); + // } const { activityId } = req.params; try { + let activity; + let evidence; + let participants; + // Fetch activity details - const activity = await Activity.findByPk(activityId); + activity = await Activity.findByPk(activityId); if (!activity) { return res.status(404).send("Activity not found"); } - const authorized = await checkPermission(req, res, [ + let authorized = await checkPermission(req, res, [ { club_rep: 4, club_id: activity.club_id }, { advisor: activity.club_id }, { executive: 4 }, @@ -415,10 +419,41 @@ router.get("/getActivity/:activityId", async (req, res) => { return; } - // Fetch evidence associated with the activity - let evidence = await ActivityEvidence.findAll({ - where: { activity_id: activityId }, - }); + authorized = await checkPermission(req, res, [ + { advisor: activity.club_id }, + ]); + + if (durationCheck.reportStatus === 2 && authorized) { + activity = await Activity_init.findByPk(activityId); + evidence = await ActivityEvidence_init.findAll({ + where: { activity_id: activityId }, + }); + participants = await ActivityMember_init.findAll({ + where: { activity_id: activityId }, + include: [ + { + model: Member, + attributes: ["name"], + as: "member_student", + }, + ], + }); + } else { + activity = await Activity.findByPk(activityId); + evidence = await ActivityEvidence.findAll({ + where: { activity_id: activityId }, + }); + participants = await ActivityMember.findAll({ + where: { activity_id: activityId }, + include: [ + { + model: Member, + attributes: ["name"], + as: "member_student", + }, + ], + }); + } // Function to extract the timestamp from the S3 URL const extractTimestamp = (url) => { @@ -435,23 +470,11 @@ router.get("/getActivity/:activityId", async (req, res) => { return timestampA - timestampB; }); - // Fetch participants associated with the activity - const participants = await ActivityMember.findAll({ - where: { activity_id: activityId }, - include: [ - { - model: Member, // Assuming Member model is imported and associated - attributes: ["name"], - as: "member_student", - }, - ], - }); // Format the response const response = { clubId: activity.club_id, name: activity.title, type: activity.activity_type_id, - category: "", // Adjust based on how you store 'category' startDate: activity.start_date, endDate: activity.end_date, location: activity.location, @@ -697,8 +720,6 @@ router.get("/search_members", async (req, res) => { semesterConditions = { semester_id: currentSemester.id }; } - console.log(semesterConditions); - // 해당 동아리에 속한 회원들 조회 const members = await MemberClub.findAll({ where: { diff --git a/back/routes/feedback.js b/back/routes/feedback.js new file mode 100644 index 0000000..10c2ce0 --- /dev/null +++ b/back/routes/feedback.js @@ -0,0 +1,121 @@ +const express = require("express"); +const router = express.Router(); +const { Op } = require("sequelize"); +const { + Semester, + Duration, + Activity, + ActivityEvidence, + ActivityMember, + Activity_init, + ActivityEvidence_init, + ActivityMember_init, +} = require("../models"); +const schedule = require("node-schedule"); + +const BATCH_SIZE = 100; // 한 번에 처리할 데이터의 양 + +router.get("/migrate", async (req, res) => { + await migrateDataInBatches(); + res.send("finish"); +}); + +const migrateDataInBatches = async () => { + try { + await Activity_init.destroy({ where: {} }); + await ActivityMember_init.destroy({ where: {} }); + await ActivityEvidence_init.destroy({ where: {} }); + + // Activity 데이터 이동 + const totalActivities = await Activity.count(); + const activityIterations = Math.ceil(totalActivities / BATCH_SIZE); + + for (let i = 0; i < activityIterations; i++) { + const activities = await Activity.findAll({ + limit: BATCH_SIZE, + offset: i * BATCH_SIZE, + }); + + const activityData = activities.map((a) => a.get({ plain: true })); + await Activity_init.bulkCreate(activityData, { ignoreDuplicates: true }); + } + + // ActivityMember 데이터 이동 + const totalMembers = await ActivityMember.count(); + const memberIterations = Math.ceil(totalMembers / BATCH_SIZE); + + for (let i = 0; i < memberIterations; i++) { + const members = await ActivityMember.findAll({ + limit: BATCH_SIZE, + offset: i * BATCH_SIZE, + }); + + const memberData = members.map((m) => m.get({ plain: true })); + await ActivityMember_init.bulkCreate(memberData, { + ignoreDuplicates: true, + }); + } + + // ActivityEvidence 데이터 이동 + const totalEvidence = await ActivityEvidence.count(); + const evidenceIterations = Math.ceil(totalEvidence / BATCH_SIZE); + + for (let i = 0; i < evidenceIterations; i++) { + const evidence = await ActivityEvidence.findAll({ + limit: BATCH_SIZE, + offset: i * BATCH_SIZE, + }); + + const evidenceData = evidence.map((e) => e.get({ plain: true })); + await ActivityEvidence_init.bulkCreate(evidenceData, { + ignoreDuplicates: true, + }); + } + + console.log("Data migration completed successfully"); + } catch (error) { + console.error("Error during batch data migration:", error); + } +}; + +const scheduleReportModifyStart = async () => { + const currentDate = new Date(); + currentDate.setHours(currentDate.getHours() + 9); + + const currentSemester = await Semester.findOne({ + where: { + start_date: { [Op.lte]: currentDate }, + end_date: { [Op.gte]: currentDate }, + }, + }); + + if (!currentSemester) { + console.log("현재 학기를 찾을 수 없습니다."); + return; + } + + const durations = await Duration.findAll({ + where: { + semester_id: currentSemester.id, + duration_name: "ReportModify", + }, + attributes: ["start_date"], + }); + + if (durations.length > 0) { + const reportModifyStartDate = new Date(durations[0].start_date); + // reportModifyStartDate.setHours(-30, -7, 0, 0); // 시작일의 자정에 설정 + reportModifyStartDate.setHours(0, 0, 0, 0); // 시작일의 자정에 설정 + + schedule.scheduleJob(reportModifyStartDate, async function () { + // 여기에 실행할 함수를 넣습니다. + console.log("ReportModify 시작!"); + await migrateDataInBatches(); + // 실행할 함수 또는 로직 + }); + } +}; + +scheduleReportModifyStart(); + +module.exports = router; diff --git a/back/server.js b/back/server.js index 05d119d..86490ce 100644 --- a/back/server.js +++ b/back/server.js @@ -62,17 +62,19 @@ sequelize }); const auth = require("./routes/auth"); +app.use("/api/auth", auth); const userRouter = require("./routes/user"); +app.use("/api/user", userRouter); const clubRouter = require("./routes/club"); +app.use("/api/club", clubRouter); const meetingRouter = require("./routes/meeting"); +app.use("/api/meeting", meetingRouter); const cafenotice = require("./routes/cafenotice"); -const activity = require("./routes/activity"); -app.use("/api/auth", auth); app.use("/api/cafenotice", cafenotice); -app.use("/api/user", userRouter); -app.use("/api/club", clubRouter); -app.use("/api/meeting", meetingRouter); +const activity = require("./routes/activity"); app.use("/api/activity", activity); +const feedback = require("./routes/feedback"); +app.use("/api/feedback", feedback); const httpServer = http.createServer(app); httpServer.listen(80, () => {