diff --git a/adminPage/components/css/common.css b/adminPage/components/css/common.css index 7a244a5..c09ccc6 100644 --- a/adminPage/components/css/common.css +++ b/adminPage/components/css/common.css @@ -40,27 +40,25 @@ section[data-css="notices-show-priority"] { background-color: unset!important; } } -section[data-css="app-content"] { - pre { - padding: 0; - word-wrap: break-word; - white-space: pre-wrap; - overflow-x: hidden; - overflow-y: auto; - } - .log-wrapper { - display: flex; - border: 1px solid lightgrey; - border-bottom: unset; - padding: 8px!important; - } - .log-wrapper:last-child { - border-bottom: 1px solid lightgrey; - } - .log-line { - overflow-wrap: anywhere; - white-space: normal; - } +.log-box { + padding: 0; + word-wrap: break-word; + white-space: pre-wrap; + overflow-x: hidden; + overflow-y: auto; +} +.log-wrapper { + display: flex; + border: 1px solid lightgrey; + border-bottom: unset; + padding: 8px!important; +} +.log-wrapper:last-child { + border-bottom: 1px solid lightgrey; +} +.log-line { + overflow-wrap: anywhere; + white-space: normal; } section.login__Wrapper { .adminjs_Text { diff --git a/adminPage/components/dashboard/Dashboard.tsx b/adminPage/components/dashboard/Dashboard.tsx index aa0c55b..48ceda3 100644 --- a/adminPage/components/dashboard/Dashboard.tsx +++ b/adminPage/components/dashboard/Dashboard.tsx @@ -8,7 +8,7 @@ import { } from "@adminjs/design-system"; import { styled } from "@adminjs/design-system/styled-components"; import { useCurrentAdmin, useTranslation } from "adminjs"; -import React, {useEffect, useRef} from "react"; +import React, { useEffect, useRef } from "react"; import { useNavigate } from "react-router"; import { useLog } from "./hook"; type BoxType = { @@ -148,7 +148,7 @@ export const Dashboard = (props) => { Log - + {lines.map((line, index) => { // 라인 번호 포맷팅: 번호를 문자열로 변환하고, 필요한 만큼 공백으로 채움 const lineNumber = `${index + 1}`.padEnd(maxLineNumberLength, ' '); diff --git a/adminPage/handlers/notice.ts b/adminPage/handlers/notice.ts index 524a1a4..6ec5122 100644 --- a/adminPage/handlers/notice.ts +++ b/adminPage/handlers/notice.ts @@ -2,8 +2,9 @@ import { ActionHandler, Filter, SortSetter, flat, populator } from "adminjs"; import { Op } from "sequelize"; import Notice from "../../models/notice.js"; import { INotice } from "../../models/types.js"; +import { cachingAllNotices } from "../../redis/caching.js"; import { redisClient } from "../../redis/connect.js"; -import { setNoticeSchedule } from "../../redis/schedule.js"; +import { delNoticeSchedule, setNoticeSchedule } from "../../redis/schedule.js"; import { NoticeActionQueryParameters } from "./index.js"; const list: ActionHandler = async (request, response, context) => { @@ -76,12 +77,12 @@ const list: ActionHandler = async (request, response, context) => { */ const after = (action: 'edit' | 'new') => async (originalResponse, request, context) => { const isPost = request.method === 'post'; - const isEdit = context.action.name === action; + const isAction = context.action.name === action; const {currentAdmin: {role}} = context; const hasRecord = originalResponse?.record?.params; const hasError = Object.keys(originalResponse.record.errors).length; // checking if object doesn't have any errors or is a edit action - if ((isPost && isEdit) && (hasRecord && !hasError)) { + if ((isPost && isAction) && (hasRecord && !hasError)) { // 학과는 로그인한 관리자의 것으로 적용 if (role != '관리자') { hasRecord.major_advisor = role; @@ -115,7 +116,33 @@ const after = (action: 'edit' | 'new') => async (originalResponse, request, cont return originalResponse } +// delete -> 삭제 후 redis 업데이트(개별 공지 제거, 전체 공지 업데이트) +const deleteAfter = () => async (originalResponse, request, context) => { + const isPost = request.method === 'post'; + const isAction = context.action.name === 'delete'; + const hasRecord = originalResponse?.record?.params; + const hasError = Object.keys(originalResponse.record.errors).length; + // checking if object doesn't have any errors or is a edit action + if ((isPost && isAction) && (hasRecord && !hasError)) { + const {priority, id} = hasRecord; + const isGeneral = priority == '일반' + const redisKeyEach = `notice:${id}`; + + // 해당 글의 캐싱데이터 제거 + await redisClient.del(redisKeyEach); + // 긴급일 경우 스케쥴에서 제거 + if (!isGeneral) { + delNoticeSchedule(hasRecord); + } + // 전체 목록 캐싱 + await cachingAllNotices(); + } + + return originalResponse +} + export const NoticeHandler = { list, - after + after, + deleteAfter } \ No newline at end of file diff --git a/adminPage/resources/notice.ts b/adminPage/resources/notice.ts index 74ceaf7..3bdced8 100644 --- a/adminPage/resources/notice.ts +++ b/adminPage/resources/notice.ts @@ -35,6 +35,9 @@ const noticeOptions: ResourceOptions = { new: { after: NoticeHandler.after('new'), component: Components.notice_edit + }, + delete: { + after: NoticeHandler.deleteAfter() } } } @@ -56,7 +59,7 @@ const noticeFeatures = [uploadFeature({ // 저장할 파일의 각종 정보들을 테이블의 어떤 속성에 저장할 지 설정 properties: { key: "image", // 저장된 경로를 image 속성에 저장 - } + }, })]; export const NOTICE = { diff --git a/redis/caching.ts b/redis/caching.ts index 5da4e7c..15644b3 100644 --- a/redis/caching.ts +++ b/redis/caching.ts @@ -1,4 +1,3 @@ -import { Op } from "sequelize"; import Event from "../models/events.js"; import Notice from "../models/notice.js"; import { IEvent, INotice } from "../models/types.js"; @@ -39,38 +38,54 @@ export const initAllOngoingEvents = async () => { ** 전체 공지글 redisKey - 일반 ? alerts:general : alerts:urgent ** 개별 공지글 redisKey - notice:id */ -export const initAllOngoingNotices = async (priority: "일반" | "긴급") => { - const isGeneral = priority == "일반"; - const redisKey = `alerts:${isGeneral ? "general" : "urgent"}`; +export const initAllOngoingNotices = async () => { const currentDate = new Date(); const eachNotices = await redisClient.keys(`notice:*`); - const noticesFromDB = await Notice.findAll({ - where: { - expired: false, // 활성화된 공지만 가져오기 - priority: { - [Op.eq]: priority, - }, - }, - }); - // 개별 공지 캐싱 + const [urgent, general] = await getAllNotices(); // 과거 캐싱 기록 제거 if (eachNotices.length) { await redisClient.del(eachNotices); eachNotices.splice(0, eachNotices.length); } - for (const notice of noticesFromDB as INotice[]) { + + for (const notice of urgent as INotice[]) { const redisKey = `notice:${notice.id}`; + // 일반 공지로 이동해야할 긴급 공지가 있는지 체크 const noticeNextDay = getNextDay(new Date(notice.date)); - if (!isGeneral && (noticeNextDay > currentDate.getTime())) { + if (noticeNextDay > currentDate.getTime()) { setNoticeSchedule(notice); eachNotices.push(redisKey); } else { notice.update({ ...notice, priority: "일반" }) } + // 개별 공지 캐싱 await redisClient.set(redisKey, JSON.stringify(notice)); } - // 전체 공지 캐싱 - await redisClient.set(redisKey, JSON.stringify(noticesFromDB)); - - priority == "긴급" && console.log(`진행중인 공지: `, eachNotices); + for (const notice of general as INotice[]) { + const redisKey = `notice:${notice.id}`; + await redisClient.set(redisKey, JSON.stringify(notice)); + } + await cachingAllNotices(urgent, general); }; + +export const cachingAllNotices = async (urgent?, general?) => { + if (!urgent && !general) [urgent, general] = await getAllNotices(); + await redisClient.set(`alerts:urgent`, JSON.stringify(urgent)); + await redisClient.set(`alerts:general`, JSON.stringify(general)); +} +/** + * @returns [urgent, general] + */ +const getAllNotices = async () => { + // 전체 긴급 공지 목록 캐싱 + const [urgent, general] = (await Notice.findAll({ + where: { + expired: false, // 활성화된 공지만 가져오기 + } + })).reduce((acc, val: INotice) => { + acc[+(val.priority == '일반')].push(val); + return acc; + }, [[], []]); + + return [urgent, general] as [INotice[], INotice[]]; +} \ No newline at end of file diff --git a/redis/initialize.ts b/redis/initialize.ts index 0f7ab23..9ad8d22 100644 --- a/redis/initialize.ts +++ b/redis/initialize.ts @@ -4,6 +4,5 @@ import { connectRedis } from "./connect.js"; export const initializeRedis = async () => { await connectRedis(); await initAllOngoingEvents(); - await initAllOngoingNotices('일반'); - await initAllOngoingNotices('긴급'); + await initAllOngoingNotices(); } diff --git a/redis/schedule.ts b/redis/schedule.ts index afa5049..9fec636 100644 --- a/redis/schedule.ts +++ b/redis/schedule.ts @@ -1,6 +1,6 @@ import { Job, scheduleJob } from "node-schedule"; -import Notice from "../models/notice.js"; import { INotice } from "../models/types.js"; +import { cachingAllNotices } from "./caching.js"; import { redisClient } from "./connect.js"; const jobArray: Job[] = []; @@ -11,16 +11,7 @@ export const setNoticeSchedule = (row: INotice) => { const updatedRow = await row.update({ ...row, priority: "일반" }); await redisClient.set(`notice:${id}`, JSON.stringify(updatedRow)); // 전체 긴급 공지 목록 캐싱 - const [urgent, general] = (await Notice.findAll({ - where: { - expired: false, // 활성화된 공지만 가져오기 - } - })).reduce((acc, val: INotice) => { - acc[+(val.priority == '일반')].push(val); - return acc; - }, [[], []]); - await redisClient.set(`alerts:urgent`, JSON.stringify(urgent)); - await redisClient.set(`alerts:general`, JSON.stringify(general)); + await cachingAllNotices(); jobArray.splice(existJobIndex, 1); }); const existJobIndex = jobArray.findIndex(j => j.name == `${id}`); @@ -33,5 +24,17 @@ export const setNoticeSchedule = (row: INotice) => { console.log('set schedule:', `${id}-${date.toLocaleString()}`); console.log('job list:', jobArray.map(j => j?.name)); }; +export const delNoticeSchedule = (row: INotice) => { + const {id, date} = row; + const existJobIndex = jobArray.findIndex(j => j.name == `${id}`); + if (existJobIndex > -1) { + jobArray[existJobIndex].cancel(); + jobArray.splice(existJobIndex, 1); + console.log('del schedule:', `${id}-${date.toLocaleString()}`); + console.log('job list:', jobArray.map(j => j?.name)); + } else { + console.warn(`jobArray hasn't schedule-${id}`); + } +} export const getNextDay = (date: Date) => date.setDate(date.getDate() + 1); \ No newline at end of file