From 6dd9cefac4daead8c8a9e8bcef06868b5bca4b22 Mon Sep 17 00:00:00 2001 From: Izan Gil <66965250+SrIzan10@users.noreply.github.com> Date: Sun, 5 Jan 2025 13:07:48 +0100 Subject: [PATCH] feat: initial upload and image show stuff --- .gitignore | 4 + dev/docker-compose.yml | 1 + package.json | 3 +- .../20241226183003_add_post/migration.sql | 14 +++ prisma/schema.prisma | 19 ++- src/app/(protected)/upload/page.tsx | 46 +++++++ src/app/(public)/page.tsx | 119 +++--------------- src/app/(public)/post/[id]/page.tsx | 80 ++++++++++++ .../app/UniversalForm/UniversalForm.tsx | 46 ++++--- src/components/app/UniversalForm/types.ts | 4 +- src/components/app/UploadFile/UploadFile.tsx | 11 ++ src/components/ui/badge.tsx | 36 ++++++ src/components/ui/card.tsx | 79 ++++++++++++ src/components/ui/separator.tsx | 31 +++++ src/lib/form/actions.ts | 41 ++++++ src/lib/form/zod.ts | 24 +++- yarn.lock | 7 ++ 17 files changed, 435 insertions(+), 130 deletions(-) create mode 100644 prisma/migrations/20241226183003_add_post/migration.sql create mode 100644 src/app/(protected)/upload/page.tsx create mode 100644 src/app/(public)/post/[id]/page.tsx create mode 100644 src/components/app/UploadFile/UploadFile.tsx create mode 100644 src/components/ui/badge.tsx create mode 100644 src/components/ui/card.tsx create mode 100644 src/components/ui/separator.tsx create mode 100644 src/lib/form/actions.ts diff --git a/.gitignore b/.gitignore index 00bba9b..3e789e4 100644 --- a/.gitignore +++ b/.gitignore @@ -35,3 +35,7 @@ yarn-error.log* # typescript *.tsbuildinfo next-env.d.ts + +public/uploads +dev/ +!dev/docker-compose.yml \ No newline at end of file diff --git a/dev/docker-compose.yml b/dev/docker-compose.yml index 910cc0b..e79af75 100644 --- a/dev/docker-compose.yml +++ b/dev/docker-compose.yml @@ -1,5 +1,6 @@ services: psql: + user: 1000:1000 image: postgres environment: POSTGRES_USER: postgres diff --git a/package.json b/package.json index 1462ae6..c21a5bd 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "version": "0.1.0", "private": true, "scripts": { - "dev": "docker compose --file dev/docker-compose.yml up -d && next dev", + "dev": "docker compose --file dev/docker-compose.yml up -d && next dev --turbo", "setup": "docker compose --file dev/docker-compose.yml up -d && prisma migrate deploy", "build": "prisma generate && next build", "start": "next start", @@ -20,6 +20,7 @@ "@radix-ui/react-avatar": "^1.0.4", "@radix-ui/react-dropdown-menu": "^2.1.2", "@radix-ui/react-label": "^2.1.1", + "@radix-ui/react-separator": "^1.1.1", "@radix-ui/react-slot": "^1.1.1", "class-variance-authority": "^0.7.1", "clsx": "^2.1.0", diff --git a/prisma/migrations/20241226183003_add_post/migration.sql b/prisma/migrations/20241226183003_add_post/migration.sql new file mode 100644 index 0000000..e782902 --- /dev/null +++ b/prisma/migrations/20241226183003_add_post/migration.sql @@ -0,0 +1,14 @@ +-- CreateTable +CREATE TABLE "Post" ( + "id" TEXT NOT NULL, + "imageUrl" TEXT NOT NULL, + "caption" TEXT NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "tags" TEXT[], + "authorId" TEXT NOT NULL, + + CONSTRAINT "Post_pkey" PRIMARY KEY ("id") +); + +-- AddForeignKey +ALTER TABLE "Post" ADD CONSTRAINT "Post_authorId_fkey" FOREIGN KEY ("authorId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 96bb15c..958608c 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -14,10 +14,11 @@ datasource db { } model User { - id String @id @default(cuid()) - username String @unique + id String @id @default(cuid()) + username String @unique hashed_password String - sessions Session[] + sessions Session[] + posts Post[] } model Session { @@ -25,4 +26,14 @@ model Session { userId String expiresAt DateTime user User @relation(references: [id], fields: [userId], onDelete: Cascade) -} \ No newline at end of file +} + +model Post { + id String @id @default(cuid()) + imageUrl String + caption String + createdAt DateTime @default(now()) + tags String[] + authorId String + author User @relation(references: [id], fields: [authorId], onDelete: Cascade) +} diff --git a/src/app/(protected)/upload/page.tsx b/src/app/(protected)/upload/page.tsx new file mode 100644 index 0000000..613e708 --- /dev/null +++ b/src/app/(protected)/upload/page.tsx @@ -0,0 +1,46 @@ +'use client'; + +import { UniversalForm } from '@/components/app/UniversalForm/UniversalForm'; +import UploadFile from '@/components/app/UploadFile/UploadFile'; +import { create } from '@/lib/form/actions'; +import { useRouter } from 'next/navigation'; + +export default function Page() { + const router = useRouter(); + return ( +
+
+ , + }, + { + name: 'caption', + label: 'Caption', + type: 'text', + placeholder: 'Caption', + textArea: true, + }, + { + name: 'tags', + label: 'Tags', + type: 'text', + placeholder: 'Tags', + description: 'Separate tags with commas', + }, + ]} + schemaName="upload" + action={create} + submitText="Upload" + submitClassname="w-full" + onActionComplete={(res) => res.success && router.push(`/post/${res.id}`)} + /> +
+
+ ); +} diff --git a/src/app/(public)/page.tsx b/src/app/(public)/page.tsx index 8b3d0bc..b73bb7d 100644 --- a/src/app/(public)/page.tsx +++ b/src/app/(public)/page.tsx @@ -1,104 +1,23 @@ -// https://v0.dev/r/DxCSk58T8pM -import { Button } from "@/components/ui/button"; -import Link from "next/link"; +import prisma from '@/lib/db'; +import Image from 'next/image'; +import Link from 'next/link'; -export default function Home() { +export default async function Home() { + const posts = await prisma.post.findMany({ take: 30 }); return ( - <> -
-
-
-
-
-

