commit 187318094858d06a5f262fafdfeeed6cf5a29de9 Author: seung6lee Date: Wed Mar 4 21:39:44 2026 +0900 class 3-5 initial diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9b1ee42 --- /dev/null +++ b/.gitignore @@ -0,0 +1,175 @@ +# Based on https://raw.githubusercontent.com/github/gitignore/main/Node.gitignore + +# Logs + +logs +_.log +npm-debug.log_ +yarn-debug.log* +yarn-error.log* +lerna-debug.log* +.pnpm-debug.log* + +# Caches + +.cache + +# Diagnostic reports (https://nodejs.org/api/report.html) + +report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json + +# Runtime data + +pids +_.pid +_.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover + +lib-cov + +# Coverage directory used by tools like istanbul + +coverage +*.lcov + +# nyc test coverage + +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) + +.grunt + +# Bower dependency directory (https://bower.io/) + +bower_components + +# node-waf configuration + +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) + +build/Release + +# Dependency directories + +node_modules/ +jspm_packages/ + +# Snowpack dependency directory (https://snowpack.dev/) + +web_modules/ + +# TypeScript cache + +*.tsbuildinfo + +# Optional npm cache directory + +.npm + +# Optional eslint cache + +.eslintcache + +# Optional stylelint cache + +.stylelintcache + +# Microbundle cache + +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history + +.node_repl_history + +# Output of 'npm pack' + +*.tgz + +# Yarn Integrity file + +.yarn-integrity + +# dotenv environment variable files + +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# parcel-bundler cache (https://parceljs.org/) + +.parcel-cache + +# Next.js build output + +.next +out + +# Nuxt.js build / generate output + +.nuxt +dist + +# Gatsby files + +# Comment in the public line in if your project uses Gatsby and not Next.js + +# https://nextjs.org/blog/next-9-1#public-directory-support + +# public + +# vuepress build output + +.vuepress/dist + +# vuepress v2.x temp and cache directory + +.temp + +# Docusaurus cache and generated files + +.docusaurus + +# Serverless directories + +.serverless/ + +# FuseBox cache + +.fusebox/ + +# DynamoDB Local files + +.dynamodb/ + +# TernJS port file + +.tern-port + +# Stores VSCode versions used for testing VSCode extensions + +.vscode-test + +# yarn v2 + +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.yarn/install-state.gz +.pnp.* + +# IntelliJ based IDEs +.idea + +# Finder (MacOS) folder config +.DS_Store diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..5a938ce --- /dev/null +++ b/.prettierrc @@ -0,0 +1,4 @@ +{ + "tabWidth": 4, + "useTabs": false +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..97e0789 --- /dev/null +++ b/README.md @@ -0,0 +1,15 @@ +# twofive + +To install dependencies: + +```bash +bun install +``` + +To run: + +```bash +bun run server.js +``` + +This project was created using `bun init` in bun v1.0.26. [Bun](https://bun.sh) is a fast all-in-one JavaScript runtime. diff --git a/bun.lockb b/bun.lockb new file mode 100755 index 0000000..80f2999 Binary files /dev/null and b/bun.lockb differ diff --git a/client_secret.json b/client_secret.json new file mode 100644 index 0000000..6041ae4 --- /dev/null +++ b/client_secret.json @@ -0,0 +1,12 @@ +{ + "web": { + "client_id": "769538006975-q773s454k2l1r4q35vat4psbjfgg59pk.apps.googleusercontent.com", + "project_id": "g-cal-task-management25", + "auth_uri": "https://accounts.google.com/o/oauth2/auth", + "token_uri": "https://oauth2.googleapis.com/token", + "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs", + "client_secret": "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", + "redirect_uris": ["http://localhost:4000/callback"], + "javascript_origins": ["http://localhost"] + } +} diff --git a/jsconfig.json b/jsconfig.json new file mode 100644 index 0000000..95c20ce --- /dev/null +++ b/jsconfig.json @@ -0,0 +1,22 @@ +{ + "compilerOptions": { + "lib": ["ESNext", "DOM"], + "target": "ESNext", + "module": "ESNext", + "moduleDetection": "force", + "jsx": "react-jsx", + "allowJs": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "noEmit": true, + + /* Linting */ + "skipLibCheck": true, + "strict": true, + "noFallthroughCasesInSwitch": true, + "forceConsistentCasingInFileNames": true + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..1834383 --- /dev/null +++ b/package.json @@ -0,0 +1,19 @@ +{ + "name": "twofive", + "module": "server.js", + "type": "module", + "devDependencies": { + "@types/bun": "latest", + "sass": "^1.85.1" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "dependencies": { + "express": "^4.21.2", + "open": "^10.1.0" + }, + "scripts": { + "sass-watch": "sass --watch ./public/style.scss ./public/style.css" + } +} \ No newline at end of file diff --git a/public/images/calendar.png b/public/images/calendar.png new file mode 100644 index 0000000..9fe0700 Binary files /dev/null and b/public/images/calendar.png differ diff --git a/public/index.html b/public/index.html new file mode 100644 index 0000000..e26ecad --- /dev/null +++ b/public/index.html @@ -0,0 +1,35 @@ + + + + + + Class 3-5 + + + + + + + +
+
+ +
+ +
+ + diff --git a/public/script.js b/public/script.js new file mode 100644 index 0000000..4653dfc --- /dev/null +++ b/public/script.js @@ -0,0 +1,83 @@ +class meal_list extends HTMLElement { + constructor() { + super(); + + const now = new Date(); + const hours = now.getHours(); + + this.date = new Date() + .toISOString() + .slice(0, 10) + .replace(/-/g, ""); + + switch (true) { + case hours < 8: + this.selectedType = 1; + break; + case hours < 14: + this.selectedType = 2; + break; + default: + this.selectedType = 3; + } + } + + connectedCallback() { + this.render(); + } + + async get_meal(date, type) { + try { + console.log(`/api/meal?date=${date}&type=${type}`) + let response = await fetch(`/api/meal?date=${date}&type=${type}`); + + if (!response.ok) throw new Error("Failed to fetch meal data"); + + let data = await response.json(); + + return data; + } catch (error) { + console.error("❌ API Error:", error); + return ["식단 정보를 불러올 수 없습니다."]; + } + } + + async render() { + const header = document.createElement("header"); + + const select = document.createElement("select"); + const types = ["아침", "점심", "저녁"]; + types.forEach((type, index) => { + const option = document.createElement("option"); + option.textContent = types[index]; + option.value = index + 1; + if (index + 1 === this.selectedType) option.selected = true; + select.appendChild(option); + }); + select.addEventListener("change", async () => { + this.selectedType = Number(select.value); + await this.render(); + }); + + const res = await this.get_meal(this.date, this.selectedType); + + const ul = document.createElement("ul"); + res.meal_list.forEach((meal) => { + const li = document.createElement("li"); + li.textContent = meal; + ul.appendChild(li); + }); + + const kcal = document.createElement("p"); + kcal.textContent = res.kcal; + + header.appendChild(select); + header.appendChild(kcal); + + this.innerHTML = ""; + this.append(header); + this.appendChild(ul); + } +} + +customElements.define("meal-list", meal_list); \ No newline at end of file diff --git a/public/style.css b/public/style.css new file mode 100644 index 0000000..aa459c0 --- /dev/null +++ b/public/style.css @@ -0,0 +1,270 @@ +@font-face { + font-family: "GMarketSans"; + src: url("https://cdn.jsdelivr.net/gh/projectnoonnu/noonfonts_2001@1.1/GmarketSansMedium.woff") format("woff"); + font-weight: 500; + font-display: swap; +} +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + width: 100%; + height: 100vh; + font-family: "GMarketSans", serif; + display: flex; + flex-direction: column; + gap: 10px; + background-color: #F0F0F0; + background-image: linear-gradient(to right, rgba(0, 0, 0, 0.06) 1px, transparent 1px), linear-gradient(to bottom, rgba(0, 0, 0, 0.06) 1px, transparent 1px); + background-size: 28px 28px; + color: #0A0A0A; +} + +.box { + border-radius: 0; + position: relative; +} + +.title { + height: 32px; + width: 100%; + border-bottom: 1px solid #C8C8C8; + margin-bottom: 12px; + font-size: 12px; + font-weight: 500; + letter-spacing: 0.12em; + text-transform: uppercase; + color: #888888; + display: flex; + align-items: center; +} +.title::before { + content: ""; + display: inline-block; + width: 3px; + height: 14px; + background-color: #FF4B00; + margin-right: 8px; + flex-shrink: 0; +} + +#header { + width: 100%; + height: 60px; + background-color: #FFFFFF; + border-bottom: 1px solid #C8C8C8; + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; + padding: 0 24px; + position: relative; +} +#header::after { + content: ""; + position: absolute; + bottom: -1px; + left: 0; + width: 100%; + height: 1px; + background: repeating-linear-gradient(to right, #FF4B00 0px, #FF4B00 4px, transparent 4px, transparent 12px); +} +#header h1 { + font-size: 16px; + font-weight: 500; + letter-spacing: 0.08em; + text-transform: uppercase; + color: #0A0A0A; +} +#header h1 span { + color: #FF4B00; +} +#header ul { + display: flex; + flex-direction: row; +} +#header ul li { + display: inline-block; + padding: 0 1.5rem; + font-size: 13px; + font-weight: 500; + letter-spacing: 0.06em; +} +#header ul li a { + text-decoration: none; + color: #4A4A4A; + text-transform: uppercase; + transition: color 0.15s ease; + position: relative; + padding-bottom: 4px; +} +#header ul li a::after { + content: ""; + position: absolute; + bottom: 0; + left: 0; + width: 0; + height: 1px; + background-color: #FF4B00; + transition: width 0.2s ease; +} +#header ul li a:hover { + color: #0A0A0A; +} +#header ul li a:hover::after { + width: 100%; +} +#header ul li a.active { + color: #FF4B00; +} +#header ul li a.active::after { + width: 100%; +} + +#main { + width: 100%; + height: calc(100% - 60px - 10px); + padding: 10px; + display: flex; + flex-direction: row; + gap: 25px; +} + +#side { + width: 400px; + height: 100%; + display: flex; + flex-direction: column; + gap: 25px; +} + +#info { + width: 100%; + height: 100%; + background-color: #FFFFFF; + border: 1px solid #C8C8C8; + padding: 10px; + background-image: linear-gradient(to right, rgba(0, 0, 0, 0.03) 1px, transparent 1px), linear-gradient(to bottom, rgba(0, 0, 0, 0.03) 1px, transparent 1px); + background-size: 20px 20px; +} +#info meal-list { + width: 100%; + height: 100%; + display: flex; + flex-direction: column; + gap: 10px; + overflow-y: hidden; +} +#info meal-list header { + width: 100%; + height: 40px; + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; +} +#info meal-list header select { + border: 1px solid #C8C8C8; + border-radius: 0; + padding: 4px 8px; + font-size: 12px; + font-family: "GMarketSans", serif; + letter-spacing: 0.04em; + background-color: #FFFFFF; + color: #0A0A0A; + cursor: pointer; + transition: border-color 0.15s, box-shadow 0.15s; +} +#info meal-list header select:hover { + border-color: #0A0A0A; + box-shadow: none; +} +#info meal-list header select:focus { + outline: none; + border-color: #FF4B00; +} +#info meal-list ul { + width: 100%; + display: flex; + flex-direction: column; + align-items: center; + gap: 10px; + overflow-y: auto; + overflow-x: hidden; + cursor: ns-resize; +} +#info meal-list ul::-webkit-scrollbar { + display: none; +} +#info meal-list ul li { + width: 100%; + height: 56px; + border-radius: 0; + border: 1px solid #C8C8C8; + border-left: 3px solid transparent; + display: flex; + flex-direction: row; + align-items: center; + padding-left: 10px; + flex-shrink: 0; + background-color: #FFFFFF; + font-size: 13px; + color: #4A4A4A; + transition: border-left-color 0.15s, background-color 0.15s, color 0.15s; +} +#info meal-list ul li:hover { + border-left-color: #FF4B00; + background-color: rgba(255, 75, 0, 0.08); + color: #0A0A0A; +} +#info meal-list ul li.active { + border-left-color: #FF4B00; + background-color: rgba(255, 75, 0, 0.08); + color: #0A0A0A; + font-weight: 500; +} + +#calendar { + border: 1px solid #C8C8C8; + border-top: 3px solid #FF4B00; + width: calc(100% - 400px - 25px); + height: 100%; + background-color: #FFFFFF; + position: relative; +} +#calendar iframe { + border-radius: 0; + width: 100%; + height: 100%; + border: none; +} + +@media (max-width: 800px) { + h1 { + font-size: 16px; + } + #main { + width: 100%; + flex-direction: column; + } + #side { + width: 100%; + height: auto; + } + #task { + height: auto; + } + #info { + height: auto; + } + #calendar { + width: 100%; + height: 400px; + flex-shrink: 0; + } +} + +/*# sourceMappingURL=style.css.map */ diff --git a/public/style.css.map b/public/style.css.map new file mode 100644 index 0000000..cd9a8e4 --- /dev/null +++ b/public/style.css.map @@ -0,0 +1 @@ +{"version":3,"sourceRoot":"","sources":["style.scss"],"names":[],"mappings":"AAAA;EACI;EACA;EACA;EACA;;AA+BJ;EACI;EACA;EACA;;;AAIJ;EACI;EACA;EACA;EACA;EACA;EACA,KA3BY;EA4BZ,kBAjCK;EAYL,kBACI;EAEJ;EAoBA,OAxCK;;;AA4CT;EACI;EACA;;;AAIJ;EACI;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,OAxDK;EAyDL;EACA;;AAGA;EACI;EACA;EACA;EACA;EACA,kBAxEO;EAyEP;EACA;;;AAKR;EACI;EACA,QAjEY;EAkEZ,kBAzEK;EA0EL;EACA;EACA;EACA;EACA;EACA;EACA;;AAGA;EACI;EACA;EACA;EACA;EACA;EACA;EACA;;AAOJ;EACI;EACA;EACA;EACA;EACA,OA5GC;;AA+GD;EACI,OAnHG;;AAuHX;EACI;EACA;;AAEA;EACI;EACA;EACA;EACA;EACA;;AAEA;EACI;EACA,OA/HP;EAgIO;EACA;EACA;EACA;;AAEA;EACI;EACA;EACA;EACA;EACA;EACA;EACA,kBAjJL;EAkJK;;AAGJ;EACI,OAnJX;;AAqJW;EACI;;AAIR;EACI,OA9JL;;AAgKK;EACI;;;AASxB;EACI;EACA;EACA,SAhKY;EAiKZ;EACA;EACA,KAlKY;;;AAsKhB;EACI,OAtKY;EAuKZ;EACA;EACA;EACA,KA3KY;;;AA+KhB;EACI;EACA;EACA,kBAvLK;EAwLL;EACA,SArLY;EAOZ,kBACI;EAEJ;;AAgLA;EACI;EACA;EACA;EACA;EACA,KA/LQ;EAgMR;;AAEA;EACI;EACA;EACA;EACA;EACA;EACA;;AAEA;EACI;EACA;EACA;EACA;EACA;EACA;EACA,kBArNP;EAsNO,OA5NP;EA6NO;EACA;;AAEA;EACI,cAjOX;EAkOW;;AAGJ;EACI;EACA,cA1OL;;AA+OP;EACI;EACA;EACA;EACA;EACA,KAvOI;EAwOJ;EACA;EACA;;AAEA;EACI;;AAGJ;EACI;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,cAzPA;EA0PA;EACA,kBA/PP;EAgQO;EACA,OArQP;EAsQO;;AAEA;EACI,mBA9QL;EA+QK,kBA9QL;EA+QK,OA7QX;;AAgRO;EACI,mBApRL;EAqRK,kBApRL;EAqRK,OAnRX;EAoRW;;;AAQpB;EACI;EACA;EACA;EACA;EACA,kBA3RK;EA4RL;;AAEA;EACI;EACA;EACA;EACA;;;AAKR;EACI;IACI;;EAGJ;IACI;IACA;;EAGJ;IACI;IACA;;EAGJ;IACI;;EAGJ;IACI;;EAGJ;IACI;IACA;IACA","file":"style.css"} \ No newline at end of file diff --git a/public/style.scss b/public/style.scss new file mode 100644 index 0000000..b6adcf0 --- /dev/null +++ b/public/style.scss @@ -0,0 +1,341 @@ +@font-face { + font-family: 'GMarketSans'; + src: url('https://cdn.jsdelivr.net/gh/projectnoonnu/noonfonts_2001@1.1/GmarketSansMedium.woff') format('woff'); + font-weight: 500; + font-display: swap; +} + +// ─── Design Tokens ──────────────────────────────────────────────────────────── +$accent: #FF4B00; +$accent-light: rgba(255, 75, 0, 0.08); + +$black: #0A0A0A; +$gray-9: #1A1A1A; +$gray-7: #4A4A4A; +$gray-5: #888888; +$gray-3: #C8C8C8; +$gray-1: #F0F0F0; +$white: #FFFFFF; + +$grid-line: rgba(0, 0, 0, 0.08); + +$small_margin: 10px; +$big_margin: 25px; +$nav_width: 400px; +$header_height: 60px; + +// ─── Grid Background Mixin ──────────────────────────────────────────────────── +@mixin grid-bg($size: 28px, $color: $grid-line) { + background-image: + linear-gradient(to right, $color 1px, transparent 1px), + linear-gradient(to bottom, $color 1px, transparent 1px); + background-size: $size $size; +} + +// ─── Reset ──────────────────────────────────────────────────────────────────── +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +// ─── Body ───────────────────────────────────────────────────────────────────── +body { + width: 100%; + height: 100vh; + font-family: "GMarketSans", serif; + display: flex; + flex-direction: column; + gap: $small_margin; + background-color: $gray-1; + @include grid-bg(28px, rgba(0,0,0,0.06)); + color: $black; +} + +// ─── Box ────────────────────────────────────────────────────────────────────── +.box { + border-radius: 0; + position: relative; +} + +// ─── Title ──────────────────────────────────────────────────────────────────── +.title { + height: 32px; + width: 100%; + border-bottom: 1px solid $gray-3; + margin-bottom: 12px; + font-size: 12px; + font-weight: 500; + letter-spacing: 0.12em; + text-transform: uppercase; + color: $gray-5; + display: flex; + align-items: center; + + // 포인트 컬러 왼쪽 태그 + &::before { + content: ''; + display: inline-block; + width: 3px; + height: 14px; + background-color: $accent; + margin-right: 8px; + flex-shrink: 0; + } +} + +// ─── Header ─────────────────────────────────────────────────────────────────── +#header { + width: 100%; + height: $header_height; + background-color: $white; + border-bottom: 1px solid $gray-3; + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; + padding: 0 24px; + position: relative; + + // 헤더 하단 그리드 강조선 + &::after { + content: ''; + position: absolute; + bottom: -1px; + left: 0; + width: 100%; + height: 1px; + background: repeating-linear-gradient( + to right, + $accent 0px, $accent 4px, + transparent 4px, transparent 12px + ); + } + + h1 { + font-size: 16px; + font-weight: 500; + letter-spacing: 0.08em; + text-transform: uppercase; + color: $black; + + // 포인트 컬러 강조 + span { + color: $accent; + } + } + + ul { + display: flex; + flex-direction: row; + + li { + display: inline-block; + padding: 0 1.5rem; + font-size: 13px; + font-weight: 500; + letter-spacing: 0.06em; + + a { + text-decoration: none; + color: $gray-7; + text-transform: uppercase; + transition: color 0.15s ease; + position: relative; + padding-bottom: 4px; + + &::after { + content: ''; + position: absolute; + bottom: 0; + left: 0; + width: 0; + height: 1px; + background-color: $accent; + transition: width 0.2s ease; + } + + &:hover { + color: $black; + + &::after { + width: 100%; + } + } + + &.active { + color: $accent; + + &::after { + width: 100%; + } + } + } + } + } +} + +// ─── Main ───────────────────────────────────────────────────────────────────── +#main { + width: 100%; + height: calc(100% - #{$header_height} - #{$small_margin}); + padding: $small_margin; + display: flex; + flex-direction: row; + gap: $big_margin; +} + +// ─── Side ───────────────────────────────────────────────────────────────────── +#side { + width: $nav_width; + height: 100%; + display: flex; + flex-direction: column; + gap: $big_margin; +} + +// ─── Info ───────────────────────────────────────────────────────────────────── +#info { + width: 100%; + height: 100%; + background-color: $white; + border: 1px solid $gray-3; + padding: $small_margin; + + // 내부 그리드 배경 + @include grid-bg(20px, rgba(0,0,0,0.03)); + + meal-list { + width: 100%; + height: 100%; + display: flex; + flex-direction: column; + gap: $small_margin; + overflow-y: hidden; + + header { + width: 100%; + height: 40px; + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; + + select { + border: 1px solid $gray-3; + border-radius: 0; + padding: 4px 8px; + font-size: 12px; + font-family: "GMarketSans", serif; + letter-spacing: 0.04em; + background-color: $white; + color: $black; + cursor: pointer; + transition: border-color 0.15s, box-shadow 0.15s; + + &:hover { + border-color: $black; + box-shadow: none; + } + + &:focus { + outline: none; + border-color: $accent; + } + } + } + + ul { + width: 100%; + display: flex; + flex-direction: column; + align-items: center; + gap: $small_margin; + overflow-y: auto; + overflow-x: hidden; + cursor: ns-resize; + + &::-webkit-scrollbar { + display: none; + } + + li { + width: 100%; + height: 56px; + border-radius: 0; + border: 1px solid $gray-3; + border-left: 3px solid transparent; // 기본 상태 + display: flex; + flex-direction: row; + align-items: center; + padding-left: $small_margin; + flex-shrink: 0; + background-color: $white; + font-size: 13px; + color: $gray-7; + transition: border-left-color 0.15s, background-color 0.15s, color 0.15s; + + &:hover { + border-left-color: $accent; + background-color: $accent-light; + color: $black; + } + + &.active { + border-left-color: $accent; + background-color: $accent-light; + color: $black; + font-weight: 500; + } + } + } + } +} + +// ─── Calendar ───────────────────────────────────────────────────────────────── +#calendar { + border: 1px solid $gray-3; + border-top: 3px solid $accent; // 포인트 컬러 상단 강조선 + width: calc(100% - #{$nav_width} - #{$big_margin}); + height: 100%; + background-color: $white; + position: relative; + + iframe { + border-radius: 0; + width: 100%; + height: 100%; + border: none; + } +} + +// ─── Responsive ─────────────────────────────────────────────────────────────── +@media (max-width: 800px) { + h1 { + font-size: 16px; + } + + #main { + width: 100%; + flex-direction: column; + } + + #side { + width: 100%; + height: auto; + } + + #task { + height: auto; + } + + #info { + height: auto; + } + + #calendar { + width: 100%; + height: 400px; + flex-shrink: 0; + } +} \ No newline at end of file diff --git a/server.js b/server.js new file mode 100644 index 0000000..8ce0f87 --- /dev/null +++ b/server.js @@ -0,0 +1,59 @@ +import express from "express"; +import path from "path"; +import open from "open"; +import fs from "fs"; + +const app = express(); + +app.use("/public", express.static(path.join(__dirname, "public"))); +// app.use(express.urlencoded({ extends: true })); +app.use(express.json()); + +app.listen(4000, () => { + console.log("✅Pass: Server running at http://127.0.0.1:4000"); +}); + +app.get("/", (req, res) => { + res.sendFile(__dirname + "/public/index.html"); +}); + +app.get("/api/meal", async (req, res) => { + const item = req.query; + const date = item.date; + const type = item.type; + + const base_url = new URL("https://open.neis.go.kr/hub/mealServiceDietInfo"); + const params = new URLSearchParams({ + key: "6b442e68fabb4bee943947613b31eeee", + Type: "json", + ATPT_OFCDC_SC_CODE: "D10", + SD_SCHUL_CODE: "7240450", + MLSV_YMD: date, + MMEAL_SC_CODE: type, + }); + const url = `${base_url}?${params.toString()}`; + + try { + let data = await fetch(url); + + if (!data.ok) { + res.send("No Information"); + } + + data = await data.json(); + + if (!data.mealServiceDietInfo[1].row[0]) { + res.send("No Information"); + } + + let meal_list = data.mealServiceDietInfo[1].row[0].DDISH_NM; + meal_list = meal_list.replace(/\([^)]+\)/g, "").split("
"); + + res.json({ + meal_list: meal_list, + kcal: data.mealServiceDietInfo[1].row[0].CAL_INFO, + }); + } catch (error) { + res.send("No Information"); + } +}); \ No newline at end of file diff --git a/token.json b/token.json new file mode 100644 index 0000000..1290e02 --- /dev/null +++ b/token.json @@ -0,0 +1,8 @@ +{ + "access_token": "ya29.a0AeXRPp6dQRljlR_5KWE8DgcWu4lkKXDAgErjVzNGkgIZwiCKXqk5MtqKfB2Wp_DG58SzfM2UmPJJpeIbflU-D0_xxCnD0u0En_OccmeRsSEE56KY5q_NtQtnpwUekEvtvEjhIiGfU0M5NdgljLvnAN7JkPvv-Kjd2liy59j17QaCgYKAWkSARASFQHGX2MiA14X1b8pOFBNNRtkZ3xKug0177", + "expires_in": 3599, + "scope": "https://www.googleapis.com/auth/calendar", + "token_type": "Bearer", + "refresh_token_expires_in": 571598, + "refresh_token": "1//0erCBDblHqeswCgYIARAAGA4SNwF-L9IrKNzH7uKvucIavo21bciIQe04jXA3w-aHCLvrUmUNoaIVNn1wx4fapAmpifxS-kjcPyk" +} \ No newline at end of file