...
Some checks reported errors
continuous-integration/drone/push Build was killed

This commit is contained in:
2025-11-07 10:43:41 +08:00
parent 6389135156
commit b30f9fb0c3
10 changed files with 507 additions and 4 deletions

2
.gitignore vendored
View File

@@ -43,3 +43,5 @@ next-env.d.ts
.env
build.sh
test.ts

334
package-lock.json generated
View File

@@ -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",

View File

@@ -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",

View File

@@ -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 };

View File

@@ -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 };

View File

@@ -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 });
}

25
src/app/login/page.tsx Normal file
View File

@@ -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<HTMLInputElement>(null);
const passwordRef = useRef<HTMLInputElement>(null);
return (
<NavbarCenterWrapper>
<ACard className="md:border-2 border-gray-200 flex items-center justify-center flex-col gap-8">
<h1 className="text-2xl md:text-4xl font-bold">Login</h1>
<form className="flex flex-col gap-2 md:text-xl">
<Input ref={usernameRef} placeholder="username" type="text" />
<Input ref={passwordRef} placeholder="password" type="password" />
<LightButton>Submit</LightButton>
</form>
</ACard>
</NavbarCenterWrapper>
);
}

22
src/components/Input.tsx Normal file
View File

@@ -0,0 +1,22 @@
interface Props {
ref?: React.Ref<HTMLInputElement>;
placeholder?: string;
type?: string;
className?: string;
}
export default function Input({
ref,
placeholder = "",
type = "text",
className = "",
}: Props) {
return (
<input
ref={ref}
placeholder={placeholder}
type={type}
className={`block focus:outline-none border-b-2 border-gray-600 ${className}`}
/>
);
}

View File

@@ -8,7 +8,7 @@ interface ACardProps {
export default function ACard({ children, className }: ACardProps) {
return (
<div
className={`${className} w-[95dvw] md:w-[61vw] h-96 p-2 shadow-2xl bg-white rounded-xl`}
className={`${className} w-[95dvw] md:w-[61vw] h-96 p-2 md:shadow-2xl rounded-xl`}
>
{children}
</div>

37
src/lib/db.ts Normal file
View File

@@ -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 {
}