Skip to content

Commit

Permalink
feat: add linktree (admin, router, conteroller)
Browse files Browse the repository at this point in the history
  • Loading branch information
ptyoiy committed Apr 12, 2024
1 parent f764a17 commit f83f127
Show file tree
Hide file tree
Showing 17 changed files with 365 additions and 21 deletions.
5 changes: 5 additions & 0 deletions adminPage/components/dashboard/Dashboard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,11 @@ const boxes = ({ translateLabel }): Array<BoxType> => [
title: translateLabel("notices"),
href: "/admin/resources/notices",
},
{
variant: "Folders",
title: translateLabel("linktrees"),
href: "/admin/resources/linktrees",
},
];

const Card = styled(Box)`
Expand Down
1 change: 1 addition & 0 deletions adminPage/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,6 @@ export const Components = {
notice_list: add('NoticeList', './notice/NoticeList.tsx'),
notice_show: add('NoticeShow', './notice/NoticeShow.tsx'),
notice_edit: add('NoticeEdit', './notice/NoticeEdit.tsx'),
linktree_edit: add('LinktreeEdit', './linktree/LinktreeEdit.tsx'),
// custom: add('Custom', './custom/Custom.tsx')
}
87 changes: 87 additions & 0 deletions adminPage/components/linktree/LinktreeEdit.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import { Box, Button, DrawerContent, DrawerFooter, Icon } from "@adminjs/design-system";
import {
ActionProps,
BasePropertyComponent,
RecordJSON,
useCurrentAdmin,
useRecord,
useTranslation
} from "adminjs";
import React, { FC, useEffect } from "react";
import { useNavigate } from "react-router";

const appendForceRefresh = (url: string, search?: string): string => {
const searchParamsIdx = url.lastIndexOf("?");
const urlSearchParams = searchParamsIdx !== -1 ? url.substring(searchParamsIdx + 1) : null;

const oldParams = new URLSearchParams(search ?? urlSearchParams ?? window.location.search ?? "");
const shouldIgnoreOldParams = new URLSearchParams(urlSearchParams || "").get("ignore_params") === "true";
const newParams = shouldIgnoreOldParams ? new URLSearchParams("") : new URLSearchParams(oldParams.toString());

newParams.set("refresh", "true");

const newUrl = searchParamsIdx !== -1 ? url.substring(0, searchParamsIdx) : url;

return `${newUrl}?${newParams.toString()}`;
};

const Edit: FC<ActionProps> = (props) => {
const { record: initialRecord, resource, action } = props;
const [currentAdmin, setCurrentAdmin] = useCurrentAdmin();
const {role} = currentAdmin as any
const { record, handleChange, submit: handleSubmit, loading, setRecord } = useRecord(initialRecord, resource.id);
const { translateButton } = useTranslation();
const navigate = useNavigate();
const getActionElementCss = (resourceId: string, actionName: string, suffix: string) => `${resourceId}-${actionName}-${suffix}`
// const value = record.params?.[property.path]
// const error = record.errors && record.errors[property.path]
// console.log(props)
useEffect(() => {
if (initialRecord) {
setRecord(initialRecord);
}
}, [initialRecord]);
const majorProp = resource.editProperties.find(p => p.name == 'major');
if (majorProp && majorProp.availableValues) {
majorProp.availableValues = majorProp.availableValues.filter(p => p.label == role)
}
console.log(resource.editProperties, {majorProp});
const submit = (event: React.FormEvent<HTMLFormElement>): boolean => {
event.preventDefault();
handleSubmit().then((response) => {
if (response.data.redirectUrl) {
navigate(appendForceRefresh(response.data.redirectUrl));
}
});
return false;
};

const contentTag = getActionElementCss(resource.id, action.name, "drawer-content");
const formTag = getActionElementCss(resource.id, action.name, "form");
const footerTag = getActionElementCss(resource.id, action.name, "drawer-footer");
const buttonTag = getActionElementCss(resource.id, action.name, "drawer-submit");
return (
<Box as="form" onSubmit={submit} flex flexDirection="column" data-css={formTag}>
<DrawerContent data-css={contentTag}>
{resource.editProperties.map((property) => (
<BasePropertyComponent
key={property.propertyPath}
where="edit"
onChange={handleChange}
property={property}
resource={resource}
record={record as RecordJSON}
/>
))}
</DrawerContent>
<DrawerFooter data-css={footerTag}>
<Button variant="contained" type="submit" data-css={buttonTag} data-testid="button-save" disabled={loading}>
{loading ? <Icon icon="Loader" spin /> : null}
{translateButton("save", resource.id)}
</Button>
</DrawerFooter>
</Box>
);
};

export default Edit;
73 changes: 73 additions & 0 deletions adminPage/handlers/linktree.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { ActionHandler, Filter, SortSetter, flat, populator } from "adminjs";

const list: ActionHandler<any> = async (request, response, context) => {
const { query } = request; // 요청 url의 query 부분 추출
// console.log(query);

const { resource, _admin, currentAdmin } = context; // db table
const { role } = currentAdmin;
const unflattenQuery = flat.unflatten(
query || {}
);
let { page, perPage } = unflattenQuery;
// 진행중인 행사 탭에서는 시작일 내림차순 정렬
// 종료된 행사 탭에서는 종료일 내림차순 정렬
const {
sortBy = "order",
direction = "asc",
filters = { major: role },
} = unflattenQuery;

// adminOptions.settings.defaultPerPage, 한 페이지에 몇 행 보여줄 지
if (perPage) {
perPage = +perPage > 500 ? 500 : +perPage;
} else {
perPage = _admin.options.settings?.defaultPerPage ?? 10;
}
page = +page || 1;

// resource(DB table)에서 어떤 데이터를 가져올 지 filter 생성
const listProperties = resource.decorate().getListProperties();
const firstProperty = listProperties.find((p) => p.isSortable());
let sort;
if (firstProperty) {
sort = SortSetter(
{ sortBy, direction },
firstProperty.name(),
resource.decorate().options
);
}
const filter = await new Filter(
{ ...filters },
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 {
meta: {
total,
perPage,
page,
direction: sort?.direction,
sortBy: sort?.sortBy,
},
records: populatedRecords.map((r) => r.toJSON(currentAdmin)),
};
};

export const LinktreeHandler = {
list,
}
4 changes: 4 additions & 0 deletions adminPage/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import { ADMIN } from './resources/admin.js';
import { COMMON, TEST } from "./resources/common.js";
import { EVENT } from "./resources/event.js";
import { NOTICE } from "./resources/notice.js";
import Linktree from "../models/linktree.js";
import { LINKTREE } from "./resources/linktree.js";

const authenticate = async (payload, context) => {
const {email, role} = payload;
Expand Down Expand Up @@ -69,6 +71,7 @@ export const adminOptions: AdminJSOptions = {
labels: {
events: '행사',
notices: '공지',
linktrees: '링크트리',
navigation: '',
},
}
Expand All @@ -92,6 +95,7 @@ export const adminOptions: AdminJSOptions = {
// post
{ resource: Event, ...EVENT},
{ resource: Notice, ...NOTICE},
{ resource: Linktree, ...LINKTREE},
// others
{ resource: Admin, ...ADMIN},
{ resource: Read, ...COMMON},
Expand Down
6 changes: 5 additions & 1 deletion adminPage/resources/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@ export const postTab = {
icon: 'Edit',
// icon name list: https://feathericons.com/
};

export const linktreeTab = {
name: '링크트리 관리',
icon: 'Edit',
// icon name list: https://feathericons.com/
};
export const COMMON = {
/**
* @returns options: { navigation: false }
Expand Down
41 changes: 41 additions & 0 deletions adminPage/resources/linktree.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { ResourceOptions } from "adminjs";
import { linktreeTab } from "./common.js";
import { Components } from "../components/index.js";
import { LinktreeHandler } from "../handlers/linktree.js";

const linktreeOptions: ResourceOptions = {
navigation: linktreeTab,

listProperties: ["order", "text", "src"],
showProperties: ["major", "text", "src", "order"],
editProperties: ["major", "text", "src", "order"],
filterProperties: ["major", "text", "src", "order"],

properties: {
content: {
type: "richtext",
},
major_advisor: {
isRequired: true,
},
image: {
isArray: true,
},
},
actions: {
new: {
component: Components.linktree_edit,
},
edit: {
component: Components.linktree_edit,
},
list: {
handler: LinktreeHandler.list
}
}
};

export const LINKTREE = {
options: linktreeOptions,
};

2 changes: 1 addition & 1 deletion controllers/common_method/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,5 @@ import { redisClient } from "../../redis/connect.js";
export const redisGetAndParse = async (key: string) => {
const get = await redisClient.get(key);
if (get) return JSON.parse(get);
throw new Error(`${key}: 캐싱된 데이터 없음`);
throw new Error(`${key.split(':')[1]}: 캐싱된 데이터 없음`);
}
5 changes: 5 additions & 0 deletions controllers/linktree/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { getLinktrees } from "./linktree.js";

export {
getLinktrees
}
19 changes: 19 additions & 0 deletions controllers/linktree/linktree.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { NextFunction, Request, Response } from "express";
import { redisGetAndParse } from "../common_method/utils.js";

// GET /events
export const getLinktrees = async (
req: Request<any, any>,
res: Response,
next: NextFunction
) => {
try {
const major = req.params.major_advisor;
if (!major) throw new Error('올바르지 않은 학과')
const linktrees = await redisGetAndParse(`linktrees:${major}`);
return res.status(200).json(linktrees);
} catch (error) {
console.error(error);
res.status(500).json({ error: error.message });
}
}
6 changes: 2 additions & 4 deletions index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,8 @@ import * as url from "url";
import { adminOptions, authProvider } from "./adminPage/index.js";
import { sequelize } from "./models/index.js";
import { initializeRedis } from "./redis/initialize.js";
import alarmRouter from "./routes/alarm.js";
import eventRouter from "./routes/event.js";
import noticeRouter from "./routes/notice.js";
import userRouter from "./routes/user.js";
import { isRunOnDist, mode } from "./adminPage/components/index.js";
import { alarmRouter, eventRouter, linktreeRouter, noticeRouter, userRouter } from "./routes/index.js";

const port = 7070;
const corsOptions = {
Expand Down Expand Up @@ -66,6 +63,7 @@ const start = async () => {
app.use("/api", userRouter);
app.use("/api", eventRouter);
app.use("/api", alarmRouter);
app.use("/api", linktreeRouter);
await sequelize
.authenticate()
.then(async () => {
Expand Down
2 changes: 1 addition & 1 deletion models/admin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ Admin.init(
{
sequelize, // assuming you have a Sequelize instance named 'sequelize'
modelName: 'Admin',
tableName: 'admin',
tableName: 'admins',
timestamps: false,
}
);
Expand Down
53 changes: 53 additions & 0 deletions models/linktree.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { DataTypes, Model } from 'sequelize';
import { sequelize } from './sequelize.js';

interface LinktreeAttributes {
id: number;
src: string;
text: string;
major: '컴퓨터' | '소프트';
order: number;
}

class Linktree extends Model<LinktreeAttributes> implements LinktreeAttributes {
public id!: number;
public src!: string;
public text!: string;
public major!: '컴퓨터' | '소프트';
public order!: number;
}

Linktree.init(
{
id: {
type: DataTypes.INTEGER.UNSIGNED,
primaryKey: true,
autoIncrement: true,
allowNull: false,
},
src: {
type: DataTypes.STRING(200),
allowNull: false,
},
text: {
type: DataTypes.STRING(100),
allowNull: false,
},
major: {
type: DataTypes.ENUM('컴퓨터', '소프트'),
allowNull: false,
},
order: {
type: DataTypes.INTEGER({unsigned: true}),
allowNull: false
}
},
{
sequelize,
modelName: 'Linktree',
tableName: 'linktrees',
timestamps: false,
}
);

export default Linktree;
Loading

0 comments on commit f83f127

Please sign in to comment.