feat: add images as fav

This commit is contained in:
2025-01-05 19:19:04 +01:00
parent 92837fb74f
commit f3e3077662
8 changed files with 173 additions and 68 deletions

View File

@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "User" ADD COLUMN "likedPosts" TEXT[];

View File

@@ -17,6 +17,7 @@ model User {
id String @id @default(cuid())
username String @unique
hashed_password String
likedPosts String[]
sessions Session[]
posts Post[]
}

View File

@@ -5,15 +5,25 @@ import Link from 'next/link';
export default async function Home() {
const posts = await prisma.post.findMany({ take: 30 });
return (
<div className='text-center pt-2'>
<div className="text-center pt-2">
<h1>This is nextbooru</h1>
<p>The simplest and most modern booru software.</p>
<p className='text-sm text-muted-foreground italic'>(very unstable and not feature complete!)</p>
<p className="text-sm text-muted-foreground italic">
(very unstable and not feature complete!)
</p>
<div className="flex gap-4 p-4">
{posts.map((post) => (
<div key={post.id}>
<Link href={`/post/${post.id}`}>
<Image width={176} height={176} src={post.imageUrl} alt={''} className="aspect-square object-contain border-2 rounded-md border-dashed" />
<Image
width={176}
height={176}
src={post.imageUrl}
alt={''}
className="aspect-square object-contain border-2 rounded-md border-dashed"
blurDataURL={`data:image/jpeg;base64,${post.previewHash}`}
placeholder="blur"
/>
</Link>
</div>
))}

View File

@@ -1,12 +1,15 @@
import LikePost from '@/components/app/LikePost/LikePost';
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 { validateRequest } from '@/lib/auth';
import prisma from '@/lib/db';
import { Heart, Download, Flag, MessageSquare, Link, User, Calendar } from 'lucide-react';
import { Download, Flag, MessageSquare, User, Calendar } from 'lucide-react';
import Image from 'next/image';
export default async function Page({ params }: { params: Promise<{ id: string }> }) {
const { user } = await validateRequest();
const { id } = await params;
const post = await prisma.post.findUnique({ where: { id }, include: { author: true } });
if (!post) {
@@ -31,9 +34,7 @@ export default async function Page({ params }: { params: Promise<{ id: string }>
</div>
<div className="p-4 flex justify-between items-center">
<div className="flex space-x-2">
<Button variant="outline" size="icon">
<Heart className="h-4 w-4" />
</Button>
<LikePost id={id} liked={user?.likedPosts!.includes(id)!} />
<a href={post.imageUrl} download>
<Button variant="outline" size="icon">
<Download className="h-4 w-4" />

View File

@@ -0,0 +1,49 @@
'use client';
import { Button } from '@/components/ui/button';
import { likePost } from '@/lib/other/actions';
import { cn } from '@/lib/utils';
import { Heart } from 'lucide-react';
import { useState } from 'react';
import { toast } from 'sonner';
export default function LikePost(props: Props) {
const [loading, setLoading] = useState(false);
const [liked, setLiked] = useState(props.liked);
return (
<Button
variant="outline"
size="icon"
onClick={() => {
setLoading(true);
likePost(props.id).then((res) => {
setLoading(false);
if (res.success) {
setLiked(res.liked!);
} else {
switch (res.error) {
case 'logIn':
// TODO: redirect to login page
toast.error('You need to be logged in to like posts');
break;
case 'notFound':
toast.error('Post not found');
break;
default:
toast.error('An error occurred');
break;
}
}
});
}}
disabled={loading}
>
<Heart className={cn("h-4 w-4", liked ? 'text-red-500' : '')} />
</Button>
);
}
interface Props {
id: string;
liked: boolean;
}

View File

@@ -1,71 +1,64 @@
import { PrismaAdapter } from "@lucia-auth/adapter-prisma";
import { Lucia, Session, User } from "lucia";
import prisma from "../db";
import { cache } from "react";
import { cookies } from "next/headers";
import { PrismaAdapter } from '@lucia-auth/adapter-prisma';
import { Lucia } from 'lucia';
import prisma from '../db';
import { cache } from 'react';
import { cookies } from 'next/headers';
const adapter = new PrismaAdapter(prisma.session, prisma.user);
export const lucia = new Lucia(adapter, {
sessionCookie: {
// this sets cookies with super long expiration
// since Next.js doesn't allow Lucia to extend cookie expiration when rendering pages
expires: false,
attributes: {
// set to `true` when using HTTPS
secure: process.env.NODE_ENV === "production"
}
},
getUserAttributes: (attributes) => {
return {
username: attributes.username
};
}
sessionCookie: {
// this sets cookies with super long expiration
// since Next.js doesn't allow Lucia to extend cookie expiration when rendering pages
expires: false,
attributes: {
// set to `true` when using HTTPS
secure: process.env.NODE_ENV === 'production',
},
},
getUserAttributes: (attributes) => {
return {
username: attributes.username,
likedPosts: attributes.likedPosts,
};
},
});
export const validateRequest = cache(async () => {
const sessionId = (await cookies()).get(lucia.sessionCookieName)?.value ?? null
if (!sessionId)
return {
user: null,
session: null,
}
const { user, session } = await lucia.validateSession(sessionId)
try {
if (session && session.fresh) {
const sessionCookie = lucia.createSessionCookie(session.id)
(await cookies()).set(
sessionCookie.name,
sessionCookie.value,
sessionCookie.attributes
)
}
if (!session) {
const sessionCookie = lucia.createBlankSessionCookie()
(await cookies()).set(
sessionCookie.name,
sessionCookie.value,
sessionCookie.attributes
)
}
} catch {
// Next.js throws error attempting to set cookies when rendering page
}
return {
user,
session,
}
})
const sessionId = (await cookies()).get(lucia.sessionCookieName)?.value ?? null;
if (!sessionId)
return {
user: null,
session: null,
};
declare module "lucia" {
interface Register {
Lucia: typeof lucia;
DatabaseUserAttributes: DatabaseUserAttributes;
}
const { user, session } = await lucia.validateSession(sessionId);
try {
if (session && session.fresh) {
const sessionCookie = lucia.createSessionCookie(session.id);
(await cookies()).set(sessionCookie.name, sessionCookie.value, sessionCookie.attributes);
}
if (!session) {
const sessionCookie = lucia.createBlankSessionCookie();
(await cookies()).set(sessionCookie.name, sessionCookie.value, sessionCookie.attributes);
}
} catch {
// Next.js throws error attempting to set cookies when rendering page
}
return {
user,
session,
};
});
declare module 'lucia' {
interface Register {
Lucia: typeof lucia;
DatabaseUserAttributes: DatabaseUserAttributes;
}
}
interface DatabaseUserAttributes {
username: string;
}
username: string;
likedPosts: string[];
}

View File

@@ -1,5 +1,4 @@
import sharp from 'sharp';
import crypto from 'crypto';
export default async function hashImage(imageBuffer: Buffer) {
try {

50
src/lib/other/actions.ts Normal file
View File

@@ -0,0 +1,50 @@
'use server'
import { validateRequest } from "../auth"
import prisma from "../db";
export async function likePost(id: string) {
const { user } = await validateRequest();
if (!user) {
return {
success: false,
error: 'logIn',
}
}
const post = await prisma.post.findUnique({ where: { id } });
if (!post) {
return {
success: false,
error: 'notFound',
}
}
const userDb = await prisma.user.findUnique({ where: { id: user.id } });
if (userDb?.likedPosts.includes(id)) {
await prisma.user.update({
where: { id: user.id },
data: {
likedPosts: {
set: userDb.likedPosts.filter((postId) => postId !== id),
},
},
});
return {
success: true,
liked: false,
}
}
await prisma.user.update({
where: { id: user.id },
data: {
likedPosts: {
push: id,
},
},
});
return {
success: true,
liked: true,
}
}