feat: add Drizzle, basic structure
This commit is contained in:
parent
8ef4235c6d
commit
c8aaf13965
1
.example.env
Normal file
1
.example.env
Normal file
@ -0,0 +1 @@
|
||||
DATABASE_URL=postgres://postgres:postgres@localhost:5432/postgres
|
4
.gitignore
vendored
4
.gitignore
vendored
@ -39,3 +39,7 @@ yarn-error.log*
|
||||
# typescript
|
||||
*.tsbuildinfo
|
||||
next-env.d.ts
|
||||
|
||||
# docker
|
||||
|
||||
/pgdata
|
||||
|
10
drizzle.config.ts
Normal file
10
drizzle.config.ts
Normal 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!,
|
||||
},
|
||||
});
|
4
drizzle/0000_dazzling_captain_flint.sql
Normal file
4
drizzle/0000_dazzling_captain_flint.sql
Normal 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
|
||||
);
|
55
drizzle/meta/0000_snapshot.json
Normal file
55
drizzle/meta/0000_snapshot.json
Normal 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": {}
|
||||
}
|
||||
}
|
13
drizzle/meta/_journal.json
Normal file
13
drizzle/meta/_journal.json
Normal 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
1250
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
16
package.json
16
package.json
@ -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"
|
||||
}
|
||||
}
|
||||
|
@ -1 +1,10 @@
|
||||
@import "tailwindcss";
|
||||
|
||||
@theme inline {
|
||||
--font-mono: var(--noto-sans-mono);
|
||||
}
|
||||
|
||||
body {
|
||||
color: #e5e5e5;
|
||||
background-color: #171717;
|
||||
}
|
||||
|
@ -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>
|
||||
|
@ -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
17
src/components/Header.tsx
Normal 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>
|
||||
);
|
||||
}
|
10
src/components/HeaderButton.tsx
Normal file
10
src/components/HeaderButton.tsx
Normal 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
7
src/components/Input.tsx
Normal 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
17
src/components/Lyrics.tsx
Normal 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
11
src/components/Tag.tsx
Normal 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
2
src/lib/db/index.ts
Normal 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
25
src/lib/db/schema.ts
Normal 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],
|
||||
}),
|
||||
}));
|
Loading…
x
Reference in New Issue
Block a user