From b30f9fb0c3f5a95b27d78f7ad9b497bc41e5a634 Mon Sep 17 00:00:00 2001 From: goddonebianu Date: Fri, 7 Nov 2025 10:43:41 +0800 Subject: [PATCH] ... --- .gitignore | 4 +- package-lock.json | 334 +++++++++++++++++++++++- package.json | 5 + src/app/api/auth/[...nextauth]/route.ts | 53 ++++ src/app/api/users/[...slug]/route.ts | 22 ++ src/app/api/users/route.ts | 7 + src/app/login/page.tsx | 25 ++ src/components/Input.tsx | 22 ++ src/components/cards/ACard.tsx | 2 +- src/lib/db.ts | 37 +++ 10 files changed, 507 insertions(+), 4 deletions(-) create mode 100644 src/app/api/auth/[...nextauth]/route.ts create mode 100644 src/app/api/users/[...slug]/route.ts create mode 100644 src/app/api/users/route.ts create mode 100644 src/app/login/page.tsx create mode 100644 src/components/Input.tsx create mode 100644 src/lib/db.ts diff --git a/.gitignore b/.gitignore index 597a03d..0ca3c82 100644 --- a/.gitignore +++ b/.gitignore @@ -42,4 +42,6 @@ next-env.d.ts .env -build.sh \ No newline at end of file +build.sh + +test.ts \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 0aa08a0..6abab2b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,9 +9,12 @@ "version": "0.1.0", "license": "GPL-3.0-only", "dependencies": { + "bcryptjs": "^3.0.3", "edge-tts-universal": "^1.3.2", "next": "15.5.3", + "next-auth": "^4.24.13", "next-intl": "^4.4.0", + "pg": "^8.16.3", "react": "19.1.0", "react-dom": "19.1.0", "zod": "^3.25.76" @@ -19,7 +22,9 @@ "devDependencies": { "@eslint/eslintrc": "^3", "@tailwindcss/postcss": "^4", + "@types/bcryptjs": "^2.4.6", "@types/node": "^20", + "@types/pg": "^8.15.6", "@types/react": "^19", "@types/react-dom": "^19", "eslint": "^9", @@ -1721,8 +1726,6 @@ "resolved": "https://mirrors.cloud.tencent.com/npm/@babel/runtime/-/runtime-7.28.4.tgz", "integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==", "license": "MIT", - "optional": true, - "peer": true, "engines": { "node": ">=6.9.0" } @@ -3521,6 +3524,15 @@ "node": ">=12.4.0" } }, + "node_modules/@panva/hkdf": { + "version": "1.2.1", + "resolved": "https://mirrors.cloud.tencent.com/npm/@panva/hkdf/-/hkdf-1.2.1.tgz", + "integrity": "sha512-6oclG6Y3PiDFcoyk8srjLfVKyMfVCKJ27JwNPViuXziFpmdz+MZnZN/aKY0JGXgYuO/VghU0jcOAZgWXZ1Dmrw==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, "node_modules/@peculiar/asn1-schema": { "version": "2.5.0", "resolved": "https://mirrors.cloud.tencent.com/npm/@peculiar/asn1-schema/-/asn1-schema-2.5.0.tgz", @@ -4282,6 +4294,13 @@ "@babel/types": "^7.28.2" } }, + "node_modules/@types/bcryptjs": { + "version": "2.4.6", + "resolved": "https://mirrors.cloud.tencent.com/npm/@types/bcryptjs/-/bcryptjs-2.4.6.tgz", + "integrity": "sha512-9xlo6R2qDs5uixm0bcIqCeMCE6HiQsIyel9KQySStiyqNl2tnj2mP3DX1Nf56MD6KMenNNlBBsy3LJ7gUEQPXQ==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/estree": { "version": "1.0.8", "resolved": "https://mirrors.cloud.tencent.com/npm/@types/estree/-/estree-1.0.8.tgz", @@ -4354,6 +4373,18 @@ "undici-types": "~6.21.0" } }, + "node_modules/@types/pg": { + "version": "8.15.6", + "resolved": "https://mirrors.cloud.tencent.com/npm/@types/pg/-/pg-8.15.6.tgz", + "integrity": "sha512-NoaMtzhxOrubeL/7UZuNTrejB4MPAJ0RpxZqXQf2qXuVlTPuG6Y8p4u9dKRaue4yjmC7ZhzVO2/Yyyn25znrPQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "pg-protocol": "*", + "pg-types": "^2.2.0" + } + }, "node_modules/@types/react": { "version": "19.2.2", "resolved": "https://mirrors.cloud.tencent.com/npm/@types/react/-/react-19.2.2.tgz", @@ -5799,6 +5830,15 @@ "baseline-browser-mapping": "dist/cli.js" } }, + "node_modules/bcryptjs": { + "version": "3.0.3", + "resolved": "https://mirrors.cloud.tencent.com/npm/bcryptjs/-/bcryptjs-3.0.3.tgz", + "integrity": "sha512-GlF5wPWnSa/X5LKM1o0wz0suXIINz1iHRLvTS+sLyi7XPbe5ycmYI3DlZqVGZZtDgl4DmasFg7gOB3JYbphV5g==", + "license": "BSD-3-Clause", + "bin": { + "bcrypt": "bin/bcrypt" + } + }, "node_modules/better-opn": { "version": "3.0.2", "resolved": "https://mirrors.cloud.tencent.com/npm/better-opn/-/better-opn-3.0.2.tgz", @@ -6430,6 +6470,15 @@ "optional": true, "peer": true }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://mirrors.cloud.tencent.com/npm/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/core-js-compat": { "version": "3.46.0", "resolved": "https://mirrors.cloud.tencent.com/npm/core-js-compat/-/core-js-compat-3.46.0.tgz", @@ -10247,6 +10296,15 @@ "jiti": "lib/jiti-cli.mjs" } }, + "node_modules/jose": { + "version": "4.15.9", + "resolved": "https://mirrors.cloud.tencent.com/npm/jose/-/jose-4.15.9.tgz", + "integrity": "sha512-1vUQX+IdDMVPj4k8kOxgUqlcK518yluMuGZwqlr44FS1ppZB/5GWh4rZG89erpOBOJjU/OBsnCVFfapsRz6nEA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://mirrors.cloud.tencent.com/npm/js-tokens/-/js-tokens-4.0.0.tgz", @@ -11548,6 +11606,47 @@ } } }, + "node_modules/next-auth": { + "version": "4.24.13", + "resolved": "https://mirrors.cloud.tencent.com/npm/next-auth/-/next-auth-4.24.13.tgz", + "integrity": "sha512-sgObCfcfL7BzIK76SS5TnQtc3yo2Oifp/yIpfv6fMfeBOiBJkDWF3A2y9+yqnmJ4JKc2C+nMjSjmgDeTwgN1rQ==", + "license": "ISC", + "dependencies": { + "@babel/runtime": "^7.20.13", + "@panva/hkdf": "^1.0.2", + "cookie": "^0.7.0", + "jose": "^4.15.5", + "oauth": "^0.9.15", + "openid-client": "^5.4.0", + "preact": "^10.6.3", + "preact-render-to-string": "^5.1.19", + "uuid": "^8.3.2" + }, + "peerDependencies": { + "@auth/core": "0.34.3", + "next": "^12.2.5 || ^13 || ^14 || ^15 || ^16", + "nodemailer": "^7.0.7", + "react": "^17.0.2 || ^18 || ^19", + "react-dom": "^17.0.2 || ^18 || ^19" + }, + "peerDependenciesMeta": { + "@auth/core": { + "optional": true + }, + "nodemailer": { + "optional": true + } + } + }, + "node_modules/next-auth/node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://mirrors.cloud.tencent.com/npm/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/next-intl": { "version": "4.4.0", "resolved": "https://mirrors.cloud.tencent.com/npm/next-intl/-/next-intl-4.4.0.tgz", @@ -11686,6 +11785,12 @@ "optional": true, "peer": true }, + "node_modules/oauth": { + "version": "0.9.15", + "resolved": "https://mirrors.cloud.tencent.com/npm/oauth/-/oauth-0.9.15.tgz", + "integrity": "sha512-a5ERWK1kh38ExDEfoO6qUHJb32rd7aYmPHuyCu3Fta/cnICvYmgd2uhuKXvPD+PXB+gCEYYEaQdIRAjCOwAKNA==", + "license": "MIT" + }, "node_modules/ob1": { "version": "0.83.2", "resolved": "https://mirrors.cloud.tencent.com/npm/ob1/-/ob1-0.83.2.tgz", @@ -11710,6 +11815,15 @@ "node": ">=0.10.0" } }, + "node_modules/object-hash": { + "version": "2.2.0", + "resolved": "https://mirrors.cloud.tencent.com/npm/object-hash/-/object-hash-2.2.0.tgz", + "integrity": "sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==", + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, "node_modules/object-inspect": { "version": "1.13.4", "resolved": "https://mirrors.cloud.tencent.com/npm/object-inspect/-/object-inspect-1.13.4.tgz", @@ -11823,6 +11937,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/oidc-token-hash": { + "version": "5.2.0", + "resolved": "https://mirrors.cloud.tencent.com/npm/oidc-token-hash/-/oidc-token-hash-5.2.0.tgz", + "integrity": "sha512-6gj2m8cJZ+iSW8bm0FXdGF0YhIQbKrfP4yWTNzxc31U6MOjfEmB1rHvlYvxI1B7t7BCi1F2vYTT6YhtQRG4hxw==", + "license": "MIT", + "engines": { + "node": "^10.13.0 || >=12.0.0" + } + }, "node_modules/on-finished": { "version": "2.3.0", "resolved": "https://mirrors.cloud.tencent.com/npm/on-finished/-/on-finished-2.3.0.tgz", @@ -11891,6 +12014,39 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/openid-client": { + "version": "5.7.1", + "resolved": "https://mirrors.cloud.tencent.com/npm/openid-client/-/openid-client-5.7.1.tgz", + "integrity": "sha512-jDBPgSVfTnkIh71Hg9pRvtJc6wTwqjRkN88+gCFtYWrlP4Yx2Dsrow8uPi3qLr/aeymPF3o2+dS+wOpglK04ew==", + "license": "MIT", + "dependencies": { + "jose": "^4.15.9", + "lru-cache": "^6.0.0", + "object-hash": "^2.2.0", + "oidc-token-hash": "^5.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "node_modules/openid-client/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://mirrors.cloud.tencent.com/npm/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/openid-client/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://mirrors.cloud.tencent.com/npm/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "license": "ISC" + }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://mirrors.cloud.tencent.com/npm/optionator/-/optionator-0.9.4.tgz", @@ -12209,6 +12365,95 @@ "optional": true, "peer": true }, + "node_modules/pg": { + "version": "8.16.3", + "resolved": "https://mirrors.cloud.tencent.com/npm/pg/-/pg-8.16.3.tgz", + "integrity": "sha512-enxc1h0jA/aq5oSDMvqyW3q89ra6XIIDZgCX9vkMrnz5DFTw/Ny3Li2lFQ+pt3L6MCgm/5o2o8HW9hiJji+xvw==", + "license": "MIT", + "dependencies": { + "pg-connection-string": "^2.9.1", + "pg-pool": "^3.10.1", + "pg-protocol": "^1.10.3", + "pg-types": "2.2.0", + "pgpass": "1.0.5" + }, + "engines": { + "node": ">= 16.0.0" + }, + "optionalDependencies": { + "pg-cloudflare": "^1.2.7" + }, + "peerDependencies": { + "pg-native": ">=3.0.1" + }, + "peerDependenciesMeta": { + "pg-native": { + "optional": true + } + } + }, + "node_modules/pg-cloudflare": { + "version": "1.2.7", + "resolved": "https://mirrors.cloud.tencent.com/npm/pg-cloudflare/-/pg-cloudflare-1.2.7.tgz", + "integrity": "sha512-YgCtzMH0ptvZJslLM1ffsY4EuGaU0cx4XSdXLRFae8bPP4dS5xL1tNB3k2o/N64cHJpwU7dxKli/nZ2lUa5fLg==", + "license": "MIT", + "optional": true + }, + "node_modules/pg-connection-string": { + "version": "2.9.1", + "resolved": "https://mirrors.cloud.tencent.com/npm/pg-connection-string/-/pg-connection-string-2.9.1.tgz", + "integrity": "sha512-nkc6NpDcvPVpZXxrreI/FOtX3XemeLl8E0qFr6F2Lrm/I8WOnaWNhIPK2Z7OHpw7gh5XJThi6j6ppgNoaT1w4w==", + "license": "MIT" + }, + "node_modules/pg-int8": { + "version": "1.0.1", + "resolved": "https://mirrors.cloud.tencent.com/npm/pg-int8/-/pg-int8-1.0.1.tgz", + "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==", + "license": "ISC", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/pg-pool": { + "version": "3.10.1", + "resolved": "https://mirrors.cloud.tencent.com/npm/pg-pool/-/pg-pool-3.10.1.tgz", + "integrity": "sha512-Tu8jMlcX+9d8+QVzKIvM/uJtp07PKr82IUOYEphaWcoBhIYkoHpLXN3qO59nAI11ripznDsEzEv8nUxBVWajGg==", + "license": "MIT", + "peerDependencies": { + "pg": ">=8.0" + } + }, + "node_modules/pg-protocol": { + "version": "1.10.3", + "resolved": "https://mirrors.cloud.tencent.com/npm/pg-protocol/-/pg-protocol-1.10.3.tgz", + "integrity": "sha512-6DIBgBQaTKDJyxnXaLiLR8wBpQQcGWuAESkRBX/t6OwA8YsqP+iVSiond2EDy6Y/dsGk8rh/jtax3js5NeV7JQ==", + "license": "MIT" + }, + "node_modules/pg-types": { + "version": "2.2.0", + "resolved": "https://mirrors.cloud.tencent.com/npm/pg-types/-/pg-types-2.2.0.tgz", + "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", + "license": "MIT", + "dependencies": { + "pg-int8": "1.0.1", + "postgres-array": "~2.0.0", + "postgres-bytea": "~1.0.0", + "postgres-date": "~1.0.4", + "postgres-interval": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/pgpass": { + "version": "1.0.5", + "resolved": "https://mirrors.cloud.tencent.com/npm/pgpass/-/pgpass-1.0.5.tgz", + "integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==", + "license": "MIT", + "dependencies": { + "split2": "^4.1.0" + } + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://mirrors.cloud.tencent.com/npm/picocolors/-/picocolors-1.1.1.tgz", @@ -12305,6 +12550,73 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/postgres-array": { + "version": "2.0.0", + "resolved": "https://mirrors.cloud.tencent.com/npm/postgres-array/-/postgres-array-2.0.0.tgz", + "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/postgres-bytea": { + "version": "1.0.0", + "resolved": "https://mirrors.cloud.tencent.com/npm/postgres-bytea/-/postgres-bytea-1.0.0.tgz", + "integrity": "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-date": { + "version": "1.0.7", + "resolved": "https://mirrors.cloud.tencent.com/npm/postgres-date/-/postgres-date-1.0.7.tgz", + "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-interval": { + "version": "1.2.0", + "resolved": "https://mirrors.cloud.tencent.com/npm/postgres-interval/-/postgres-interval-1.2.0.tgz", + "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", + "license": "MIT", + "dependencies": { + "xtend": "^4.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/preact": { + "version": "10.27.2", + "resolved": "https://mirrors.cloud.tencent.com/npm/preact/-/preact-10.27.2.tgz", + "integrity": "sha512-5SYSgFKSyhCbk6SrXyMpqjb5+MQBgfvEKE/OC+PujcY34sOpqtr+0AZQtPYx5IA6VxynQ7rUPCtKzyovpj9Bpg==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/preact" + } + }, + "node_modules/preact-render-to-string": { + "version": "5.2.6", + "resolved": "https://mirrors.cloud.tencent.com/npm/preact-render-to-string/-/preact-render-to-string-5.2.6.tgz", + "integrity": "sha512-JyhErpYOvBV1hEPwIxc/fHWXPfnEGdRKxc8gFdAZ7XV4tlzyzG847XAyEZqoDnynP88akM4eaHcSOzNcLWFguw==", + "license": "MIT", + "dependencies": { + "pretty-format": "^3.8.0" + }, + "peerDependencies": { + "preact": ">=10" + } + }, + "node_modules/preact-render-to-string/node_modules/pretty-format": { + "version": "3.8.0", + "resolved": "https://mirrors.cloud.tencent.com/npm/pretty-format/-/pretty-format-3.8.0.tgz", + "integrity": "sha512-WuxUnVtlWL1OfZFQFuqvnvs6MiAGk9UNsBostyBOB0Is9wb5uRESevA6rnl/rkksXaGX3GzZhPup5d6Vp1nFew==", + "license": "MIT" + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://mirrors.cloud.tencent.com/npm/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -13568,6 +13880,15 @@ "node": ">=0.10.0" } }, + "node_modules/split2": { + "version": "4.2.0", + "resolved": "https://mirrors.cloud.tencent.com/npm/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "license": "ISC", + "engines": { + "node": ">= 10.x" + } + }, "node_modules/sprintf-js": { "version": "1.0.3", "resolved": "https://mirrors.cloud.tencent.com/npm/sprintf-js/-/sprintf-js-1.0.3.tgz", @@ -15172,6 +15493,15 @@ "node": ">=8.0" } }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://mirrors.cloud.tencent.com/npm/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "license": "MIT", + "engines": { + "node": ">=0.4" + } + }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://mirrors.cloud.tencent.com/npm/y18n/-/y18n-5.0.8.tgz", diff --git a/package.json b/package.json index 5f78ef7..1a6d411 100644 --- a/package.json +++ b/package.json @@ -10,9 +10,12 @@ "lint": "eslint" }, "dependencies": { + "bcryptjs": "^3.0.3", "edge-tts-universal": "^1.3.2", "next": "15.5.3", + "next-auth": "^4.24.13", "next-intl": "^4.4.0", + "pg": "^8.16.3", "react": "19.1.0", "react-dom": "19.1.0", "zod": "^3.25.76" @@ -20,7 +23,9 @@ "devDependencies": { "@eslint/eslintrc": "^3", "@tailwindcss/postcss": "^4", + "@types/bcryptjs": "^2.4.6", "@types/node": "^20", + "@types/pg": "^8.15.6", "@types/react": "^19", "@types/react-dom": "^19", "eslint": "^9", diff --git a/src/app/api/auth/[...nextauth]/route.ts b/src/app/api/auth/[...nextauth]/route.ts new file mode 100644 index 0000000..84efd22 --- /dev/null +++ b/src/app/api/auth/[...nextauth]/route.ts @@ -0,0 +1,53 @@ +import { pool } from "@/lib/db"; +import NextAuth, { SessionStrategy } from "next-auth"; +import CredentialsProvider from "next-auth/providers/credentials"; +import bcrypt from "bcryptjs"; + +export const authOptions = { + providers: [ + CredentialsProvider({ + name: "Credentials", + credentials: { + username: { label: "Username", type: "text", placeholder: "jsmith" }, + password: { label: "Password", type: "password" }, + }, + async authorize(credentials) { + if (!credentials?.username || !credentials?.password) { + return null; + } + + try { + const result = await pool.query( + "SELECT * FROM users WHERE username = $1", + [credentials.username], + ); + + const user = result.rows[0]; + + if (!user) { + return null; + } + + const isValidPassword = await bcrypt.compare( + credentials.password, + user.password, + ); + if (!isValidPassword) return null; + + return { + id: user.id, + username: user.username, + }; + } catch (error) { + console.error("Auth error:", error); + return null; + } + }, + }), + ], + session: { strategy: "jwt" as SessionStrategy }, + pages: { signIn: "/login" }, +}; + +const handler = NextAuth(authOptions); +export { handler as GET, handler as POST }; diff --git a/src/app/api/users/[...slug]/route.ts b/src/app/api/users/[...slug]/route.ts new file mode 100644 index 0000000..7dd6325 --- /dev/null +++ b/src/app/api/users/[...slug]/route.ts @@ -0,0 +1,22 @@ +import { UserController } from "@/lib/db"; +import { NextRequest } from "next/server"; + +async function handler( + req: NextRequest, + { params }: { params: { slug: string[] } }, +) { + const { slug } = params; + if (slug.length !== 1) { + return new Response("Invalid slug", { status: 400 }); + } + + if (req.method === "GET") { + return UserController.getUsers(); + } else if (req.method === "POST") { + return UserController.createUser(await req.json()); + } else { + return new Response("Method not allowed", { status: 405 }); + } +} + +export { handler as GET, handler as POST }; diff --git a/src/app/api/users/route.ts b/src/app/api/users/route.ts new file mode 100644 index 0000000..d9c0520 --- /dev/null +++ b/src/app/api/users/route.ts @@ -0,0 +1,7 @@ +import { UserController } from "@/lib/db"; +import { NextRequest, NextResponse } from "next/server"; + +export async function GET() { + const users = await UserController.getUsers(); + return NextResponse.json(users, { status: 200 }); +} diff --git a/src/app/login/page.tsx b/src/app/login/page.tsx new file mode 100644 index 0000000..964274a --- /dev/null +++ b/src/app/login/page.tsx @@ -0,0 +1,25 @@ +"use client"; + +import LightButton from "@/components/buttons/LightButton"; +import ACard from "@/components/cards/ACard"; +import Input from "@/components/Input"; +import NavbarCenterWrapper from "@/components/NavbarCenterWrapper"; +import { useRef } from "react"; + +export default function Login() { + const usernameRef = useRef(null); + const passwordRef = useRef(null); + + return ( + + +

Login

+
+ + + Submit +
+
+
+ ); +} diff --git a/src/components/Input.tsx b/src/components/Input.tsx new file mode 100644 index 0000000..ccd6afe --- /dev/null +++ b/src/components/Input.tsx @@ -0,0 +1,22 @@ +interface Props { + ref?: React.Ref; + placeholder?: string; + type?: string; + className?: string; +} + +export default function Input({ + ref, + placeholder = "", + type = "text", + className = "", +}: Props) { + return ( + + ); +} diff --git a/src/components/cards/ACard.tsx b/src/components/cards/ACard.tsx index 08714aa..798cd1a 100644 --- a/src/components/cards/ACard.tsx +++ b/src/components/cards/ACard.tsx @@ -8,7 +8,7 @@ interface ACardProps { export default function ACard({ children, className }: ACardProps) { return (
{children}
diff --git a/src/lib/db.ts b/src/lib/db.ts new file mode 100644 index 0000000..0b46399 --- /dev/null +++ b/src/lib/db.ts @@ -0,0 +1,37 @@ +import bcrypt from "bcryptjs"; +import { Pool } from "pg"; + +export const pool = new Pool({ + user: "postgres", + host: "localhost", + max: 20, + idleTimeoutMillis: 3000, + connectionTimeoutMillis: 2000, + maxLifetimeSeconds: 60, +}); + +export class UserController { + static async createUser(username: string, password: string) { + const encodedPassword = await bcrypt.hash(password, 10); + try { + await pool.query( + "INSERT INTO users (username, password) VALUES ($1, $2)", + [username, encodedPassword], + ); + } catch (e) { + console.log(e); + } + } + static async getUserByUsername(username: string) { + try { + const user = await pool.query("SELECT * FROM users WHERE username = $1", [username]); + return user.rows[0]; + } catch (e) { + console.log(e); + } + } +} + +export class FolderController { + +}