Add reposts, like receipts, Catppuccin styling, refactors, fixes and limit the data thats sent to the client

This commit is contained in:
Joonas 2023-11-14 13:38:11 +02:00
parent b4e0b8b4d2
commit cc2e299a63
24 changed files with 329 additions and 104 deletions

View File

@ -10,7 +10,7 @@ export function Button({
}) {
return (
<button
className="px-6 py-2 rounded-lg border w-fit hover:bg-gray-100"
className="px-6 py-2 rounded-lg border w-fit hover:bg-ctp-crust"
type={type}
>
<Text>{children}</Text>

View File

@ -9,7 +9,7 @@ export function Card({
}) {
return (
<div
className={`flex z-10 flex-col bg-gray-50/9 gap-5 p-8 border w-full rounded-lg ${className}`}
className={`flex z-10 flex-col bg-ctp-mantle gap-5 p-8 border w-full rounded-lg ${className}`}
>
{children}
</div>

View File

@ -9,7 +9,7 @@ export function FormInput({ type, name }: { type: string; name: string }) {
<input
className={`file:bg-transparent file:border-0 ${
type === "file" ? "p-4 cursor-pointer" : "p-2"
} file:bg-sky-100 file:hover:bg-sky-100/75 file:text-gray-600/75 file:p-2 file:rounded-lg text-gray-600 rounded-lg border`}
} file:bg-sky-100 file:hover:bg-sky-100/75 file:text-gray-600/75 file:p-2 file:rounded-lg bg-ctp-crust/50 text-ctp-subtext0 rounded-lg border`}
type={type}
name={name}
accept={type === "file" ? "image/*" : undefined}
@ -27,7 +27,7 @@ export function TextArea({
}) {
return (
<textarea
className="p-4 w-full text-gray-600 rounded-lg border"
className="p-4 w-full bg-ctp-crust/50 text-ctp-subtext0 rounded-lg border"
placeholder={placeholder}
name={name}
></textarea>

View File

@ -3,7 +3,7 @@ import { Button } from "./Button";
import { Card } from "./Card";
import { FormLabel, TextArea } from "./Form";
import { SubTitle, Text } from "./Typography";
import type { Post } from "@prisma/client";
import type { User } from "@prisma/client";
import type { PostWithRelations } from "~/utils/prisma.server";
export function Poster({
@ -49,18 +49,31 @@ export function Poster({
export function Post({
userId,
post,
liker,
following,
}: {
post: PostWithRelations;
userId?: number;
liker?: User;
following?: { id: number }[];
}) {
const fetcher = useFetcher();
const liked = post.likes.filter((like) => like.id === userId).length > 0;
const reposted =
post.reposts.filter((repost) => repost.id === userId).length > 0;
return (
<div
key={post.id}
className="flex relative flex-col gap-4 p-6 transition-all hover:bg-gray-100/50"
className="flex relative flex-col gap-4 p-6 transition-all bg-ctp-mantle hover:bg-ctp-base first:rounded-t-lg last:rounded-b-lg "
>
{liker ? (
<Text type="subtitle">
<strong>{liker.username}</strong> liked
</Text>
) : (
""
)}
<div className="flex items-center">
<Poster
pfp={post.author.pfp}
@ -89,17 +102,23 @@ export function Post({
</button>
</fetcher.Form>
<fetcher.Form action={`/home`} method="POST">
<input type="hidden" name="intent" value={"repost"} />
<input
type="hidden"
name="intent"
value={reposted ? "unrepost" : "repost"}
/>
<input type="hidden" name="postId" value={post.id} />
<button type="submit">
<Text type="link">Repost</Text>
<Text type="link">
{reposted ? "Unrepost" : "Repost"} ({post.reposts.length})
</Text>
</button>
</fetcher.Form>
<details className="space-y-2">
<summary className="flex cursor-pointer">
<Text type="link">Reply</Text>
</summary>
<Card className="absolute max-w-xs bg-white z-100">
<Card className="absolute max-w-xs z-100">
<SubTitle>Reply to {post.author.username}</SubTitle>
<fetcher.Form
action={`/home`}
@ -111,7 +130,6 @@ export function Post({
<FormLabel>
<Text>Reply body</Text>
<TextArea name="reply" />
<Text type="error">-</Text>
</FormLabel>
<Button type="submit">Post</Button>
</fetcher.Form>

View File

@ -5,7 +5,7 @@ export function Toast({ children }: { children: ReactNode }) {
return (
<>
<input className="peer/toast" type="checkbox" id="toast-toggle" hidden />
<div className="peer-checked/toast:hidden z-10 bg-white w-80 p-4 fixed bottom-10 right-10 border rounded-lg shadow-lg">
<div className="peer-checked/toast:hidden z-10 bg-ctp-base w-80 p-4 fixed bottom-10 right-10 border rounded-lg shadow-lg">
<div className="relative">
<label
className="absolute right-0 cursor-pointer text-gray-300 hover:text-gray-400"

View File

@ -13,14 +13,14 @@ export function Text({
<p
className={`${
type === "link"
? "text-sky-400 hover:text-sky-600"
? "text-ctp-sky hover:text-ctp-blue"
: type === "error"
? "text-red-600/75 text-sm"
? "text-ctp-red text-sm"
: type === "subtitle"
? "text-gray-600 text-sm"
? "text-ctp-subtext1 text-sm"
: type === "success"
? "text-green-600/75"
: "text-gray-600"
? "text-ctp-green/25"
: "text-ctp-text"
} ${className}`}
>
{children}
@ -35,11 +35,7 @@ export function Title({
children: ReactNode;
className?: string;
}) {
return (
<h1 className={`text-2xl font-bold text-gray-800 ${className}`}>
{children}
</h1>
);
return <h1 className={`text-2xl font-bold ${className}`}>{children}</h1>;
}
export function SubTitle({
@ -54,7 +50,7 @@ export function SubTitle({
return (
<h2
className={`text-xl font-semibold ${
type ? "text-sky-600 hover:text-sky-800" : "text-gray-700"
type ? "text-ctp-sky hover:text-ctp-blue" : "text-ctp-text/75"
} ${className}`}
>
{children}

View File

@ -3,13 +3,13 @@ import { getPostById } from "./post.server";
interface PostErrors {
postId: string | undefined;
all: string | undefined;
like: string | undefined;
}
export async function likePost(postId: number, id: number) {
const errors: PostErrors = {
postId: undefined,
all: undefined,
like: undefined,
};
const post = await getPostById(postId);
@ -36,7 +36,7 @@ export async function likePost(postId: number, id: number) {
});
if (!like) {
errors.all = "Something went wrong liking the post";
errors.like = "Something went wrong liking the post";
return errors;
}
@ -46,7 +46,7 @@ export async function likePost(postId: number, id: number) {
export async function unLikePost(postId: number, id: number) {
const errors: PostErrors = {
postId: undefined,
all: undefined,
like: undefined,
};
const post = await getPostById(postId);
@ -71,7 +71,7 @@ export async function unLikePost(postId: number, id: number) {
});
if (!like) {
errors.all = "Something went wrong unliking the post";
errors.like = "Something went wrong unliking the post";
return errors;
}

View File

@ -29,18 +29,41 @@ export async function getFeed(id: number) {
const posts = await prisma.post.findMany({
where: {
userId: {
in: [...following.following.map((user) => user.id)],
},
OR: [
{
userId: {
in: [...following.following.map((user) => user.id)],
},
},
{
reposts: {
some: {
id: {
in: [...following.following.map((user) => user.id), id],
},
},
},
},
],
},
include: {
author: {
include: {
likes: true,
posts: true,
select: {
username: true,
name: true,
pfp: true,
},
},
reposts: {
select: {
id: true,
},
},
likes: {
select: {
id: true,
},
},
likes: true,
},
});
@ -66,10 +89,8 @@ function validatePostBody(body: string) {
export async function createPost(body: Post["text"], id: number) {
const errors: {
body: string | undefined;
all: string | undefined;
} = {
body: validatePostBody(body),
all: undefined,
};
if (Object.values(errors).some(Boolean)) {
@ -84,7 +105,7 @@ export async function createPost(body: Post["text"], id: number) {
});
if (!post) {
errors.all = "Something went wrong creating the post";
errors.body = "Something went wrong creating the post";
return errors;
}

View File

@ -0,0 +1,64 @@
import { prisma } from "~/utils/prisma.server";
import { getPostById } from "./post.server";
export async function repost(postId: number, id: number) {
const errors = {};
const post = await getPostById(postId);
if (!post) {
errors.repost = "Post doesn't exist";
}
if (Object.values(errors).some(Boolean)) {
return errors;
}
const repost = await prisma.user.update({
where: {
id: id,
},
data: {
reposts: {
connect: { id: postId },
},
},
});
if (!repost) {
errors.repost = "Something went wrong";
}
return errors;
}
export async function unRepost(postId: number, id: number) {
const errors = {};
const post = await getPostById(postId);
if (!post) {
errors.repost = "Post doesn't exist";
}
if (Object.values(errors).some(Boolean)) {
return errors;
}
const repost = await prisma.user.update({
where: {
id: id,
},
data: {
reposts: {
disconnect: { id: postId },
},
},
});
if (!repost) {
errors.repost = "Something went wrong";
}
return errors;
}

View File

@ -25,16 +25,73 @@ export async function getUserByName(
id: true,
},
},
following: {
select: {
id: true,
name: true,
},
},
posts: {
include: {
author: true,
likes: true,
author: {
select: {
username: true,
name: true,
pfp: true,
},
},
reposts: {
select: {
id: true,
},
},
likes: {
select: {
id: true,
},
},
},
},
likes: {
include: {
author: true,
likes: true,
author: {
select: {
username: true,
name: true,
pfp: true,
},
},
reposts: {
select: {
id: true,
},
},
likes: {
select: {
id: true,
},
},
},
},
reposts: {
include: {
author: {
select: {
username: true,
name: true,
pfp: true,
},
},
reposts: {
select: {
id: true,
},
},
likes: {
select: {
id: true,
},
},
},
},
_count: {
@ -84,13 +141,12 @@ export interface UserErrors {
opassword: string | undefined;
npassword: string | undefined;
pfp: string | undefined;
all: string | undefined;
}
export async function followUser(followId: number, id: number) {
const errors: { followId: string | undefined; all: string | undefined } = {
const errors: { followId: string | undefined; follow: string | undefined } = {
followId: undefined,
all: undefined,
follow: undefined,
};
if (!(await getUserById(followId))) {
@ -113,17 +169,18 @@ export async function followUser(followId: number, id: number) {
});
if (!follow) {
errors.all = "Something went wrong";
errors.follow = "Something went wrong";
}
return errors;
}
export async function unFollowUser(followId: number, id: number) {
const errors: { followId: string | undefined; all: string | undefined } = {
followId: undefined,
all: undefined,
};
const errors: { followId: string | undefined; unfollow: string | undefined } =
{
followId: undefined,
unfollow: undefined,
};
if (!(await getUserById(followId))) {
errors.followId = "User does not exist";
@ -145,7 +202,7 @@ export async function unFollowUser(followId: number, id: number) {
});
if (!unFollow) {
errors.all = "Something went wrong";
errors.unfollow = "Something went wrong";
}
return errors;
@ -173,7 +230,6 @@ export async function editUser(
opassword: undefined,
npassword: undefined,
pfp: undefined,
all: undefined,
};
switch (data.intent) {
@ -194,7 +250,7 @@ export async function editUser(
});
if (!username) {
errors.all = "Something went wrong";
errors.username = "Something went wrong";
}
return { username: username.username, errors: errors };
@ -221,7 +277,7 @@ export async function editUser(
});
if (!name) {
errors.all = "Something went wrong";
errors.name = "Something went wrong";
}
return { errors: errors };
@ -244,7 +300,7 @@ export async function editUser(
});
if (!desc) {
errors.all = "Something went wrong";
errors.desc = "Something went wrong";
}
return { errors: errors };
@ -281,7 +337,7 @@ export async function editUser(
});
if (!pw) {
errors.all = "Something went wrong";
errors.opassword = "Something went wrong";
}
return { errors: errors };
@ -320,7 +376,6 @@ export async function editUser(
return { errors: errors };
default:
errors.all = "Doesn't exist";
return { errors: errors };
}
}
@ -380,12 +435,10 @@ export async function createUser(
username: string | undefined;
password: string | undefined;
rpassword: string | undefined;
all: string | undefined;
} = {
username: await validateUsername(username),
password: await validatePassword(password),
rpassword: await validateRepeatPassword(password, repeatPassword),
all: undefined,
};
if (Object.values(errors).some(Boolean)) {
@ -401,7 +454,7 @@ export async function createUser(
});
if (!user) {
errors.all = "Failed to create user";
errors.username = "Failed to create user";
return { errors: errors };
}

View File

@ -86,14 +86,14 @@ export default function App() {
const data = useLoaderData<LoaderFunction>();
return (
<html lang="en">
<html className="ctp-frappe" lang="en">
<head>
<meta charSet="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<Meta />
<Links />
</head>
<body className="min-h-screen subpixel-antialiased">
<body className="min-h-screen bg-ctp-crust text-ctp-text">
<div className="container flex flex-col gap-20 m-8 mx-auto xl:flex-row">
<div className="flex mx-auto justify-center flex-grow w-80">
<nav className="flex w-full flex-col gap-5">
@ -122,7 +122,7 @@ export default function App() {
</div>
<div className="flex mx-auto justify-center flex-grow w-80">
<div>
<div className="hover:bg-sky-100/50 rounded-lg transition-all p-4">
<div className="hover:bg-ctp-blue/5 rounded-lg transition-all p-4">
<Link className="flex flex-col gap-5 items-center" to={"/"}>
<img src="/logo.png" alt="logo" width={50} />
<SubTitle>Twitter clone</SubTitle>
@ -144,11 +144,11 @@ function NavItem({ to, text }: { to: string; text: string }) {
return (
<NavLink
className={({ isActive, isPending }) =>
isPending ? "pending" : isActive ? "bg-gray-100/40" : ""
isPending ? "pending" : isActive ? "bg-ctp-surface0 rounded-lg" : ""
}
to={to}
>
<div className="px-4 w-full py-2 text-center rounded-lg border hover:bg-gray-100">
<div className="px-4 w-full py-2 text-center rounded-lg border bg-ctp-surface0/40 hover:bg-ctp-surface2/25">
<SubTitle>{text}</SubTitle>
</div>
</NavLink>

View File

@ -14,7 +14,7 @@ export async function loader({ request }: LoaderFunctionArgs) {
export default function Index() {
return (
<div className="bg-sky-100 rounded-lg">
<div className="bg-ctp-sky/10 rounded-lg">
<div className="flex flex-col gap-1 h-96 justify-center items-center">
<img src="/logo.png" alt="twitter" width={64} />
<h1 className="text-7xl font-black">

View File

@ -1,4 +1,7 @@
import type { PostWithRelations } from "~/utils/prisma.server";
import type {
PostWithRelations,
UserWithRelations,
} from "~/utils/prisma.server";
import type {
ActionFunction,
ActionFunctionArgs,
@ -18,9 +21,11 @@ import { FormLabel, TextArea } from "~/components/Form";
import { Post } from "~/components/Post";
import { SubTitle, Text, Title } from "~/components/Typography";
import { likePost, unLikePost } from "~/models/like.server";
import { createPost, getAllPosts, getFeed } from "~/models/post.server";
import { createPost, getFeed } from "~/models/post.server";
import { commitSession, getSession } from "~/utils/session.server";
import type { RootLoaderTypes } from "~/root";
import { repost, unRepost } from "~/models/repost.server";
import { getUserByName } from "~/models/user.server";
export async function action({ request }: ActionFunctionArgs) {
const session = await getSession(request.headers.get("Cookie"));
@ -62,7 +67,27 @@ export async function action({ request }: ActionFunctionArgs) {
return unLikeErrors;
}
session.flash("globalMessage", "Post unliked!");
session.flash("globalMessage", "Post un-liked!");
break;
case "repost":
const repostErrors = await repost(Number(formData.postId), userId);
if (Object.values(repostErrors).some(Boolean)) {
return repostErrors;
}
session.flash("globalMessage", "Post reposted!");
break;
case "unrepost":
const unrepostErrors = await unRepost(Number(formData.postId), userId);
if (Object.values(unrepostErrors).some(Boolean)) {
return unrepostErrors;
}
session.flash("globalMessage", "Post un-reposted!");
break;
default:
@ -85,12 +110,17 @@ export async function loader({ request }: LoaderFunctionArgs) {
return redirect("/");
}
return await getFeed(session.get("userId"));
return {
feed: await getFeed(session.get("userId")),
self: await getUserByName(session.get("username")),
};
}
export default function Index() {
const rootData = useRouteLoaderData<RootLoaderTypes>("root");
const data: PostWithRelations[] = useLoaderData<LoaderFunction>();
const data: { feed: PostWithRelations[]; self: UserWithRelations } =
useLoaderData<LoaderFunction>();
console.log(data.self.following);
const errors = useActionData<ActionFunction>();
return (
@ -99,7 +129,7 @@ export default function Index() {
<Card>
{rootData?.id ? (
<>
<Title>New post</Title>
<SubTitle>New post</SubTitle>
<Form className="flex flex-col gap-3" method="POST">
<input type="hidden" name="intent" value={"newpost"} />
<FormLabel>
@ -118,9 +148,9 @@ export default function Index() {
)}
</Card>
<SubTitle>Feed</SubTitle>
{data.length > 0 ? (
{data.feed.length > 0 ? (
<Card className="!p-0 !gap-0 divide-y">
{data.map((post: PostWithRelations) => (
{data.feed.map((post) => (
<Post userId={rootData?.id} key={post.id} post={post} />
))}
</Card>

View File

@ -66,7 +66,7 @@ export default function Login() {
<Text>Password</Text> <FormInput type="password" name="password" />{" "}
<Text type="error">{error ? error : ""}</Text>
</FormLabel>
<Button type="submit" />
<Button type="submit">Login</Button>
</Form>
</Card>
);

View File

@ -12,7 +12,7 @@ import { Form, useActionData } from "@remix-run/react";
import { Button } from "~/components/Button";
import { Card } from "~/components/Card";
import { FormInput, FormLabel } from "~/components/Form";
import { SubTitle, Title, Text } from "~/components/Typography";
import { Title, Text } from "~/components/Typography";
import { editUser } from "~/models/user.server";
import { profilePictureUploadHandler } from "~/utils/file.server";
import { commitSession, getSession } from "~/utils/session.server";
@ -82,7 +82,7 @@ export default function Settings() {
<div className="flex flex-col gap-5">
<details className="space-y-4">
<summary className="text-gray-600 cursor-pointer select-none">
Change username
<span className="text-ctp-text/75">Change username</span>
</summary>
<Card>
<Form method="POST" className="relative flex flex-col gap-3">
@ -95,9 +95,7 @@ export default function Settings() {
</FormLabel>
<Button type="submit">Submit</Button>
{data?.success?.username ? (
<Text className="!absolute right-0" type="success">
{data.success.username}
</Text>
<Text type="success">{data.success.username}</Text>
) : (
""
)}
@ -106,7 +104,7 @@ export default function Settings() {
</details>
<details className="space-y-4">
<summary className="text-gray-600 cursor-pointer select-none">
Change name
<span className="text-ctp-text/75">Change name</span>
</summary>
<Card>
<Form method="POST" className="relative flex flex-col gap-3">
@ -119,9 +117,7 @@ export default function Settings() {
</FormLabel>
<Button type="submit">Submit</Button>
{data?.success?.name ? (
<Text className="!absolute right-0" type="success">
{data.success.name}
</Text>
<Text type="success">{data.success.name}</Text>
) : (
""
)}
@ -130,7 +126,7 @@ export default function Settings() {
</details>
<details className="space-y-4">
<summary className="text-gray-600 cursor-pointer select-none">
Change description
<span className="text-ctp-text/75">Change description</span>
</summary>
<Card>
<Form method="POST" className="relative flex flex-col gap-3">
@ -143,9 +139,7 @@ export default function Settings() {
</FormLabel>
<Button type="submit">Submit</Button>
{data?.success?.desc ? (
<Text className="!absolute right-0" type="success">
{data.success.desc}
</Text>
<Text type="success">{data.success.desc}</Text>
) : (
""
)}
@ -154,7 +148,7 @@ export default function Settings() {
</details>
<details className="space-y-4">
<summary className="text-gray-600 cursor-pointer select-none">
Change password
<span className="text-ctp-text/75">Change password</span>
</summary>
<Card>
<Form method="POST" className="relative flex flex-col gap-3">
@ -175,9 +169,7 @@ export default function Settings() {
</FormLabel>
<Button type="submit">Submit</Button>
{data?.success?.password ? (
<Text className="!absolute right-0" type="success">
{data.success.password}
</Text>
<Text type="success">{data.success.password}</Text>
) : (
""
)}
@ -186,7 +178,7 @@ export default function Settings() {
</details>
<details className="space-y-4">
<summary className="text-gray-600 cursor-pointer select-none">
Change profile picture
<span className="text-ctp-text/75">Change profile picture</span>
</summary>
<Card>
<Form
@ -204,9 +196,7 @@ export default function Settings() {
</FormLabel>
<Button type="submit">Submit</Button>
{data?.success?.pfp ? (
<Text className="!absolute right-0" type="success">
{data.success.pfp}
</Text>
<Text type="success">{data.success.pfp}</Text>
) : (
""
)}

View File

@ -143,7 +143,12 @@ export default function UserProfile() {
{data.likes.length > 0 ? (
<Card className="!p-0 !gap-0 divide-y">
{data.likes.map((post) => (
<Post userId={rootData?.id} key={post.id} post={post} />
<Post
userId={rootData?.id}
key={post.id}
post={post}
liker={data}
/>
))}
</Card>
) : (

View File

@ -1,3 +1,10 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
*,
::before,
::after {
@apply dark:border-ctp-overlay0/75;
}
}

View File

@ -3,7 +3,7 @@ import { PrismaClient } from "@prisma/client";
let prisma: PrismaClient;
export type PostWithRelations = Prisma.PostGetPayload<{
include: { likes: true; author: true };
include: { likes: true; author: true; reposts: true };
}>;
export type UserWithRelations = Prisma.UserGetPayload<{
include: {
@ -12,12 +12,22 @@ export type UserWithRelations = Prisma.UserGetPayload<{
id: true;
};
};
following: {
select: {
id: true;
};
};
posts: {
include: {
author: true;
likes: true;
};
};
reposts: {
include: {
author: true;
};
};
likes: {
include: {
author: true;

View File

@ -21,11 +21,12 @@
"react-dom": "^18.2.0"
},
"devDependencies": {
"@catppuccin/tailwindcss": "^0.1.6",
"@remix-run/dev": "^2.2.0",
"@remix-run/eslint-config": "^2.2.0",
"@types/bcryptjs": "^2.4.5",
"@types/react": "^18.2.20",
"@types/react-dom": "^18.2.7",
"@types/bcryptjs": "^2.4.5",
"eslint": "^8.38.0",
"prisma": "^5.5.2",
"tailwindcss": "^3.3.5",

View File

@ -34,6 +34,9 @@ dependencies:
version: 18.2.0(react@18.2.0)
devDependencies:
'@catppuccin/tailwindcss':
specifier: ^0.1.6
version: 0.1.6(tailwindcss@3.3.5)
'@remix-run/dev':
specifier: ^2.2.0
version: 2.2.0(@remix-run/serve@2.2.0)(typescript@5.2.2)
@ -486,6 +489,14 @@ packages:
to-fast-properties: 2.0.0
dev: true
/@catppuccin/tailwindcss@0.1.6(tailwindcss@3.3.5):
resolution: {integrity: sha512-V+Y0AwZ5SSyvOVAcDl7Ng30xy+m82OKnEJ+9+kcZZ7lRyXuZrAb2GScdq9XR3v+ggt8qiZ/G4TvaC9cJ88AAXA==}
peerDependencies:
tailwindcss: '>=3.0.0'
dependencies:
tailwindcss: 3.3.5
dev: true
/@emotion/hash@0.9.1:
resolution: {integrity: sha512-gJB6HLm5rYwSLI6PQa+X1t5CFGrv1J1TWG+sOyMCeKz2ojaj6Fnl/rZEspogG+cvqbt4AE/2eIyD2QfLKTBNlQ==}
dev: true

View File

@ -0,0 +1,13 @@
-- CreateTable
CREATE TABLE "_UserReposts" (
"A" INTEGER NOT NULL,
"B" INTEGER NOT NULL,
CONSTRAINT "_UserReposts_A_fkey" FOREIGN KEY ("A") REFERENCES "Post" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT "_UserReposts_B_fkey" FOREIGN KEY ("B") REFERENCES "User" ("id") ON DELETE CASCADE ON UPDATE CASCADE
);
-- CreateIndex
CREATE UNIQUE INDEX "_UserReposts_AB_unique" ON "_UserReposts"("A", "B");
-- CreateIndex
CREATE INDEX "_UserReposts_B_index" ON "_UserReposts"("B");

View File

@ -19,6 +19,7 @@ model User {
pfp String
posts Post[] @relation("UserPosts")
likes Post[] @relation("UserLikes")
reposts Post[] @relation("UserReposts")
following User[] @relation("UserFollows")
followedBy User[] @relation("UserFollows")
@ -27,11 +28,12 @@ model User {
}
model Post {
id Int @id @default(autoincrement())
text String
author User @relation("UserPosts", fields: [userId], references: [id])
userId Int
likes User[] @relation("UserLikes")
id Int @id @default(autoincrement())
text String
author User @relation("UserPosts", fields: [userId], references: [id])
userId Int
likes User[] @relation("UserLikes")
reposts User[] @relation("UserReposts")
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt

View File

@ -5,5 +5,9 @@ export default {
theme: {
extend: {},
},
plugins: [],
plugins: [
require("@catppuccin/tailwindcss")({
prefix: "ctp",
}),
],
} satisfies Config;