191 lines
5.5 KiB
TypeScript
191 lines
5.5 KiB
TypeScript
import { z } from "zod";
|
|
|
|
import {
|
|
createTRPCRouter,
|
|
protectedProcedure,
|
|
publicProcedure,
|
|
} from "@/server/api/trpc";
|
|
import { Article, articles } from "@/server/db/schema";
|
|
import {
|
|
articleFilterSchema,
|
|
articleSchema,
|
|
createArticleSchema,
|
|
} from "@/lib/validation/zod/article";
|
|
import { and, asc, count, desc, eq, gt, ilike, like, sql } from "drizzle-orm";
|
|
import { hasPermission, Role } from "@/lib/validation/permissions";
|
|
import { generateSlug } from "@/lib/utils";
|
|
|
|
const getArticleSorting = (sort: string) => {
|
|
switch (sort) {
|
|
case "newest":
|
|
return desc(articles.createdAt);
|
|
case "oldest":
|
|
return asc(articles.createdAt);
|
|
case "abc":
|
|
return asc(articles.title);
|
|
case "cba":
|
|
return desc(articles.title);
|
|
default:
|
|
return desc(articles.createdAt); // Default to newest
|
|
}
|
|
};
|
|
|
|
export const articleRouter = createTRPCRouter({
|
|
// queries
|
|
search: publicProcedure
|
|
.input(z.object({ query: z.string() }))
|
|
.query(async ({ ctx, input }) => {
|
|
return (await ctx.db.query.articles.findMany({
|
|
where: like(articles.title, "%" + input.query + "%"),
|
|
with: { category: true },
|
|
})) as Article[];
|
|
}),
|
|
get: publicProcedure
|
|
.input(z.object({ slug: z.string() }))
|
|
.query(async ({ ctx, input }) => {
|
|
return await ctx.db.query.articles.findFirst({
|
|
where: eq(articles.slug, input.slug),
|
|
with: { category: true },
|
|
});
|
|
}),
|
|
getByCursor: publicProcedure
|
|
.input(
|
|
z.object({
|
|
limit: z.number().optional(),
|
|
cursor: z.string().optional(),
|
|
filter: articleFilterSchema.optional(),
|
|
}),
|
|
)
|
|
.query(async ({ ctx, input }) => {
|
|
const { cursor } = input!;
|
|
const limit = input?.limit ?? 50;
|
|
const cursorArg = cursor ? gt(articles.slug, cursor) : undefined;
|
|
|
|
const queryFilterArg = input?.filter?.query?.length
|
|
? ilike(articles.title, "%" + input.filter.query + "%")
|
|
: undefined;
|
|
const categoryArg = input?.filter?.category
|
|
? eq(articles.categoryId, input.filter.category)
|
|
: undefined;
|
|
const orderBy = getArticleSorting(input?.filter?.sort ?? "newest");
|
|
const items = await ctx.db.query.articles.findMany({
|
|
where: and(
|
|
cursorArg,
|
|
categoryArg,
|
|
queryFilterArg,
|
|
eq(articles.published, true),
|
|
),
|
|
limit: limit + 1,
|
|
orderBy,
|
|
columns: {
|
|
title: true,
|
|
slug: true,
|
|
createdAt: true,
|
|
},
|
|
});
|
|
let nextCursor: typeof cursor | undefined = undefined;
|
|
if (items.length > limit) {
|
|
const nextItem = items.pop();
|
|
nextCursor = nextItem!.slug;
|
|
}
|
|
return {
|
|
items,
|
|
nextCursor,
|
|
previousCursor: cursor,
|
|
};
|
|
}),
|
|
getAll: publicProcedure
|
|
.input(
|
|
z
|
|
.object({
|
|
categoryId: z.string().optional(),
|
|
limit: z.number().optional(),
|
|
})
|
|
.optional(),
|
|
)
|
|
.query(async ({ ctx, input }) => {
|
|
return await ctx.db.query.articles.findMany({
|
|
where: input?.categoryId
|
|
? eq(articles.categoryId, input.categoryId)
|
|
: undefined,
|
|
limit: input?.limit,
|
|
columns: {
|
|
title: true,
|
|
slug: true,
|
|
createdAt: true,
|
|
},
|
|
});
|
|
}),
|
|
|
|
getCount: publicProcedure
|
|
.input(z.object({ categoryId: z.string() }).optional())
|
|
.query(async ({ ctx, input }) => {
|
|
return (
|
|
await ctx.db
|
|
.select({ count: count() })
|
|
.from(articles)
|
|
.where(
|
|
input?.categoryId
|
|
? eq(articles.categoryId, input.categoryId)
|
|
: undefined,
|
|
)
|
|
)[0]?.count;
|
|
}),
|
|
// mutations
|
|
create: protectedProcedure
|
|
.input(z.object({ article: createArticleSchema }))
|
|
.mutation(async ({ ctx, input }) => {
|
|
const isEditor = hasPermission(ctx.session.user.role, Role.EDITOR);
|
|
if (!isEditor) {
|
|
throw new Error("You are not allowed to create articles");
|
|
}
|
|
const slug = generateSlug(input.article.title);
|
|
return await ctx.db
|
|
.insert(articles)
|
|
.values({ ...input.article, slug })
|
|
.returning({
|
|
slug: articles.slug,
|
|
})
|
|
.onConflictDoUpdate({
|
|
target: articles.slug,
|
|
set: {
|
|
slug: sql`${slug} || '-' || (SELECT COUNT(*) FROM ${articles} WHERE slug LIKE ${slug + "-%"})`,
|
|
},
|
|
});
|
|
}),
|
|
update: protectedProcedure
|
|
.input(z.object({ article: articleSchema, articleId: z.string() }))
|
|
.mutation(async ({ ctx, input }) => {
|
|
const isEditor = hasPermission(ctx.session.user.role, Role.EDITOR);
|
|
if (!isEditor) {
|
|
throw new Error("You are not allowed to update articles");
|
|
}
|
|
console.log(
|
|
"Content before save",
|
|
JSON.stringify(input.article.content),
|
|
);
|
|
|
|
return await ctx.db
|
|
.update(articles)
|
|
.set(input.article)
|
|
.where(eq(articles.id, input.articleId))
|
|
.returning({
|
|
slug: articles.slug,
|
|
});
|
|
}),
|
|
delete: protectedProcedure
|
|
.input(z.object({ articleId: z.string() }))
|
|
.mutation(async ({ ctx, input }) => {
|
|
const isEditor = hasPermission(ctx.session.user.role, Role.EDITOR);
|
|
if (!isEditor) {
|
|
throw new Error("You are not allowed to delete articles");
|
|
}
|
|
return await ctx.db
|
|
.delete(articles)
|
|
.where(eq(articles.id, input.articleId))
|
|
.returning({
|
|
id: articles.id,
|
|
});
|
|
}),
|
|
});
|