Files
conve-x/convex/posts.ts
T

244 lines
6.1 KiB
TypeScript

import { paginationOptsValidator } from 'convex/server';
import { v } from 'convex/values';
import { PostWithUserDto } from '../src/app/types';
import { Doc } from './_generated/dataModel.d';
import { QueryCtx, mutation, query } from './_generated/server';
import { createUserDto, userQuery } from './users';
export const createOrUpdatePost = mutation({
args: {
id: v.optional(v.id('posts')),
clerkUserId: v.string(),
content: v.array(v.string()),
parentPostId: v.optional(v.id('posts')),
},
handler: async (ctx, args) => {
const userIdentity = await ctx.auth.getUserIdentity();
const user = await userQuery(ctx, args.clerkUserId);
if (userIdentity === null) {
return 'USER_NOT_AUTHORIZED';
}
if (user === null) {
return 'USER_NOT_FOUND';
}
if (userIdentity.subject !== args.clerkUserId) {
return 'CANNOT_POST_ON_BEHALF_OF_ANOTHER_USER';
}
const contentText = args.content.join('\n');
if (args.id) {
await ctx.db.patch(args.id, { content: args.content, contentText });
return;
}
return await ctx.db.insert('posts', {
userId: user._id,
content: args.content,
parentPostId: args.parentPostId,
contentText,
});
},
});
export const getUsersPostById = query({
args: { userId: v.id('users'), paginationOpts: paginationOptsValidator },
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))
.order('desc')
.paginate(args.paginationOpts);
const postDtos = posts.page.map(createPostDto);
return { ...posts, page: postDtos };
},
});
export const deletePostById = mutation({
args: { id: v.id('posts') },
handler: async (ctx, args) => {
await ctx.db.delete(args.id);
},
});
export const getPostById = query({
args: { id: v.id('posts') },
handler: async (
ctx,
args
): Promise<
PostWithUserDto | 'POST_NOT_FOUND' | 'USER_FOR_POST_NOT_FOUND'
> => {
const post = await ctx.db.get(args.id);
if (post === null) {
return 'POST_NOT_FOUND';
}
const user = await ctx.db.get(post.userId);
if (user === null) {
return 'USER_FOR_POST_NOT_FOUND';
}
return {
...createPostDto(post),
user: createUserDto(user),
};
},
});
export const getRepliesByParentId = query({
args: { id: v.id('posts'), paginationOpts: paginationOptsValidator },
handler: async (ctx, args) => {
const replies = await ctx.db
.query('posts')
.withIndex('by_parent_id', q => q.eq('parentPostId', args.id))
.order('desc')
.paginate(args.paginationOpts);
const repliesWithUserData = await getPostsWithUsers({
ctx,
posts: replies.page,
});
return { ...replies, page: repliesWithUserData };
},
});
export const getPostReplyCount = query({
args: { postId: v.id('posts') },
handler: async (ctx, args) => {
const replies = await ctx.db
.query('posts')
.withIndex('by_parent_id', q => q.eq('parentPostId', args.postId))
.collect();
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 getPostsWithUsers({ ctx, posts: posts.page });
return { ...posts, page: postsWithUser };
},
});
export const getAllPostsForFollowings = query({
args: { paginationOpts: paginationOptsValidator },
handler: async (ctx, args) => {
const currentUser = await ctx.auth.getUserIdentity();
if (currentUser === null) {
return { isDone: true, continueCursor: '', page: [] };
}
const user = await userQuery(ctx, currentUser.subject);
if (user === null) {
return { isDone: true, continueCursor: '', page: [] };
}
const followings = await ctx.db
.query('follows')
.withIndex('by_follower', q => q.eq('follower', user._id))
.collect();
const posts = await ctx.db
.query('posts')
.filter(q =>
q.and(
q.or(
...followings.map(following =>
q.eq(q.field('userId'), following.following)
)
),
q.eq(q.field('parentPostId'), undefined)
)
)
.order('desc')
.paginate(args.paginationOpts);
const postsWithUser = await getPostsWithUsers({ ctx, posts: posts.page });
return { ...posts, page: postsWithUser };
},
});
export const getPostsBySearchTerm = query({
args: { term: v.string(), paginationOpts: paginationOptsValidator },
handler: async (ctx, args) => {
const posts = await ctx.db
.query('posts')
.withSearchIndex('search_by_content', q =>
q.search('contentText', args.term)
)
.filter(q => q.eq(q.field('parentPostId'), undefined))
.paginate(args.paginationOpts);
const postsWithUser = await getPostsWithUsers({ ctx, posts: posts.page });
return { ...posts, page: postsWithUser };
},
});
async function getPostsWithUsers({
ctx,
posts,
}: {
ctx: QueryCtx;
posts: Doc<'posts'>[];
}) {
return await Promise.all(
posts.map(async post => {
const postDto = createPostDto(post);
const user = await ctx.db.get(post.userId);
if (user === null) {
return {
...postDto,
user: createUserDto(user),
};
}
return {
...postDto,
user: createUserDto(user),
};
})
);
}
function createPostDto(post: Doc<'posts'>) {
const { contentText, ...postDto } = post;
return postDto;
}