feat: add Drizzle, basic structure

This commit is contained in:
Kirill Siukhin 2025-07-03 18:26:05 +05:00
parent 8ef4235c6d
commit c8aaf13965
18 changed files with 1458 additions and 17 deletions

1
.example.env Normal file
View File

@ -0,0 +1 @@
DATABASE_URL=postgres://postgres:postgres@localhost:5432/postgres

4
.gitignore vendored
View File

@ -39,3 +39,7 @@ yarn-error.log*
# typescript
*.tsbuildinfo
next-env.d.ts
# docker
/pgdata

10
drizzle.config.ts Normal file
View File

@ -0,0 +1,10 @@
import { defineConfig } from "drizzle-kit";
export default defineConfig({
out: "./drizzle",
schema: "./src/lib/db/schema.ts",
dialect: "postgresql",
dbCredentials: {
url: process.env.DATABASE_URL!,
},
});

View File

@ -0,0 +1,4 @@
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
);

View File

@ -0,0 +1,55 @@
{
"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": {}
}
}

View File

@ -0,0 +1,13 @@
{
"version": "7",
"dialect": "postgresql",
"entries": [
{
"idx": 0,
"version": "7",
"when": 1751547902605,
"tag": "0000_dazzling_captain_flint",
"breakpoints": true
}
]
}

1250
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -9,19 +9,23 @@
"lint": "next lint"
},
"dependencies": {
"drizzle-orm": "^0.44.2",
"lucide-react": "^0.525.0",
"next": "15.3.4",
"pg": "^8.16.3",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"next": "15.3.4"
"react-dom": "^19.0.0"
},
"devDependencies": {
"typescript": "^5",
"@eslint/eslintrc": "^3",
"@tailwindcss/postcss": "^4",
"@types/node": "^20",
"@types/react": "^19",
"@types/react-dom": "^19",
"@tailwindcss/postcss": "^4",
"tailwindcss": "^4",
"drizzle-kit": "^0.31.4",
"eslint": "^9",
"eslint-config-next": "15.3.4",
"@eslint/eslintrc": "^3"
"tailwindcss": "^4",
"typescript": "^5"
}
}

View File

@ -1 +1,10 @@
@import "tailwindcss";
@theme inline {
--font-mono: var(--noto-sans-mono);
}
body {
color: #e5e5e5;
background-color: #171717;
}

View File

@ -1,15 +1,16 @@
import type { Metadata } from "next";
import { Geist, Geist_Mono } from "next/font/google";
import { Noto_Sans_Mono } from "next/font/google";
import Header from "@/components/Header";
import "./globals.css";
const geistMono = Geist_Mono({
variable: "--font-geist-mono",
subsets: ["latin"],
const notoSansMono = Noto_Sans_Mono({
variable: "--noto-sans-mono",
subsets: ["latin", "cyrillic"],
});
export const metadata: Metadata = {
title: "Create Next App",
description: "Generated by create next app",
title: "Rhyme",
description: "Lyrics & Poetry Writing Assistant",
};
export default function RootLayout({
@ -19,9 +20,8 @@ export default function RootLayout({
}>) {
return (
<html lang="en">
<body
className={`${geistMono.className} antialiased`}
>
<body className={`${notoSansMono.variable} font-mono antialiased`}>
<Header />
{children}
</body>
</html>

View File

@ -1,7 +1,9 @@
import Lyrics from "@/components/Lyrics";
export default function Home() {
return (
<div>
hello world
<div className="flex flex-col items-center">
<Lyrics />
</div>
);
}

17
src/components/Header.tsx Normal file
View File

@ -0,0 +1,17 @@
import Link from "next/link";
import { Plus, UserRound } from "lucide-react";
import HeaderButton from "./HeaderButton";
export default function Header() {
return (
<header className="flex items-center gap-6 m-4">
<Link href="/" className="font-bold px-2 py-1 bg-orange-600 hover:bg-orange-700 rounded">
RHYME
</Link>
<HeaderButton title="new" icon={<Plus size={20} />} />
<div className="ml-auto">
<HeaderButton title="log in" icon={<UserRound size={20} />} />
</div>
</header>
);
}

View File

@ -0,0 +1,10 @@
import { ReactNode } from "react";
export default function HeaderButton({ title, icon }: { title: string, icon?: ReactNode }) {
return (
<button className="flex rounded bg-neutral-800 hover:bg-neutral-700 px-2 py-1 gap-2 items-center cursor-pointer">
{title}
{icon}
</button>
);
}

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

@ -0,0 +1,7 @@
import { InputHTMLAttributes } from "react";
export default function Input(props: InputHTMLAttributes<HTMLInputElement>) {
return (
<input type="text" className="border-b-2 border-b-neutral-600 focus:border-b-neutral-500 bg-neutral-800/30 p-2 my-2 rounded-t-md text-lg focus:outline-none font-mono w-full" {...props} />
);
}

17
src/components/Lyrics.tsx Normal file
View File

@ -0,0 +1,17 @@
"use client";
import { ChangeEvent } from "react";
import Input from "./Input";
export default function Lyrics() {
const onChange = (e: ChangeEvent<HTMLInputElement>) => {
// TODO
}
return (
<div className="flex flex-col items-center px-4 max-w-2xl w-full">
<h1 className="my-4 font-bold text-xl">Song #1</h1>
{[...Array(4)].map((_, i) => <Input key={i} onChange={onChange} />)}
</div>
);
}

11
src/components/Tag.tsx Normal file
View File

@ -0,0 +1,11 @@
export default function Tag({ name }: { name: string }) {
return (
<div className="flex gap-3 items-center my-4">
<hr className="border border-neutral-800 w-full" />
<div className="bg-pink-500 px-2 rounded-full font-bold min-w-fit text-sm">
{name}
</div>
<hr className="border border-neutral-800 w-full" />
</div>
);
}

2
src/lib/db/index.ts Normal file
View File

@ -0,0 +1,2 @@
import { drizzle } from 'drizzle-orm/node-postgres';
export const db = drizzle(process.env.DATABASE_URL!);

25
src/lib/db/schema.ts Normal file
View File

@ -0,0 +1,25 @@
import { integer, pgTable, text, 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(),
});
export const usersRelations = relations(usersTable, ({ many }) => ({
lyrics: many(lyricsTable),
}));
export const lyricsTable = pgTable("lyrics", {
id: integer("id").primaryKey().generatedAlwaysAsIdentity(),
content: text("content").notNull().default(""),
authorId: integer("author_id"),
});
export const lyricsRelations = relations(lyricsTable, ({ one }) => ({
author: one(usersTable, {
fields: [lyricsTable.authorId],
references: [usersTable.id],
}),
}));