lists.ts•4.72 kB
import { experimental_trpcMiddleware } from "@trpc/server";
import { z } from "zod";
import {
zBookmarkListSchema,
zEditBookmarkListSchemaWithValidation,
zMergeListSchema,
zNewBookmarkListSchema,
} from "@karakeep/shared/types/lists";
import type { AuthedContext } from "../index";
import { authedProcedure, router } from "../index";
import { List } from "../models/lists";
import { ensureBookmarkOwnership } from "./bookmarks";
export const ensureListOwnership = experimental_trpcMiddleware<{
ctx: AuthedContext;
input: { listId: string };
}>().create(async (opts) => {
const list = await List.fromId(opts.ctx, opts.input.listId);
return opts.next({
ctx: {
...opts.ctx,
list,
},
});
});
export const listsAppRouter = router({
create: authedProcedure
.input(zNewBookmarkListSchema)
.output(zBookmarkListSchema)
.mutation(async ({ input, ctx }) => {
return await List.create(ctx, input).then((l) => l.list);
}),
edit: authedProcedure
.input(zEditBookmarkListSchemaWithValidation)
.output(zBookmarkListSchema)
.use(ensureListOwnership)
.mutation(async ({ input, ctx }) => {
await ctx.list.update(input);
return ctx.list.list;
}),
merge: authedProcedure
.input(zMergeListSchema)
.mutation(async ({ input, ctx }) => {
const [sourceList, targetList] = await Promise.all([
List.fromId(ctx, input.sourceId),
List.fromId(ctx, input.targetId),
]);
return await sourceList.mergeInto(
targetList,
input.deleteSourceAfterMerge,
);
}),
delete: authedProcedure
.input(
z.object({
listId: z.string(),
deleteChildren: z.boolean().optional().default(false),
}),
)
.use(ensureListOwnership)
.mutation(async ({ ctx, input }) => {
if (input.deleteChildren) {
const children = await ctx.list.getChildren();
await Promise.all(children.map((l) => l.delete()));
}
await ctx.list.delete();
}),
addToList: authedProcedure
.input(
z.object({
listId: z.string(),
bookmarkId: z.string(),
}),
)
.use(ensureListOwnership)
.use(ensureBookmarkOwnership)
.mutation(async ({ input, ctx }) => {
await ctx.list.addBookmark(input.bookmarkId);
}),
removeFromList: authedProcedure
.input(
z.object({
listId: z.string(),
bookmarkId: z.string(),
}),
)
.use(ensureListOwnership)
.use(ensureBookmarkOwnership)
.mutation(async ({ input, ctx }) => {
await ctx.list.removeBookmark(input.bookmarkId);
}),
get: authedProcedure
.input(
z.object({
listId: z.string(),
}),
)
.output(zBookmarkListSchema)
.use(ensureListOwnership)
.query(({ ctx }) => {
return ctx.list.list;
}),
list: authedProcedure
.output(
z.object({
lists: z.array(zBookmarkListSchema),
}),
)
.query(async ({ ctx }) => {
const results = await List.getAll(ctx);
return { lists: results.map((l) => l.list) };
}),
getListsOfBookmark: authedProcedure
.input(z.object({ bookmarkId: z.string() }))
.output(
z.object({
lists: z.array(zBookmarkListSchema),
}),
)
.use(ensureBookmarkOwnership)
.query(async ({ input, ctx }) => {
const lists = await List.forBookmark(ctx, input.bookmarkId);
return { lists: lists.map((l) => l.list) };
}),
stats: authedProcedure
.output(
z.object({
stats: z.map(z.string(), z.number()),
}),
)
.query(async ({ ctx }) => {
const lists = await List.getAll(ctx);
const sizes = await Promise.all(lists.map((l) => l.getSize()));
return { stats: new Map(lists.map((l, i) => [l.list.id, sizes[i]])) };
}),
// Rss endpoints
regenRssToken: authedProcedure
.input(
z.object({
listId: z.string(),
}),
)
.output(
z.object({
token: z.string(),
}),
)
.use(ensureListOwnership)
.mutation(async ({ ctx }) => {
const token = await ctx.list.regenRssToken();
return { token: token! };
}),
clearRssToken: authedProcedure
.input(
z.object({
listId: z.string(),
}),
)
.use(ensureListOwnership)
.mutation(async ({ ctx }) => {
await ctx.list.clearRssToken();
}),
getRssToken: authedProcedure
.input(
z.object({
listId: z.string(),
}),
)
.output(
z.object({
token: z.string().nullable(),
}),
)
.use(ensureListOwnership)
.query(async ({ ctx }) => {
return { token: await ctx.list.getRssToken() };
}),
});