From a0c806537ca2b23f4e337684a01d74b14fa7852a Mon Sep 17 00:00:00 2001 From: winchoose Date: Wed, 18 Feb 2026 15:54:09 +0900 Subject: [PATCH 1/3] =?UTF-8?q?feat:=20feed=20=EC=A1=B0=ED=9A=8C=20?= =?UTF-8?q?=EC=82=AC=EC=A0=84=EC=9E=91=EC=97=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 4 +- pnpm-lock.yaml | 194 +++++- src/page/main-page.tsx | 3 + src/shared/api/domain/main/query.ts | 17 + src/shared/api/end-point.ts | 5 + src/shared/api/query-key.ts | 3 + src/shared/types/.gitkeep | 0 src/shared/types/schema.ts | 947 ++++++++++++++++++++++++++++ src/shared/types/type.ts | 4 + 9 files changed, 1160 insertions(+), 17 deletions(-) create mode 100644 src/shared/api/domain/main/query.ts create mode 100644 src/shared/api/end-point.ts create mode 100644 src/shared/api/query-key.ts delete mode 100644 src/shared/types/.gitkeep create mode 100644 src/shared/types/schema.ts create mode 100644 src/shared/types/type.ts diff --git a/package.json b/package.json index 07ddfe4..1c24977 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,8 @@ "dev": "vite", "build": "tsc -b && vite build", "lint": "eslint .", - "preview": "vite preview" + "preview": "vite preview", + "generate:api": "openapi-typescript http://3.34.130.196:8081/v3/api-docs -o src/shared/types/schema.ts" }, "dependencies": { "@tanstack/react-query": "^5.90.17", @@ -40,6 +41,7 @@ "eslint-plugin-react-hooks": "^7.0.1", "eslint-plugin-react-refresh": "^0.4.24", "globals": "^16.5.0", + "openapi-typescript": "^7.13.0", "postcss": "^8.5.6", "prettier": "^3.7.4", "tailwindcss": "^3.4.19", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index de38cdd..e05cb55 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -93,6 +93,9 @@ importers: globals: specifier: ^16.5.0 version: 16.5.0 + openapi-typescript: + specifier: ^7.13.0 + version: 7.13.0(typescript@5.9.3) postcss: specifier: ^8.5.6 version: 8.5.6 @@ -458,6 +461,16 @@ packages: resolution: {integrity: sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==} engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} + '@redocly/ajv@8.17.4': + resolution: {integrity: sha512-BieiCML/IgP6x99HZByJSt7fJE4ipgzO7KAFss92Bs+PEI35BhY7vGIysFXLT+YmS7nHtQjZjhOQyPPEf7xGHA==} + + '@redocly/config@0.22.2': + resolution: {integrity: sha512-roRDai8/zr2S9YfmzUfNhKjOF0NdcOIqF7bhf4MVC5UxpjIysDjyudvlAiVbpPHp3eDRWbdzUgtkK1a7YiDNyQ==} + + '@redocly/openapi-core@1.34.6': + resolution: {integrity: sha512-2+O+riuIUgVSuLl3Lyh5AplWZyVMNuG2F98/o6NrutKJfW4/GTZdPpZlIphS0HGgcOHgmWcCSHj+dWFlZaGSHw==} + engines: {node: '>=18.17.0', npm: '>=9.5.0'} + '@rolldown/pluginutils@1.0.0-beta.53': resolution: {integrity: sha512-vENRlFU4YbrwVqNDZ7fLvy+JR1CRkyr01jhSiDpE1u6py3OMzQfztQU2jxykW3ALNxO4kSlqIDeYyD0Y9RcQeQ==} @@ -932,9 +945,17 @@ packages: engines: {node: '>=0.4.0'} hasBin: true + agent-base@7.1.4: + resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} + engines: {node: '>= 14'} + ajv@6.12.6: resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} + ansi-colors@4.1.3: + resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==} + engines: {node: '>=6'} + ansi-styles@4.3.0: resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} engines: {node: '>=8'} @@ -1056,6 +1077,9 @@ packages: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} + change-case@5.4.4: + resolution: {integrity: sha512-HRQyTk2/YPEkt9TnUPbOpr64Uw3KOicFWPVBb+xiHvd6eBx/qPr9xqfBFDT8P2vWsvvz4jbEkfDe71W3VyNu2w==} + chokidar@3.6.0: resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} engines: {node: '>= 8.10.0'} @@ -1074,6 +1098,9 @@ packages: color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + colorette@1.4.0: + resolution: {integrity: sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==} + commander@4.1.1: resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} engines: {node: '>= 6'} @@ -1385,6 +1412,9 @@ packages: fast-levenshtein@2.0.6: resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + fast-uri@3.1.0: + resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==} + fastq@1.20.1: resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==} @@ -1521,6 +1551,10 @@ packages: hermes-parser@0.25.1: resolution: {integrity: sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==} + https-proxy-agent@7.0.6: + resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} + engines: {node: '>= 14'} + ignore@5.3.2: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} @@ -1537,6 +1571,10 @@ packages: resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} engines: {node: '>=0.8.19'} + index-to-position@1.2.0: + resolution: {integrity: sha512-Yg7+ztRkqslMAS2iFaU+Oa4KTSidr63OsFGlOrJoW981kIYO3CGCS3wA95P1mUi/IVSJkn0D479KTJpVpvFNuw==} + engines: {node: '>=18'} + internal-slot@1.1.0: resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==} engines: {node: '>= 0.4'} @@ -1665,6 +1703,10 @@ packages: resolution: {integrity: sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==} hasBin: true + js-levenshtein@1.1.6: + resolution: {integrity: sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==} + engines: {node: '>=0.10.0'} + js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} @@ -1686,6 +1728,9 @@ packages: json-schema-traverse@0.4.1: resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + json-schema-traverse@1.0.0: + resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + json-stable-stringify-without-jsonify@1.0.1: resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} @@ -1822,6 +1867,10 @@ packages: minimatch@3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + minimatch@5.1.6: + resolution: {integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==} + engines: {node: '>=10'} + minimatch@9.0.5: resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} engines: {node: '>=16 || 14 >=14.17'} @@ -1894,6 +1943,12 @@ packages: resolution: {integrity: sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==} engines: {node: '>= 0.4'} + openapi-typescript@7.13.0: + resolution: {integrity: sha512-EFP392gcqXS7ntPvbhBzbF8TyBA+baIYEm791Hy5YkjDYKTnk/Tn5OQeKm5BIZvJihpp8Zzr4hzx0Irde1LNGQ==} + hasBin: true + peerDependencies: + typescript: ^5.x + optionator@0.9.4: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} @@ -1918,6 +1973,10 @@ packages: resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} engines: {node: '>=8'} + parse-json@8.3.0: + resolution: {integrity: sha512-ybiGyvspI+fAoRQbIPRddCcSTV9/LsJbf0e/S85VLowVGzRmokfneg2kwVW/KU5rOXrPSbF1qAKPMgNTqqROQQ==} + engines: {node: '>=18'} + path-exists@4.0.0: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} @@ -1952,6 +2011,10 @@ packages: resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==} engines: {node: '>= 6'} + pluralize@8.0.0: + resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==} + engines: {node: '>=4'} + possible-typed-array-names@1.1.0: resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} engines: {node: '>= 0.4'} @@ -2084,6 +2147,10 @@ packages: resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==} engines: {node: '>= 0.4'} + require-from-string@2.0.2: + resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} + engines: {node: '>=0.10.0'} + resolve-from@4.0.0: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} @@ -2222,6 +2289,10 @@ packages: engines: {node: '>=16 || 14 >=14.17'} hasBin: true + supports-color@10.2.2: + resolution: {integrity: sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==} + engines: {node: '>=18'} + supports-color@7.2.0: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} @@ -2295,6 +2366,10 @@ packages: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} + type-fest@4.41.0: + resolution: {integrity: sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==} + engines: {node: '>=16'} + typed-array-buffer@1.0.3: resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==} engines: {node: '>= 0.4'} @@ -2426,6 +2501,13 @@ packages: yallist@3.1.1: resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + yaml-ast-parser@0.0.43: + resolution: {integrity: sha512-2PTINUwsRqSd+s8XxKaJWQlUuEMHJQyEuh2edBbW8KNJz0SJPwUSD2zRWqezFEdN7IzAgeuYHFUCF7o8zRdZ0A==} + + yargs-parser@21.1.1: + resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} + engines: {node: '>=12'} + yocto-queue@0.1.0: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} @@ -2464,7 +2546,7 @@ snapshots: '@babel/types': 7.28.5 '@jridgewell/remapping': 2.3.5 convert-source-map: 2.0.0 - debug: 4.4.3 + debug: 4.4.3(supports-color@10.2.2) gensync: 1.0.0-beta.2 json5: 2.2.3 semver: 6.3.1 @@ -2546,7 +2628,7 @@ snapshots: '@babel/parser': 7.28.5 '@babel/template': 7.27.2 '@babel/types': 7.28.5 - debug: 4.4.3 + debug: 4.4.3(supports-color@10.2.2) transitivePeerDependencies: - supports-color @@ -2659,7 +2741,7 @@ snapshots: '@eslint/config-array@0.21.1': dependencies: '@eslint/object-schema': 2.1.7 - debug: 4.4.3 + debug: 4.4.3(supports-color@10.2.2) minimatch: 3.1.2 transitivePeerDependencies: - supports-color @@ -2675,7 +2757,7 @@ snapshots: '@eslint/eslintrc@3.3.3': dependencies: ajv: 6.12.6 - debug: 4.4.3 + debug: 4.4.3(supports-color@10.2.2) espree: 10.4.0 globals: 14.0.0 ignore: 5.3.2 @@ -2746,6 +2828,29 @@ snapshots: '@pkgr/core@0.2.9': {} + '@redocly/ajv@8.17.4': + dependencies: + fast-deep-equal: 3.1.3 + fast-uri: 3.1.0 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + + '@redocly/config@0.22.2': {} + + '@redocly/openapi-core@1.34.6(supports-color@10.2.2)': + dependencies: + '@redocly/ajv': 8.17.4 + '@redocly/config': 0.22.2 + colorette: 1.4.0 + https-proxy-agent: 7.0.6(supports-color@10.2.2) + js-levenshtein: 1.1.6 + js-yaml: 4.1.1 + minimatch: 5.1.6 + pluralize: 8.0.0 + yaml-ast-parser: 0.0.43 + transitivePeerDependencies: + - supports-color + '@rolldown/pluginutils@1.0.0-beta.53': {} '@rollup/pluginutils@5.3.0(rollup@4.54.0)': @@ -2991,7 +3096,7 @@ snapshots: '@typescript-eslint/types': 8.51.0 '@typescript-eslint/typescript-estree': 8.51.0(typescript@5.9.3) '@typescript-eslint/visitor-keys': 8.51.0 - debug: 4.4.3 + debug: 4.4.3(supports-color@10.2.2) eslint: 9.39.2(jiti@1.21.7) typescript: 5.9.3 transitivePeerDependencies: @@ -3003,7 +3108,7 @@ snapshots: '@typescript-eslint/types': 8.52.0 '@typescript-eslint/typescript-estree': 8.52.0(typescript@5.9.3) '@typescript-eslint/visitor-keys': 8.52.0 - debug: 4.4.3 + debug: 4.4.3(supports-color@10.2.2) eslint: 9.39.2(jiti@1.21.7) typescript: 5.9.3 transitivePeerDependencies: @@ -3013,7 +3118,7 @@ snapshots: dependencies: '@typescript-eslint/tsconfig-utils': 8.51.0(typescript@5.9.3) '@typescript-eslint/types': 8.51.0 - debug: 4.4.3 + debug: 4.4.3(supports-color@10.2.2) typescript: 5.9.3 transitivePeerDependencies: - supports-color @@ -3022,7 +3127,7 @@ snapshots: dependencies: '@typescript-eslint/tsconfig-utils': 8.52.0(typescript@5.9.3) '@typescript-eslint/types': 8.52.0 - debug: 4.4.3 + debug: 4.4.3(supports-color@10.2.2) typescript: 5.9.3 transitivePeerDependencies: - supports-color @@ -3050,7 +3155,7 @@ snapshots: '@typescript-eslint/types': 8.51.0 '@typescript-eslint/typescript-estree': 8.51.0(typescript@5.9.3) '@typescript-eslint/utils': 8.51.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3) - debug: 4.4.3 + debug: 4.4.3(supports-color@10.2.2) eslint: 9.39.2(jiti@1.21.7) ts-api-utils: 2.3.0(typescript@5.9.3) typescript: 5.9.3 @@ -3062,7 +3167,7 @@ snapshots: '@typescript-eslint/types': 8.52.0 '@typescript-eslint/typescript-estree': 8.52.0(typescript@5.9.3) '@typescript-eslint/utils': 8.52.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3) - debug: 4.4.3 + debug: 4.4.3(supports-color@10.2.2) eslint: 9.39.2(jiti@1.21.7) ts-api-utils: 2.4.0(typescript@5.9.3) typescript: 5.9.3 @@ -3079,7 +3184,7 @@ snapshots: '@typescript-eslint/tsconfig-utils': 8.51.0(typescript@5.9.3) '@typescript-eslint/types': 8.51.0 '@typescript-eslint/visitor-keys': 8.51.0 - debug: 4.4.3 + debug: 4.4.3(supports-color@10.2.2) minimatch: 9.0.5 semver: 7.7.3 tinyglobby: 0.2.15 @@ -3094,7 +3199,7 @@ snapshots: '@typescript-eslint/tsconfig-utils': 8.52.0(typescript@5.9.3) '@typescript-eslint/types': 8.52.0 '@typescript-eslint/visitor-keys': 8.52.0 - debug: 4.4.3 + debug: 4.4.3(supports-color@10.2.2) minimatch: 9.0.5 semver: 7.7.3 tinyglobby: 0.2.15 @@ -3212,6 +3317,8 @@ snapshots: acorn@8.15.0: {} + agent-base@7.1.4: {} + ajv@6.12.6: dependencies: fast-deep-equal: 3.1.3 @@ -3219,6 +3326,8 @@ snapshots: json-schema-traverse: 0.4.1 uri-js: 4.4.1 + ansi-colors@4.1.3: {} + ansi-styles@4.3.0: dependencies: color-convert: 2.0.1 @@ -3373,6 +3482,8 @@ snapshots: ansi-styles: 4.3.0 supports-color: 7.2.0 + change-case@5.4.4: {} + chokidar@3.6.0: dependencies: anymatch: 3.1.3 @@ -3397,6 +3508,8 @@ snapshots: color-name@1.1.4: {} + colorette@1.4.0: {} + commander@4.1.1: {} concat-map@0.0.1: {} @@ -3446,9 +3559,11 @@ snapshots: dependencies: ms: 2.1.3 - debug@4.4.3: + debug@4.4.3(supports-color@10.2.2): dependencies: ms: 2.1.3 + optionalDependencies: + supports-color: 10.2.2 deep-is@0.1.4: {} @@ -3653,7 +3768,7 @@ snapshots: eslint-import-resolver-typescript@4.4.4(eslint-plugin-import@2.32.0)(eslint@9.39.2(jiti@1.21.7)): dependencies: - debug: 4.4.3 + debug: 4.4.3(supports-color@10.2.2) eslint: 9.39.2(jiti@1.21.7) eslint-import-context: 0.1.9(unrs-resolver@1.11.1) get-tsconfig: 4.13.0 @@ -3778,7 +3893,7 @@ snapshots: ajv: 6.12.6 chalk: 4.1.2 cross-spawn: 7.0.6 - debug: 4.4.3 + debug: 4.4.3(supports-color@10.2.2) escape-string-regexp: 4.0.0 eslint-scope: 8.4.0 eslint-visitor-keys: 4.2.1 @@ -3838,6 +3953,8 @@ snapshots: fast-levenshtein@2.0.6: {} + fast-uri@3.1.0: {} + fastq@1.20.1: dependencies: reusify: 1.1.0 @@ -3969,6 +4086,13 @@ snapshots: dependencies: hermes-estree: 0.25.1 + https-proxy-agent@7.0.6(supports-color@10.2.2): + dependencies: + agent-base: 7.1.4 + debug: 4.4.3(supports-color@10.2.2) + transitivePeerDependencies: + - supports-color + ignore@5.3.2: {} ignore@7.0.5: {} @@ -3980,6 +4104,8 @@ snapshots: imurmurhash@0.1.4: {} + index-to-position@1.2.0: {} + internal-slot@1.1.0: dependencies: es-errors: 1.3.0 @@ -4119,6 +4245,8 @@ snapshots: jiti@1.21.7: {} + js-levenshtein@1.1.6: {} + js-tokens@4.0.0: {} js-yaml@4.1.1: @@ -4133,6 +4261,8 @@ snapshots: json-schema-traverse@0.4.1: {} + json-schema-traverse@1.0.0: {} + json-stable-stringify-without-jsonify@1.0.1: {} json5@1.0.2: @@ -4244,6 +4374,10 @@ snapshots: dependencies: brace-expansion: 1.1.12 + minimatch@5.1.6: + dependencies: + brace-expansion: 2.0.2 + minimatch@9.0.5: dependencies: brace-expansion: 2.0.2 @@ -4317,6 +4451,16 @@ snapshots: define-properties: 1.2.1 es-object-atoms: 1.1.1 + openapi-typescript@7.13.0(typescript@5.9.3): + dependencies: + '@redocly/openapi-core': 1.34.6(supports-color@10.2.2) + ansi-colors: 4.1.3 + change-case: 5.4.4 + parse-json: 8.3.0 + supports-color: 10.2.2 + typescript: 5.9.3 + yargs-parser: 21.1.1 + optionator@0.9.4: dependencies: deep-is: 0.1.4 @@ -4351,6 +4495,12 @@ snapshots: json-parse-even-better-errors: 2.3.1 lines-and-columns: 1.2.4 + parse-json@8.3.0: + dependencies: + '@babel/code-frame': 7.27.1 + index-to-position: 1.2.0 + type-fest: 4.41.0 + path-exists@4.0.0: {} path-key@3.1.1: {} @@ -4369,6 +4519,8 @@ snapshots: pirates@4.0.7: {} + pluralize@8.0.0: {} + possible-typed-array-names@1.1.0: {} postcss-import@15.1.0(postcss@8.5.6): @@ -4487,6 +4639,8 @@ snapshots: gopd: 1.2.0 set-function-name: 2.0.2 + require-from-string@2.0.2: {} + resolve-from@4.0.0: {} resolve-pkg-maps@1.0.0: {} @@ -4692,6 +4846,8 @@ snapshots: tinyglobby: 0.2.15 ts-interface-checker: 0.1.13 + supports-color@10.2.2: {} + supports-color@7.2.0: dependencies: has-flag: 4.0.0 @@ -4778,6 +4934,8 @@ snapshots: dependencies: prelude-ls: 1.2.1 + type-fest@4.41.0: {} + typed-array-buffer@1.0.3: dependencies: call-bound: 1.0.4 @@ -4882,7 +5040,7 @@ snapshots: vite-tsconfig-paths@6.0.4(typescript@5.9.3)(vite@7.3.0(@types/node@24.10.4)(jiti@1.21.7)(lightningcss@1.30.2)): dependencies: - debug: 4.4.3 + debug: 4.4.3(supports-color@10.2.2) globrex: 0.1.2 tsconfck: 3.1.6(typescript@5.9.3) optionalDependencies: @@ -4954,6 +5112,10 @@ snapshots: yallist@3.1.1: {} + yaml-ast-parser@0.0.43: {} + + yargs-parser@21.1.1: {} + yocto-queue@0.1.0: {} zod-validation-error@4.0.2(zod@4.3.4): diff --git a/src/page/main-page.tsx b/src/page/main-page.tsx index 48b16c6..0d9915d 100644 --- a/src/page/main-page.tsx +++ b/src/page/main-page.tsx @@ -11,6 +11,8 @@ import { NotificationPanel } from '@widgets/main/notification/notificationPanel' import { useNavigate } from 'react-router-dom'; import { FloatingActionButton } from '@shared/ui/floatingActionButton'; import PlusIcon from '@shared/assets/icon/plus.svg?react'; +import { useQuery } from '@tanstack/react-query'; +import { FEED_QUERY_OPTIONS } from '@shared/api/domain/main/query'; export type SortType = 'latest' | 'near'; type SheetType = 'location' | 'sort' | null; @@ -76,6 +78,7 @@ const MainPage = () => { const [sortType, setSortType] = useState('latest'); const [openSheet, setOpenSheet] = useState(null); const [isNotificationOpen, setIsNotificationOpen] = useState(false); + const { data } = useQuery(FEED_QUERY_OPTIONS.LIST()); return (
=> { + return api.get(END_POINT.FEED.LIST).json(); +}; + +export const FEED_QUERY_OPTIONS = { + LIST: () => + queryOptions({ + queryKey: FEED_QUERY_KEY.LIST(), + queryFn: getFeeds, + }), +}; diff --git a/src/shared/api/end-point.ts b/src/shared/api/end-point.ts new file mode 100644 index 0000000..bac204e --- /dev/null +++ b/src/shared/api/end-point.ts @@ -0,0 +1,5 @@ +export const END_POINT = { + FEED: { + LIST: 'api/feeds', + }, +} as const; diff --git a/src/shared/api/query-key.ts b/src/shared/api/query-key.ts new file mode 100644 index 0000000..d247c7a --- /dev/null +++ b/src/shared/api/query-key.ts @@ -0,0 +1,3 @@ +export const FEED_QUERY_KEY = { + LIST: () => ['feeds'] as const, +}; diff --git a/src/shared/types/.gitkeep b/src/shared/types/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/src/shared/types/schema.ts b/src/shared/types/schema.ts new file mode 100644 index 0000000..6252733 --- /dev/null +++ b/src/shared/types/schema.ts @@ -0,0 +1,947 @@ +/** + * This file was auto-generated by openapi-typescript. + * Do not make direct changes to the file. + */ + +export interface paths { + "/api/feeds/{feedId}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * 피드 상세 조회 + * @description 피드 ID로 상세 정보를 조회합니다. + */ + get: operations["getFeed"]; + /** + * 피드 수정 + * @description 기존 피드를 수정합니다. + */ + put: operations["updateFeed"]; + post?: never; + /** + * 피드 삭제 + * @description 피드를 삭제합니다. + */ + delete: operations["deleteFeed"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/token/refresh": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["refresh"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/token/logout": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["logout"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/members/fcm-token": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["saveFcmToken"]; + delete: operations["deleteFcmToken"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/feeds": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * 피드 목록 조회 + * @description 조건에 따라 피드 목록을 조회합니다. + */ + get: operations["getFeeds"]; + put?: never; + /** + * 피드 생성 + * @description 새로운 피드를 생성합니다. + */ + post: operations["createFeed"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/feeds/{feedId}/participations": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * 참가자 목록 조회 + * @description 특정 피드의 전체 참가자 목록을 조회합니다. + */ + get: operations["getParticipations"]; + put?: never; + /** + * 참가 신청 + * @description 특정 피드에 참가 신청을 합니다. + */ + post: operations["apply"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/feeds/{feedId}/comments": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * 댓글 목록 조회 + * @description 특정 피드의 댓글 목록을 조회합니다. + */ + get: operations["getComments"]; + put?: never; + /** + * 댓글/대댓글 작성 + * @description 특정 피드에 댓글 또는 대댓글을 작성합니다. + */ + post: operations["createComment"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/participations/{id}/reject": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + /** + * 참가 거절 + * @description 참가 신청을 거절합니다. + */ + patch: operations["reject"]; + trace?: never; + }; + "/api/participations/{id}/approve": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + /** + * 참가 승인 + * @description 참가 신청을 승인합니다. + */ + patch: operations["approve"]; + trace?: never; + }; + "/api/members/nickname": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch: operations["updateNickname"]; + trace?: never; + }; + "/health": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["health"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/s3/presigned-upload": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["getPreSignedUploadUrl"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/notifications": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["getMyNotifications"]; + put?: never; + post?: never; + delete: operations["deleteMyNotifications"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/me/participations": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * 내 경기 참가 기록 조회 (커서 기반 무한 스크롤) + * @description 로그인한 사용자의 경기 참가 기록을 조회합니다. + * + * - cursorTime, cursorId가 없으면 최초 조회 + * - cursorTime, cursorId가 있으면 다음 페이지 조회 + * - status 파라미터로 참가 상태별 필터링 가능 + * - 정렬 기준: createdAt DESC, id DESC + */ + get: operations["getMyParticipations"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/feeds/{feedId}/comments/{commentId}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post?: never; + /** + * 댓글 삭제 + * @description 댓글 ID를 기준으로 댓글을 삭제합니다. + */ + delete: operations["deleteComment"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; +} +export type webhooks = Record; +export interface components { + schemas: { + FeedUpdateRequest: { + title?: string; + image?: string; + description?: string; + /** Format: int32 */ + timer?: number; + playGround?: string; + /** Format: date-time */ + playDate?: string; + /** Format: int32 */ + round?: number; + address?: string; + /** Format: double */ + latitude?: number; + /** Format: double */ + longitude?: number; + allNull?: boolean; + }; + FcmTokenRequest: { + fcmToken: string; + }; + FeedCreateRequest: { + image?: string; + title: string; + playGround: string; + /** Format: date-time */ + playDate: string; + /** Format: int32 */ + round?: number; + /** Format: int32 */ + playCount?: number; + description?: string; + /** Format: int32 */ + timer?: number; + address?: string; + /** Format: double */ + latitude?: number; + /** Format: double */ + longitude?: number; + }; + CommentCreateRequest: { + description: string; + /** Format: int64 */ + parentId?: number; + }; + NicknameUpdateRequest: { + nickname: string; + }; + NotificationResponse: { + /** Format: int64 */ + notificationId?: number; + message?: string; + read?: boolean; + /** Format: date-time */ + createdAt?: string; + /** @enum {string} */ + targetType?: "PARTICIPATION_APPROVED" | "PARTICIPATION_REJECTED" | "GAME_STARTED" | "GAME_FINISHED"; + /** Format: int64 */ + targetId?: number; + }; + MyParticipationResponse: { + /** Format: int64 */ + participationId?: number; + /** Format: int64 */ + feedId?: number; + feedTitle?: string; + playGround?: string; + /** Format: date-time */ + playDate?: string; + /** @enum {string} */ + status?: "PENDING" | "APPROVED" | "REJECTED" | "CANCELED"; + }; + FeedResponse: { + /** Format: int64 */ + feedId?: number; + title?: string; + image?: string; + playGround?: string; + /** Format: date-time */ + playDate?: string; + /** Format: int32 */ + playCount?: number; + /** Format: date-time */ + createdAt?: string; + }; + FeedDetailResponse: { + /** Format: int64 */ + feedId?: number; + image?: string; + title?: string; + playGround?: string; + /** Format: date-time */ + playDate?: string; + /** Format: int32 */ + round?: number; + /** Format: int32 */ + playCount?: number; + description?: string; + /** Format: int32 */ + timer?: number; + writer?: components["schemas"]["WriterResponse"]; + }; + WriterResponse: { + /** Format: int64 */ + writerId?: number; + nickname?: string; + }; + ParticipationResponse: { + /** Format: int64 */ + participationId?: number; + /** Format: int64 */ + applicantId?: number; + applicantNickname?: string; + /** @enum {string} */ + status?: "PENDING" | "APPROVED" | "REJECTED" | "CANCELED"; + /** Format: date-time */ + appliedAt?: string; + }; + CommentResponse: { + /** Format: int64 */ + commentId?: number; + description?: string; + /** Format: int64 */ + parentId?: number; + /** Format: int32 */ + depth?: number; + commentType?: string; + /** Format: int64 */ + memberId?: number; + nickname?: string; + /** Format: date-time */ + createdAt?: string; + }; + }; + responses: never; + parameters: never; + requestBodies: never; + headers: never; + pathItems: never; +} +export type $defs = Record; +export interface operations { + getFeed: { + parameters: { + query?: never; + header?: never; + path: { + /** @description 피드 ID */ + feedId: number; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description 피드 상세 조회 성공 */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["FeedDetailResponse"]; + }; + }; + }; + }; + updateFeed: { + parameters: { + query?: never; + header?: never; + path: { + /** @description 피드 ID */ + feedId: number; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["FeedUpdateRequest"]; + }; + }; + responses: { + /** @description 피드 수정 성공 */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + deleteFeed: { + parameters: { + query?: never; + header?: never; + path: { + /** @description 피드 ID */ + feedId: number; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description 피드 삭제 성공 */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + refresh: { + parameters: { + query?: never; + header: { + "Refresh-Token": string; + }; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "*/*": Record; + }; + }; + }; + }; + logout: { + parameters: { + query?: never; + header: { + "Refresh-Token": string; + }; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + saveFcmToken: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["FcmTokenRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + deleteFcmToken: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + getFeeds: { + parameters: { + query?: { + /** @description 커서 ID */ + cursorId?: number; + /** @description 커서 생성 시간 */ + cursorCreatedAt?: string; + /** @description 정렬 기준 (LATEST / DISTANCE) */ + sort?: string; + /** @description 위도 */ + latitude?: number; + /** @description 경도 */ + longitude?: number; + /** @description 조회 개수 */ + size?: number; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description 피드 목록 조회 성공 */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["FeedResponse"]; + }; + }; + }; + }; + createFeed: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["FeedCreateRequest"]; + }; + }; + responses: { + /** @description 피드 생성 성공 */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "*/*": number; + }; + }; + }; + }; + getParticipations: { + parameters: { + query?: never; + header?: never; + path: { + /** @description 피드 ID */ + feedId: number; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description 참가자 목록 조회 성공 */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "*/*": components["schemas"]["ParticipationResponse"][]; + }; + }; + }; + }; + apply: { + parameters: { + query?: never; + header?: never; + path: { + /** @description 피드 ID */ + feedId: number; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description 참가 신청 성공 */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + getComments: { + parameters: { + query?: never; + header?: never; + path: { + /** @description 피드 ID */ + feedId: number; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description 댓글 목록 조회 성공 */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "*/*": components["schemas"]["CommentResponse"][]; + }; + }; + }; + }; + createComment: { + parameters: { + query?: never; + header?: never; + path: { + /** @description 피드 ID */ + feedId: number; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["CommentCreateRequest"]; + }; + }; + responses: { + /** @description 댓글 작성 성공 */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + reject: { + parameters: { + query?: never; + header?: never; + path: { + /** @description 참가 ID */ + id: number; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description 참가 거절 성공 */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + approve: { + parameters: { + query?: never; + header?: never; + path: { + /** @description 참가 ID */ + id: number; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description 참가 승인 성공 */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + updateNickname: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["NicknameUpdateRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + health: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "*/*": string; + }; + }; + }; + }; + getPreSignedUploadUrl: { + parameters: { + query: { + type: string; + contentType: string; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "*/*": { + [key: string]: string; + }; + }; + }; + }; + }; + getMyNotifications: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "*/*": components["schemas"]["NotificationResponse"][]; + }; + }; + }; + }; + deleteMyNotifications: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + getMyParticipations: { + parameters: { + query?: { + /** @description 참가 상태 (PENDING, APPROVED, REJECTED) */ + status?: string; + /** @description 커서 기준 시간 (마지막 요소의 createdAt) */ + cursorTime?: string; + /** @description 커서 기준 ID (마지막 요소의 id) */ + cursorId?: number; + /** @description 조회 개수 (기본값 10) */ + size?: number; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description 내 경기 참가 기록 조회 성공 */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "*/*": components["schemas"]["MyParticipationResponse"][]; + }; + }; + }; + }; + deleteComment: { + parameters: { + query?: never; + header?: never; + path: { + /** @description 댓글 ID */ + commentId: number; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description 댓글 삭제 성공 */ + 204: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; +} diff --git a/src/shared/types/type.ts b/src/shared/types/type.ts new file mode 100644 index 0000000..ada5db0 --- /dev/null +++ b/src/shared/types/type.ts @@ -0,0 +1,4 @@ +import type { paths } from '@shared/types/schema'; + +export type GetFeedsResponse = + paths['/api/feeds']['get']['responses']['200']['content']['application/json']; From 3e748eaebe8e66e824c02ac9bd915c60ada90ea3 Mon Sep 17 00:00:00 2001 From: winchoose Date: Wed, 25 Feb 2026 22:38:34 +0900 Subject: [PATCH 2/3] =?UTF-8?q?feat:=20=ED=94=BC=EB=93=9C=20=EB=AA=A9?= =?UTF-8?q?=EB=A1=9D=20=EC=A1=B0=ED=9A=8C,=20=ED=94=BC=EB=93=9C=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1=20api=20=EC=97=B0=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/page/create-page.tsx | 76 +++++++++++++++---- src/page/main-page.tsx | 63 +++------------ src/shared/api/domain/controller/query.ts | 14 ++++ .../api/domain/{main => feeds}/query.ts | 18 ++++- src/shared/api/end-point.ts | 3 + src/shared/types/controller/type.ts | 4 + src/shared/types/{ => feeds}/type.ts | 3 + src/shared/types/schema.ts | 18 +++-- src/shared/utils/date.ts | 10 +++ src/widgets/create/add-image.tsx | 6 +- 10 files changed, 139 insertions(+), 76 deletions(-) create mode 100644 src/shared/api/domain/controller/query.ts rename src/shared/api/domain/{main => feeds}/query.ts (51%) create mode 100644 src/shared/types/controller/type.ts rename src/shared/types/{ => feeds}/type.ts (59%) diff --git a/src/page/create-page.tsx b/src/page/create-page.tsx index 01634d1..0ac6b8f 100644 --- a/src/page/create-page.tsx +++ b/src/page/create-page.tsx @@ -13,6 +13,10 @@ import { AddImage } from '@widgets/create/add-image'; import Modal from '@widgets/create/modal/modal'; import { ModalLocationSearch } from '@widgets/create/modal/contents/modal-location-search'; import { DateTimePicker } from '@widgets/create/modal/contents/wheel/date-time-picker'; +import { useMutation, useQueryClient } from '@tanstack/react-query'; +import { FEED_MUTATION_OPTIONS } from '@shared/api/domain/feeds/query'; // 네가 만든 위치 +import { FEED_QUERY_KEY } from '@shared/api/query-key'; +import { getPresignedUpload } from '@shared/api/domain/controller/query'; type CreateModalType = 'location' | 'datetime' | null; const CreatPage = () => { @@ -30,12 +34,49 @@ const CreatPage = () => { hour: string; minute: string; } | null>(null); + const [imageUrl, setImageUrl] = useState(''); const [tempDateTime, setTempDateTime] = useState<{ dateText: string; hour: string; minute: string; } | null>(null); + const queryClient = useQueryClient(); + + const { mutate } = useMutation({ + ...FEED_MUTATION_OPTIONS.CREATE(), + onSuccess: () => { + queryClient.invalidateQueries({ + queryKey: FEED_QUERY_KEY.LIST(), + }); + + navigate('/main'); + }, + }); + const handleImageUpload = async (file: File) => { + try { + // 1️⃣ presigned 요청 (우리 서버) + const { uploadUrl, fileUrl } = await getPresignedUpload( + 'feed', + file.type, + ); + + // 2️⃣ S3 업로드 (외부 URL) + await fetch(uploadUrl, { + method: 'put', + headers: { + 'Content-Type': file.type, + }, + body: file, + }); + + // 3️⃣ 상태 저장 + setImageUrl(fileUrl); + } catch (e) { + console.error('이미지 업로드 실패', e); + } + }; + return (
{ onChange={(e) => setText(e.target.value)} />
- +
diff --git a/src/shared/api/domain/feeds/query.ts b/src/shared/api/domain/feeds/query.ts index cfcb375..400085f 100644 --- a/src/shared/api/domain/feeds/query.ts +++ b/src/shared/api/domain/feeds/query.ts @@ -4,6 +4,7 @@ import { END_POINT } from '@shared/api/end-point'; import { FEED_QUERY_KEY } from '@shared/api/query-key'; import type { CreateFeedRequest, + GetFeedDetailResponse, GetFeedsResponse, } from '@shared/types/feeds/type'; @@ -11,16 +12,28 @@ const getFeeds = async (): Promise => { return api.get(END_POINT.FEED.LIST).json(); }; +const getFeedDetail = async ( + feedId: number, +): Promise => { + return api.get(END_POINT.FEED.DETAIL(feedId)).json(); +}; + +const postFeed = async (body: CreateFeedRequest) => { + return api.post(END_POINT.FEED.LIST, { json: body }).json(); +}; + export const FEED_QUERY_OPTIONS = { LIST: () => queryOptions({ queryKey: FEED_QUERY_KEY.LIST(), queryFn: getFeeds, }), -}; - -const postFeed = async (body: CreateFeedRequest) => { - return api.post(END_POINT.FEED.LIST, { json: body }).json(); + DETAIL: (feedId: number) => + queryOptions({ + queryKey: FEED_QUERY_KEY.DETAIL(feedId), + queryFn: () => getFeedDetail(feedId), + enabled: !!feedId, + }), }; export const FEED_MUTATION_OPTIONS = { diff --git a/src/shared/api/end-point.ts b/src/shared/api/end-point.ts index 58f146e..3fbe654 100644 --- a/src/shared/api/end-point.ts +++ b/src/shared/api/end-point.ts @@ -1,6 +1,7 @@ export const END_POINT = { FEED: { LIST: 'api/feeds', + DETAIL: (feedId: number) => `api/feeds/${feedId}`, }, S3: { PRESIGNED_UPLOAD: 'api/s3/presigned-upload', diff --git a/src/shared/api/query-key.ts b/src/shared/api/query-key.ts index d247c7a..9be8820 100644 --- a/src/shared/api/query-key.ts +++ b/src/shared/api/query-key.ts @@ -1,3 +1,4 @@ export const FEED_QUERY_KEY = { LIST: () => ['feeds'] as const, + DETAIL: (feedId: number) => ['feed', feedId] as const, }; diff --git a/src/shared/types/feeds/type.ts b/src/shared/types/feeds/type.ts index 22fabfc..f62e05f 100644 --- a/src/shared/types/feeds/type.ts +++ b/src/shared/types/feeds/type.ts @@ -5,3 +5,6 @@ export type GetFeedsResponse = export type CreateFeedRequest = paths['/api/feeds']['post']['requestBody']['content']['application/json']; + +export type GetFeedDetailResponse = + paths['/api/feeds/{feedId}']['get']['responses']['200']['content']['application/json']; diff --git a/src/widgets/postDetail/detail-info.tsx b/src/widgets/postDetail/detail-info.tsx index 62f0900..d1da084 100644 --- a/src/widgets/postDetail/detail-info.tsx +++ b/src/widgets/postDetail/detail-info.tsx @@ -1,21 +1,37 @@ import { CardInfo } from '@widgets/main/card/card-info'; +import { formatDate } from '@shared/utils/date'; -export function DetailInfo({}) { +interface DetailInfoProps { + playDate?: string; + playCount?: number; + location?: string; + writerNickname?: string; +} + +export function DetailInfo({ + playDate, + playCount, + location, + writerNickname, +}: DetailInfoProps) { return (
- {'게시글 작성자'} - {'게시글 시간'} + {writerNickname} + + {playDate ? formatDate(playDate) : ''} +
+