diff --git a/app/api/posts/route.ts b/app/api/posts/route.ts new file mode 100644 index 0000000..8f747f7 --- /dev/null +++ b/app/api/posts/route.ts @@ -0,0 +1,82 @@ +import { getAuthSession } from "@/lib/auth"; +import { db } from "@/lib/db"; +import { z } from "zod"; + +export async function GET(req: Request) { + const url = new URL(req.url); + + const session = await getAuthSession(); + + let followedCommunitiesIds: string[] = []; + + if (session) { + const followedCommunities = await db.subscription.findMany({ + where: { + userId: session.user.id, + }, + include: { + subgroup: true, + }, + }); + + followedCommunitiesIds = followedCommunities.map((sub) => sub.subgroup.id); + } + + // Fetch posts + try { + const { limit, page, subgroupName } = z + .object({ + limit: z.string(), + page: z.string(), + subgroupName: z.string().nullish().optional(), // for specific subgroup + }) + .parse({ + subgroupName: url.searchParams.get("sub"), + limit: url.searchParams.get("limit"), + page: url.searchParams.get("page"), + }); + + // Filter posts by subgroup or followed communities + let whereClause = {}; + + if (subgroupName) { + whereClause = { + subgroup: { + name: subgroupName, + }, + }; + } else if (session) { + whereClause = { + subgroup: { + id: { + in: followedCommunitiesIds, + }, + }, + }; + } + + // Infinite scrolling pagination + const posts = await db.post.findMany({ + take: parseInt(limit), + skip: (parseInt(page) - 1) * parseInt(limit), // skip should start from 0 for page 1 + orderBy: { + createdAt: "desc", + }, + include: { + subgroup: true, + votes: true, + author: true, + comments: true, + }, + where: whereClause, + }); + + return new Response(JSON.stringify(posts)); + } catch (error) { + if (error instanceof z.ZodError) { + return new Response("Invalid data passed", { status: 422 }); // Unprocessable entity + } + + return new Response("Could not fetch posts", { status: 500 }); + } +} diff --git a/app/sub/[subId]/layout.tsx b/app/sub/[subId]/layout.tsx index b3adf78..74ab157 100644 --- a/app/sub/[subId]/layout.tsx +++ b/app/sub/[subId]/layout.tsx @@ -1,4 +1,3 @@ -// import ToFeedButton from '@/components/ToFeedButton' import SubscribeLeaveToggle from "@/components/SubscribeLeaveToggle"; import { buttonVariants } from "@/components/ui/button"; import { getAuthSession } from "@/lib/auth"; @@ -66,71 +65,70 @@ export default async function layout({ }); return ( -
-
- {/* */} - -
-
    {children}
- - {/* Info Sidebar */} -
-
-

- About sub/{subgroup.name} -

+
+ {/* */} + +
+
+
    {children}
+
+ + {/* Info Sidebar */} +
-
+
); diff --git a/app/sub/[subId]/post/[postId]/page.tsx b/app/sub/[subId]/post/[postId]/page.tsx new file mode 100644 index 0000000..2ad7d3d --- /dev/null +++ b/app/sub/[subId]/post/[postId]/page.tsx @@ -0,0 +1,112 @@ +import EditorOutput from "@/components/EditorOutput"; +import PostVoteServer from "@/components/pages/PostVoteServer"; +import { buttonVariants } from "@/components/ui/button"; +import { db } from "@/lib/db"; +import { redis } from "@/lib/redis"; +import { formatTimeToNow } from "@/lib/utils"; +import { CachedPost } from "@/types/redis"; +import { Post, User, Vote } from "@prisma/client"; +import { ArrowBigDown, ArrowBigUp, Loader2 } from "lucide-react"; +import { notFound } from "next/navigation"; +import { Suspense } from "react"; + +interface SubgroupPostPageProps { + params: { + postId: string; + subId: string; + }; +} + +export const dynamic = "force-dynamic"; +export const fetchCache = "force-no-store"; + +const SubgroupPostPage = async ({ params }: SubgroupPostPageProps) => { + const cachedPost = (await redis.hgetall( + `post:${params.postId}` + )) as CachedPost; + + let post: (Post & { votes: Vote[]; author: User }) | null = null; + + if (!cachedPost) { + post = await db.post.findFirst({ + where: { + id: params.postId, + }, + include: { + votes: true, + author: true, + }, + }); + } + + if (!post && !cachedPost) return notFound(); + + return ( +
+
+ }> + { + return await db.post.findUnique({ + where: { + id: params.postId, + }, + include: { + votes: true, + }, + }); + }} + /> + + +
+

