added category infinite grid and fixed missing items at useInfiniteQuery
This commit is contained in:
@@ -11,22 +11,50 @@ import {
|
||||
articleSchema,
|
||||
createArticleSchema,
|
||||
} from "@/lib/validation/zod/article";
|
||||
import { and, asc, count, desc, eq, gt, ilike, like, sql } from "drizzle-orm";
|
||||
import {
|
||||
and,
|
||||
asc,
|
||||
count,
|
||||
desc,
|
||||
eq,
|
||||
gte,
|
||||
ilike,
|
||||
like,
|
||||
lte,
|
||||
sql,
|
||||
} from "drizzle-orm";
|
||||
import { hasPermission, Role } from "@/lib/validation/permissions";
|
||||
import { generateSlug } from "@/lib/utils";
|
||||
import SuperJSON from "superjson";
|
||||
|
||||
type ArticleCursor = Pick<Article, "slug" | "createdAt" | "title">;
|
||||
const getArticleSorting = (sort: string, cursor?: ArticleCursor) => {
|
||||
// Default to newest
|
||||
const baseCase = {
|
||||
orderBy: [desc(articles.createdAt), asc(articles.slug)],
|
||||
cursor: cursor ? lte(articles.createdAt, cursor.createdAt) : undefined,
|
||||
};
|
||||
|
||||
const getArticleSorting = (sort: string) => {
|
||||
switch (sort) {
|
||||
case "newest":
|
||||
return desc(articles.createdAt);
|
||||
return baseCase;
|
||||
case "oldest":
|
||||
return asc(articles.createdAt);
|
||||
return {
|
||||
orderBy: [asc(articles.createdAt), asc(articles.slug)],
|
||||
cursor: cursor ? gte(articles.createdAt, cursor.createdAt) : undefined,
|
||||
};
|
||||
case "abc":
|
||||
return asc(articles.title);
|
||||
return {
|
||||
orderBy: [asc(articles.title), asc(articles.slug)],
|
||||
cursor: cursor ? gte(articles.title, cursor.title) : undefined,
|
||||
};
|
||||
case "cba":
|
||||
return desc(articles.title);
|
||||
return {
|
||||
orderBy: [desc(articles.title), asc(articles.slug)],
|
||||
cursor: cursor ? lte(articles.title, cursor.title) : undefined,
|
||||
};
|
||||
default:
|
||||
return desc(articles.createdAt); // Default to newest
|
||||
return baseCase;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -59,15 +87,32 @@ export const articleRouter = createTRPCRouter({
|
||||
.query(async ({ ctx, input }) => {
|
||||
const { cursor } = input!;
|
||||
const limit = input?.limit ?? 50;
|
||||
const cursorArg = cursor ? gt(articles.slug, cursor) : undefined;
|
||||
|
||||
// Decode cursor if using it
|
||||
let cursorObj: ArticleCursor | 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(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 sortConfig = input?.filter?.sort ?? "newest";
|
||||
const { orderBy, cursor: cursorArg } = getArticleSorting(
|
||||
sortConfig,
|
||||
cursorObj,
|
||||
);
|
||||
|
||||
const items = await ctx.db.query.articles.findMany({
|
||||
where: and(
|
||||
cursorArg,
|
||||
@@ -83,15 +128,26 @@ export const articleRouter = createTRPCRouter({
|
||||
createdAt: true,
|
||||
},
|
||||
});
|
||||
let nextCursor: typeof cursor | undefined = undefined;
|
||||
|
||||
let nextCursor: string | undefined = undefined;
|
||||
if (items.length > limit) {
|
||||
const nextItem = items.pop();
|
||||
nextCursor = nextItem!.slug;
|
||||
console.log("Configure next cursor");
|
||||
const cursorItem = items.pop();
|
||||
// Create a cursor object with the relevant fields for sorting
|
||||
const cursorData: ArticleCursor = {
|
||||
slug: cursorItem!.slug,
|
||||
createdAt: cursorItem!.createdAt,
|
||||
title: cursorItem!.title,
|
||||
};
|
||||
// Encode the cursor as base64
|
||||
nextCursor = Buffer.from(SuperJSON.stringify(cursorData)).toString(
|
||||
"base64",
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
items,
|
||||
nextCursor,
|
||||
previousCursor: cursor,
|
||||
};
|
||||
}),
|
||||
getAll: publicProcedure
|
||||
@@ -160,10 +216,6 @@ export const articleRouter = createTRPCRouter({
|
||||
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)
|
||||
|
||||
@@ -6,10 +6,58 @@ import {
|
||||
} from "@/server/api/trpc";
|
||||
import { categories, Category } from "@/server/db/schema";
|
||||
|
||||
import { count, eq, like } from "drizzle-orm";
|
||||
import {
|
||||
and,
|
||||
asc,
|
||||
count,
|
||||
desc,
|
||||
eq,
|
||||
gt,
|
||||
gte,
|
||||
ilike,
|
||||
like,
|
||||
lte,
|
||||
} from "drizzle-orm";
|
||||
import { hasPermission, Role } from "@/lib/validation/permissions";
|
||||
import { categorySchema } from "@/lib/validation/zod/category";
|
||||
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
|
||||
@@ -42,6 +90,71 @@ export const categoryRouter = createTRPCRouter({
|
||||
});
|
||||
}),
|
||||
|
||||
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;
|
||||
}),
|
||||
|
||||
@@ -29,7 +29,7 @@ export const articles = createTable(
|
||||
.primaryKey()
|
||||
.$defaultFn(() => createId())
|
||||
.notNull(),
|
||||
title: varchar("title", { length: 256 }),
|
||||
title: varchar("title", { length: 256 }).notNull(),
|
||||
slug: varchar("slug", { length: 256 }).unique().notNull(),
|
||||
authorId: varchar("author_id", { length: 255 }),
|
||||
content: jsonb("content").$type<JSONContent>(),
|
||||
@@ -68,7 +68,7 @@ export const categories = createTable(
|
||||
.primaryKey()
|
||||
.$defaultFn(() => createId())
|
||||
.notNull(),
|
||||
name: varchar("name", { length: 256 }),
|
||||
name: varchar("name", { length: 256 }).notNull(),
|
||||
description: text("description"),
|
||||
slug: varchar("slug", { length: 256 }).unique().notNull(),
|
||||
|
||||
|
||||
Reference in New Issue
Block a user