From bc66ce939d707c679b9cb9e6256c4234e15d9304 Mon Sep 17 00:00:00 2001 From: HA0N1 Date: Sun, 5 Jan 2025 17:41:08 +0900 Subject: [PATCH 1/7] =?UTF-8?q?feature:=20swagger=20=EA=B8=B0=EB=B3=B8=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 4 + pnpm-lock.yaml | 186 ++++++++++++++++++++++++++++++++++ src/app.ts | 5 + src/configs/swagger.config.ts | 16 +++ 4 files changed, 211 insertions(+) create mode 100644 src/configs/swagger.config.ts diff --git a/package.json b/package.json index 61055cd..b18877e 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,8 @@ "express": "^4.21.1", "pg": "^8.13.1", "reflect-metadata": "^0.2.2", + "swagger-jsdoc": "^6.2.8", + "swagger-ui-express": "^5.0.1", "winston": "^3.17.0", "winston-daily-rotate-file": "^5.0.0" }, @@ -46,6 +48,8 @@ "@types/node": "^22.9.0", "@types/node-fetch": "^2.6.12", "@types/pg": "^8.11.10", + "@types/swagger-jsdoc": "^6.0.4", + "@types/swagger-ui-express": "^4.1.7", "eslint": "^9.15.0", "eslint-config-prettier": "^9.1.0", "eslint-plugin-jest": "^28.9.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f33289f..9c181ba 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -35,6 +35,12 @@ importers: reflect-metadata: specifier: ^0.2.2 version: 0.2.2 + swagger-jsdoc: + specifier: ^6.2.8 + version: 6.2.8(openapi-types@12.1.3) + swagger-ui-express: + specifier: ^5.0.1 + version: 5.0.1(express@4.21.1) winston: specifier: ^3.17.0 version: 3.17.0 @@ -69,6 +75,12 @@ importers: '@types/pg': specifier: ^8.11.10 version: 8.11.10 + '@types/swagger-jsdoc': + specifier: ^6.0.4 + version: 6.0.4 + '@types/swagger-ui-express': + specifier: ^4.1.7 + version: 4.1.7 eslint: specifier: ^9.15.0 version: 9.15.0 @@ -115,6 +127,21 @@ packages: resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} engines: {node: '>=6.0.0'} + '@apidevtools/json-schema-ref-parser@9.1.2': + resolution: {integrity: sha512-r1w81DpR+KyRWd3f+rk6TNqMgedmAxZP5v5KWlXQWlgMUUtyEJch0DKEci1SorPMiSeM8XPl7MZ3miJ60JIpQg==} + + '@apidevtools/openapi-schemas@2.1.0': + resolution: {integrity: sha512-Zc1AlqrJlX3SlpupFGpiLi2EbteyP7fXmUOGup6/DnkRgjP9bgMM/ag+n91rsv0U1Gpz0H3VILA/o3bW7Ua6BQ==} + engines: {node: '>=10'} + + '@apidevtools/swagger-methods@3.0.2': + resolution: {integrity: sha512-QAkD5kK2b1WfjDS/UQn/qQkbwF31uqRjPTrsCs5ZG9BQGAkjwvqGFjjPqAuzac/IYzpPtRzjCP1WrTuAIjMrXg==} + + '@apidevtools/swagger-parser@10.0.3': + resolution: {integrity: sha512-sNiLY51vZOmSPFZA5TF35KZ2HbgYklQnTSDnkghamzLb3EkNtcQnrBQEj5AOCxHpTtXpqMCRM1CrmV2rG6nw4g==} + peerDependencies: + openapi-types: '>=7' + '@babel/code-frame@7.26.2': resolution: {integrity: sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==} engines: {node: '>=6.9.0'} @@ -580,6 +607,9 @@ packages: '@jridgewell/trace-mapping@0.3.9': resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} + '@jsdevtools/ono@7.1.3': + resolution: {integrity: sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==} + '@nodelib/fs.scandir@2.1.5': resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} engines: {node: '>= 8'} @@ -596,6 +626,9 @@ packages: resolution: {integrity: sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==} engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} + '@scarf/scarf@1.4.0': + resolution: {integrity: sha512-xxeapPiUXdZAE3che6f3xogoJPeZgig6omHEy1rIY5WVsB3H2BHNnZH+gHG6x91SCWyQCzWGsuL2Hh3ClO5/qQ==} + '@sinclair/typebox@0.27.8': resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} @@ -700,6 +733,12 @@ packages: '@types/stack-utils@2.0.3': resolution: {integrity: sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==} + '@types/swagger-jsdoc@6.0.4': + resolution: {integrity: sha512-W+Xw5epcOZrF/AooUM/PccNMSAFOKWZA5dasNyMujTwsBkU74njSJBpvCCJhHAJ95XRMzQrrW844Btu0uoetwQ==} + + '@types/swagger-ui-express@4.1.7': + resolution: {integrity: sha512-ovLM9dNincXkzH4YwyYpll75vhzPBlWx6La89wwvYH7mHjVpf0X0K/vR/aUM7SRxmr5tt9z7E5XJcjQ46q+S3g==} + '@types/triple-beam@1.3.5': resolution: {integrity: sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==} @@ -921,6 +960,9 @@ packages: resolution: {integrity: sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==} engines: {node: '>= 0.4'} + call-me-maybe@1.0.2: + resolution: {integrity: sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ==} + callsites@3.1.0: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} @@ -1017,6 +1059,10 @@ packages: resolution: {integrity: sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==} engines: {node: '>=18'} + commander@6.2.0: + resolution: {integrity: sha512-zP4jEKbe8SHzKJYQmq8Y9gYjtO/POJLgIdKgV7B9qNmABVFVc+ctqSX6iXh4mCpJfRBOabiZ2YKPg8ciDw6C+Q==} + engines: {node: '>= 6'} + commander@9.5.0: resolution: {integrity: sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==} engines: {node: ^12.20.0 || >=14} @@ -1130,6 +1176,10 @@ packages: resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} engines: {node: '>=8'} + doctrine@3.0.0: + resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} + engines: {node: '>=6.0.0'} + dotenv@16.4.5: resolution: {integrity: sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==} engines: {node: '>=12'} @@ -1440,6 +1490,10 @@ packages: resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} engines: {node: '>=10.13.0'} + glob@7.1.6: + resolution: {integrity: sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==} + deprecated: Glob versions prior to v9 are no longer supported + glob@7.2.3: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} deprecated: Glob versions prior to v9 are no longer supported @@ -1820,12 +1874,21 @@ packages: resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} engines: {node: '>=10'} + lodash.get@4.4.2: + resolution: {integrity: sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==} + + lodash.isequal@4.5.0: + resolution: {integrity: sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==} + lodash.memoize@4.1.2: resolution: {integrity: sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==} lodash.merge@4.6.2: resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + lodash.mergewith@4.6.2: + resolution: {integrity: sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==} + log-update@6.1.0: resolution: {integrity: sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==} engines: {node: '>=18'} @@ -1980,6 +2043,9 @@ packages: resolution: {integrity: sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==} engines: {node: '>=18'} + openapi-types@12.1.3: + resolution: {integrity: sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==} + optionator@0.9.4: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} @@ -2403,6 +2469,24 @@ packages: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} + swagger-jsdoc@6.2.8: + resolution: {integrity: sha512-VPvil1+JRpmJ55CgAtn8DIcpBs0bL5L3q5bVQvF4tAW/k/9JYSj7dCpaYCAv5rufe0vcCbBRQXGvzpkWjvLklQ==} + engines: {node: '>=12.0.0'} + hasBin: true + + swagger-parser@10.0.3: + resolution: {integrity: sha512-nF7oMeL4KypldrQhac8RyHerJeGPD1p2xDh900GPvc+Nk7nWP6jX2FcC7WmkinMoAmoO774+AFXcWsW8gMWEIg==} + engines: {node: '>=10'} + + swagger-ui-dist@5.18.2: + resolution: {integrity: sha512-J+y4mCw/zXh1FOj5wGJvnAajq6XgHOyywsa9yITmwxIlJbMqITq3gYRZHaeqLVH/eV/HOPphE6NjF+nbSNC5Zw==} + + swagger-ui-express@5.0.1: + resolution: {integrity: sha512-SrNU3RiBGTLLmFU8GIJdOdanJTl4TOmT27tt3bWWHppqYmAZ6IDuEuBvMU6nZq0zLEe6b/1rACXCgLZqO6ZfrA==} + engines: {node: '>= v0.10.32'} + peerDependencies: + express: '>=4.0.0 || >=5.0.0-beta' + synckit@0.9.2: resolution: {integrity: sha512-vrozgXDQwYO72vHjUb/HnFbQx1exDjoKzqx23aXEg2a9VIg2TSFZ8FmeZpTjUCFMYw7mpX4BE2SFu8wI7asYsw==} engines: {node: ^14.18.0 || >=16.0.0} @@ -2611,6 +2695,10 @@ packages: yallist@3.1.1: resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + yaml@2.0.0-1: + resolution: {integrity: sha512-W7h5dEhywMKenDJh2iX/LABkbFnBxasD27oyXWDS/feDsxiw0dD5ncXdYXgkvAsXIY2MpW/ZKkr9IU30DBdMNQ==} + engines: {node: '>= 6'} + yaml@2.5.1: resolution: {integrity: sha512-bLQOjaX/ADgQ20isPJRvF0iRUHIxVhYvr53Of7wGcWlO2jvtUlH5m87DsmulFVxRpNLOnI4tB6p/oh8D7kpn9Q==} engines: {node: '>= 14'} @@ -2632,6 +2720,11 @@ packages: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} + z-schema@5.0.5: + resolution: {integrity: sha512-D7eujBWkLa3p2sIpJA0d1pr7es+a7m0vFAnZLlCEKq/Ij2k0MLi9Br2UPxoxdYystm5K1yeBGzub0FlYUEWj2Q==} + engines: {node: '>=8.0.0'} + hasBin: true + snapshots: '@ampproject/remapping@2.3.0': @@ -2639,6 +2732,27 @@ snapshots: '@jridgewell/gen-mapping': 0.3.5 '@jridgewell/trace-mapping': 0.3.25 + '@apidevtools/json-schema-ref-parser@9.1.2': + dependencies: + '@jsdevtools/ono': 7.1.3 + '@types/json-schema': 7.0.15 + call-me-maybe: 1.0.2 + js-yaml: 4.1.0 + + '@apidevtools/openapi-schemas@2.1.0': {} + + '@apidevtools/swagger-methods@3.0.2': {} + + '@apidevtools/swagger-parser@10.0.3(openapi-types@12.1.3)': + dependencies: + '@apidevtools/json-schema-ref-parser': 9.1.2 + '@apidevtools/openapi-schemas': 2.1.0 + '@apidevtools/swagger-methods': 3.0.2 + '@jsdevtools/ono': 7.1.3 + call-me-maybe: 1.0.2 + openapi-types: 12.1.3 + z-schema: 5.0.5 + '@babel/code-frame@7.26.2': dependencies: '@babel/helper-validator-identifier': 7.25.9 @@ -3158,6 +3272,8 @@ snapshots: '@jridgewell/sourcemap-codec': 1.5.0 optional: true + '@jsdevtools/ono@7.1.3': {} + '@nodelib/fs.scandir@2.1.5': dependencies: '@nodelib/fs.stat': 2.0.5 @@ -3172,6 +3288,8 @@ snapshots: '@pkgr/core@0.1.1': {} + '@scarf/scarf@1.4.0': {} + '@sinclair/typebox@0.27.8': {} '@sinonjs/commons@3.0.1': @@ -3305,6 +3423,13 @@ snapshots: '@types/stack-utils@2.0.3': {} + '@types/swagger-jsdoc@6.0.4': {} + + '@types/swagger-ui-express@4.1.7': + dependencies: + '@types/express': 5.0.0 + '@types/serve-static': 1.15.7 + '@types/triple-beam@1.3.5': {} '@types/validator@13.12.2': {} @@ -3586,6 +3711,8 @@ snapshots: get-intrinsic: 1.2.4 set-function-length: 1.2.2 + call-me-maybe@1.0.2: {} + callsites@3.1.0: {} camelcase@5.3.1: {} @@ -3681,6 +3808,8 @@ snapshots: commander@12.1.0: {} + commander@6.2.0: {} + commander@9.5.0: {} concat-map@0.0.1: {} @@ -3770,6 +3899,10 @@ snapshots: dependencies: path-type: 4.0.0 + doctrine@3.0.0: + dependencies: + esutils: 2.0.3 + dotenv@16.4.5: {} ee-first@1.1.1: {} @@ -4129,6 +4262,15 @@ snapshots: dependencies: is-glob: 4.0.3 + glob@7.1.6: + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 3.1.2 + once: 1.4.0 + path-is-absolute: 1.0.1 + glob@7.2.3: dependencies: fs.realpath: 1.0.0 @@ -4683,10 +4825,16 @@ snapshots: dependencies: p-locate: 5.0.0 + lodash.get@4.4.2: {} + + lodash.isequal@4.5.0: {} + lodash.memoize@4.1.2: {} lodash.merge@4.6.2: {} + lodash.mergewith@4.6.2: {} + log-update@6.1.0: dependencies: ansi-escapes: 7.0.0 @@ -4817,6 +4965,8 @@ snapshots: dependencies: mimic-function: 5.0.1 + openapi-types@12.1.3: {} + optionator@0.9.4: dependencies: deep-is: 0.1.4 @@ -5201,6 +5351,32 @@ snapshots: supports-preserve-symlinks-flag@1.0.0: {} + swagger-jsdoc@6.2.8(openapi-types@12.1.3): + dependencies: + commander: 6.2.0 + doctrine: 3.0.0 + glob: 7.1.6 + lodash.mergewith: 4.6.2 + swagger-parser: 10.0.3(openapi-types@12.1.3) + yaml: 2.0.0-1 + transitivePeerDependencies: + - openapi-types + + swagger-parser@10.0.3(openapi-types@12.1.3): + dependencies: + '@apidevtools/swagger-parser': 10.0.3(openapi-types@12.1.3) + transitivePeerDependencies: + - openapi-types + + swagger-ui-dist@5.18.2: + dependencies: + '@scarf/scarf': 1.4.0 + + swagger-ui-express@5.0.1(express@4.21.1): + dependencies: + express: 4.21.1 + swagger-ui-dist: 5.18.2 + synckit@0.9.2: dependencies: '@pkgr/core': 0.1.1 @@ -5409,6 +5585,8 @@ snapshots: yallist@3.1.1: {} + yaml@2.0.0-1: {} + yaml@2.5.1: {} yargs-parser@21.1.1: {} @@ -5427,3 +5605,11 @@ snapshots: optional: true yocto-queue@0.1.0: {} + + z-schema@5.0.5: + dependencies: + lodash.get: 4.4.2 + lodash.isequal: 4.5.0 + validator: 13.12.0 + optionalDependencies: + commander: 9.5.0 diff --git a/src/app.ts b/src/app.ts index 4b9554a..deae21c 100644 --- a/src/app.ts +++ b/src/app.ts @@ -4,11 +4,15 @@ import dotenv from 'dotenv'; import cors from 'cors'; import cookieParser from 'cookie-parser'; import router from './routes'; +import swaggerUi from 'swagger-ui-express'; +import swaggerJSDoc from 'swagger-jsdoc'; +import { options } from '@/configs/swagger.config'; import { errorHandlingMiddleware } from './middlewares/errorHandling.middleware'; dotenv.config(); const app: Application = express(); +const swaggerSpec = swaggerJSDoc(options); app.use(cookieParser()); app.use(express.json()); @@ -21,6 +25,7 @@ app.use( credentials: true, }), ); +app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerSpec)); app.use('/api', router); app.get('/', (req, res) => { res.send('Hello, V.D.!'); diff --git a/src/configs/swagger.config.ts b/src/configs/swagger.config.ts new file mode 100644 index 0000000..304c012 --- /dev/null +++ b/src/configs/swagger.config.ts @@ -0,0 +1,16 @@ +export const options = { + definition: { + openapi: '3.0.0', + info: { + title: 'V.D Project API', + version: '1.0.0', + description: 'API 문서화', + }, + servers: [ + { + url: 'http://localhost:8080', + }, + ], + }, + apis: ['./src/routes/*.ts'], +}; From 64a9885a4814d41975bf29404b562596aeaf23de Mon Sep 17 00:00:00 2001 From: HA0N1 Date: Tue, 7 Jan 2025 20:49:38 +0900 Subject: [PATCH 2/7] =?UTF-8?q?feature:=20swagger=20=EC=84=B1=EA=B3=B5=20?= =?UTF-8?q?=EC=8B=9C=20=EB=AC=B8=EC=84=9C=ED=99=94=20=EC=99=84=EB=A3=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/configs/swagger.config.ts | 6 +- src/routes/post.router.ts | 75 +++++++++ src/routes/tracking.router.ts | 43 +++++ src/routes/user.router.ts | 63 ++++++- src/types/dto/requests/eventRequest.type.ts | 24 +++ .../dto/requests/getAllPostsQuery.type.ts | 35 ++++ src/types/dto/requests/getPostQuery.type.ts | 18 ++ src/types/dto/requests/loginRequest.type.ts | 33 ++++ src/types/dto/requests/stayTimeRequest.dto.ts | 21 +++ src/types/dto/responses/baseResponse.type.ts | 33 +++- src/types/dto/responses/emptyReponse.type.ts | 13 ++ src/types/dto/responses/loginResponse.type.ts | 39 +++++ src/types/dto/responses/postResponse.type.ts | 158 +++++++++++++++++- 13 files changed, 551 insertions(+), 10 deletions(-) create mode 100644 src/types/dto/requests/loginRequest.type.ts diff --git a/src/configs/swagger.config.ts b/src/configs/swagger.config.ts index 304c012..1c60546 100644 --- a/src/configs/swagger.config.ts +++ b/src/configs/swagger.config.ts @@ -4,13 +4,13 @@ export const options = { info: { title: 'V.D Project API', version: '1.0.0', - description: 'API 문서화', + description: '모든 API는 로그인 후 진행이 가능합니다.', }, servers: [ { - url: 'http://localhost:8080', + url: 'http://localhost:8080/api', }, ], }, - apis: ['./src/routes/*.ts'], + apis: ['./src/routes/*.ts', './src/types/**/*.ts'], }; diff --git a/src/routes/post.router.ts b/src/routes/post.router.ts index 8a6ed31..8ff691f 100644 --- a/src/routes/post.router.ts +++ b/src/routes/post.router.ts @@ -15,13 +15,88 @@ const postRepository = new PostRepository(pool); const postService = new PostService(postRepository); const postController = new PostController(postService); +/** + * @swagger + * /posts: + * get: + * summary: 게시물 목록 조회 + * tags: + * - Post + * parameters: + * - in: query + * name: cursor + * schema: + * $ref: '#/components/schemas/GetAllPostsQueryDto/properties/cursor' + * - in: query + * name: sort + * schema: + * $ref: '#/components/schemas/GetAllPostsQueryDto/properties/sort' + * - in: query + * name: asc + * schema: + * $ref: '#/components/schemas/GetAllPostsQueryDto/properties/asc' + * responses: + * '200': + * description: 성공 + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/PostsResponseDto' + */ router.get( '/posts', authMiddleware.verify, validateRequestDto(GetAllPostsQueryDto, 'query'), postController.getAllPost, ); + +/** + * @swagger + * /posts-stats: + * get: + * summary: 게시물 전체 통계 조회 + * tags: + * - Post + * responses: + * '200': + * description: 성공 + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/PostStatisticsResponseDto' + */ router.get('/posts-stats', authMiddleware.verify, postController.getAllPostStatistics); + +/** + * @swagger + * /post/{postId}: + * get: + * summary: 게시물 상세 조회 + * tags: + * - Post + * parameters: + * - in: path + * name: postId + * required: true + * schema: + * type: integer + * description: 조회할 게시물 ID + * - in: query + * name: start + * schema: + * $ref: '#/components/schemas/GetPostQueryDto/properties/start' + * - in: query + * name: end + * schema: + * $ref: '#/components/schemas/GetPostQueryDto/properties/end' + * responses: + * '200': + * description: 성공 + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/PostResponseDto' + */ router.get('/post/:postId', authMiddleware.verify, postController.getPost); export default router; diff --git a/src/routes/tracking.router.ts b/src/routes/tracking.router.ts index 2a08a97..178ac43 100644 --- a/src/routes/tracking.router.ts +++ b/src/routes/tracking.router.ts @@ -16,7 +16,50 @@ const trackingRepository = new TrackingRepository(pool); const trackingService = new TrackingService(trackingRepository); const trackingController = new TrackingController(trackingService); +/** + * @swagger + * /event: + * post: + * tags: + * - Tracking + * summary: 사용자 이벤트 등록 + * requestBody: + * required: true + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/EventRequestDto' + * responses: + * 200: + * description: 성공 + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/EmptyResponseDto' + */ router.post('/event', authMiddleware.verify, validateRequestDto(EventRequestDto, 'body'), trackingController.event); + +/** + * @swagger + * /stay: + * post: + * tags: + * - Tracking + * summary: 사용자 이벤트 등록 + * requestBody: + * required: true + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/StayTimeRequestDto' + * responses: + * 200: + * description: 성공 + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/EmptyResponseDto' + */ router.post('/stay', authMiddleware.verify, validateRequestDto(StayTimeRequestDto, 'body'), trackingController.stay); export default router; diff --git a/src/routes/user.router.ts b/src/routes/user.router.ts index 4206790..4367482 100644 --- a/src/routes/user.router.ts +++ b/src/routes/user.router.ts @@ -1,21 +1,78 @@ import express, { Router } from 'express'; +import pool from '@/configs/db.config'; import { UserController } from '@/controllers/user.controller'; import { UserRepository } from '@/repositories/user.repository'; import { UserService } from '@/services/user.service'; -import pool from '@/configs/db.config'; import { authMiddleware } from '@/middlewares/auth.middleware'; import { validateRequestDto } from '@/middlewares/validation.middleware'; -import dotenv from 'dotenv'; import { VelogUserLoginDto } from '@/types'; const router: Router = express.Router(); -dotenv.config(); const userRepository = new UserRepository(pool); const userService = new UserService(userRepository); const userController = new UserController(userService); +/** + * @swagger + * /login: + * post: + * tags: + * - User + * summary: 사용자 로그인 + * requestBody: + * required: false + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/LoginRequestDto' + * responses: + * '200': + * description: 성공 + * headers: + * Set-Cookie: + * schema: + * type: string + * description: 인증 쿠키 + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/LoginResponseDto' + */ router.post('/login', authMiddleware.login, validateRequestDto(VelogUserLoginDto, 'user'), userController.login); + +/** + * @swagger + * /logout: + * post: + * tags: + * - User + * summary: 사용자 로그아웃 + * responses: + * '200': + * description: 성공적으로 로그아웃하고 모든 쿠키가 삭제됨 + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/EmptyResponseDto' + */ router.post('/logout', authMiddleware.login, userController.logout); + +/** + * @swagger + * /me: + * get: + * tags: + * - User + * summary: 사용자 정보 조회 + * responses: + * '200': + * description: 성공 + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/LoginResponseDto' + */ router.get('/me', authMiddleware.login, userController.fetchCurrentUser); + export default router; diff --git a/src/types/dto/requests/eventRequest.type.ts b/src/types/dto/requests/eventRequest.type.ts index cb255c5..57c9409 100644 --- a/src/types/dto/requests/eventRequest.type.ts +++ b/src/types/dto/requests/eventRequest.type.ts @@ -1,5 +1,29 @@ import { IsEnum, IsNotEmpty } from 'class-validator'; +/** + * @swagger + * components: + * schemas: + * UserEventType: + * type: string + * enum: ['01', '02', '03', '04', '99'] + * description: | + * 사용자 이벤트 타입 코드 + * * '01' - 로그인 + * * '02' - 포스트 클릭 + * * '03' - 포스트 그래프 클릭 + * * '04' - 종료 + * * '99' - 해당 없음 + * EventRequestDto: + * type: object + * required: + * - eventType + * properties: + * eventType: + * $ref: '#/components/schemas/UserEventType' + * example: + * eventType: '01' + */ export enum UserEventType { LOGIN = '01', POST_CLICK = '02', diff --git a/src/types/dto/requests/getAllPostsQuery.type.ts b/src/types/dto/requests/getAllPostsQuery.type.ts index ee6bec4..9fefc4d 100644 --- a/src/types/dto/requests/getAllPostsQuery.type.ts +++ b/src/types/dto/requests/getAllPostsQuery.type.ts @@ -1,6 +1,20 @@ import { Transform } from 'class-transformer'; import { IsBoolean, IsOptional, IsString } from 'class-validator'; +/** + * @swagger + * components: + * schemas: + * PostSortType: + * type: string + * enum: ['', 'dailyViewCount', 'dailyLikeCount'] + * description: | + * 포스트 정렬 기준 + * * '' - 작성일 + * * 'dailyViewCount' - 조회수 + * * 'dailyLikeCount' - 좋아요수 + * default: '' + */ export type PostSortType = '' | 'dailyViewCount' | 'dailyLikeCount'; export interface GetAllPostsQuery { @@ -9,6 +23,27 @@ export interface GetAllPostsQuery { asc?: boolean; } +/** + * @swagger + * components: + * schemas: + * GetAllPostsQueryDto: + * type: object + * properties: + * cursor: + * type: string + * description: 다음 페이지 조회를 위한 커서값 + * nullable: true + * sort: + * $ref: '#/components/schemas/PostSortType' + * description: 포스트 정렬 기준 + * nullable: true + * asc: + * type: boolean + * description: 오름차순 정렬 여부 + * nullable: true + * default: false + */ export class GetAllPostsQueryDto { @IsOptional() @IsString() diff --git a/src/types/dto/requests/getPostQuery.type.ts b/src/types/dto/requests/getPostQuery.type.ts index c4dffe4..6ebf663 100644 --- a/src/types/dto/requests/getPostQuery.type.ts +++ b/src/types/dto/requests/getPostQuery.type.ts @@ -10,6 +10,24 @@ export interface GetPostQuery { end?: string; } +/** + * @swagger + * components: + * schemas: + * GetPostQueryDto: + * type: object + * properties: + * start: + * type: string + * format: date + * description: 조회 시작 날짜 + * nullable: true + * end: + * type: string + * format: date + * description: 조회 종료 날짜 + * nullable: true + */ export class GetPostQueryDto { @IsOptional() @IsDate() diff --git a/src/types/dto/requests/loginRequest.type.ts b/src/types/dto/requests/loginRequest.type.ts new file mode 100644 index 0000000..faf53e4 --- /dev/null +++ b/src/types/dto/requests/loginRequest.type.ts @@ -0,0 +1,33 @@ +import { IsNotEmpty, IsString } from 'class-validator'; + +/** + * @swagger + * components: + * schemas: + * LoginRequestDto: + * type: object + * required: + * - accessToken + * - refreshToken + * properties: + * accessToken: + * type: string + * description: accessToken + * refreshToken: + * type: string + * description: refreshToken + */ +export class LoginRequestDto { + @IsString() + @IsNotEmpty() + accessToken: string; + + @IsString() + @IsNotEmpty() + refreshToken: string; + + constructor(accessToken: string, refreshToken: string) { + this.accessToken = accessToken; + this.refreshToken = refreshToken; + } +} diff --git a/src/types/dto/requests/stayTimeRequest.dto.ts b/src/types/dto/requests/stayTimeRequest.dto.ts index 7b4a145..14777de 100644 --- a/src/types/dto/requests/stayTimeRequest.dto.ts +++ b/src/types/dto/requests/stayTimeRequest.dto.ts @@ -1,5 +1,26 @@ import { IsISO8601, IsNotEmpty } from 'class-validator'; +/** + * @swagger + * components: + * schemas: + * StayTimeRequestDto: + * type: object + * required: + * - loadDate + * - unloadDate + * properties: + * loadDate: + * type: string + * format: date-time + * description: 시작 날짜 + * example: '2024-01-01' + * unloadDate: + * type: string + * format: date-time + * description: 종료 날짜 + * example: '2024-01-03' + */ export class StayTimeRequestDto { @IsISO8601() @IsNotEmpty() diff --git a/src/types/dto/responses/baseResponse.type.ts b/src/types/dto/responses/baseResponse.type.ts index 29e0a2b..bdedb96 100644 --- a/src/types/dto/responses/baseResponse.type.ts +++ b/src/types/dto/responses/baseResponse.type.ts @@ -1,10 +1,37 @@ +/** + * @swagger + * components: + * schemas: + * BaseResponseDto: + * type: object + * required: + * - success + * - message + * - data + * - error + * properties: + * success: + * type: boolean + * description: 요청 성공 여부 + * message: + * type: string + * description: 응답 메시지 + * data: + * type: object + * description: 응답 데이터 (구체적인 타입은 각 엔드포인트에서 정의) + * nullable: true + * error: + * type: string + * description: 에러 메시지 + * nullable: true + */ export class BaseResponseDto { success: boolean; message: string; - data?: T; - error?: string | null; + data: T; + error: string | null; - constructor(success: boolean, message: string, data?: T, error?: string | null) { + constructor(success: boolean, message: string, data: T, error: string | null) { this.success = success; this.message = message; this.data = data; diff --git a/src/types/dto/responses/emptyReponse.type.ts b/src/types/dto/responses/emptyReponse.type.ts index e708962..27dce3b 100644 --- a/src/types/dto/responses/emptyReponse.type.ts +++ b/src/types/dto/responses/emptyReponse.type.ts @@ -1,4 +1,17 @@ import { BaseResponseDto } from '@/types/dto/responses/baseResponse.type'; type EmptyResponseData = Record; +/** + * @swagger + * components: + * schemas: + * EmptyResponseDto: + * allOf: + * - $ref: '#/components/schemas/BaseResponseDto' + * - type: object + * properties: + * data: + * type: object + * properties: {} + */ export class EmptyResponseDto extends BaseResponseDto {} diff --git a/src/types/dto/responses/loginResponse.type.ts b/src/types/dto/responses/loginResponse.type.ts index f842020..c3c1cc0 100644 --- a/src/types/dto/responses/loginResponse.type.ts +++ b/src/types/dto/responses/loginResponse.type.ts @@ -1,13 +1,52 @@ import { BaseResponseDto } from '@/types/dto/responses/baseResponse.type'; +/** + * @swagger + * components: + * schemas: + * ProfileType: + * type: object + * properties: + * thumbnail: + * type: string + * description: 프로필 이미지 URL + */ interface ProfileType { thumbnail: string; } +/** + * @swagger + * components: + * schemas: + * LoginResponseData: + * type: object + * properties: + * id: + * type: integer + * description: 사용자 ID + * username: + * type: string + * description: 사용자 이름 + * profile: + * $ref: '#/components/schemas/ProfileType' + */ interface LoginResponseData { id: number; username: string; profile: ProfileType; } +/** + * @swagger + * components: + * schemas: + * LoginResponseDto: + * allOf: + * - $ref: '#/components/schemas/BaseResponseDto' + * - type: object + * properties: + * data: + * $ref: '#/components/schemas/LoginResponseData' + */ export class LoginResponseDto extends BaseResponseDto {} diff --git a/src/types/dto/responses/postResponse.type.ts b/src/types/dto/responses/postResponse.type.ts index 94d0414..40649f7 100644 --- a/src/types/dto/responses/postResponse.type.ts +++ b/src/types/dto/responses/postResponse.type.ts @@ -1,6 +1,40 @@ import { BaseResponseDto } from '@/types/dto/responses/baseResponse.type'; // ------ 전체 조회 ------ +/** + * @swagger + * components: + * schemas: + * GetAllPostType: + * type: object + * properties: + * id: + * type: integer + * description: 게시물 ID + * title: + * type: string + * description: 게시물 제목` + * views: + * type: integer + * description: 총 조회수 + * likes: + * type: integer + * description: 총 좋아요수 + * yesterdayViews: + * type: integer + * description: 어제 조회수 + * yesterdayLikes: + * type: integer + * description: 어제 좋아요수 + * createdAt: + * type: string + * format: date-time + * description: 생성일시 + * releasedAt: + * type: string + * format: date-time + * description: 공개일시 + */ interface GetAllPostType { id: number; title: string; @@ -12,27 +46,124 @@ interface GetAllPostType { releasedAt: string; } +/** + * @swagger + * components: + * schemas: + * PostsResponseData: + * type: object + * properties: + * nextCursor: + * type: string + * nullable: true + * description: 다음 페이지 커서값 + * posts: + * type: array + * items: + * $ref: '#/components/schemas/GetAllPostType' + */ interface PostsResponseData { nextCursor: string | null; posts: GetAllPostType[]; } +/** + * @swagger + * components: + * schemas: + * PostsResponseDto: + * allOf: + * - $ref: '#/components/schemas/BaseResponseDto' + * - type: object + * properties: + * data: + * $ref: '#/components/schemas/PostsResponseData' + */ + export class PostsResponseDto extends BaseResponseDto {} // ------ 단건 조회 ------ +/** + * @swagger + * components: + * schemas: + * GetPostType: + * type: object + * properties: + * date: + * type: string + * format: date + * description: 통계 날짜 + * dailyViewCount: + * type: integer + * description: 일일 조회수 + * dailyLikeCount: + * type: integer + * description: 일일 좋아요수 + */ interface GetPostType { date: string; dailyViewCount: number; dailyLikeCount: number; } +/** + * @swagger + * components: + * schemas: + * PostResponseData: + * type: object + * properties: + * post: + * type: array + * nullable: true + * description: 기간 별 조회된 포스터 통계 + * items: + * $ref: '#/components/schemas/GetPostType' + */ interface PostResponseData { post: GetPostType[]; } +/** + * @swagger + * components: + * schemas: + * PostResponseDto: + * allOf: + * - $ref: '#/components/schemas/BaseResponseDto' + * - type: object + * properties: + * data: + * $ref: '#/components/schemas/PostResponseData' + */ export class PostResponseDto extends BaseResponseDto {} -// ------ 전체 통계 ------ +// ------ 전체 통계 조회 ------ +/** + * @swagger + * components: + * schemas: + * PostStatisticsType: + * type: object + * properties: + * totalViews: + * type: integer + * description: 전체 조회수 + * totalLikes: + * type: integer + * description: 전체 좋아요수 + * yesterdayViews: + * type: integer + * description: 어제 조회수 + * yesterdayLikes: + * type: integer + * description: 어제 좋아요수 + * lastUpdatedDate: + * type: string + * format: date-time + * description: 마지막 업데이트 일시 + */ interface PostStatisticsType { totalViews: number; totalLikes: number; @@ -41,9 +172,34 @@ interface PostStatisticsType { lastUpdatedDate: string; } +/** + * @swagger + * components: + * schemas: + * PostStatisticsData: + * type: object + * properties: + * totalPostCount: + * type: integer + * description: 전체 게시물 수 + * stats: + * $ref: '#/components/schemas/PostStatisticsType' + */ interface PostStatisticsData { totalPostCount: number; stats: PostStatisticsType; } +/** + * @swagger + * components: + * schemas: + * PostStatisticsResponseDto: + * allOf: + * - $ref: '#/components/schemas/BaseResponseDto' + * - type: object + * properties: + * data: + * $ref: '#/components/schemas/PostStatisticsData' + */ export class PostStatisticsResponseDto extends BaseResponseDto {} From 68423cae4ff83be369924585318027905213a5bb Mon Sep 17 00:00:00 2001 From: HA0N1 Date: Wed, 8 Jan 2025 20:55:54 +0900 Subject: [PATCH 3/7] =?UTF-8?q?refactor:=20=EC=8B=A4=ED=8C=A8=20=EC=8B=9C?= =?UTF-8?q?=20=EC=97=90=EB=9F=AC=20=EC=BD=94=EB=93=9C=20=EB=AC=B8=EC=84=9C?= =?UTF-8?q?=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/routes/post.router.ts | 14 +++++++++++--- src/routes/tracking.router.ts | 12 ++++++++++-- src/routes/user.router.ts | 10 ++++++++-- 3 files changed, 29 insertions(+), 7 deletions(-) diff --git a/src/routes/post.router.ts b/src/routes/post.router.ts index 8ff691f..ac9c58f 100644 --- a/src/routes/post.router.ts +++ b/src/routes/post.router.ts @@ -37,11 +37,13 @@ const postController = new PostController(postService); * $ref: '#/components/schemas/GetAllPostsQueryDto/properties/asc' * responses: * '200': - * description: 성공 + * description: 게시물 목록 조회 성공 * content: * application/json: * schema: * $ref: '#/components/schemas/PostsResponseDto' + * '500': + * description: 서버 오류 / 데이터 베이스 조회 오류 */ router.get( '/posts', @@ -59,11 +61,13 @@ router.get( * - Post * responses: * '200': - * description: 성공 + * description: 게시물 전체 통계 조회 성공 * content: * application/json: * schema: * $ref: '#/components/schemas/PostStatisticsResponseDto' + * '500': + * description: 서버 오류 / 데이터 베이스 조회 오류 */ router.get('/posts-stats', authMiddleware.verify, postController.getAllPostStatistics); @@ -91,11 +95,15 @@ router.get('/posts-stats', authMiddleware.verify, postController.getAllPostStati * $ref: '#/components/schemas/GetPostQueryDto/properties/end' * responses: * '200': - * description: 성공 + * description: 게시물 상세 조회 성공 * content: * application/json: * schema: * $ref: '#/components/schemas/PostResponseDto' + * '401': + * description: Post조회 실패 / Unauthorized + * '500': + * description: 서버 오류 */ router.get('/post/:postId', authMiddleware.verify, postController.getPost); diff --git a/src/routes/tracking.router.ts b/src/routes/tracking.router.ts index 178ac43..097b6d1 100644 --- a/src/routes/tracking.router.ts +++ b/src/routes/tracking.router.ts @@ -30,12 +30,16 @@ const trackingController = new TrackingController(trackingService); * schema: * $ref: '#/components/schemas/EventRequestDto' * responses: - * 200: + * '200': * description: 성공 * content: * application/json: * schema: * $ref: '#/components/schemas/EmptyResponseDto' + * '400': + * description: DTO 검증 실패 / BadRequestError + * '500': + * description: 서버 오류 / 데이터 베이스 조회 오류 */ router.post('/event', authMiddleware.verify, validateRequestDto(EventRequestDto, 'body'), trackingController.event); @@ -53,12 +57,16 @@ router.post('/event', authMiddleware.verify, validateRequestDto(EventRequestDto, * schema: * $ref: '#/components/schemas/StayTimeRequestDto' * responses: - * 200: + * '200': * description: 성공 * content: * application/json: * schema: * $ref: '#/components/schemas/EmptyResponseDto' + * '400': + * description: DTO 검증 실패 + * '500': + * description: 서버 오류 / 데이터 베이스 조회 오류 */ router.post('/stay', authMiddleware.verify, validateRequestDto(StayTimeRequestDto, 'body'), trackingController.stay); diff --git a/src/routes/user.router.ts b/src/routes/user.router.ts index 4367482..c4a8506 100644 --- a/src/routes/user.router.ts +++ b/src/routes/user.router.ts @@ -38,6 +38,12 @@ const userController = new UserController(userService); * application/json: * schema: * $ref: '#/components/schemas/LoginResponseDto' + * '400': + * description: DTO 검증 실패 + * '401': + * description: 로그인 실패 / 그룹 id 조회 실패 / 유효하지 않은 토큰 + * '500': + * description: 서버 오류 / 데이터 베이스 조회 오류 */ router.post('/login', authMiddleware.login, validateRequestDto(VelogUserLoginDto, 'user'), userController.login); @@ -50,13 +56,13 @@ router.post('/login', authMiddleware.login, validateRequestDto(VelogUserLoginDto * summary: 사용자 로그아웃 * responses: * '200': - * description: 성공적으로 로그아웃하고 모든 쿠키가 삭제됨 + * description: 쿠키가 삭제되며 성공적으로 로그아웃함 * content: * application/json: * schema: * $ref: '#/components/schemas/EmptyResponseDto' */ -router.post('/logout', authMiddleware.login, userController.logout); +router.post('/logout', userController.logout); /** * @swagger From 9a4dbee3597c777c8a38da746392e36d15f71e8d Mon Sep 17 00:00:00 2001 From: HA0N1 Date: Sat, 11 Jan 2025 02:21:38 +0900 Subject: [PATCH 4/7] =?UTF-8?q?feature:=20UUID=EB=A1=9C=20=EA=B2=8C?= =?UTF-8?q?=EC=8B=9C=EA=B8=93=20=EB=8B=A8=EA=B1=B4=20=EC=A1=B0=ED=9A=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/routes/post.router.ts | 31 +++++++++++++++++++-- src/types/dto/requests/getPostQuery.type.ts | 2 +- 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/src/routes/post.router.ts b/src/routes/post.router.ts index 0db2ea7..4b39bb2 100644 --- a/src/routes/post.router.ts +++ b/src/routes/post.router.ts @@ -71,11 +71,39 @@ router.get( */ router.get('/posts-stats', authMiddleware.verify, postController.getAllPostStatistics); +/** + * @swagger + * /post/velog/{postId}: + * get: + * summary: UUID를 통한 게시물 상세 조회 + * tags: + * - Post + * parameters: + * - in: path + * name: postId + * required: true + * schema: + * type: string + * description: 조회할 게시물 UUID + * responses: + * '200': + * description: 게시물 상세 조회 성공 + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/PostResponseDto' + * '401': + * description: Post조회 실패 / Unauthorized + * '500': + * description: 서버 오류 + */ +router.get('/post/velog/:postId', authMiddleware.verify, postController.getPostByUUID); + /** * @swagger * /post/{postId}: * get: - * summary: 게시물 상세 조회 + * summary: post id를 통한 게시물 상세 조회 * tags: * - Post * parameters: @@ -105,7 +133,6 @@ router.get('/posts-stats', authMiddleware.verify, postController.getAllPostStati * '500': * description: 서버 오류 */ -router.get('/post/velog/:postId', authMiddleware.verify, postController.getPostByUUID); router.get('/post/:postId', authMiddleware.verify, postController.getPostByPostId); export default router; diff --git a/src/types/dto/requests/getPostQuery.type.ts b/src/types/dto/requests/getPostQuery.type.ts index 675dbb2..778ed48 100644 --- a/src/types/dto/requests/getPostQuery.type.ts +++ b/src/types/dto/requests/getPostQuery.type.ts @@ -1,7 +1,7 @@ import { Type } from 'class-transformer'; import { IsDate, IsOptional } from 'class-validator'; -export interface PostParam { +export interface PostParam extends Record { postId: string; } From 0b520b485338c7f688ac4f2aa80b58cab3729978 Mon Sep 17 00:00:00 2001 From: HA0N1 Date: Sat, 11 Jan 2025 02:24:52 +0900 Subject: [PATCH 5/7] =?UTF-8?q?refactor:=20=EB=8B=A8=EA=B1=B4=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=20=EC=97=90=EB=9F=AC=20=EC=9D=91=EB=8B=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/routes/post.router.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/routes/post.router.ts b/src/routes/post.router.ts index 4b39bb2..b24e273 100644 --- a/src/routes/post.router.ts +++ b/src/routes/post.router.ts @@ -95,7 +95,7 @@ router.get('/posts-stats', authMiddleware.verify, postController.getAllPostStati * '401': * description: Post조회 실패 / Unauthorized * '500': - * description: 서버 오류 + * description: 서버 오류 / 데이터 베이스 조회 오류 */ router.get('/post/velog/:postId', authMiddleware.verify, postController.getPostByUUID); @@ -131,7 +131,7 @@ router.get('/post/velog/:postId', authMiddleware.verify, postController.getPostB * '401': * description: Post조회 실패 / Unauthorized * '500': - * description: 서버 오류 + * description: 서버 오류 / 데이터 베이스 조회 오류 */ router.get('/post/:postId', authMiddleware.verify, postController.getPostByPostId); From 0cf097f7ae95a3bd6632c62b480b118853ddbecd Mon Sep 17 00:00:00 2001 From: HA0N1 Date: Mon, 13 Jan 2025 16:42:44 +0900 Subject: [PATCH 6/7] fix: event type docs --- src/types/dto/requests/eventRequest.type.ts | 27 ++++++++++++--------- 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/src/types/dto/requests/eventRequest.type.ts b/src/types/dto/requests/eventRequest.type.ts index 220dfd8..1d6642e 100644 --- a/src/types/dto/requests/eventRequest.type.ts +++ b/src/types/dto/requests/eventRequest.type.ts @@ -6,13 +6,16 @@ import { IsEnum, IsNotEmpty } from 'class-validator'; * schemas: * UserEventType: * type: string - * enum: ['01', '02', '03', '04', '99'] + * enum: ['11', '12', '13', '21', '22', '23', '31', '99'] * description: | * 사용자 이벤트 타입 코드 - * * '01' - 로그인 - * * '02' - 포스트 클릭 - * * '03' - 포스트 그래프 클릭 - * * '04' - 종료 + * * '11' - 로그인 성공 + * * '12' - 페이지 이동 + * * '13' - 로그아웃 + * * '21' - 메인 페이지 - 통계 블록 열림/닫힘 + * * '22' - 메인 페이지 - 정렬(오름차순, 방식) 선택 + * * '23' - 메인 페이지 - 새로고침 버튼 + * * '31' - 리더보드 페이지 - 정렬 방식 선택 * * '99' - 해당 없음 * EventRequestDto: * type: object @@ -25,13 +28,13 @@ import { IsEnum, IsNotEmpty } from 'class-validator'; * eventType: '01' */ export enum UserEventType { - LOGIN = '11', // 로그인 성공 - NAVIGATE = '12', // 페이지 이동 (헤더 클릭 등) - LOGOUT = '13', // 로그아웃 - SECTION_INTERACT_MAIN = '21', // 메인 페이지 - 통계 블록 열림/닫힘 - SORT_INTERACT_MAIN = '22', // 메인 페이지 - 정렬(오름차순, 방식) 선택 - REFRESH_INTERACT_MAIN = '23', // 메인 페이지 - 새로고침 버튼 - SORT_INTERACT_BOARD = '31', // 리더보드 페이지 - 정렬 방식 선택 + LOGIN = '11', + NAVIGATE = '12', + LOGOUT = '13', + SECTION_INTERACT_MAIN = '21', + SORT_INTERACT_MAIN = '22', + REFRESH_INTERACT_MAIN = '23', + SORT_INTERACT_BOARD = '31', NOTHING = '99', } From 52b549f25e417e2720b3bf90e26b68f6c4cd41a5 Mon Sep 17 00:00:00 2001 From: HA0N1 Date: Mon, 13 Jan 2025 17:14:27 +0900 Subject: [PATCH 7/7] =?UTF-8?q?hotfix:=20swagger=20=EB=B3=B4=EC=95=88=20?= =?UTF-8?q?=EA=B4=80=EB=A0=A8=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/configs/swagger.config.ts | 22 ++++++++++++++++++++++ src/controllers/user.controller.ts | 4 +++- src/routes/tracking.router.ts | 2 +- src/routes/user.router.ts | 3 ++- 4 files changed, 28 insertions(+), 3 deletions(-) diff --git a/src/configs/swagger.config.ts b/src/configs/swagger.config.ts index 1c60546..026c769 100644 --- a/src/configs/swagger.config.ts +++ b/src/configs/swagger.config.ts @@ -11,6 +11,28 @@ export const options = { url: 'http://localhost:8080/api', }, ], + components: { + securitySchemes: { + AccessTokenAuth: { + type: 'apiKey', + description: 'API 인증을 위한 액세스 토큰입니다. 헤더, 쿠키 또는 요청 본문을 통해 전달할 수 있습니다.', + name: 'access_token', + in: 'header', + }, + RefreshTokenAuth: { + type: 'apiKey', + description: '토큰 갱신을 위한 리프레시 토큰입니다. 헤더, 쿠키 또는 요청 본문을 통해 전달할 수 있습니다.', + name: 'refresh_token', + in: 'header', + }, + }, + }, + security: [ + { + AccessTokenAuth: [], + RefreshTokenAuth: [], + }, + ], }, apis: ['./src/routes/*.ts', './src/types/**/*.ts'], }; diff --git a/src/controllers/user.controller.ts b/src/controllers/user.controller.ts index 36ae3e9..1335111 100644 --- a/src/controllers/user.controller.ts +++ b/src/controllers/user.controller.ts @@ -11,11 +11,13 @@ export class UserController { const baseOptions: CookieOptions = { httpOnly: isProd, secure: isProd, - domain: process.env.COOKIE_DOMAIN || 'localhost', }; if (isProd) { baseOptions.sameSite = 'lax'; + baseOptions.domain = process.env.ALLOWED_ORIGINS; + } else { + baseOptions.domain = 'localhost'; } return baseOptions; diff --git a/src/routes/tracking.router.ts b/src/routes/tracking.router.ts index 097b6d1..665e4c9 100644 --- a/src/routes/tracking.router.ts +++ b/src/routes/tracking.router.ts @@ -49,7 +49,7 @@ router.post('/event', authMiddleware.verify, validateRequestDto(EventRequestDto, * post: * tags: * - Tracking - * summary: 사용자 이벤트 등록 + * summary: 사용자 체류 시간 등록 * requestBody: * required: true * content: diff --git a/src/routes/user.router.ts b/src/routes/user.router.ts index c4a8506..9cc781c 100644 --- a/src/routes/user.router.ts +++ b/src/routes/user.router.ts @@ -20,6 +20,7 @@ const userController = new UserController(userService); * tags: * - User * summary: 사용자 로그인 + * security: [] * requestBody: * required: false * content: @@ -62,7 +63,7 @@ router.post('/login', authMiddleware.login, validateRequestDto(VelogUserLoginDto * schema: * $ref: '#/components/schemas/EmptyResponseDto' */ -router.post('/logout', userController.logout); +router.post('/logout', authMiddleware.verify, userController.logout); /** * @swagger