- The modern tech stack for your next(.js) project -

-

- stack is a comprehensive tech stack that includes everything you need to build and deploy your next - web application. -

-
-
- - - -
-
-
-
-
-
-
-
-
- Key Features -
-

- Everything you need to build and deploy -

-

- Stack is a comprehensive tech stack that includes everything you need to build and deploy your next - web application. -

-
-
-
-
-
    -
  • -
    -

    Next.js

    -

    - Build server-rendered React applications with Next.js. -

    -
    -
  • -
  • -
    -

    Prisma

    -

    - Access your database with Prisma, the best way to work with databases in Ethan's opinion -

    -
    -
  • -
  • -
    -

    Tailwind CSS

    -

    - Style your application with Tailwind CSS, the utility-first CSS framework. -

    -
    -
  • -
  • -
    -

    shadcn/ui

    -

    - The customizability of the components makes it one of the best UI libraries out there. -

    -
    -
  • -
  • -
    -

    Vercel

    -

    - Deploy your application to the cloud with Vercel, the serverless platform. -

    -
    -
  • -
  • -
    -

    Lucia

    -

    - Manage authentication with Lucia auth, the best selfhosted authentication library. -

    -
    -
  • -
-
-
-
-
-
- +
+

This is nextbooru

+

The simplest and most modern booru software.

+

(very unstable and not feature complete!)

+
+ {posts.map((post) => ( +
+ + {''} + +
+ ))} +
+
); } diff --git a/src/app/(public)/post/[id]/page.tsx b/src/app/(public)/post/[id]/page.tsx new file mode 100644 index 0000000..779b9c6 --- /dev/null +++ b/src/app/(public)/post/[id]/page.tsx @@ -0,0 +1,80 @@ +import { Badge } from '@/components/ui/badge'; +import { Button } from '@/components/ui/button'; +import { Card, CardContent } from '@/components/ui/card'; +import { Separator } from '@/components/ui/separator'; +import prisma from '@/lib/db'; +import { Heart, Download, Flag, MessageSquare, Link, User, Calendar } from 'lucide-react'; +import Image from 'next/image'; + +export default async function Page({ params }: { params: Promise<{ id: string }> }) { + const { id } = await params; + const post = await prisma.post.findUnique({ where: { id }, include: { author: true } }); + if (!post) { + return
Post not found
; + } + + return ( +
+
+
+ + +
+ Post image +
+
+
+ + + + + +
+ +
+
+
+

comments here

+
+
+

Image information

+
+ + Uploaded by {post.author.username} +
+
+ + Uploaded on: {post.createdAt.toLocaleString()} +
+ +
+

Tags

+
+ {post.tags.map((tag) => ( + + {tag} + + ))} +
+
+ {/* related images missing */} +
+
+
+ ); +} diff --git a/src/components/app/UniversalForm/UniversalForm.tsx b/src/components/app/UniversalForm/UniversalForm.tsx index d6dd253..250cf07 100644 --- a/src/components/app/UniversalForm/UniversalForm.tsx +++ b/src/components/app/UniversalForm/UniversalForm.tsx @@ -1,6 +1,6 @@ 'use client'; import { zodResolver } from '@hookform/resolvers/zod'; -import { Path, PathValue, useForm } from 'react-hook-form'; +import { Path, useForm } from 'react-hook-form'; import { Form, FormControl, @@ -19,11 +19,12 @@ import React from 'react'; import { toast } from 'sonner'; import { Textarea } from '@/components/ui/textarea'; import { cn } from '@/lib/utils'; -import { accountSchema } from '@/lib/form/zod'; +import { accountSchema, uploadSchema } from '@/lib/form/zod'; export const schemaDb = [ { name: 'login', zod: accountSchema }, { name: 'register', zod: accountSchema }, + { name: 'upload', zod: uploadSchema }, ] as const; export function UniversalForm({ @@ -78,23 +79,28 @@ export function UniversalForm({ name={field.name as Path>} render={({ field: formField }) => ( - {field.type !== 'hidden' && {field.label}} + {field.type !== 'hidden' && !field.hiddenShowLabel && ( + {field.label} + )} - {field.textArea ? ( -