Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/feat-router&controller'
Browse files Browse the repository at this point in the history
  • Loading branch information
j-nary committed May 27, 2024
2 parents 5895011 + 285f1d5 commit c921be3
Show file tree
Hide file tree
Showing 47 changed files with 2,574 additions and 2,281 deletions.
2 changes: 2 additions & 0 deletions adminPage/components/event/EventEdit.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ const Edit: FC<ActionProps> = (props) => {
useEffect(() => {
if (initialRecord) {
setRecord(initialRecord);
} else {
setRecord({...record, params: {major_advisor: role}})
}
}, [initialRecord]);
if (role != "관리자") {
Expand Down
17 changes: 12 additions & 5 deletions adminPage/components/event/EventList.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { Box, Pagination, Tab, Tabs, Text } from "@adminjs/design-system";
import { ActionProps, RecordsTable, useRecords, useSelectedRecords } from "adminjs";
import React, { useEffect } from "react";
import React, { useEffect, useLayoutEffect } from "react";
import { useLocation, useNavigate } from "react-router";
import {useSearchParams} from "react-router-dom";
import { TabLabel, useTabWithPagePersistence } from "./hooks.js";

export const List: React.FC<ActionProps> = (props) => {
Expand All @@ -11,18 +12,25 @@ export const List: React.FC<ActionProps> = (props) => {
const { selectedRecords, handleSelect, handleSelectAll, setSelectedRecords } = useSelectedRecords(records);
const location = useLocation();
const navigate = useNavigate();
const [searchParams, setSearchParams] = useSearchParams();
// custom css 부착용
const getActionElementCss = (resourceId: string, actionName: string, suffix: string) => `${resourceId}-${actionName}-${suffix}`
const contentTag = getActionElementCss(resource.id, "list", "table-wrapper");
const [ currentTab, handleTabChange ] = useTabWithPagePersistence('ongoing');

/** list 제목 옆에 chip형태로 나오는 행 수 업데이트 */
useEffect(() => {
if (setTag) {
setTag(total.toString());
}
}, [total]);

useLayoutEffect(() => {
if (location.search.includes("refresh=true")) {
searchParams.delete("refresh");
setSearchParams(searchParams);
}
}, [location])

/** 탭 변경시 선택 데이터 초기화 */
useEffect(() => {
setSelectedRecords([]);
Expand All @@ -33,9 +41,8 @@ export const List: React.FC<ActionProps> = (props) => {

/** 페이지 변경 핸들러 함수 */
const handlePaginationChange = (pageNumber: number): void => {
const search = new URLSearchParams(location.search);
search.set("page", pageNumber.toString());
navigate({ search: search.toString() });
searchParams.set("page", pageNumber.toString());
setSearchParams(searchParams);
};

return (
Expand Down
2 changes: 1 addition & 1 deletion adminPage/components/event/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { useLocalStorage } from "adminjs";
import { useEffect, useState } from "react";
import { useLocation, useNavigate } from 'react-router';

export type TabLabel = 'ongoing' | 'expired'
export type TabLabel = 'ongoing' | 'expired' | string

/** tab 전환 후 그 tab의 page를 불러오기 위한 커스텀 훅
* @param {TabLabel} initialTab - 초기 Tab 설정
Expand Down
3 changes: 3 additions & 0 deletions adminPage/components/notice/NoticeEdit.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,11 @@ const Edit: FC<ActionProps> = (props) => {
useEffect(() => {
if (initialRecord) {
setRecord(initialRecord);
} else {
setRecord({...record, params: {major_advisor: role, date: new Date().toISOString()}})
}
}, [initialRecord]);

if (role != "관리자") {
const majorProp = resource.editProperties.find(p => p.name == 'major_advisor');
if (majorProp && majorProp.availableValues) {
Expand Down
18 changes: 14 additions & 4 deletions adminPage/components/notice/NoticeList.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { Box, Pagination, Tab, Tabs, Text } from "@adminjs/design-system";
import { ActionProps, RecordsTable, useCurrentAdmin, useRecords, useSelectedRecords } from "adminjs";
import React, { useEffect } from "react";
import React, { useEffect, useLayoutEffect } from "react";
import { useLocation, useNavigate } from "react-router";
import {useSearchParams} from "react-router-dom";
import { TabLabel, useTabWithPagePersistence } from "./hooks.js";

const YesColor = 'rgb(194, 0, 18)'; // 붉은색
Expand All @@ -17,6 +18,8 @@ export const List: React.FC<ActionProps> = (props) => {
const { selectedRecords, handleSelect, handleSelectAll, setSelectedRecords } = useSelectedRecords(records);
const location = useLocation();
const navigate = useNavigate();
const [searchParams, setSearchParams] = useSearchParams();

// custom css 부착용
const getActionElementCss = (resourceId: string, actionName: string, suffix: string) => `${resourceId}-${actionName}-${suffix}`
const contentTag = getActionElementCss(resource.id, "list", "table-wrapper");
Expand All @@ -32,6 +35,14 @@ export const List: React.FC<ActionProps> = (props) => {
element.style.setProperty('background-color', 'white', 'important')
});
}, [data]);

useLayoutEffect(() => {
if (location.search.includes("refresh=true")) {
searchParams.delete("refresh");
setSearchParams(searchParams);
}
}, [location])

/** list 제목 옆에 chip형태로 나오는 행 수 업데이트 */
useEffect(() => {
if (setTag) {
Expand All @@ -49,9 +60,8 @@ export const List: React.FC<ActionProps> = (props) => {

/** 페이지 변경 핸들러 함수 */
const handlePaginationChange = (pageNumber: number): void => {
const search = new URLSearchParams(location.search);
search.set("page", pageNumber.toString());
navigate({ search: search.toString() });
searchParams.set("page", pageNumber.toString());
setSearchParams(searchParams);
};

return (
Expand Down
2 changes: 1 addition & 1 deletion adminPage/components/notice/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { useLocalStorage } from "adminjs";
import { useEffect, useState } from "react";
import { useLocation, useNavigate } from 'react-router';

export type TabLabel = 'urgent' | 'general' | 'expired'
export type TabLabel = 'urgent' | 'general' | 'expired' | string

/** tab 전환 후 그 tab의 page를 불러오기 위한 커스텀 훅
* @param {TabLabel} initialTab - 초기 Tab 설정
Expand Down
222 changes: 179 additions & 43 deletions adminPage/handlers/event.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,29 @@ 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 {
cacheYearMonthData,
calculateMonthsBetween,
} from "../../redis/caching.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 { role } = currentAdmin;
const unflattenQuery = flat.unflatten(
query || {}
) as EventActionQueryParameters;
let { page, perPage, type = "ongoing" } = unflattenQuery;
const isOngoing = type == "ongoing";
// 진행중인 행사 탭에서는 시작일 내림차순 정렬
// 종료된 행사 탭에서는 종료일 내림차순 정렬
const { sortBy = isOngoing ? 'start' : 'end', direction = 'desc', filters = { major_advisor: role} } = unflattenQuery;
const {
sortBy = isOngoing ? "start" : "end",
direction = "desc",
filters = { major_advisor: role },
} = unflattenQuery;

// adminOptions.settings.defaultPerPage, 한 페이지에 몇 행 보여줄 지
if (perPage) {
Expand All @@ -30,20 +42,26 @@ const list: ActionHandler<any> = async (request, response, context) => {
sort = SortSetter(
{ sortBy, direction },
firstProperty.name(),
resource.decorate().options,
)
};
resource.decorate().options
);
}
// 진행중인 행사 탭이면 expired == false인 데이터만
// 종료된 행사 탭이면 expired == true인 데이터만 가져오기
const filter = await new Filter({ ...filters, expired: isOngoing ? 'false' : 'true' }, resource).populate(context);
const records = await resource.find(filter, {
limit: perPage,
offset: (page - 1) * perPage,
sort,
}, context);
const filter = await new Filter(
{ ...filters, expired: isOngoing ? "false" : "true" },
resource
).populate(context);
const records = await resource.find(
filter,
{
limit: perPage,
offset: (page - 1) * perPage,
sort,
},
context
);
const populatedRecords = await populator(records, context);
context.records = populatedRecords;

// 메타데이터 및 가져온 데이터 return
const total = await resource.count(filter, context);
return {
Expand All @@ -56,46 +74,164 @@ const list: ActionHandler<any> = async (request, response, context) => {
},
records: populatedRecords.map((r) => r.toJSON(currentAdmin)),
};
}
};

/**
* 게시글 create, update 후 redis에 캐싱하는 함수
* @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 = originalResponse?.record?.params;
const hasError = Object.keys(originalResponse.record.errors).length;
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 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);
}

return originalResponse;
};

const deleteAfter = () => async (originalResponse, request, context) => {
const isPost = request?.method === "post";
const isAction = context?.action.name === "delete";
const { record } = originalResponse;
console.log({isPost, action: context?.action.name, record});
// checking if object doesn't have any errors or is a edit action
if ((isPost && isEdit) && (hasRecord && !hasError)) {
// 학과는 로그인한 관리자의 것으로 적용
if (role != '관리자') {
hasRecord.major_advisor = role;
if (isPost && isAction && record) {
const { id, start, end } = record;
const redisKeyAll = "allEvents";
const promises = [];
promises.push(redisClient.del(`event:${id}`));

if (end > new Date()) {
delEventSchedule(record);
}
// redis 캐싱
const redisKeyEach = `event:${hasRecord.id}`;
const redisKeyAll = 'allEvents';
// 캐싱
const allEvents = await Event.findAll({
order: [["start", "ASC"]],
});
// 수정한 글의 시작~종료일 사이 연-월 리스트 추출
const ranges = calculateMonthsBetween(start, end);

await redisClient.set(redisKeyEach, JSON.stringify(hasRecord));
// 전체 목록 캐싱
const allEventsFromDb = await Event.findAll({
where: {
expired: false // 진행중인 행사만 가져오기
// 진행 중인 행사글, 수정한 글의 시작~종료일 사이에 있는 모든 행사글(=관련글) 추출
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;
},
order: [
['start', 'ASC']
]
});
await redisClient.set(redisKeyAll, JSON.stringify(allEventsFromDb));
[[], []] as IEvent[][]
);
// 전체 목록 캐싱
promises.push(redisClient.set(redisKeyAll, JSON.stringify(onGoings)));
// 관련글들을 연:월로 grouping
promises.push(...cacheYearMonthData(relations));
await Promise.all(promises);
}

return originalResponse
}
return originalResponse;
};

const bulkDelete = () => async (originalResponse, request, context) => {
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 promises = records.map(async ({ params: record }) => {
// redis 캐싱 제거
if (record.end > new Date()) {
// 스케쥴에 등록된 행사들 제거
delEventSchedule(record);
}
return redisClient.del(`event:${record.id}`);
});
const redisKeyAll = "allEvents";
// 전체 목록 캐싱
const allEvents = await Event.findAll({
order: [["start", "ASC"]],
});

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

export const EventHandler = {
list,
after
}
after,
deleteAfter,
bulkDelete,
};
Loading

0 comments on commit c921be3

Please sign in to comment.