From 0b8aa37351b3d7594f3d0e5db2dcf8396e331320 Mon Sep 17 00:00:00 2001 From: misterkirill Date: Sat, 5 Jul 2025 19:26:45 +0500 Subject: [PATCH] feat: start user authentication --- .example.env | 1 + drizzle/0000_dazzling_captain_flint.sql | 4 - drizzle/meta/0000_snapshot.json | 55 ------- drizzle/meta/_journal.json | 10 +- package-lock.json | 188 +++++++++++++++++++++++- package.json | 4 + src/app/(editor)/page.tsx | 7 +- src/app/(noneditor)/login/page.tsx | 3 +- src/app/actions.ts | 70 +++++++++ src/app/layout.tsx | 2 +- src/components/Editor.tsx | 2 +- src/components/forms/LoginForm.tsx | 17 +++ src/lib/db/index.ts | 4 +- src/lib/db/schema.ts | 23 +-- 14 files changed, 304 insertions(+), 86 deletions(-) delete mode 100644 drizzle/0000_dazzling_captain_flint.sql delete mode 100644 drizzle/meta/0000_snapshot.json create mode 100644 src/app/actions.ts create mode 100644 src/components/forms/LoginForm.tsx diff --git a/.example.env b/.example.env index 5f05b75..4510764 100644 --- a/.example.env +++ b/.example.env @@ -1 +1,2 @@ DATABASE_URL=postgres://postgres:postgres@localhost:5432/postgres +JWT_SECRET=verysecret diff --git a/drizzle/0000_dazzling_captain_flint.sql b/drizzle/0000_dazzling_captain_flint.sql deleted file mode 100644 index bcdfe85..0000000 --- a/drizzle/0000_dazzling_captain_flint.sql +++ /dev/null @@ -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 -); diff --git a/drizzle/meta/0000_snapshot.json b/drizzle/meta/0000_snapshot.json deleted file mode 100644 index 4f2756c..0000000 --- a/drizzle/meta/0000_snapshot.json +++ /dev/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": {} - } -} \ No newline at end of file diff --git a/drizzle/meta/_journal.json b/drizzle/meta/_journal.json index 9bda70f..eaa8fcf 100644 --- a/drizzle/meta/_journal.json +++ b/drizzle/meta/_journal.json @@ -1,13 +1,5 @@ { "version": "7", "dialect": "postgresql", - "entries": [ - { - "idx": 0, - "version": "7", - "when": 1751547902605, - "tag": "0000_dazzling_captain_flint", - "breakpoints": true - } - ] + "entries": [] } \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index c126e03..2255bc3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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" diff --git a/package.json b/package.json index 9c00636..01c86c8 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/app/(editor)/page.tsx b/src/app/(editor)/page.tsx index 1c791c1..e455b64 100644 --- a/src/app/(editor)/page.tsx +++ b/src/app/(editor)/page.tsx @@ -1,9 +1,14 @@ +import Link from "next/link"; import Editor from "@/components/Editor"; export default function Home() { return ( -
+
+ + Changes are not saved!
+ Log in to save your notes. +
); } diff --git a/src/app/(noneditor)/login/page.tsx b/src/app/(noneditor)/login/page.tsx index d939f02..b8fec5e 100644 --- a/src/app/(noneditor)/login/page.tsx +++ b/src/app/(noneditor)/login/page.tsx @@ -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 ( <>

Login Page

- TODO + ); } diff --git a/src/app/actions.ts b/src/app/actions.ts new file mode 100644 index 0000000..4541230 --- /dev/null +++ b/src/app/actions.ts @@ -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("/"); +} diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 0bbeae9..0a82250 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -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({ diff --git a/src/components/Editor.tsx b/src/components/Editor.tsx index 0837940..4aa567e 100644 --- a/src/components/Editor.tsx +++ b/src/components/Editor.tsx @@ -25,7 +25,7 @@ export default function Editor() { return (
- + {state.map((block) => )} } />
diff --git a/src/components/forms/LoginForm.tsx b/src/components/forms/LoginForm.tsx new file mode 100644 index 0000000..1e5db96 --- /dev/null +++ b/src/components/forms/LoginForm.tsx @@ -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 ( +
+ + + + {state?.error &&

{state.error}

} +
+ ); +} diff --git a/src/lib/db/index.ts b/src/lib/db/index.ts index 8d8e001..e9e240a 100644 --- a/src/lib/db/index.ts +++ b/src/lib/db/index.ts @@ -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(process.env.DATABASE_URL!); diff --git a/src/lib/db/schema.ts b/src/lib/db/schema.ts index b85aba4..a3dee1b 100644 --- a/src/lib/db/schema.ts +++ b/src/lib/db/schema.ts @@ -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], }), }));