import { experimental_trpcMiddleware } from "@trpc/server";
import { and, eq, gt } from "drizzle-orm";
import { z } from "zod";
import { importStagingBookmarks } from "@karakeep/db/schema";
import {
zCreateImportSessionRequestSchema,
zDeleteImportSessionRequestSchema,
zGetImportSessionStatsRequestSchema,
zImportSessionWithStatsSchema,
zListImportSessionsRequestSchema,
zListImportSessionsResponseSchema,
} from "@karakeep/shared/types/importSessions";
import type { AuthedContext } from "../index";
import { authedProcedure, router } from "../index";
import { ImportSession } from "../models/importSessions";
const ensureImportSessionAccess = experimental_trpcMiddleware<{
ctx: AuthedContext;
input: { importSessionId: string };
}>().create(async (opts) => {
const importSession = await ImportSession.fromId(
opts.ctx,
opts.input.importSessionId,
);
return opts.next({
ctx: {
...opts.ctx,
importSession,
},
});
});
export const importSessionsRouter = router({
createImportSession: authedProcedure
.input(zCreateImportSessionRequestSchema)
.output(z.object({ id: z.string() }))
.mutation(async ({ input, ctx }) => {
const session = await ImportSession.create(ctx, input);
return { id: session.session.id };
}),
getImportSessionStats: authedProcedure
.input(zGetImportSessionStatsRequestSchema)
.output(zImportSessionWithStatsSchema)
.query(async ({ input, ctx }) => {
const session = await ImportSession.fromId(ctx, input.importSessionId);
return await session.getWithStats();
}),
listImportSessions: authedProcedure
.input(zListImportSessionsRequestSchema)
.output(zListImportSessionsResponseSchema)
.query(async ({ ctx }) => {
const sessions = await ImportSession.getAllWithStats(ctx);
return { sessions };
}),
deleteImportSession: authedProcedure
.input(zDeleteImportSessionRequestSchema)
.output(z.object({ success: z.boolean() }))
.mutation(async ({ input, ctx }) => {
const session = await ImportSession.fromId(ctx, input.importSessionId);
await session.delete();
return { success: true };
}),
stageImportedBookmarks: authedProcedure
.input(
z.object({
importSessionId: z.string(),
bookmarks: z
.array(
z.object({
type: z.enum(["link", "text", "asset"]),
url: z.string().optional(),
title: z.string().optional(),
content: z.string().optional(),
note: z.string().optional(),
tags: z.array(z.string()).default([]),
listIds: z.array(z.string()).default([]),
sourceAddedAt: z.date().optional(),
}),
)
.max(50),
}),
)
.use(ensureImportSessionAccess)
.mutation(async ({ input, ctx }) => {
await ctx.importSession.stageBookmarks(input.bookmarks);
}),
finalizeImportStaging: authedProcedure
.input(z.object({ importSessionId: z.string() }))
.use(ensureImportSessionAccess)
.mutation(async ({ ctx }) => {
await ctx.importSession.finalize();
}),
pauseImportSession: authedProcedure
.input(z.object({ importSessionId: z.string() }))
.use(ensureImportSessionAccess)
.mutation(async ({ ctx }) => {
await ctx.importSession.pause();
}),
resumeImportSession: authedProcedure
.input(z.object({ importSessionId: z.string() }))
.use(ensureImportSessionAccess)
.mutation(async ({ ctx }) => {
await ctx.importSession.resume();
}),
getImportSessionResults: authedProcedure
.input(
z.object({
importSessionId: z.string(),
filter: z
.enum(["all", "accepted", "rejected", "skipped_duplicate", "pending"])
.optional(),
cursor: z.string().optional(),
limit: z.number().default(50),
}),
)
.use(ensureImportSessionAccess)
.query(async ({ ctx, input }) => {
const results = await ctx.db
.select()
.from(importStagingBookmarks)
.where(
and(
eq(
importStagingBookmarks.importSessionId,
ctx.importSession.session.id,
),
input.filter && input.filter !== "all"
? input.filter === "pending"
? eq(importStagingBookmarks.status, "pending")
: eq(importStagingBookmarks.result, input.filter)
: undefined,
input.cursor
? gt(importStagingBookmarks.id, input.cursor)
: undefined,
),
)
.orderBy(importStagingBookmarks.id)
.limit(input.limit + 1);
// Return with pagination info
const hasMore = results.length > input.limit;
return {
items: results.slice(0, input.limit),
nextCursor: hasMore ? results[input.limit - 1].id : null,
};
}),
});