207 lines
5.8 KiB
TypeScript
207 lines
5.8 KiB
TypeScript
import { z } from "zod";
|
|
import {
|
|
createTRPCRouter,
|
|
protectedProcedure,
|
|
publicProcedure,
|
|
} from "@/server/api/trpc";
|
|
import { categories, Category } from "@/server/db/schema";
|
|
|
|
import {
|
|
and,
|
|
asc,
|
|
count,
|
|
desc,
|
|
eq,
|
|
gt,
|
|
gte,
|
|
ilike,
|
|
like,
|
|
lte,
|
|
} from "drizzle-orm";
|
|
import { hasPermission, Role } from "@/lib/validation/permissions";
|
|
import {
|
|
categoryFilterSchema,
|
|
categorySchema,
|
|
} from "@/lib/validation/zod/category";
|
|
import { generateSlug } from "@/lib/utils";
|
|
import SuperJSON from "superjson";
|
|
|
|
type CategoryCursor = Pick<Category, "slug" | "createdAt" | "name">;
|
|
const getCategorySorting = (sort: string, cursor?: CategoryCursor) => {
|
|
// Default to newest
|
|
const baseCase = {
|
|
orderBy: [desc(categories.createdAt), asc(categories.slug)],
|
|
cursor: cursor ? lte(categories.createdAt, cursor.createdAt) : undefined,
|
|
};
|
|
|
|
switch (sort) {
|
|
case "newest":
|
|
return baseCase;
|
|
case "oldest":
|
|
return {
|
|
orderBy: [asc(categories.createdAt), asc(categories.slug)],
|
|
cursor: cursor
|
|
? gte(categories.createdAt, cursor.createdAt)
|
|
: undefined,
|
|
};
|
|
case "abc":
|
|
return {
|
|
orderBy: [asc(categories.name), asc(categories.slug)],
|
|
cursor: cursor ? gte(categories.name, cursor.name) : undefined,
|
|
};
|
|
case "cba":
|
|
return {
|
|
orderBy: [desc(categories.name), asc(categories.slug)],
|
|
cursor: cursor ? lte(categories.name, cursor.name) : undefined,
|
|
};
|
|
default:
|
|
return baseCase;
|
|
}
|
|
};
|
|
|
|
export const categoryRouter = createTRPCRouter({
|
|
search: publicProcedure
|
|
.input(z.object({ query: z.string() }))
|
|
.query(async ({ ctx, input }) => {
|
|
return await ctx.db.query.categories.findMany({
|
|
where: like(categories.name, "%" + input.query + "%"),
|
|
});
|
|
}),
|
|
get: publicProcedure
|
|
.input(z.object({ slug: z.string(), with: z.any() }))
|
|
.query(async ({ ctx, input }) => {
|
|
return (await ctx.db.query.categories.findFirst({
|
|
where: eq(categories.slug, input.slug),
|
|
with: input?.with,
|
|
})) as Category;
|
|
}),
|
|
|
|
getAll: publicProcedure
|
|
.input(
|
|
z
|
|
.object({
|
|
limit: z.number().optional(),
|
|
})
|
|
.optional(),
|
|
)
|
|
.query(async ({ ctx, input }) => {
|
|
return await ctx.db.query.categories.findMany({
|
|
limit: input?.limit,
|
|
});
|
|
}),
|
|
|
|
getByCursor: publicProcedure
|
|
.input(
|
|
z.object({
|
|
limit: z.number().optional(),
|
|
cursor: z.string().optional(),
|
|
filter: categoryFilterSchema.optional(),
|
|
}),
|
|
)
|
|
.query(async ({ ctx, input }) => {
|
|
const { cursor } = input!;
|
|
const limit = input?.limit ?? 50;
|
|
|
|
// Decode cursor if using it
|
|
let cursorObj: CategoryCursor | undefined;
|
|
if (cursor) {
|
|
try {
|
|
cursorObj = SuperJSON.parse(Buffer.from(cursor, "base64").toString());
|
|
} catch (e) {
|
|
// Handle invalid cursor
|
|
cursorObj = undefined;
|
|
}
|
|
}
|
|
const queryFilterArg = input?.filter?.query?.length
|
|
? ilike(categories.name, "%" + input.filter.query + "%")
|
|
: undefined;
|
|
|
|
const sortConfig = input?.filter?.sort ?? "newest";
|
|
const { orderBy, cursor: cursorArg } = getCategorySorting(
|
|
sortConfig,
|
|
cursorObj,
|
|
);
|
|
|
|
const items = (await ctx.db.query.categories.findMany({
|
|
where: and(cursorArg, queryFilterArg),
|
|
limit: limit + 1,
|
|
orderBy,
|
|
columns: {
|
|
name: true,
|
|
slug: true,
|
|
createdAt: true,
|
|
},
|
|
})) as Category[];
|
|
|
|
let nextCursor: string | undefined = undefined;
|
|
if (items.length > limit) {
|
|
console.log("Configure next cursor");
|
|
const cursorItem = items.pop();
|
|
// Create a cursor object with the relevant fields for sorting
|
|
const cursorData: CategoryCursor = {
|
|
slug: cursorItem!.slug,
|
|
createdAt: cursorItem!.createdAt,
|
|
name: cursorItem!.name,
|
|
};
|
|
// Encode the cursor as base64
|
|
nextCursor = Buffer.from(SuperJSON.stringify(cursorData)).toString(
|
|
"base64",
|
|
);
|
|
}
|
|
|
|
return {
|
|
items,
|
|
nextCursor,
|
|
};
|
|
}),
|
|
|
|
getCount: publicProcedure.query(async ({ ctx }) => {
|
|
return (await ctx.db.select({ count: count() }).from(categories))[0]?.count;
|
|
}),
|
|
|
|
create: protectedProcedure
|
|
.input(z.object({ category: categorySchema }))
|
|
.mutation(async ({ ctx, input }) => {
|
|
const isEditor = hasPermission(ctx.session.user.role, Role.EDITOR);
|
|
if (!isEditor) {
|
|
throw new Error("You are not allowed to create categories");
|
|
}
|
|
const slug = generateSlug(input.category.name);
|
|
return await ctx.db
|
|
.insert(categories)
|
|
.values({ ...input.category, slug })
|
|
.returning({
|
|
slug: categories.slug,
|
|
});
|
|
}),
|
|
update: protectedProcedure
|
|
.input(z.object({ category: categorySchema, categoryId: 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 categories");
|
|
}
|
|
return await ctx.db
|
|
.update(categories)
|
|
.set(input.category)
|
|
.where(eq(categories.id, input.categoryId))
|
|
.returning({
|
|
id: categories.id,
|
|
});
|
|
}),
|
|
delete: protectedProcedure
|
|
.input(z.object({ categoryId: 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(categories)
|
|
.where(eq(categories.id, input.categoryId))
|
|
.returning({
|
|
id: categories.id,
|
|
});
|
|
}),
|
|
});
|