From 485a8450e95c2069ff680f51ca30f0b0c15e916e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=84=B1=ED=9B=88=20/=20Koder?= Date: Fri, 3 Jan 2025 17:01:48 +0900 Subject: [PATCH 1/5] feat : get notion api's raw data --- package-lock.json | 132 +++++++++++++++++++++++++++++++++++++++++++- package.json | 1 + pages/api/member.ts | 14 +++++ src/notion.ts | 14 +++++ 4 files changed, 158 insertions(+), 3 deletions(-) create mode 100644 pages/api/member.ts create mode 100644 src/notion.ts diff --git a/package-lock.json b/package-lock.json index 3303761..1fe0a92 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,7 @@ "@emotion/styled": "^11.13.5", "@mui/material": "^6.1.9", "@mui/material-nextjs": "^6.1.9", + "@notionhq/client": "^2.2.15", "next": "^15.1.3", "react": "^18", "react-dom": "^18" @@ -1320,6 +1321,19 @@ "node": ">= 8" } }, + "node_modules/@notionhq/client": { + "version": "2.2.15", + "resolved": "https://registry.npmjs.org/@notionhq/client/-/client-2.2.15.tgz", + "integrity": "sha512-XhdSY/4B1D34tSco/GION+23GMjaS9S2zszcqYkMHo8RcWInymF6L1x+Gk7EmHdrSxNFva2WM8orhC4BwQCwgw==", + "license": "MIT", + "dependencies": { + "@types/node-fetch": "^2.5.10", + "node-fetch": "^2.6.1" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", @@ -1371,11 +1385,20 @@ "version": "20.11.18", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.18.tgz", "integrity": "sha512-ABT5VWnnYneSBcNWYSCuR05M826RoMyMSGiFivXGx6ZUIsXb9vn4643IEwkg2zbEOSgAiSogtapN2fgc4mAPlw==", - "dev": true, "dependencies": { "undici-types": "~5.26.4" } }, + "node_modules/@types/node-fetch": { + "version": "2.6.12", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.12.tgz", + "integrity": "sha512-8nneRWKCg3rMtF69nLQJnOYUcbafYeFSjqkw3jCRLsqkWFlHaoQrr5mXmofFGOx3DKn7UfmBMyov8ySvLRVldA==", + "license": "MIT", + "dependencies": { + "@types/node": "*", + "form-data": "^4.0.0" + } + }, "node_modules/@types/parse-json": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", @@ -1826,6 +1849,12 @@ "has-symbols": "^1.0.3" } }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, "node_modules/autoprefixer": { "version": "10.4.17", "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.17.tgz", @@ -2161,6 +2190,18 @@ "simple-swizzle": "^0.2.2" } }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/commander": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", @@ -2303,6 +2344,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/dequal": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", @@ -3141,6 +3191,20 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/form-data": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz", + "integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/fraction.js": { "version": "4.3.7", "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", @@ -4139,6 +4203,27 @@ "node": ">=8.6" } }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -4290,6 +4375,26 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, "node_modules/node-releases": { "version": "2.0.14", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", @@ -5607,6 +5712,12 @@ "node": ">=8.0" } }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" + }, "node_modules/ts-api-utils": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.2.1.tgz", @@ -5764,8 +5875,7 @@ "node_modules/undici-types": { "version": "5.26.5", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", - "dev": true + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" }, "node_modules/update-browserslist-db": { "version": "1.0.13", @@ -5813,6 +5923,22 @@ "dev": true, "license": "MIT" }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", diff --git a/package.json b/package.json index 19753a1..2f17452 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "@emotion/styled": "^11.13.5", "@mui/material": "^6.1.9", "@mui/material-nextjs": "^6.1.9", + "@notionhq/client": "^2.2.15", "next": "^15.1.3", "react": "^18", "react-dom": "^18" diff --git a/pages/api/member.ts b/pages/api/member.ts new file mode 100644 index 0000000..46b5ba1 --- /dev/null +++ b/pages/api/member.ts @@ -0,0 +1,14 @@ +import { QueryMember } from "@/src/notion"; +import type { NextApiRequest, NextApiResponse } from "next"; + +type ResponseData = { + message: JSON; +}; + +export default async function handler( + req: NextApiRequest, + res: NextApiResponse +) { + const val = await QueryMember(); + res.status(200).json({ message: val }); +} diff --git a/src/notion.ts b/src/notion.ts new file mode 100644 index 0000000..0b07f7f --- /dev/null +++ b/src/notion.ts @@ -0,0 +1,14 @@ +const { Client } = require("@notionhq/client"); + +const notion = new Client({ auth: process.env.NOTION }); + +async function QueryMember(): Promise { + const databaseId = process.env.MEMBERDB; + const response = await notion.databases.query({ + database_id: databaseId, + }); + console.log(response); + return response; +} + +export { QueryMember }; From 587f92aabcde5e594e033e0d34c7a63cda1500e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=84=B1=ED=9B=88=20/=20Koder?= Date: Fri, 3 Jan 2025 18:06:14 +0900 Subject: [PATCH 2/5] feat: raw data processed --- pages/api/member.ts | 10 ++++++---- src/notion.ts | 29 ++++++++++++++++++++++++++--- src/object.ts | 44 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 76 insertions(+), 7 deletions(-) create mode 100644 src/object.ts diff --git a/pages/api/member.ts b/pages/api/member.ts index 46b5ba1..3d431fd 100644 --- a/pages/api/member.ts +++ b/pages/api/member.ts @@ -1,14 +1,16 @@ -import { QueryMember } from "@/src/notion"; +import { Member } from "@/src/object"; import type { NextApiRequest, NextApiResponse } from "next"; type ResponseData = { - message: JSON; + response: any; }; +const member = new Member(); + export default async function handler( req: NextApiRequest, res: NextApiResponse ) { - const val = await QueryMember(); - res.status(200).json({ message: val }); + const val = await member.Get(); + res.status(200).json({ response: val }); } diff --git a/src/notion.ts b/src/notion.ts index 0b07f7f..9eadbce 100644 --- a/src/notion.ts +++ b/src/notion.ts @@ -2,13 +2,36 @@ const { Client } = require("@notionhq/client"); const notion = new Client({ auth: process.env.NOTION }); -async function QueryMember(): Promise { +function parseProp(prop: any): any { + if (prop["type"] == "email") return prop["email"]; + try { + switch (prop["type"]) { + case "title": + return prop["title"][0]["plain_text"]; + case "email": + return prop["email"]; + case "number": + return prop["number"]; + case "rich_text": + return prop["rich_text"][0]["plain_text"]; + case "phone_number": + return prop["phone_number"]; + case "checkbox": + return prop["checkbox"]; + default: + return null; + } + } catch (e) { + return null; + } +} + +async function QueryMember(): Promise { const databaseId = process.env.MEMBERDB; const response = await notion.databases.query({ database_id: databaseId, }); - console.log(response); return response; } -export { QueryMember }; +export { QueryMember, parseProp }; diff --git a/src/object.ts b/src/object.ts new file mode 100644 index 0000000..64ff5d1 --- /dev/null +++ b/src/object.ts @@ -0,0 +1,44 @@ +import { parseProp, QueryMember } from "./notion"; + +type MemberData = { + name: String; + active: boolean; + num: number; + department: String; + phone: String; + boj: String; + email: String; +}; + +class Member { + data: Array = []; + + constructor() { + this.Update(); + } + + Update = async () => { + const query = await QueryMember(); + + query["results"].forEach((elem: any) => { + const data = elem["properties"]; + const ret: MemberData = { + name: parseProp(data["이름"]), + active: parseProp(data["Active"]), + num: parseProp(data["학번"]), + department: parseProp(data["학과"]), + phone: parseProp(data["전화번호"]), + boj: parseProp(data["백준 핸들"]), + email: parseProp(data["이메일"]), + }; + this.data.push(ret); + }); + }; + + Get = async () => { + await this.Update(); + return this.data; + }; +} + +export { Member }; From 8811b4945d09ccf7209a9950db9cbae952d2419d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=84=B1=ED=9B=88=20/=20Koder?= Date: Fri, 3 Jan 2025 18:58:35 +0900 Subject: [PATCH 3/5] feat : masking sensitive information --- pages/api/member.ts | 7 ++--- src/object.ts | 67 +++++++++++++++++++++++++++------------------ 2 files changed, 43 insertions(+), 31 deletions(-) diff --git a/pages/api/member.ts b/pages/api/member.ts index 3d431fd..9d19e96 100644 --- a/pages/api/member.ts +++ b/pages/api/member.ts @@ -1,16 +1,15 @@ -import { Member } from "@/src/object"; +import { MemberList } from "@/src/object"; import type { NextApiRequest, NextApiResponse } from "next"; type ResponseData = { response: any; }; -const member = new Member(); +const member = new MemberList(); export default async function handler( req: NextApiRequest, res: NextApiResponse ) { - const val = await member.Get(); - res.status(200).json({ response: val }); + res.status(200).json({ response: await member.Get() }); } diff --git a/src/object.ts b/src/object.ts index 64ff5d1..77c42cd 100644 --- a/src/object.ts +++ b/src/object.ts @@ -1,44 +1,57 @@ import { parseProp, QueryMember } from "./notion"; -type MemberData = { - name: String; - active: boolean; - num: number; - department: String; - phone: String; - boj: String; - email: String; -}; +let MemberArray: Array = []; class Member { - data: Array = []; + name: String = ""; + active: boolean = true; + num: number = 0; + department: String = ""; + phone: String = ""; + boj: String = ""; + email: String = ""; + constructor(prop: any) { + this.name = parseProp(prop["이름"]); + this.active = parseProp(prop["Active"]); + this.num = parseProp(prop["학번"]); + this.department = parseProp(prop["학과"]); + this.phone = parseProp(prop["전화번호"]); + this.boj = parseProp(prop["백준 핸들"]); + this.email = parseProp(prop["이메일"]); + + return this; + } + + Get() { + return { + name: this.name, + department: this.department, + boj: this.boj, + email: this.email, + }; + } +} + +class MemberList { constructor() { this.Update(); } Update = async () => { - const query = await QueryMember(); - - query["results"].forEach((elem: any) => { - const data = elem["properties"]; - const ret: MemberData = { - name: parseProp(data["이름"]), - active: parseProp(data["Active"]), - num: parseProp(data["학번"]), - department: parseProp(data["학과"]), - phone: parseProp(data["전화번호"]), - boj: parseProp(data["백준 핸들"]), - email: parseProp(data["이메일"]), - }; - this.data.push(ret); - }); + // caching logic if expression. TBD + if (MemberArray.length == 0) { + const query = await QueryMember(); + MemberArray = query["results"].map( + (elem: any) => new Member(elem["properties"]) + ); + } }; Get = async () => { await this.Update(); - return this.data; + return MemberArray.map((elem) => elem.Get()); }; } -export { Member }; +export { Member, MemberList }; From 3c2487662ffa99b459d0862d1b98ca9bfbb60ff0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=84=B1=ED=9B=88=20/=20Koder?= Date: Fri, 3 Jan 2025 19:09:14 +0900 Subject: [PATCH 4/5] feat : implemented achieve page Since there is no databases having achievements information, I just make a test database. --- pages/api/achieve.ts | 15 ++++++++++++++ src/notion.ts | 12 ++++++----- src/object.ts | 47 +++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 66 insertions(+), 8 deletions(-) create mode 100644 pages/api/achieve.ts diff --git a/pages/api/achieve.ts b/pages/api/achieve.ts new file mode 100644 index 0000000..343103e --- /dev/null +++ b/pages/api/achieve.ts @@ -0,0 +1,15 @@ +import { AchieveList } from "@/src/object"; +import type { NextApiRequest, NextApiResponse } from "next"; + +type ResponseData = { + response: any; +}; + +const achieve = new AchieveList(); + +export default async function handler( + req: NextApiRequest, + res: NextApiResponse +) { + res.status(200).json({ response: await achieve.Get() }); +} diff --git a/src/notion.ts b/src/notion.ts index 9eadbce..2453f36 100644 --- a/src/notion.ts +++ b/src/notion.ts @@ -26,12 +26,14 @@ function parseProp(prop: any): any { } } -async function QueryMember(): Promise { - const databaseId = process.env.MEMBERDB; - const response = await notion.databases.query({ +async function Query(type: "Member" | "Achievements"): Promise { + let databaseId; + if (type == "Member") databaseId = process.env.MEMBERDB; + else databaseId = process.env.ACHIEVEDB; + + return await notion.databases.query({ database_id: databaseId, }); - return response; } -export { QueryMember, parseProp }; +export { Query, parseProp }; diff --git a/src/object.ts b/src/object.ts index 77c42cd..d9c8ccc 100644 --- a/src/object.ts +++ b/src/object.ts @@ -1,6 +1,7 @@ -import { parseProp, QueryMember } from "./notion"; +import { parseProp, Query } from "./notion"; let MemberArray: Array = []; +let AchieveArray: Array = []; class Member { name: String = ""; @@ -33,6 +34,25 @@ class Member { } } +class Achieve { + contest: String = ""; + team: String = ""; + member: String = ""; + comments: String = ""; + + constructor(prop: any) { + this.contest = parseProp(prop["대회명"]); + this.team = parseProp(prop["팀명"]); + this.member = parseProp(prop["팀 멤버"]); + this.comments = parseProp(prop["비고"]); + return this; + } + + Get() { + return this; + } +} + class MemberList { constructor() { this.Update(); @@ -41,7 +61,7 @@ class MemberList { Update = async () => { // caching logic if expression. TBD if (MemberArray.length == 0) { - const query = await QueryMember(); + const query = await Query("Member"); MemberArray = query["results"].map( (elem: any) => new Member(elem["properties"]) ); @@ -54,4 +74,25 @@ class MemberList { }; } -export { Member, MemberList }; +class AchieveList { + constructor() { + this.Update(); + } + + Update = async () => { + // caching logic if expression. TBD + if (AchieveArray.length == 0) { + const query = await Query("Achievements"); + AchieveArray = query["results"].map( + (elem: any) => new Achieve(elem["properties"]) + ); + } + }; + + Get = async () => { + await this.Update(); + return AchieveArray.map((elem) => elem.Get()); + }; +} + +export { MemberList, AchieveList }; From 91ca56d5073778019f15b672edd87a2ef599d749 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=84=B1=ED=9B=88=20/=20Koder?= Date: Fri, 3 Jan 2025 19:14:50 +0900 Subject: [PATCH 5/5] refactor : switch-case is suck --- src/notion.ts | 26 ++++++++------------------ 1 file changed, 8 insertions(+), 18 deletions(-) diff --git a/src/notion.ts b/src/notion.ts index 2453f36..47ec273 100644 --- a/src/notion.ts +++ b/src/notion.ts @@ -3,33 +3,23 @@ const { Client } = require("@notionhq/client"); const notion = new Client({ auth: process.env.NOTION }); function parseProp(prop: any): any { - if (prop["type"] == "email") return prop["email"]; try { - switch (prop["type"]) { - case "title": - return prop["title"][0]["plain_text"]; - case "email": - return prop["email"]; - case "number": - return prop["number"]; - case "rich_text": - return prop["rich_text"][0]["plain_text"]; - case "phone_number": - return prop["phone_number"]; - case "checkbox": - return prop["checkbox"]; - default: - return null; - } + if (prop["type"] == "title") return prop["title"][0]["plain_text"]; + if (prop["type"] == "email") return prop["email"]; + if (prop["type"] == "number") return prop["number"]; + if (prop["type"] == "rich_text") return prop["rich_text"][0]["plain_text"]; + if (prop["type"] == "phone_number") return prop["phone_number"]; + if (prop["type"] == "checkbox") return prop["checkbox"]; } catch (e) { return null; } + return null; } async function Query(type: "Member" | "Achievements"): Promise { let databaseId; if (type == "Member") databaseId = process.env.MEMBERDB; - else databaseId = process.env.ACHIEVEDB; + if (type == "Achievements") databaseId = process.env.ACHIEVEDB; return await notion.databases.query({ database_id: databaseId,