Skip to content

Commit

Permalink
feat: code spliting to common_method, add scheduler & handler for fcm
Browse files Browse the repository at this point in the history
  • Loading branch information
ptyoiy committed Jul 3, 2024
1 parent dd89e8a commit d2581c8
Show file tree
Hide file tree
Showing 26 changed files with 488 additions and 236 deletions.
3 changes: 1 addition & 2 deletions adminPage/components/dashboard/Dashboard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { useCurrentAdmin, useTranslation } from "adminjs";
import React, { useEffect, useRef } from "react";
import { useNavigate } from "react-router";
import { useLog } from "./hook";

type BoxType = {
variant: string;
title: string;
Expand Down Expand Up @@ -103,7 +104,6 @@ export const Dashboard = (props) => {
const lines = log.split('\n');
const logRef = useRef<HTMLPreElement>(null);
const maxLineNumberLength = String(lines.length).length;

useEffect(() => {
// logRef가 현재 가리키는 요소의 스크롤을 맨 밑으로 설정
if (logRef.current && log) {
Expand Down Expand Up @@ -146,7 +146,6 @@ export const Dashboard = (props) => {
<Text>{box.subtitle}</Text>
</Text>
</Card>

</Box>
))}
<Box width={[1]} p="lg">
Expand Down
244 changes: 142 additions & 102 deletions adminPage/handlers/event.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,31 @@
import { ActionHandler, Filter, SortSetter, flat, populator } from "adminjs";
import Event from "../../models/events.js";
import { redisClient } from "../../redis/connect.js";
import { EventActionQueryParameters } from "./index.js";
import { IEvent } from "../../models/types.js";
import { delEventSchedule, setEventSchedule } from "../../redis/schedule.js";
import { ActionHandler, Filter, SortSetter, flat, populator } from 'adminjs';
import {
cacheYearMonthData,
calculateMonthsBetween,
} from "../../redis/caching.js";
delEventToExpiredSchedule,
delEventsPushForBookmarkSchedule,
delEventsPushForEntireSchedule,
sendFCM,
setEventToExpiredSchedule,
setEventsPushForBookmarkSchedule,
setEventsPushForEntireSchedule,
} from '../../common_method/index.js';
import Event from '../../models/events.js';
import { IEvent } from '../../models/types.js';
import { cacheYearMonthData, calculateMonthsBetween } from '../../redis/caching.js';
import { redisClient } from '../../redis/connect.js';
import { EventActionQueryParameters } from './index.js';

const list: ActionHandler<any> = async (request, response, context) => {
const { query } = request; // 요청 url의 query 부분 추출
const { resource, _admin, currentAdmin } = context; // db table
const { role } = currentAdmin;
const unflattenQuery = flat.unflatten(
query || {}
) as EventActionQueryParameters;
let { page, perPage, type = "ongoing" } = unflattenQuery;
const isOngoing = type == "ongoing";
const unflattenQuery = flat.unflatten(query || {}) as EventActionQueryParameters;
let { page, perPage, type = 'ongoing' } = unflattenQuery;
const isOngoing = type == 'ongoing';
// 진행중인 행사 탭에서는 시작일 내림차순 정렬
// 종료된 행사 탭에서는 종료일 내림차순 정렬
const {
sortBy = isOngoing ? "start" : "end",
direction = "desc",
sortBy = isOngoing ? 'start' : 'end',
direction = 'desc',
filters = { major_advisor: role },
} = unflattenQuery;

Expand All @@ -39,16 +42,12 @@ const list: ActionHandler<any> = async (request, response, context) => {
const firstProperty = listProperties.find((p) => p.isSortable());
let sort;
if (firstProperty) {
sort = SortSetter(
{ sortBy, direction },
firstProperty.name(),
resource.decorate().options
);
sort = SortSetter({ sortBy, direction }, firstProperty.name(), resource.decorate().options);
}
// 진행중인 행사 탭이면 expired == false인 데이터만
// 종료된 행사 탭이면 expired == true인 데이터만 가져오기
const filter = await new Filter(
{ ...filters, expired: isOngoing ? "false" : "true" },
{ ...filters, expired: isOngoing ? 'false' : 'true' },
resource
).populate(context);
const records = await resource.find(
Expand Down Expand Up @@ -81,91 +80,122 @@ const list: ActionHandler<any> = async (request, response, context) => {
* @param action after함수를 사용할 action
* @returns action 실행 후 호출할 hook 함수
*/
const after = (action: "edit" | "new") => async (originalResponse, request, context) => {
const isPost = request.method === "post";
const isEdit = context.action.name === action;
const {
currentAdmin: { role },
} = context;
const hasRecord: IEvent = 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) {
const { id, start, end } = hasRecord;
const promises = [];
// 학과는 로그인한 관리자의 것으로 적용
if (role != "관리자") {
hasRecord.major_advisor = role;
}
// redis 캐싱
const redisKeyEach = `event:${id}`;
const redisKeyAll = "allEvents";
promises.push(redisClient.set(redisKeyEach, JSON.stringify(hasRecord)));
// end날이 아직 안 지났다면
if (end > new Date()) {
// end날 이후 종료 상태로 변경하는 스케쥴 등록
const recordModel = await Event.findOne({
where: { id },
});
setEventSchedule(recordModel);
}
// 캐싱
const allEvents = await Event.findAll({
order: [["start", "ASC"]],
const after = (action: 'edit' | 'new') => async (originalResponse, request, context) => {
const isPost = request.method === 'post';
const isEdit = context.action.name === action;
const {
currentAdmin: { role },
} = context;
const hasRecord: IEvent = 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) {
const { id, start, end, title, content } = hasRecord;
const promises = []; // 병렬로 처리 시키기 위한 비동기 함수 배열
const currentDate = new Date();
// 학과는 로그인한 관리자의 것으로 적용
if (role != '관리자') {
hasRecord.major_advisor = role;
}

// #1. 행사 종료 스케쥴 등록
if (end > currentDate) {
// end날짜가 아직 안 지났다면 end날짜 이후 종료 상태로 변경하는 스케쥴 등록
const recordModel = await Event.findOne({
where: { id },
});
// 수정한 글의 시작~종료일 사이 연-월 리스트 추출
const ranges = calculateMonthsBetween(start, end);

// 진행 중인 행사글, 수정한 글의 시작~종료일 사이에 있는 모든 행사글(=관련글) 추출
const [onGoings, relations] = allEvents.reduce(
(acc, event) => {
if (!event.expired) acc[0].push(event);
if (
ranges.some((range) => {
const [year, month] = range.split("-");
const startOfMonth = new Date(+year, +month - 1, 1);
const endOfMonth = new Date(+year, +month, 0, 23, 59, 59);
return (
(event.start >= startOfMonth && event.start <= endOfMonth) ||
(event.end >= startOfMonth && event.end <= endOfMonth) ||
(event.start <= startOfMonth && event.end >= endOfMonth)
);
})
)
acc[1].push(event);
return acc;
},
[[], []] as IEvent[][]
);
// 전체 목록 캐싱
promises.push(redisClient.set(redisKeyAll, JSON.stringify(onGoings)));
console.log({ relations: relations.map((v) => v.id) });
// 관련글들을 연:월로 grouping
promises.push(...cacheYearMonthData(relations));
await Promise.all(promises);
setEventToExpiredSchedule(recordModel);
}

return originalResponse;
};
// #2. redis 캐싱
const redisKeyEach = `event:${id}`;
const redisKeyAll = 'allEvents';
promises.push(redisClient.set(redisKeyEach, JSON.stringify(hasRecord)));
const allEvents = await Event.findAll({
order: [['start', 'ASC']],
});
// 수정한 글의 시작~종료일 사이 연-월 리스트 추출
const ranges = calculateMonthsBetween(start, end);

// 진행 중인 행사글, 수정한 글의 시작~종료일 사이에 있는 모든 행사글(=관련글) 추출
const [onGoings, relations] = allEvents.reduce(
(acc, event) => {
if (!event.expired) acc[0].push(event);
if (
ranges.some((range) => {
const [year, month] = range.split('-');
const startOfMonth = new Date(+year, +month - 1, 1);
const endOfMonth = new Date(+year, +month, 0, 23, 59, 59);
return (
(event.start >= startOfMonth && event.start <= endOfMonth) ||
(event.end >= startOfMonth && event.end <= endOfMonth) ||
(event.start <= startOfMonth && event.end >= endOfMonth)
);
})
)
acc[1].push(event);
return acc;
},
[[], []] as IEvent[][]
);
// 전체 목록 캐싱
promises.push(redisClient.set(redisKeyAll, JSON.stringify(onGoings)));
console.log({ relations: relations.map((v) => v.id) });
// 달력용 데이터 캐싱(관련글들을 연:월로 grouping)
promises.push(...cacheYearMonthData(relations));
await Promise.all(promises); // 병렬 처리

// #3. 행사 등록 알림 전송
const notification = {
title,
body: content,
};
const type = 'events';
// 수정일땐 보내지 않음
if (action === 'new') sendFCM(notification, `events_post`, {
type,
id: `${id}`
});

// #4. 행사 시작 알림 전송 스케쥴 등록
// 시작날이 지나지 않은 경우에만 등록
if (hasRecord.start > currentDate) {
setEventsPushForEntireSchedule(hasRecord);
setEventsPushForBookmarkSchedule(hasRecord);
}
}

return originalResponse;
};

const deleteAfter = () => async (originalResponse, request, context) => {
const isPost = request?.method === "post";
const isAction = context?.action.name === "delete";
const isPost = request?.method === 'post';
const isAction = context?.action.name === 'delete';
const { record } = originalResponse;
console.log({isPost, action: context?.action.name, record});
console.log({ isPost, action: context?.action.name, record });
// checking if object doesn't have any errors or is a edit action
if (isPost && isAction && record) {
const { id, start, end } = record;
const redisKeyAll = "allEvents";
const redisKeyAll = 'allEvents';
const promises = [];
const currentDate = new Date();
promises.push(redisClient.del(`event:${id}`));

if (end > new Date()) {
delEventSchedule(record);
// 스케쥴 제거
if (end > currentDate) {
// expired 스케쥴 제거
delEventToExpiredSchedule(record);
}
// fcm 스케쥴 제거
// 시작일이 지나지 않은 경우에만 제거
if (record.start > currentDate) {
delEventsPushForEntireSchedule(record);
delEventsPushForBookmarkSchedule(record);
}

// 캐싱
const allEvents = await Event.findAll({
order: [["start", "ASC"]],
order: [['start', 'ASC']],
});
// 수정한 글의 시작~종료일 사이 연-월 리스트 추출
const ranges = calculateMonthsBetween(start, end);
Expand All @@ -176,7 +206,7 @@ const deleteAfter = () => async (originalResponse, request, context) => {
if (!event.expired) acc[0].push(event);
if (
ranges.some((range) => {
const [year, month] = range.split("-");
const [year, month] = range.split('-');
const startOfMonth = new Date(+year, +month - 1, 1);
const endOfMonth = new Date(+year, +month, 0, 23, 59, 59);
return (
Expand All @@ -202,27 +232,37 @@ const deleteAfter = () => async (originalResponse, request, context) => {
};

const bulkDelete = () => async (originalResponse, request, context) => {
const isPost = request?.method === "post";
const isAction = context?.action.name === "bulkDelete";
const isPost = request?.method === 'post';
const isAction = context?.action.name === 'bulkDelete';
const { records } = originalResponse;

// checking if object doesn't have any errors or is a edit action
if (isPost && isAction && records) {
const currentDate = new Date();
const promises = records.map(async ({ params: record }) => {
// redis 캐싱 제거
if (record.end > new Date()) {
// 스케쥴에 등록된 행사들 제거
delEventSchedule(record);
// 스케쥴 제거
if (record.end > currentDate) {
// expired 스케쥴 제거
delEventToExpiredSchedule(record);
}
// fcm 스케쥴 제거
// 시작일이 지나지 않은 경우에만 제거
if (record.start > currentDate) {
delEventsPushForEntireSchedule(record);
delEventsPushForBookmarkSchedule(record);
}
// redis 캐싱 제거
return redisClient.del(`event:${record.id}`);
});
const redisKeyAll = "allEvents";
const redisKeyAll = 'allEvents';
// 전체 목록 캐싱
const allEvents = await Event.findAll({
order: [["start", "ASC"]],
order: [['start', 'ASC']],
});

promises.push(redisClient.set(redisKeyAll, JSON.stringify(allEvents.filter(e => !e.expired))));
promises.push(
redisClient.set(redisKeyAll, JSON.stringify(allEvents.filter((e) => !e.expired)))
);
promises.push(...cacheYearMonthData(allEvents));
await Promise.all(promises);
}
Expand Down
Loading

0 comments on commit d2581c8

Please sign in to comment.