diff --git a/convex/_generated/api.d.ts b/convex/_generated/api.d.ts
index 12f244c..e85cab7 100644
--- a/convex/_generated/api.d.ts
+++ b/convex/_generated/api.d.ts
@@ -14,6 +14,7 @@ import type {
FilterApi,
FunctionReference,
} from "convex/server";
+import type * as follows from "../follows";
import type * as http from "../http";
import type * as likes from "../likes";
import type * as posts from "../posts";
@@ -28,6 +29,7 @@ import type * as users from "../users";
* ```
*/
declare const fullApi: ApiFromModules<{
+ follows: typeof follows;
http: typeof http;
likes: typeof likes;
posts: typeof posts;
diff --git a/convex/follows.ts b/convex/follows.ts
new file mode 100644
index 0000000..38895ed
--- /dev/null
+++ b/convex/follows.ts
@@ -0,0 +1,116 @@
+import { v } from 'convex/values';
+import { mutation, query } from './_generated/server';
+import { userQuery } from './users';
+
+export const addFollow = mutation({
+ args: { followingId: v.id('users') },
+ handler: async (ctx, args) => {
+ const clerkUser = await ctx.auth.getUserIdentity();
+
+ if (clerkUser === null) {
+ return;
+ }
+
+ const user = await userQuery(ctx, clerkUser.subject);
+
+ if (user === null) {
+ return;
+ }
+
+ const existingFollow = await ctx.db
+ .query('follows')
+ .withIndex('by_following_follower_id', q =>
+ q.eq('following', args.followingId).eq('follower', user._id)
+ )
+ .unique();
+
+ if (existingFollow !== null) {
+ return;
+ }
+
+ await ctx.db.insert('follows', {
+ following: args.followingId,
+ follower: user._id,
+ });
+ },
+});
+
+export const removeFollow = mutation({
+ args: { followingId: v.id('users') },
+ handler: async (ctx, args) => {
+ const clerkUser = await ctx.auth.getUserIdentity();
+
+ if (clerkUser === null) {
+ return;
+ }
+
+ const user = await userQuery(ctx, clerkUser.subject);
+
+ if (user === null) {
+ return;
+ }
+
+ const existingFollow = await ctx.db
+ .query('follows')
+ .withIndex('by_following_follower_id', q =>
+ q.eq('following', args.followingId).eq('follower', user._id)
+ )
+ .unique();
+
+ if (existingFollow === null) {
+ return;
+ }
+
+ await ctx.db.delete(existingFollow._id);
+ },
+});
+
+export const getUserFollowerCount = query({
+ args: { userId: v.id('users') },
+ handler: async (ctx, args) => {
+ const followers = await ctx.db
+ .query('follows')
+ .withIndex('by_following', q => q.eq('following', args.userId))
+ .collect();
+
+ return followers.length;
+ },
+});
+
+export const getUserFollowingCount = query({
+ args: { userId: v.id('users') },
+ handler: async (ctx, args) => {
+ const followings = await ctx.db
+ .query('follows')
+ .withIndex('by_follower', q => q.eq('follower', args.userId))
+ .collect();
+
+ return followings.length;
+ },
+});
+
+export const getFollowing = query({
+ args: { followingId: v.id('users') },
+ handler: async (ctx, args) => {
+ const currentClerkUser = await ctx.auth.getUserIdentity();
+
+ if (currentClerkUser == null) {
+ return false;
+ }
+
+ const user = await userQuery(ctx, currentClerkUser.subject);
+
+ if (user == null) {
+ return false;
+ }
+
+ const following = await ctx.db
+ .query('follows')
+ .withIndex('by_following_follower_id', q =>
+ q.eq('following', args.followingId).eq('follower', user._id)
+ )
+ .unique();
+
+ return following !== null;
+ },
+});
diff --git a/convex/posts.ts b/convex/posts.ts
index c443cea..c96b296 100644
--- a/convex/posts.ts
+++ b/convex/posts.ts
@@ -147,3 +147,59 @@ export const getPostReplyCount = query({
return replies.length;
},
});
+
+export const getUserPostCount = query({
+ args: { userId: v.id('users') },
+ handler: async (ctx, args) => {
+ const posts = await ctx.db
+ .query('posts')
+ .withIndex('by_user_id', q => q.eq('userId', args.userId))
+ .filter(q => q.eq(q.field('parentPostId'), undefined))
+ .collect();
+
+ return posts.length;
+ },
+});
+
+export const getAllPostsWithUser = query({
+ args: { paginationOpts: paginationOptsValidator },
+ handler: async (ctx, args) => {
+ const posts = await ctx.db
+ .query('posts')
+ .filter(q => q.eq(q.field('parentPostId'), undefined))
+ .order('desc')
+ .paginate(args.paginationOpts);
+
+ const postsWithUser = await Promise.all(
+ posts.page.map(async post => {
+ const user = await ctx.db.get(post.userId);
+
+ if (user === null) {
+ return {
+ ...post,
+ user: {
+ _id: '' as Id<'users'>,
+ _creationTime: 0,
+ clerkUsername: null,
+ clerkImageUrl: '',
+ clerkUserId: '',
+ },
+ };
+ }
+
+ return {
+ ...post,
+ user: {
+ _id: user._id,
+ _creationTime: user._creationTime,
+ clerkUsername: user.clerkUser.username,
+ clerkImageUrl: user.clerkUser.image_url,
+ clerkUserId: user.clerkUser.id,
+ },
+ };
+ })
+ );
+
+ return { ...posts, page: postsWithUser };
+ },
+});
diff --git a/convex/schema.ts b/convex/schema.ts
index 3e8033d..8d40c56 100644
--- a/convex/schema.ts
+++ b/convex/schema.ts
@@ -48,6 +48,13 @@ export default defineSchema(
})
.index('by_user_post_id', ['userId', 'postId'])
.index('by_post_user_id', ['postId', 'userId']),
+ follows: defineTable({
+ following: v.id('users'), // person being followed
+ follower: v.id('users'), // person following
+ })
+ .index('by_following', ['following'])
+ .index('by_follower', ['follower'])
+ .index('by_following_follower_id', ['following', 'follower']),
},
{ schemaValidation: false }
);
diff --git a/convex/users.ts b/convex/users.ts
index 0e11f13..b90f9c1 100644
--- a/convex/users.ts
+++ b/convex/users.ts
@@ -93,6 +93,15 @@ export const deleteUser = internalMutation({
.withIndex('by_user_post_id', q => q.eq('userId', userRecord._id))
.collect();
+ const userFollowers = await ctx.db
+ .query('follows')
+ .withIndex('by_following', q => q.eq('following', userRecord._id))
+ .collect();
+ const userFollowings = await ctx.db
+ .query('follows')
+ .withIndex('by_follower', q => q.eq('follower', userRecord._id))
+ .collect();
+
await Promise.all(
userPosts.map(async post => await ctx.db.delete(post._id))
);
@@ -101,6 +110,14 @@ export const deleteUser = internalMutation({
userLikes.map(async like => await ctx.db.delete(like._id))
);
+ await Promise.all(
+ userFollowers.map(async follower => await ctx.db.delete(follower._id))
+ );
+
+ await Promise.all(
+ userFollowings.map(async following => await ctx.db.delete(following._id))
+ );
+
await ctx.db.delete(userRecord._id);
},
});
diff --git a/public/next.svg b/public/next.svg
deleted file mode 100644
index 5174b28..0000000
--- a/public/next.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/public/vercel.svg b/public/vercel.svg
deleted file mode 100644
index d2f8422..0000000
--- a/public/vercel.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/src/app/page.tsx b/src/app/page.tsx
index 1cdf8fa..0f8b653 100644
--- a/src/app/page.tsx
+++ b/src/app/page.tsx
@@ -1,3 +1,12 @@
+import AllPosts from '@/components/AllPosts';
+
export default function Home() {
- return ;
+ return (
+ Home
+