+ + Posted by u/{post?.author.username ?? cachedPost.authorUsername} + + + + {formatTimeToNow( + new Date(post?.createdAt ?? cachedPost.createdAt) + )} + +

+

+ {post?.title ?? cachedPost.title} +

+ + + + } + > +
+
+
+ ); +}; + +function PostSkeleton() { + return ( +
+ {/* upvote */} +
+ +
+ + {/* score */} +
+ +
+ + {/* downvote */} +
+ +
+
+ ); +} + +export default SubgroupPostPage; diff --git a/components/pages/CustomFeed.tsx b/components/pages/CustomFeed.tsx new file mode 100644 index 0000000..9b17b8a --- /dev/null +++ b/components/pages/CustomFeed.tsx @@ -0,0 +1,39 @@ +import { Infinite_Scrolling_Pagination_Results } from "@/config"; +import { db } from "@/lib/db"; +import PostFeed from "./PostFeed"; +import { getAuthSession } from "@/lib/auth"; + +export default async function CustomFeed() { + const session = await getAuthSession(); + + const followedCommunitiesPosts = await db.subscription.findMany({ + where: { + userId: session?.user.id, + }, + include: { + subgroup: true, + }, + }); + + const posts = await db.post.findMany({ + where: { + subgroup: { + name: { + in: followedCommunitiesPosts.map(({ subgroup }) => subgroup.id), + }, + }, + }, + orderBy: { + createdAt: "desc", + }, + include: { + votes: true, + author: true, + comments: true, + subgroup: true, + }, + take: Infinite_Scrolling_Pagination_Results, + }); + + return ; +} diff --git a/components/pages/DummyPosts.tsx b/components/pages/DummyPosts.tsx deleted file mode 100644 index 67d162e..0000000 --- a/components/pages/DummyPosts.tsx +++ /dev/null @@ -1,107 +0,0 @@ -import React from "react"; - -import { Avatar, AvatarImage, AvatarFallback } from "@/components/ui/avatar"; -import { Card, CardHeader, CardContent, CardFooter } from "@/components/ui/card"; -import { - ArrowDown, - ArrowUp, - Filter, - MessageCircle, - MessageCircleReply, - MoveDiagonal, -} from "lucide-react"; -import Image from "next/image"; -import { Button } from "@/components/ui/button"; - -type PostType = { - content: string | JSX.Element; - timeAgo: string; - upvotes: number; - comments: number; -}; -export function Posts() { - return ( -
-
-

Your Feed

- -
-
- - - } - timeAgo="1 day ago" - upvotes={456} - comments={78} - /> - -
-
- ); -} - -function Post({ content, timeAgo, upvotes, comments }: PostType) { - return ( - - -
- - - UN - - -
-
Anonymous User
-
{timeAgo}
-
-
- - -
- - -
{content}
-
- - -
{upvotes} upvotes
- - -
{comments} comments
-
-
- ); -} diff --git a/components/pages/GeneralFeed.tsx b/components/pages/GeneralFeed.tsx new file mode 100644 index 0000000..f8bf905 --- /dev/null +++ b/components/pages/GeneralFeed.tsx @@ -0,0 +1,20 @@ +import { Infinite_Scrolling_Pagination_Results } from "@/config"; +import { db } from "@/lib/db"; +import PostFeed from "./PostFeed"; + +export default async function GeneralFeed() { + const posts = await db.post.findMany({ + orderBy: { + createdAt: "desc", + }, + include: { + votes: true, + author: true, + comments: true, + subgroup: true, + }, + take: Infinite_Scrolling_Pagination_Results, + }); + + return ; +} diff --git a/components/pages/Homepage.tsx b/components/pages/Homepage.tsx index 07528a1..afb8838 100644 --- a/components/pages/Homepage.tsx +++ b/components/pages/Homepage.tsx @@ -1,9 +1,12 @@ import { Communities } from "@/components/pages/Communities"; -import { Posts } from "@/components/pages/DummyPosts"; +import { getAuthSession } from "@/lib/auth"; +import CustomFeed from "./CustomFeed"; +import GeneralFeed from "./GeneralFeed"; + +export async function Homepage() { + const session = await getAuthSession(); -export function Homepage() { return ( - //
@@ -11,7 +14,8 @@ export function Homepage() {
- + {/* */} + {session ? : }
);