feat: start user authentication
This commit is contained in:
parent
1694ded668
commit
0b8aa37351
@ -1 +1,2 @@
|
||||
DATABASE_URL=postgres://postgres:postgres@localhost:5432/postgres
|
||||
JWT_SECRET=verysecret
|
||||
|
@ -1,4 +0,0 @@
|
||||
CREATE TABLE "users" (
|
||||
"id" integer PRIMARY KEY GENERATED ALWAYS AS IDENTITY (sequence name "users_id_seq" INCREMENT BY 1 MINVALUE 1 MAXVALUE 2147483647 START WITH 1 CACHE 1),
|
||||
"name" varchar(255) NOT NULL
|
||||
);
|
@ -1,55 +0,0 @@
|
||||
{
|
||||
"id": "af1f84a8-11fe-4a15-8062-423752d0b07c",
|
||||
"prevId": "00000000-0000-0000-0000-000000000000",
|
||||
"version": "7",
|
||||
"dialect": "postgresql",
|
||||
"tables": {
|
||||
"public.users": {
|
||||
"name": "users",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "integer",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"identity": {
|
||||
"type": "always",
|
||||
"name": "users_id_seq",
|
||||
"schema": "public",
|
||||
"increment": "1",
|
||||
"startWith": "1",
|
||||
"minValue": "1",
|
||||
"maxValue": "2147483647",
|
||||
"cache": "1",
|
||||
"cycle": false
|
||||
}
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
}
|
||||
},
|
||||
"enums": {},
|
||||
"schemas": {},
|
||||
"sequences": {},
|
||||
"roles": {},
|
||||
"policies": {},
|
||||
"views": {},
|
||||
"_meta": {
|
||||
"columns": {},
|
||||
"schemas": {},
|
||||
"tables": {}
|
||||
}
|
||||
}
|
@ -1,13 +1,5 @@
|
||||
{
|
||||
"version": "7",
|
||||
"dialect": "postgresql",
|
||||
"entries": [
|
||||
{
|
||||
"idx": 0,
|
||||
"version": "7",
|
||||
"when": 1751547902605,
|
||||
"tag": "0000_dazzling_captain_flint",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
"entries": []
|
||||
}
|
188
package-lock.json
generated
188
package-lock.json
generated
@ -8,7 +8,9 @@
|
||||
"name": "rhyme",
|
||||
"version": "0.1.0",
|
||||
"dependencies": {
|
||||
"bcrypt": "^6.0.0",
|
||||
"drizzle-orm": "^0.44.2",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"lucide-react": "^0.525.0",
|
||||
"next": "15.3.4",
|
||||
"pg": "^8.16.3",
|
||||
@ -18,6 +20,8 @@
|
||||
"devDependencies": {
|
||||
"@eslint/eslintrc": "^3",
|
||||
"@tailwindcss/postcss": "^4",
|
||||
"@types/bcrypt": "^5.0.2",
|
||||
"@types/jsonwebtoken": "^9.0.10",
|
||||
"@types/node": "^20",
|
||||
"@types/react": "^19",
|
||||
"@types/react-dom": "^19",
|
||||
@ -2145,6 +2149,16 @@
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/bcrypt": {
|
||||
"version": "5.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/bcrypt/-/bcrypt-5.0.2.tgz",
|
||||
"integrity": "sha512-6atioO8Y75fNcbmj0G7UjI9lXN2pQ/IGJ2FWT4a/btd0Lk9lQalHLKhkgKVZ3r+spnmWUKfbMi1GEe9wyHQfNQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/estree": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
|
||||
@ -2166,6 +2180,24 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/jsonwebtoken": {
|
||||
"version": "9.0.10",
|
||||
"resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.10.tgz",
|
||||
"integrity": "sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/ms": "*",
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/ms": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz",
|
||||
"integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "20.19.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.4.tgz",
|
||||
@ -3045,6 +3077,20 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/bcrypt": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-6.0.0.tgz",
|
||||
"integrity": "sha512-cU8v/EGSrnH+HnxV2z0J7/blxH8gq7Xh2JFT6Aroax7UohdmiJJlxApMxtKfuI7z68NvvVcmR78k2LbT6efhRg==",
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"node-addon-api": "^8.3.0",
|
||||
"node-gyp-build": "^4.8.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 18"
|
||||
}
|
||||
},
|
||||
"node_modules/brace-expansion": {
|
||||
"version": "1.1.12",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
|
||||
@ -3069,6 +3115,12 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/buffer-equal-constant-time": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
|
||||
"integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==",
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/buffer-from": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
|
||||
@ -3575,6 +3627,15 @@
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/ecdsa-sig-formatter": {
|
||||
"version": "1.0.11",
|
||||
"resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
|
||||
"integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"safe-buffer": "^5.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/emoji-regex": {
|
||||
"version": "9.2.2",
|
||||
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
|
||||
@ -5238,6 +5299,28 @@
|
||||
"json5": "lib/cli.js"
|
||||
}
|
||||
},
|
||||
"node_modules/jsonwebtoken": {
|
||||
"version": "9.0.2",
|
||||
"resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz",
|
||||
"integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"jws": "^3.2.2",
|
||||
"lodash.includes": "^4.3.0",
|
||||
"lodash.isboolean": "^3.0.3",
|
||||
"lodash.isinteger": "^4.0.4",
|
||||
"lodash.isnumber": "^3.0.3",
|
||||
"lodash.isplainobject": "^4.0.6",
|
||||
"lodash.isstring": "^4.0.1",
|
||||
"lodash.once": "^4.0.0",
|
||||
"ms": "^2.1.1",
|
||||
"semver": "^7.5.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12",
|
||||
"npm": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/jsx-ast-utils": {
|
||||
"version": "3.3.5",
|
||||
"resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz",
|
||||
@ -5254,6 +5337,27 @@
|
||||
"node": ">=4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/jwa": {
|
||||
"version": "1.4.2",
|
||||
"resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.2.tgz",
|
||||
"integrity": "sha512-eeH5JO+21J78qMvTIDdBXidBd6nG2kZjg5Ohz/1fpa28Z4CcsWUzJ1ZZyFq/3z3N17aZy+ZuBoHljASbL1WfOw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"buffer-equal-constant-time": "^1.0.1",
|
||||
"ecdsa-sig-formatter": "1.0.11",
|
||||
"safe-buffer": "^5.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/jws": {
|
||||
"version": "3.2.2",
|
||||
"resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz",
|
||||
"integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"jwa": "^1.4.1",
|
||||
"safe-buffer": "^5.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/keyv": {
|
||||
"version": "4.5.4",
|
||||
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
|
||||
@ -5553,6 +5657,42 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/lodash.includes": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz",
|
||||
"integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lodash.isboolean": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz",
|
||||
"integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lodash.isinteger": {
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz",
|
||||
"integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lodash.isnumber": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz",
|
||||
"integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lodash.isplainobject": {
|
||||
"version": "4.0.6",
|
||||
"resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
|
||||
"integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lodash.isstring": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz",
|
||||
"integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lodash.merge": {
|
||||
"version": "4.6.2",
|
||||
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
|
||||
@ -5560,6 +5700,12 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lodash.once": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz",
|
||||
"integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/loose-envify": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
|
||||
@ -5692,7 +5838,6 @@
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/nanoid": {
|
||||
@ -5818,6 +5963,26 @@
|
||||
"node": "^10 || ^12 || >=14"
|
||||
}
|
||||
},
|
||||
"node_modules/node-addon-api": {
|
||||
"version": "8.4.0",
|
||||
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.4.0.tgz",
|
||||
"integrity": "sha512-D9DI/gXHvVmjHS08SVch0Em8G5S1P+QWtU31appcKT/8wFSPRcdHadIFSAntdMMVM5zz+/DL+bL/gz3UDppqtg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "^18 || ^20 || >= 21"
|
||||
}
|
||||
},
|
||||
"node_modules/node-gyp-build": {
|
||||
"version": "4.8.4",
|
||||
"resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz",
|
||||
"integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==",
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"node-gyp-build": "bin.js",
|
||||
"node-gyp-build-optional": "optional.js",
|
||||
"node-gyp-build-test": "build-test.js"
|
||||
}
|
||||
},
|
||||
"node_modules/object-assign": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||
@ -6456,6 +6621,26 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/safe-buffer": {
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
|
||||
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
],
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/safe-push-apply": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz",
|
||||
@ -6501,7 +6686,6 @@
|
||||
"version": "7.7.2",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz",
|
||||
"integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==",
|
||||
"devOptional": true,
|
||||
"license": "ISC",
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
|
@ -9,7 +9,9 @@
|
||||
"lint": "next lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"bcrypt": "^6.0.0",
|
||||
"drizzle-orm": "^0.44.2",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"lucide-react": "^0.525.0",
|
||||
"next": "15.3.4",
|
||||
"pg": "^8.16.3",
|
||||
@ -19,6 +21,8 @@
|
||||
"devDependencies": {
|
||||
"@eslint/eslintrc": "^3",
|
||||
"@tailwindcss/postcss": "^4",
|
||||
"@types/bcrypt": "^5.0.2",
|
||||
"@types/jsonwebtoken": "^9.0.10",
|
||||
"@types/node": "^20",
|
||||
"@types/react": "^19",
|
||||
"@types/react-dom": "^19",
|
||||
|
@ -1,9 +1,14 @@
|
||||
import Link from "next/link";
|
||||
import Editor from "@/components/Editor";
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
<div className="flex justify-center">
|
||||
<div className="flex flex-col items-center gap-8">
|
||||
<Editor />
|
||||
<i className="text-center text-sm text-neutral-400">
|
||||
Changes are not saved!<br />
|
||||
<Link href="/login" className="font-bold hover:underline">Log in</Link> to save your notes.
|
||||
</i>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { Metadata } from "next";
|
||||
import LoginForm from "@/components/forms/LoginForm";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Rhyme – Log In",
|
||||
@ -9,7 +10,7 @@ export default function About() {
|
||||
return (
|
||||
<>
|
||||
<h1 className="font-bold text-2xl mb-4">Login Page</h1>
|
||||
<span>TODO</span>
|
||||
<LoginForm />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
70
src/app/actions.ts
Normal file
70
src/app/actions.ts
Normal file
@ -0,0 +1,70 @@
|
||||
"use server";
|
||||
|
||||
import { cookies } from "next/headers";
|
||||
import { redirect } from "next/navigation";
|
||||
import { eq } from "drizzle-orm";
|
||||
import { db } from "@/lib/db";
|
||||
import { usersTable } from "@/lib/db/schema";
|
||||
import jwt from "jsonwebtoken";
|
||||
import bcrypt from "bcrypt";
|
||||
|
||||
const JWT_SECRET = process.env.JWT_SECRET!;
|
||||
|
||||
export async function login(_prevState: unknown, formData: FormData) {
|
||||
const username = formData.get("username") as string;
|
||||
const password = formData.get("password") as string;
|
||||
|
||||
const users = await db.select()
|
||||
.from(usersTable)
|
||||
.where(eq(usersTable.username, username));
|
||||
|
||||
if (users.length === 0) {
|
||||
return { error: "Invalid username or password" };
|
||||
}
|
||||
|
||||
const user = users[0];
|
||||
|
||||
if (!bcrypt.compareSync(password, user.password)) {
|
||||
return { error: "Invalid username or password" };
|
||||
}
|
||||
|
||||
const token = jwt.sign({ sub: user.username }, JWT_SECRET, { expiresIn: "7d" });
|
||||
|
||||
const cookieStore = await cookies();
|
||||
cookieStore.set("session", token, {
|
||||
httpOnly: true,
|
||||
path: "/",
|
||||
maxAge: 60 * 60 * 24 * 7,
|
||||
});
|
||||
|
||||
redirect("/");
|
||||
}
|
||||
|
||||
export async function register(_prevState: unknown, formData: FormData) {
|
||||
const username = formData.get("username") as string;
|
||||
const password = formData.get("password") as string;
|
||||
|
||||
const users = await db.select()
|
||||
.from(usersTable)
|
||||
.where(eq(usersTable.username, username));
|
||||
|
||||
if (users.length !== 0) {
|
||||
return { error: "Username already taken!" };
|
||||
}
|
||||
|
||||
await db.insert(usersTable).values({
|
||||
username,
|
||||
password: bcrypt.hashSync(password, bcrypt.genSaltSync()),
|
||||
});
|
||||
|
||||
const token = jwt.sign({ sub: username }, JWT_SECRET, { expiresIn: "7d" });
|
||||
|
||||
const cookieStore = await cookies();
|
||||
cookieStore.set("session", token, {
|
||||
httpOnly: true,
|
||||
path: "/",
|
||||
maxAge: 60 * 60 * 24 * 7,
|
||||
});
|
||||
|
||||
redirect("/");
|
||||
}
|
@ -9,7 +9,7 @@ const notoSansMono = Noto_Sans_Mono({
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Rhyme",
|
||||
description: "Lyrics & Poetry Writing Assistant",
|
||||
description: "Line notes writing and sharing",
|
||||
};
|
||||
|
||||
export default function RootLayout({
|
||||
|
@ -25,7 +25,7 @@ export default function Editor() {
|
||||
|
||||
return (
|
||||
<div className="flex flex-col items-center px-4 max-w-2xl w-full gap-4">
|
||||
<input className="font-bold text-xl w-full text-center focus:outline-none" defaultValue="Song" />
|
||||
<input className="font-bold text-xl w-full text-center focus:outline-none" defaultValue="Untitled" />
|
||||
{state.map((block) => <Block key={block.id} block={block} dispatch={dispatch} /> )}
|
||||
<IconOnlyButton onClick={handleAddBlock} icon={<Plus size={24} />} />
|
||||
</div>
|
||||
|
17
src/components/forms/LoginForm.tsx
Normal file
17
src/components/forms/LoginForm.tsx
Normal file
@ -0,0 +1,17 @@
|
||||
"use client";
|
||||
|
||||
import { useActionState } from "react";
|
||||
import { login } from "@/app/actions";
|
||||
|
||||
export default function LoginForm() {
|
||||
const [state, formAction] = useActionState(login, null);
|
||||
|
||||
return (
|
||||
<form action={formAction}>
|
||||
<input name="username" placeholder="Username" required />
|
||||
<input name="password" placeholder="Password" type="password" required />
|
||||
<button type="submit">Login</button>
|
||||
{state?.error && <p>{state.error}</p>}
|
||||
</form>
|
||||
);
|
||||
}
|
@ -1,2 +1,4 @@
|
||||
import { drizzle } from 'drizzle-orm/node-postgres';
|
||||
export const db = drizzle(process.env.DATABASE_URL!);
|
||||
import * as schema from "./schema";
|
||||
|
||||
export const db = drizzle<typeof schema>(process.env.DATABASE_URL!);
|
||||
|
@ -1,25 +1,26 @@
|
||||
import { integer, pgTable, text, varchar } from "drizzle-orm/pg-core";
|
||||
import { pgTable, integer, json, varchar } from "drizzle-orm/pg-core";
|
||||
import { relations } from "drizzle-orm";
|
||||
|
||||
export const usersTable = pgTable("users", {
|
||||
id: integer("id").primaryKey().generatedAlwaysAsIdentity(),
|
||||
username: varchar("username", { length: 50 }).notNull(),
|
||||
password: varchar("password").notNull(),
|
||||
id: integer().primaryKey().generatedAlwaysAsIdentity(),
|
||||
username: varchar({ length: 50 }).notNull().unique(),
|
||||
password: varchar().notNull(),
|
||||
});
|
||||
|
||||
export const usersRelations = relations(usersTable, ({ many }) => ({
|
||||
lyrics: many(lyricsTable),
|
||||
blocks: many(blocksTable),
|
||||
}));
|
||||
|
||||
export const lyricsTable = pgTable("lyrics", {
|
||||
id: integer("id").primaryKey().generatedAlwaysAsIdentity(),
|
||||
content: text("content").notNull().default(""),
|
||||
authorId: integer("author_id"),
|
||||
export const blocksTable = pgTable("blocks", {
|
||||
id: integer().primaryKey().generatedAlwaysAsIdentity(),
|
||||
tag: varchar({ length: 100 }).notNull().default(""),
|
||||
lines: json().notNull().default([]),
|
||||
authorId: integer(),
|
||||
});
|
||||
|
||||
export const lyricsRelations = relations(lyricsTable, ({ one }) => ({
|
||||
export const blocksRelations = relations(blocksTable, ({ one }) => ({
|
||||
author: one(usersTable, {
|
||||
fields: [lyricsTable.authorId],
|
||||
fields: [blocksTable.authorId],
|
||||
references: [usersTable.id],
|
||||
}),
|
||||
}));
|
||||
|
Loading…
x
Reference in New Issue
Block a user