From 836446bbd688cba3eaabe4513b3599f79feaaab3 Mon Sep 17 00:00:00 2001 From: tolluset Date: Fri, 2 Feb 2024 19:51:00 +0900 Subject: [PATCH 1/2] chore: add next-auth --- package.json | 1 + pnpm-lock.yaml | 84 ++++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 82 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index ebf9f58..f0c842f 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "class-variance-authority": "^0.7.0", "clsx": "^2.1.0", "next": "14.0.4", + "next-auth": "^4.24.5", "react": "^18", "react-dom": "^18", "react-hook-form": "^7.49.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0658d4e..258485b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -32,6 +32,9 @@ dependencies: next: specifier: 14.0.4 version: 14.0.4(react-dom@18.2.0)(react@18.2.0) + next-auth: + specifier: ^4.24.5 + version: 4.24.5(next@14.0.4)(react-dom@18.2.0)(react@18.2.0) react: specifier: ^18 version: 18.2.0 @@ -356,6 +359,10 @@ packages: '@nodelib/fs.scandir': 2.1.5 fastq: 1.16.0 + /@panva/hkdf@1.1.1: + resolution: {integrity: sha512-dhPeilub1NuIG0X5Kvhh9lH4iW3ZsHlnzwgwbOlgwQ2wG1IqFzsgHqmKPk3WzsdWAeaxKJxgM0+W433RmN45GA==} + dev: false + /@pkgjs/parseargs@0.11.0: resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} @@ -1357,7 +1364,6 @@ packages: /cookie@0.5.0: resolution: {integrity: sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==} engines: {node: '>= 0.6'} - dev: true /cors@2.8.5: resolution: {integrity: sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==} @@ -2493,6 +2499,10 @@ packages: resolution: {integrity: sha512-gFqAIbuKyyso/3G2qhiO2OM6shY6EPP/R0+mkDbyspxKazh8BXDC5FiFsUjlczgdNz/vfra0da2y+aHrusLG/Q==} hasBin: true + /jose@4.15.4: + resolution: {integrity: sha512-W+oqK4H+r5sITxfxpSU+MMdr/YSWGvgZMQDIsNoBDGGy4i7GBPTtvFKibQzW06n3U3TqHjhvBJsirShsEJ6eeQ==} + dev: false + /js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} @@ -2598,7 +2608,6 @@ packages: engines: {node: '>=10'} dependencies: yallist: 4.0.0 - dev: true /media-typer@0.3.0: resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==} @@ -2696,6 +2705,31 @@ packages: engines: {node: '>= 0.6'} dev: true + /next-auth@4.24.5(next@14.0.4)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-3RafV3XbfIKk6rF6GlLE4/KxjTcuMCifqrmD+98ejFq73SRoj2rmzoca8u764977lH/Q7jo6Xu6yM+Re1Mz/Og==} + peerDependencies: + next: ^12.2.5 || ^13 || ^14 + nodemailer: ^6.6.5 + react: ^17.0.2 || ^18 + react-dom: ^17.0.2 || ^18 + peerDependenciesMeta: + nodemailer: + optional: true + dependencies: + '@babel/runtime': 7.23.8 + '@panva/hkdf': 1.1.1 + cookie: 0.5.0 + jose: 4.15.4 + next: 14.0.4(react-dom@18.2.0)(react@18.2.0) + oauth: 0.9.15 + openid-client: 5.6.4 + preact: 10.19.3 + preact-render-to-string: 5.2.6(preact@10.19.3) + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + uuid: 8.3.2 + dev: false + /next@14.0.4(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-qbwypnM7327SadwFtxXnQdGiKpkuhaRLE2uq62/nRul9cj9KhQ5LhHmlziTNqUidZotw/Q1I9OjirBROdUJNgA==} engines: {node: '>=18.17.0'} @@ -2754,10 +2788,19 @@ packages: engines: {node: '>=0.10.0'} dev: true + /oauth@0.9.15: + resolution: {integrity: sha512-a5ERWK1kh38ExDEfoO6qUHJb32rd7aYmPHuyCu3Fta/cnICvYmgd2uhuKXvPD+PXB+gCEYYEaQdIRAjCOwAKNA==} + dev: false + /object-assign@4.1.1: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} + /object-hash@2.2.0: + resolution: {integrity: sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==} + engines: {node: '>= 6'} + dev: false + /object-hash@3.0.0: resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==} engines: {node: '>= 6'} @@ -2824,6 +2867,11 @@ packages: es-abstract: 1.22.3 dev: true + /oidc-token-hash@5.0.3: + resolution: {integrity: sha512-IF4PcGgzAr6XXSff26Sk/+P4KZFJVuHAJZj3wgO3vX2bMdNVp/QXTP3P7CEm9V1IdG8lDLY3HhiqpsE/nOwpPw==} + engines: {node: ^10.13.0 || >=12.0.0} + dev: false + /on-finished@2.4.1: resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} engines: {node: '>= 0.8'} @@ -2837,6 +2885,15 @@ packages: wrappy: 1.0.2 dev: true + /openid-client@5.6.4: + resolution: {integrity: sha512-T1h3B10BRPKfcObdBklX639tVz+xh34O7GjofqrqiAQdm7eHsQ00ih18x6wuJ/E6FxdtS2u3FmUGPDeEcMwzNA==} + dependencies: + jose: 4.15.4 + lru-cache: 6.0.0 + object-hash: 2.2.0 + oidc-token-hash: 5.0.3 + dev: false + /optionator@0.9.3: resolution: {integrity: sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==} engines: {node: '>= 0.8.0'} @@ -3037,11 +3094,28 @@ packages: xtend: 4.0.2 dev: false + /preact-render-to-string@5.2.6(preact@10.19.3): + resolution: {integrity: sha512-JyhErpYOvBV1hEPwIxc/fHWXPfnEGdRKxc8gFdAZ7XV4tlzyzG847XAyEZqoDnynP88akM4eaHcSOzNcLWFguw==} + peerDependencies: + preact: '>=10' + dependencies: + preact: 10.19.3 + pretty-format: 3.8.0 + dev: false + + /preact@10.19.3: + resolution: {integrity: sha512-nHHTeFVBTHRGxJXKkKu5hT8C/YWBkPso4/Gad6xuj5dbptt9iF9NZr9pHbPhBrnT2klheu7mHTxTZ/LjwJiEiQ==} + dev: false + /prelude-ls@1.2.1: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} dev: true + /pretty-format@3.8.0: + resolution: {integrity: sha512-WuxUnVtlWL1OfZFQFuqvnvs6MiAGk9UNsBostyBOB0Is9wb5uRESevA6rnl/rkksXaGX3GzZhPup5d6Vp1nFew==} + dev: false + /prop-types@15.8.1: resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} dependencies: @@ -3784,6 +3858,11 @@ packages: engines: {node: '>= 0.4.0'} dev: true + /uuid@8.3.2: + resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} + hasBin: true + dev: false + /vary@1.1.2: resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} engines: {node: '>= 0.8'} @@ -3900,7 +3979,6 @@ packages: /yallist@4.0.0: resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} - dev: true /yaml@2.3.4: resolution: {integrity: sha512-8aAvwVUSHpfEqTQ4w/KMlf3HcRdt50E5ODIQJBw1fQ5RL34xabzxtUlzTXVqc4rkZsPbvrXKWnABCD7kWSmocA==} From 9f3d99fbcfa345e39ce2207b35ab241a409fe9d5 Mon Sep 17 00:00:00 2001 From: tolluset Date: Tue, 6 Feb 2024 08:44:45 +0900 Subject: [PATCH 2/2] feat: add auth resolve #9 --- next.config.js | 7 + package.json | 3 + pnpm-lock.yaml | 181 +++++++++++++++++++++++- src/app/activities/model.ts | 1 + src/app/activities/repository.ts | 26 +++- src/app/api/auth/[...nextauth]/route.ts | 6 + src/app/page.tsx | 9 ++ src/constants/env.ts | 14 +- src/constants/route.ts | 3 + src/lib/auth.ts | 69 +++++++++ src/lib/ddl.sql | 67 +++++++++ src/lib/ddl.ts | 12 -- src/mocks/http.ts | 77 ---------- src/types/auth.ts | 9 ++ 14 files changed, 387 insertions(+), 97 deletions(-) create mode 100644 src/app/api/auth/[...nextauth]/route.ts create mode 100644 src/constants/route.ts create mode 100644 src/lib/auth.ts create mode 100644 src/lib/ddl.sql delete mode 100644 src/lib/ddl.ts delete mode 100644 src/mocks/http.ts create mode 100644 src/types/auth.ts diff --git a/next.config.js b/next.config.js index 5ed5dc5..b7d2510 100644 --- a/next.config.js +++ b/next.config.js @@ -1,5 +1,12 @@ /** @type {import('next').NextConfig} */ const nextConfig = { + images: { + remotePatterns: [ + { + hostname: "lh3.googleusercontent.com", + }, + ], + }, webpack: (config, { isServer }) => { /** * @see https://github.com/mswjs/msw/issues/1801#issuecomment-1794145119 diff --git a/package.json b/package.json index f0c842f..ddc55d7 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "lint": "next lint" }, "dependencies": { + "@auth/pg-adapter": "^0.4.1", "@hookform/resolvers": "^3.3.4", "@radix-ui/react-icons": "^1.3.0", "@radix-ui/react-label": "^2.0.2", @@ -20,6 +21,7 @@ "clsx": "^2.1.0", "next": "14.0.4", "next-auth": "^4.24.5", + "pg": "^8.11.3", "react": "^18", "react-dom": "^18", "react-hook-form": "^7.49.3", @@ -31,6 +33,7 @@ "@types/cors": "^2.8.17", "@types/express": "^4.17.21", "@types/node": "^20", + "@types/pg": "^8.11.0", "@types/react": "^18", "@types/react-dom": "^18", "autoprefixer": "^10.0.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 258485b..f70f2a6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -5,6 +5,9 @@ settings: excludeLinksFromLockfile: false dependencies: + '@auth/pg-adapter': + specifier: ^0.4.1 + version: 0.4.1(pg@8.11.3) '@hookform/resolvers': specifier: ^3.3.4 version: 3.3.4(react-hook-form@7.49.3) @@ -35,6 +38,9 @@ dependencies: next-auth: specifier: ^4.24.5 version: 4.24.5(next@14.0.4)(react-dom@18.2.0)(react@18.2.0) + pg: + specifier: ^8.11.3 + version: 8.11.3 react: specifier: ^18 version: 18.2.0 @@ -64,6 +70,9 @@ devDependencies: '@types/node': specifier: ^20 version: 20.11.5 + '@types/pg': + specifier: ^8.11.0 + version: 8.11.0 '@types/react': specifier: ^18 version: 18.2.48 @@ -109,6 +118,34 @@ packages: resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} engines: {node: '>=10'} + /@auth/core@0.25.1: + resolution: {integrity: sha512-Xt/0oV0ItS38BUeqc98LVYspoXSUifWS48BeEzWXkkdQgLcXEFnF008+CDLyZvnyv/zF4vahwJHeR/J5SK9RaA==} + peerDependencies: + nodemailer: ^6.8.0 + peerDependenciesMeta: + nodemailer: + optional: true + dependencies: + '@panva/hkdf': 1.1.1 + '@types/cookie': 0.6.0 + cookie: 0.6.0 + jose: 5.2.1 + oauth4webapi: 2.10.2 + preact: 10.11.3 + preact-render-to-string: 5.2.3(preact@10.11.3) + dev: false + + /@auth/pg-adapter@0.4.1(pg@8.11.3): + resolution: {integrity: sha512-Syh/AQOcUMi03WRvwJB2itvrrEAw3nJ+Z32MhQeA1PVevcrvUrWZZBT8GXGuslOuZABqSpQa0Qgt20x2KajCpA==} + peerDependencies: + pg: ^8 + dependencies: + '@auth/core': 0.25.1 + pg: 8.11.3 + transitivePeerDependencies: + - nodemailer + dev: false + /@babel/runtime@7.23.8: resolution: {integrity: sha512-Y7KbAP984rn1VGMbGqKmBLio9V7y5Je9GvU4rQPCPinCyNfUcToxIXl06d59URp/F3LwinvODxab5N/G6qggkw==} engines: {node: '>=6.9.0'} @@ -791,6 +828,10 @@ packages: '@types/node': 20.11.5 dev: true + /@types/cookie@0.6.0: + resolution: {integrity: sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==} + dev: false + /@types/cors@2.8.17: resolution: {integrity: sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==} dependencies: @@ -836,6 +877,14 @@ packages: dependencies: undici-types: 5.26.5 + /@types/pg@8.11.0: + resolution: {integrity: sha512-sDAlRiBNthGjNFfvt0k6mtotoVYVQ63pA8R4EMWka7crawSR60waVYR0HAgmPRs/e2YaeJTD/43OoZ3PFw80pw==} + dependencies: + '@types/node': 20.11.5 + pg-protocol: 1.6.0 + pg-types: 4.0.2 + dev: true + /@types/pg@8.6.6: resolution: {integrity: sha512-O2xNmXebtwVekJDD+02udOncjVcMZQuTEQEMpKJ0ZRf5E7/9JJX3izhKUcUifBkyKpljyUM6BTgy2trmviKlpw==} dependencies: @@ -1221,6 +1270,11 @@ packages: update-browserslist-db: 1.0.13(browserslist@4.22.2) dev: true + /buffer-writer@2.0.0: + resolution: {integrity: sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw==} + engines: {node: '>=4'} + dev: false + /bufferutil@4.0.8: resolution: {integrity: sha512-4T53u4PdgsXqKaIctwF8ifXlRTTmEPJ8iEPWFdGZvcf7sbwYo6FKFEX9eNNAnzFZ7EzJAQ3CJeOtCRA4rDp7Pw==} engines: {node: '>=6.14.2'} @@ -1365,6 +1419,11 @@ packages: resolution: {integrity: sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==} engines: {node: '>= 0.6'} + /cookie@0.6.0: + resolution: {integrity: sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==} + engines: {node: '>= 0.6'} + dev: false + /cors@2.8.5: resolution: {integrity: sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==} engines: {node: '>= 0.10'} @@ -2503,6 +2562,10 @@ packages: resolution: {integrity: sha512-W+oqK4H+r5sITxfxpSU+MMdr/YSWGvgZMQDIsNoBDGGy4i7GBPTtvFKibQzW06n3U3TqHjhvBJsirShsEJ6eeQ==} dev: false + /jose@5.2.1: + resolution: {integrity: sha512-qiaQhtQRw6YrOaOj0v59h3R6hUY9NvxBmmnMfKemkqYmBB0tEc97NbLP7ix44VP5p9/0YHG8Vyhzuo5YBNwviA==} + dev: false + /js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} @@ -2788,6 +2851,10 @@ packages: engines: {node: '>=0.10.0'} dev: true + /oauth4webapi@2.10.2: + resolution: {integrity: sha512-ib0x1f4tCaZkTEEnRpkt96D8F2e38AFWzTOwpha1Wmme5kD+RFFgDVkrXyBSxBefFeQUoODVaieS/w9QmkZbnQ==} + dev: false + /oauth@0.9.15: resolution: {integrity: sha512-a5ERWK1kh38ExDEfoO6qUHJb32rd7aYmPHuyCu3Fta/cnICvYmgd2uhuKXvPD+PXB+gCEYYEaQdIRAjCOwAKNA==} dev: false @@ -2867,6 +2934,10 @@ packages: es-abstract: 1.22.3 dev: true + /obuf@1.1.2: + resolution: {integrity: sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==} + dev: true + /oidc-token-hash@5.0.3: resolution: {integrity: sha512-IF4PcGgzAr6XXSff26Sk/+P4KZFJVuHAJZj3wgO3vX2bMdNVp/QXTP3P7CEm9V1IdG8lDLY3HhiqpsE/nOwpPw==} engines: {node: ^10.13.0 || >=12.0.0} @@ -2920,6 +2991,10 @@ packages: p-limit: 3.1.0 dev: true + /packet-reader@1.0.0: + resolution: {integrity: sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ==} + dev: false + /parent-module@1.0.1: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} engines: {node: '>=6'} @@ -2965,14 +3040,35 @@ packages: engines: {node: '>=8'} dev: true + /pg-cloudflare@1.1.1: + resolution: {integrity: sha512-xWPagP/4B6BgFO+EKz3JONXv3YDgvkbVrGw2mTo3D6tVDQRh1e7cqVGvyR3BE+eQgAvx1XhW/iEASj4/jCWl3Q==} + requiresBuild: true + dev: false + optional: true + + /pg-connection-string@2.6.2: + resolution: {integrity: sha512-ch6OwaeaPYcova4kKZ15sbJ2hKb/VP48ZD2gE7i1J+L4MspCtBMAx8nMgz7bksc7IojCIIWuEhHibSMFH8m8oA==} + dev: false + /pg-int8@1.0.1: resolution: {integrity: sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==} engines: {node: '>=4.0.0'} + + /pg-numeric@1.0.2: + resolution: {integrity: sha512-BM/Thnrw5jm2kKLE5uJkXqqExRUY/toLHda65XgFTBTFYZyopbKjBe29Ii3RbkvlsMoFwD+tHeGaCjjv0gHlyw==} + engines: {node: '>=4'} + dev: true + + /pg-pool@3.6.1(pg@8.11.3): + resolution: {integrity: sha512-jizsIzhkIitxCGfPRzJn1ZdcosIt3pz9Sh3V01fm1vZnbnCMgmGl5wvGGdNN2EL9Rmb0EcFoCkixH4Pu+sP9Og==} + peerDependencies: + pg: '>=8.0' + dependencies: + pg: 8.11.3 dev: false /pg-protocol@1.6.0: resolution: {integrity: sha512-M+PDm637OY5WM307051+bsDia5Xej6d9IR4GwJse1qA1DIhiKlksvrneZOYQq42OM+spubpcNYEo2FcKQrDk+Q==} - dev: false /pg-types@2.2.0: resolution: {integrity: sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==} @@ -2985,6 +3081,45 @@ packages: postgres-interval: 1.2.0 dev: false + /pg-types@4.0.2: + resolution: {integrity: sha512-cRL3JpS3lKMGsKaWndugWQoLOCoP+Cic8oseVcbr0qhPzYD5DWXK+RZ9LY9wxRf7RQia4SCwQlXk0q6FCPrVng==} + engines: {node: '>=10'} + dependencies: + pg-int8: 1.0.1 + pg-numeric: 1.0.2 + postgres-array: 3.0.2 + postgres-bytea: 3.0.0 + postgres-date: 2.1.0 + postgres-interval: 3.0.0 + postgres-range: 1.1.4 + dev: true + + /pg@8.11.3: + resolution: {integrity: sha512-+9iuvG8QfaaUrrph+kpF24cXkH1YOOUeArRNYIxq1viYHZagBxrTno7cecY1Fa44tJeZvaoG+Djpkc3JwehN5g==} + engines: {node: '>= 8.0.0'} + peerDependencies: + pg-native: '>=3.0.1' + peerDependenciesMeta: + pg-native: + optional: true + dependencies: + buffer-writer: 2.0.0 + packet-reader: 1.0.0 + pg-connection-string: 2.6.2 + pg-pool: 3.6.1(pg@8.11.3) + pg-protocol: 1.6.0 + pg-types: 2.2.0 + pgpass: 1.0.5 + optionalDependencies: + pg-cloudflare: 1.1.1 + dev: false + + /pgpass@1.0.5: + resolution: {integrity: sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==} + dependencies: + split2: 4.2.0 + dev: false + /picocolors@1.0.0: resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==} @@ -3077,16 +3212,33 @@ packages: engines: {node: '>=4'} dev: false + /postgres-array@3.0.2: + resolution: {integrity: sha512-6faShkdFugNQCLwucjPcY5ARoW1SlbnrZjmGl0IrrqewpvxvhSLHimCVzqeuULCbG0fQv7Dtk1yDbG3xv7Veog==} + engines: {node: '>=12'} + dev: true + /postgres-bytea@1.0.0: resolution: {integrity: sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==} engines: {node: '>=0.10.0'} dev: false + /postgres-bytea@3.0.0: + resolution: {integrity: sha512-CNd4jim9RFPkObHSjVHlVrxoVQXz7quwNFpz7RY1okNNme49+sVyiTvTRobiLV548Hx/hb1BG+iE7h9493WzFw==} + engines: {node: '>= 6'} + dependencies: + obuf: 1.1.2 + dev: true + /postgres-date@1.0.7: resolution: {integrity: sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==} engines: {node: '>=0.10.0'} dev: false + /postgres-date@2.1.0: + resolution: {integrity: sha512-K7Juri8gtgXVcDfZttFKVmhglp7epKb1K4pgrkLxehjqkrgPhfG6OO8LHLkfaqkbpjNRnra018XwAr1yQFWGcA==} + engines: {node: '>=12'} + dev: true + /postgres-interval@1.2.0: resolution: {integrity: sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==} engines: {node: '>=0.10.0'} @@ -3094,6 +3246,24 @@ packages: xtend: 4.0.2 dev: false + /postgres-interval@3.0.0: + resolution: {integrity: sha512-BSNDnbyZCXSxgA+1f5UU2GmwhoI0aU5yMxRGO8CdFEcY2BQF9xm/7MqKnYoM1nJDk8nONNWDk9WeSmePFhQdlw==} + engines: {node: '>=12'} + dev: true + + /postgres-range@1.1.4: + resolution: {integrity: sha512-i/hbxIE9803Alj/6ytL7UHQxRvZkI9O4Sy+J3HGc4F4oo/2eQAjTSNJ0bfxyse3bH0nuVesCk+3IRLaMtG3H6w==} + dev: true + + /preact-render-to-string@5.2.3(preact@10.11.3): + resolution: {integrity: sha512-aPDxUn5o3GhWdtJtW0svRC2SS/l8D9MAgo2+AWml+BhDImb27ALf04Q2d+AHqUUOc6RdSXFIBVa2gxzgMKgtZA==} + peerDependencies: + preact: '>=10' + dependencies: + preact: 10.11.3 + pretty-format: 3.8.0 + dev: false + /preact-render-to-string@5.2.6(preact@10.19.3): resolution: {integrity: sha512-JyhErpYOvBV1hEPwIxc/fHWXPfnEGdRKxc8gFdAZ7XV4tlzyzG847XAyEZqoDnynP88akM4eaHcSOzNcLWFguw==} peerDependencies: @@ -3103,6 +3273,10 @@ packages: pretty-format: 3.8.0 dev: false + /preact@10.11.3: + resolution: {integrity: sha512-eY93IVpod/zG3uMF22Unl8h9KkrcKIRs2EGar8hwLZZDU1lkjph303V9HZBwufh2s736U6VXuhD109LYqPoffg==} + dev: false + /preact@10.19.3: resolution: {integrity: sha512-nHHTeFVBTHRGxJXKkKu5hT8C/YWBkPso4/Gad6xuj5dbptt9iF9NZr9pHbPhBrnT2klheu7mHTxTZ/LjwJiEiQ==} dev: false @@ -3474,6 +3648,11 @@ packages: resolution: {integrity: sha512-zC8zGoGkmc8J9ndvml8Xksr1Amk9qBujgbF0JAIWO7kXr43w0h/0GJNM/Vustixu+YE8N/MTrQ7N31FvHUACxQ==} dev: true + /split2@4.2.0: + resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} + engines: {node: '>= 10.x'} + dev: false + /statuses@2.0.1: resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==} engines: {node: '>= 0.8'} diff --git a/src/app/activities/model.ts b/src/app/activities/model.ts index ed49968..f89ff95 100644 --- a/src/app/activities/model.ts +++ b/src/app/activities/model.ts @@ -5,6 +5,7 @@ type Status = "idle" | "playing" | "stopped"; type Activity = { id: string; + userId: string; name: string; description: string; started_at: number; diff --git a/src/app/activities/repository.ts b/src/app/activities/repository.ts index 287a9c4..73aab2e 100644 --- a/src/app/activities/repository.ts +++ b/src/app/activities/repository.ts @@ -1,5 +1,6 @@ import { sql } from "@vercel/postgres"; import { Activity } from "./model"; +import { auth } from "~/lib/auth"; export type ActivityRepository = { findById({ activityId }: { activityId: Activity["id"] }): Promise; @@ -23,34 +24,47 @@ export type ActivityRepository = { export const repository: ActivityRepository = { async findById({ activityId }) { - const result = await sql`SELECT * FROM activities where id = ${activityId}`; + const session = await auth(); + + const result = + await sql`SELECT * FROM activities WHERE "userId" = ${session.user.id} AND id = ${activityId}`; return result.rows[0] as unknown as Activity; }, async findAll({ order }) { + const session = await auth(); + const result = await sql.query( - `SELECT * FROM activities ORDER BY Id ${order}`, + `SELECT * FROM activities WHERE "userId" = ${session.user.id} ORDER BY Id ${order}`, ); return result.rows as unknown as Activity[]; }, async save({ activity }: { activity: Omit }) { - await sql.query(`INSERT INTO activities (name, description) - VALUES ('${activity.name} ', '${activity.description}')`); + const session = await auth(); + + await sql.query(`INSERT INTO activities ("userId", name, description) + VALUES ('${session.user.id}', '${activity.name} ', '${activity.description}')`); }, async start({ activityId, startedAt }) { + const session = await auth(); + await sql.query(`UPDATE activities SET status = 'playing', started_at = to_timestamp(${startedAt}) - WHERE id = ${activityId}`); + WHERE "userId" = ${session.user.id} + AND id = ${activityId}`); }, async stop({ activityId, stoppedAt }) { + const session = await auth(); + await sql.query(`UPDATE activities SET status = 'stopped', stopped_at = to_timestamp(${stoppedAt}) - WHERE id = ${activityId}`); + WHERE "userId" = ${session.user.id} + AND id = ${activityId}`); }, }; diff --git a/src/app/api/auth/[...nextauth]/route.ts b/src/app/api/auth/[...nextauth]/route.ts new file mode 100644 index 0000000..67532e6 --- /dev/null +++ b/src/app/api/auth/[...nextauth]/route.ts @@ -0,0 +1,6 @@ +import NextAuth from "next-auth"; +import { config } from "~/lib/auth"; + +const handler = NextAuth(config); + +export { handler as GET, handler as POST }; diff --git a/src/app/page.tsx b/src/app/page.tsx index 762039d..465d9e9 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,10 +1,19 @@ import AddButton from "~/app/activities/features/AddButton"; import AcgivityList from "./activities/widgets/ActivityList"; import BasicLayout from "~/components/ui/BasicLayout"; +import { auth } from "~/lib/auth"; +import { redirect } from "next/navigation"; +import { SIGNIN } from "~/constants/route"; export const dynamic = "force-dynamic"; export default async function HomePage() { + const session = await auth(); + + if (!session) { + redirect(SIGNIN); + } + return (
diff --git a/src/constants/env.ts b/src/constants/env.ts index 570b0dd..979efc1 100644 --- a/src/constants/env.ts +++ b/src/constants/env.ts @@ -3,4 +3,16 @@ const API_ENDPOINT = IS_API_MOCKING ? "http://localhost:9090" : process.env.NEXT_PUBLIC_API_ENDPOINT; -export { IS_API_MOCKING, API_ENDPOINT }; +const GOOGLE = { + ID: process.env.GOOGLE_ID ?? "", + SECRET: process.env.GOOGLE_SECRET ?? "", +}; + +const DB = { + HOST: process.env.POSTGRES_HOST, + DATABASE: process.env.POSTGRES_DATABASE, + USER: process.env.POSTGRES_USER, + PASSWORD: process.env.POSTGRES_PASSWORD, +}; + +export { IS_API_MOCKING, API_ENDPOINT, GOOGLE, DB }; diff --git a/src/constants/route.ts b/src/constants/route.ts new file mode 100644 index 0000000..088302a --- /dev/null +++ b/src/constants/route.ts @@ -0,0 +1,3 @@ +const SIGNIN = "/api/auth/signin"; + +export { SIGNIN }; diff --git a/src/lib/auth.ts b/src/lib/auth.ts new file mode 100644 index 0000000..da4efe8 --- /dev/null +++ b/src/lib/auth.ts @@ -0,0 +1,69 @@ +import type { + GetServerSidePropsContext, + NextApiRequest, + NextApiResponse, +} from "next"; +import type { NextAuthOptions } from "next-auth"; +import { getServerSession } from "next-auth"; +import GoogleProvider from "next-auth/providers/google"; +import PostgresAdapter from "@auth/pg-adapter"; +import { Pool } from "pg"; +import { Adapter } from "next-auth/adapters"; + +import { DB, GOOGLE } from "~/constants/env"; +import { redirect } from "next/navigation"; +import { SIGNIN } from "~/constants/route"; + +const pool = new Pool({ + host: DB.HOST, + database: DB.DATABASE, + user: DB.USER, + password: DB.PASSWORD, + max: 20, + idleTimeoutMillis: 30000, + connectionTimeoutMillis: 20000, + ssl: true, +}); + +export const config = { + // @FIXME: https://github.com/nextauthjs/next-auth/issues/9493 + adapter: PostgresAdapter(pool) as Adapter, + providers: [ + GoogleProvider({ + clientId: GOOGLE.ID, + clientSecret: GOOGLE.SECRET, + profile: (profile) => { + return { ...profile, id: profile?.sub, image: profile?.picture }; + }, + }), + ], + callbacks: { + session: async ({ session, token }) => { + if (!token?.sub) { + throw new Error("user token not have sub"); + } + + session.user.id = token.sub; + + return session; + }, + }, + session: { + strategy: "jwt", + }, +} satisfies NextAuthOptions; + +export async function auth( + ...args: + | [GetServerSidePropsContext["req"], GetServerSidePropsContext["res"]] + | [NextApiRequest, NextApiResponse] + | [] +) { + const session = await getServerSession(...args, config); + + if (!session) { + redirect(SIGNIN); + } + + return session; +} diff --git a/src/lib/ddl.sql b/src/lib/ddl.sql new file mode 100644 index 0000000..de0ebad --- /dev/null +++ b/src/lib/ddl.sql @@ -0,0 +1,67 @@ +CREATE TYPE status_enum AS ENUM +( + 'idle', + 'playing', + 'stopped' +); + +CREATE TABLE activities +( + id SERIAL, + "userId" INTEGER NOT NULL, + name VARCHAR(255) NOT NULL, + description TEXT, + started_at TIMESTAMPTZ, + stopped_at TIMESTAMPTZ, + status status_enum DEFAULT 'idle' NOT NULL, + + PRIMARY KEY (id) +); + +CREATE TABLE verification_token +( + identifier TEXT NOT NULL, + expires TIMESTAMPTZ NOT NULL, + token TEXT NOT NULL, + + PRIMARY KEY (identifier, token) +); + +CREATE TABLE accounts +( + id SERIAL, + "userId" INTEGER NOT NULL, + type VARCHAR(255) NOT NULL, + provider VARCHAR(255) NOT NULL, + "providerAccountId" VARCHAR(255) NOT NULL, + refresh_token TEXT, + access_token TEXT, + expires_at BIGINT, + id_token TEXT, + scope TEXT, + session_state TEXT, + token_type TEXT, + + PRIMARY KEY (id) +); + +CREATE TABLE sessions +( + id SERIAL, + "userId" INTEGER NOT NULL, + expires TIMESTAMPTZ NOT NULL, + "sessionToken" VARCHAR(255) NOT NULL, + + PRIMARY KEY (id) +); + +CREATE TABLE users +( + id SERIAL, + name VARCHAR(255), + email VARCHAR(255), + "emailVerified" TIMESTAMPTZ, + image TEXT, + + PRIMARY KEY (id) +); diff --git a/src/lib/ddl.ts b/src/lib/ddl.ts deleted file mode 100644 index 0f978dd..0000000 --- a/src/lib/ddl.ts +++ /dev/null @@ -1,12 +0,0 @@ -const statusEnum = `CREATE TYPE status_enum AS ENUM ('idle', 'playing', 'stopped')`; - -const activitiesTable = `CREATE TABLE activities ( - id SERIAL PRIMARY KEY, - name VARCHAR(255) NOT NULL, - description TEXT, - started_at TIMESTAMP, - stopped_at TIMESTAMP, - status status_enum DEFAULT 'idle' NOT NULL - );`; - -export { statusEnum, activitiesTable }; diff --git a/src/mocks/http.ts b/src/mocks/http.ts deleted file mode 100644 index c710f59..0000000 --- a/src/mocks/http.ts +++ /dev/null @@ -1,77 +0,0 @@ -import express from "express"; -import cors from "cors"; -import { Activity } from "~/app/activities/model"; - -const app = express(); -const port = 9090; - -app.use(express.json()); -app.use(cors({ origin: "http://localhost:3000" })); -let count = 0; - -const genId = () => { - count = (count + 1) % Number.MAX_SAFE_INTEGER; - return count.toString(); -}; -const data: { activities: Activity[] } = { - activities: [ - { - id: "0", - name: "사이드 프로젝트/giroker", - description: "시간단위로 기록하는 프로젝트", - started_at: -1, - stopped_at: -1, - status: "idle", - }, - ], -}; - -const PATHS = { - activities: "/activities", - activitiesStart: "/activities/:activityId/start", - activitiesStop: "/activities/:activityId/stop", -}; - -app.listen(port, () => console.log(`Mock server is running on port: ${port}`)); - -app.get(PATHS.activities + "/:activity_id", (req, res) => { - const id = req.params["activity_id"]; - const activity = data.activities.find((activity) => activity.id === id); - - res.json({ data: activity }); -}); - -app.get(PATHS.activities, (_, res) => { - res.json({ data }); -}); - -app.post(PATHS.activities, (req, res) => { - data.activities.unshift({ id: genId(), started_at: -1, ...req.body }); - res.status(201).end(); -}); - -app.patch(PATHS.activitiesStart, (req, res) => { - const id = req.params["activityId"]; - const index = data.activities.findIndex((activity) => activity.id === id); - - data.activities[index] = { - ...data.activities[index], - ...req.body, - status: "playing", - }; - - res.status(204).end(); -}); - -app.patch(PATHS.activitiesStop, (req, res) => { - const id = req.params["activityId"]; - const index = data.activities.findIndex((activity) => activity.id === id); - - data.activities[index] = { - ...data.activities[index], - ...req.body, - status: "stopped", - }; - - res.status(204).end(); -}); diff --git a/src/types/auth.ts b/src/types/auth.ts new file mode 100644 index 0000000..41abda7 --- /dev/null +++ b/src/types/auth.ts @@ -0,0 +1,9 @@ +import { DefaultSession } from "next-auth"; + +declare module "next-auth" { + interface Session { + user: { + id: string; + } & DefaultSession["user"]; + } +}