aboutsummaryrefslogtreecommitdiffstats
path: root/packages
diff options
context:
space:
mode:
Diffstat (limited to 'packages')
-rw-r--r--packages/api/index.ts50
-rw-r--r--packages/api/middlewares/auth.ts37
-rw-r--r--packages/api/middlewares/trpcAdapter.ts41
-rw-r--r--packages/api/package.json41
-rw-r--r--packages/api/routes/assets.ts48
-rw-r--r--packages/api/routes/bookmarks.ts319
-rw-r--r--packages/api/routes/highlights.ts54
-rw-r--r--packages/api/routes/lists.ts70
-rw-r--r--packages/api/routes/public.ts47
-rw-r--r--packages/api/routes/rss.ts53
-rw-r--r--packages/api/routes/tags.ts70
-rw-r--r--packages/api/routes/users.ts20
-rw-r--r--packages/api/tsconfig.json13
-rw-r--r--packages/api/utils/assets.ts57
-rw-r--r--packages/api/utils/pagination.ts30
-rw-r--r--packages/api/utils/rss.ts54
-rw-r--r--packages/api/utils/types.ts28
-rw-r--r--packages/api/utils/upload.ts110
-rw-r--r--packages/db/drizzle/0046_add_rss_feed_enabled_col.sql1
-rw-r--r--packages/db/drizzle/0047_add_summarization_status.sql1
-rw-r--r--packages/db/drizzle/0048_add_user_settings.sql6
-rw-r--r--packages/db/drizzle/0049_add_rss_token.sql1
-rw-r--r--packages/db/drizzle/0050_add_user_settings_archive_display_behaviour.sql1
-rw-r--r--packages/db/drizzle/0051_public_lists.sql1
-rw-r--r--packages/db/drizzle/meta/0046_snapshot.json1959
-rw-r--r--packages/db/drizzle/meta/0047_snapshot.json1967
-rw-r--r--packages/db/drizzle/meta/0048_snapshot.json2006
-rw-r--r--packages/db/drizzle/meta/0049_snapshot.json2013
-rw-r--r--packages/db/drizzle/meta/0050_snapshot.json2021
-rw-r--r--packages/db/drizzle/meta/0051_snapshot.json2029
-rw-r--r--packages/db/drizzle/meta/_journal.json42
-rw-r--r--packages/db/package.json1
-rw-r--r--packages/db/schema.ts39
-rw-r--r--packages/e2e_tests/docker-compose.yml1
-rw-r--r--packages/e2e_tests/tests/api/assets.test.ts4
-rw-r--r--packages/e2e_tests/tests/api/bookmarks.test.ts274
-rw-r--r--packages/e2e_tests/tests/api/public.test.ts322
-rw-r--r--packages/e2e_tests/tests/api/rss.test.ts155
-rw-r--r--packages/e2e_tests/tests/api/tags.test.ts27
-rw-r--r--packages/e2e_tests/utils/api.ts2
-rw-r--r--packages/e2e_tests/vitest.config.ts3
-rw-r--r--packages/open-api/karakeep-openapi-spec.json195
-rw-r--r--packages/open-api/lib/bookmarks.ts6
-rw-r--r--packages/open-api/lib/lists.ts23
-rw-r--r--packages/open-api/lib/tags.ts48
-rw-r--r--packages/open-api/package.json1
-rw-r--r--packages/open-api/tsconfig.json2
-rw-r--r--packages/sdk/src/karakeep-api.d.ts64
-rw-r--r--packages/shared-react/hooks/bookmarks.ts3
-rw-r--r--packages/shared-react/hooks/users.ts14
-rw-r--r--packages/shared-react/utils/bookmarkUtils.ts70
-rw-r--r--packages/shared/config.ts19
-rw-r--r--packages/shared/inference.ts4
-rw-r--r--packages/shared/langs.ts1
-rw-r--r--packages/shared/package.json1
-rw-r--r--packages/shared/prompts.ts16
-rw-r--r--packages/shared/queues.ts7
-rw-r--r--packages/shared/signedTokens.ts71
-rw-r--r--packages/shared/types/assets.ts6
-rw-r--r--packages/shared/types/bookmarks.ts38
-rw-r--r--packages/shared/types/feeds.ts7
-rw-r--r--packages/shared/types/lists.ts2
-rw-r--r--packages/shared/types/pagination.ts2
-rw-r--r--packages/shared/types/tags.ts4
-rw-r--r--packages/shared/types/users.ts15
-rw-r--r--packages/shared/types/webhooks.ts1
-rw-r--r--packages/shared/utils/assetUtils.ts (renamed from packages/shared-react/utils/assetUtils.ts)0
-rw-r--r--packages/shared/utils/bookmarkUtils.ts90
-rw-r--r--packages/trpc/lib/__tests__/search.test.ts69
-rw-r--r--packages/trpc/lib/attachments.ts2
-rw-r--r--packages/trpc/lib/impersonate.ts30
-rw-r--r--packages/trpc/lib/search.ts110
-rw-r--r--packages/trpc/models/bookmarks.ts422
-rw-r--r--packages/trpc/models/lists.ts140
-rw-r--r--packages/trpc/routers/_app.ts2
-rw-r--r--packages/trpc/routers/admin.ts36
-rw-r--r--packages/trpc/routers/bookmarks.test.ts6
-rw-r--r--packages/trpc/routers/bookmarks.ts286
-rw-r--r--packages/trpc/routers/feeds.ts2
-rw-r--r--packages/trpc/routers/lists.ts43
-rw-r--r--packages/trpc/routers/publicBookmarks.ts49
-rw-r--r--packages/trpc/routers/tags.ts36
-rw-r--r--packages/trpc/routers/users.test.ts37
-rw-r--r--packages/trpc/routers/users.ts43
84 files changed, 15398 insertions, 643 deletions
diff --git a/packages/api/index.ts b/packages/api/index.ts
new file mode 100644
index 00000000..5147ea37
--- /dev/null
+++ b/packages/api/index.ts
@@ -0,0 +1,50 @@
+import { Hono } from "hono";
+import { logger } from "hono/logger";
+import { poweredBy } from "hono/powered-by";
+
+import { Context } from "@karakeep/trpc";
+
+import trpcAdapter from "./middlewares/trpcAdapter";
+import assets from "./routes/assets";
+import bookmarks from "./routes/bookmarks";
+import highlights from "./routes/highlights";
+import lists from "./routes/lists";
+import publicRoute from "./routes/public";
+import rss from "./routes/rss";
+import tags from "./routes/tags";
+import users from "./routes/users";
+
+const v1 = new Hono<{
+ Variables: {
+ ctx: Context;
+ };
+}>()
+ .route("/highlights", highlights)
+ .route("/bookmarks", bookmarks)
+ .route("/lists", lists)
+ .route("/tags", tags)
+ .route("/users", users)
+ .route("/assets", assets)
+ .route("/rss", rss);
+
+const app = new Hono<{
+ Variables: {
+ // This is going to be coming from the web app
+ ctx: Context;
+ };
+}>()
+ .use(logger())
+ .use(poweredBy())
+ .use(async (c, next) => {
+ // Ensure that the ctx is set
+ if (!c.var.ctx) {
+ throw new Error("Context is not set");
+ }
+ await next();
+ })
+ .use(trpcAdapter)
+ .route("/v1", v1)
+ .route("/assets", assets)
+ .route("/public", publicRoute);
+
+export default app;
diff --git a/packages/api/middlewares/auth.ts b/packages/api/middlewares/auth.ts
new file mode 100644
index 00000000..42bca6c8
--- /dev/null
+++ b/packages/api/middlewares/auth.ts
@@ -0,0 +1,37 @@
+import { createMiddleware } from "hono/factory";
+import { HTTPException } from "hono/http-exception";
+
+import { AuthedContext, Context, createCallerFactory } from "@karakeep/trpc";
+import { appRouter } from "@karakeep/trpc/routers/_app";
+
+const createCaller = createCallerFactory(appRouter);
+
+export const unauthedMiddleware = createMiddleware<{
+ Variables: {
+ ctx: Context;
+ api: ReturnType<typeof createCaller>;
+ };
+}>(async (c, next) => {
+ if (!c.var.ctx) {
+ throw new HTTPException(401, {
+ message: "Unauthorized",
+ });
+ }
+ c.set("api", createCaller(c.get("ctx")));
+ await next();
+});
+
+export const authMiddleware = createMiddleware<{
+ Variables: {
+ ctx: AuthedContext;
+ api: ReturnType<typeof createCaller>;
+ };
+}>(async (c, next) => {
+ if (!c.var.ctx || !c.var.ctx.user || c.var.ctx.user === null) {
+ throw new HTTPException(401, {
+ message: "Unauthorized",
+ });
+ }
+ c.set("api", createCaller(c.get("ctx")));
+ await next();
+});
diff --git a/packages/api/middlewares/trpcAdapter.ts b/packages/api/middlewares/trpcAdapter.ts
new file mode 100644
index 00000000..6bb4a790
--- /dev/null
+++ b/packages/api/middlewares/trpcAdapter.ts
@@ -0,0 +1,41 @@
+import { TRPCError } from "@trpc/server";
+import { createMiddleware } from "hono/factory";
+import { HTTPException } from "hono/http-exception";
+
+function trpcCodeToHttpCode(code: TRPCError["code"]) {
+ switch (code) {
+ case "BAD_REQUEST":
+ case "PARSE_ERROR":
+ return 400;
+ case "UNAUTHORIZED":
+ return 401;
+ case "FORBIDDEN":
+ return 403;
+ case "NOT_FOUND":
+ return 404;
+ case "METHOD_NOT_SUPPORTED":
+ return 405;
+ case "TIMEOUT":
+ return 408;
+ case "PAYLOAD_TOO_LARGE":
+ return 413;
+ case "INTERNAL_SERVER_ERROR":
+ return 500;
+ default:
+ return 500;
+ }
+}
+
+const trpcAdapter = createMiddleware(async (c, next) => {
+ await next();
+ const e = c.error;
+ if (e instanceof TRPCError) {
+ const code = trpcCodeToHttpCode(e.code);
+ throw new HTTPException(code, {
+ message: e.message,
+ cause: e.cause,
+ });
+ }
+});
+
+export default trpcAdapter;
diff --git a/packages/api/package.json b/packages/api/package.json
new file mode 100644
index 00000000..82b2b9d0
--- /dev/null
+++ b/packages/api/package.json
@@ -0,0 +1,41 @@
+{
+ "$schema": "https://json.schemastore.org/package.json",
+ "name": "@karakeep/api",
+ "version": "0.1.0",
+ "private": true,
+ "type": "module",
+ "scripts": {
+ "typecheck": "tsc --noEmit",
+ "format": "prettier . --ignore-path ../../.prettierignore",
+ "format:fix": "prettier . --write --ignore-path ../../.prettierignore",
+ "lint": "eslint .",
+ "lint:fix": "eslint . --fix",
+ "test": "vitest"
+ },
+ "dependencies": {
+ "@hono/zod-validator": "^0.5.0",
+ "@karakeep/db": "workspace:*",
+ "@karakeep/shared": "workspace:*",
+ "@karakeep/trpc": "workspace:*",
+ "hono": "^4.7.10",
+ "rss": "^1.2.2",
+ "zod": "^3.24.2"
+ },
+ "devDependencies": {
+ "@karakeep/eslint-config": "workspace:^0.2.0",
+ "@karakeep/prettier-config": "workspace:^0.1.0",
+ "@karakeep/tsconfig": "workspace:^0.1.0",
+ "@types/bcryptjs": "^2.4.6",
+ "@types/deep-equal": "^1.0.4",
+ "@types/rss": "^0.0.32",
+ "vite-tsconfig-paths": "^4.3.1",
+ "vitest": "^1.6.1"
+ },
+ "eslintConfig": {
+ "root": true,
+ "extends": [
+ "@karakeep/eslint-config/base"
+ ]
+ },
+ "prettier": "@karakeep/prettier-config"
+}
diff --git a/packages/api/routes/assets.ts b/packages/api/routes/assets.ts
new file mode 100644
index 00000000..9d9a60b3
--- /dev/null
+++ b/packages/api/routes/assets.ts
@@ -0,0 +1,48 @@
+import { zValidator } from "@hono/zod-validator";
+import { and, eq } from "drizzle-orm";
+import { Hono } from "hono";
+import { z } from "zod";
+
+import { assets } from "@karakeep/db/schema";
+
+import { authMiddleware } from "../middlewares/auth";
+import { serveAsset } from "../utils/assets";
+import { uploadAsset } from "../utils/upload";
+
+const app = new Hono()
+ .use(authMiddleware)
+ .post(
+ "/",
+ zValidator(
+ "form",
+ z
+ .object({ file: z.instanceof(File) })
+ .or(z.object({ image: z.instanceof(File) })),
+ ),
+ async (c) => {
+ const body = c.req.valid("form");
+ const up = await uploadAsset(c.var.ctx.user, c.var.ctx.db, body);
+ if ("error" in up) {
+ return c.json({ error: up.error }, up.status);
+ }
+ return c.json({
+ assetId: up.assetId,
+ contentType: up.contentType,
+ size: up.size,
+ fileName: up.fileName,
+ });
+ },
+ )
+ .get("/:assetId", async (c) => {
+ const assetId = c.req.param("assetId");
+ const assetDb = await c.var.ctx.db.query.assets.findFirst({
+ where: and(eq(assets.id, assetId), eq(assets.userId, c.var.ctx.user.id)),
+ });
+
+ if (!assetDb) {
+ return c.json({ error: "Asset not found" }, { status: 404 });
+ }
+ return await serveAsset(c, assetId, c.var.ctx.user.id);
+ });
+
+export default app;
diff --git a/packages/api/routes/bookmarks.ts b/packages/api/routes/bookmarks.ts
new file mode 100644
index 00000000..abf0daae
--- /dev/null
+++ b/packages/api/routes/bookmarks.ts
@@ -0,0 +1,319 @@
+import { zValidator } from "@hono/zod-validator";
+import { Hono } from "hono";
+import { z } from "zod";
+
+import {
+ BookmarkTypes,
+ zAssetSchema,
+ zManipulatedTagSchema,
+ zNewBookmarkRequestSchema,
+ zUpdateBookmarksRequestSchema,
+} from "@karakeep/shared/types/bookmarks";
+
+import { authMiddleware } from "../middlewares/auth";
+import { adaptPagination, zPagination } from "../utils/pagination";
+import {
+ zGetBookmarkQueryParamsSchema,
+ zGetBookmarkSearchParamsSchema,
+ zIncludeContentSearchParamsSchema,
+ zStringBool,
+} from "../utils/types";
+import { uploadAsset } from "../utils/upload";
+
+const app = new Hono()
+ .use(authMiddleware)
+
+ // GET /bookmarks
+ .get(
+ "/",
+ zValidator(
+ "query",
+ z
+ .object({
+ favourited: zStringBool.optional(),
+ archived: zStringBool.optional(),
+ })
+ .and(zGetBookmarkQueryParamsSchema)
+ .and(zPagination),
+ ),
+ async (c) => {
+ const searchParams = c.req.valid("query");
+ const bookmarks = await c.var.api.bookmarks.getBookmarks(searchParams);
+ return c.json(adaptPagination(bookmarks), 200);
+ },
+ )
+
+ // POST /bookmarks
+ .post("/", zValidator("json", zNewBookmarkRequestSchema), async (c) => {
+ const body = c.req.valid("json");
+ const bookmark = await c.var.api.bookmarks.createBookmark(body);
+ return c.json(bookmark, 201);
+ })
+
+ // GET /bookmarks/search
+ .get(
+ "/search",
+ zValidator(
+ "query",
+ z
+ .object({
+ q: z.string(),
+ limit: z.coerce.number().optional(),
+ cursor: z
+ .string()
+ .optional()
+ .transform((val) =>
+ val ? { ver: 1 as const, offset: parseInt(val) } : undefined,
+ ),
+ })
+ .and(zGetBookmarkSearchParamsSchema),
+ ),
+ async (c) => {
+ const searchParams = c.req.valid("query");
+ const bookmarks = await c.var.api.bookmarks.searchBookmarks({
+ text: searchParams.q,
+ cursor: searchParams.cursor,
+ limit: searchParams.limit,
+ includeContent: searchParams.includeContent,
+ });
+ return c.json(
+ {
+ bookmarks: bookmarks.bookmarks,
+ nextCursor: bookmarks.nextCursor
+ ? `${bookmarks.nextCursor.offset}`
+ : null,
+ },
+ 200,
+ );
+ },
+ )
+ .post(
+ "/singlefile",
+ zValidator(
+ "query",
+ z.object({
+ ifexists: z
+ .enum([
+ "skip",
+ "overwrite",
+ "overwrite-recrawl",
+ "append",
+ "append-recrawl",
+ ])
+ .optional()
+ .default("skip"),
+ }),
+ ),
+ zValidator(
+ "form",
+ z.object({
+ url: z.string(),
+ file: z.instanceof(File),
+ }),
+ ),
+ async (c) => {
+ const form = c.req.valid("form");
+ const up = await uploadAsset(c.var.ctx.user, c.var.ctx.db, form);
+ if ("error" in up) {
+ return c.json({ error: up.error }, up.status);
+ }
+ const bookmark = await c.var.api.bookmarks.createBookmark({
+ type: BookmarkTypes.LINK,
+ url: form.url,
+ precrawledArchiveId: up.assetId,
+ });
+ if (bookmark.alreadyExists) {
+ const ifexists = c.req.valid("query").ifexists;
+ switch (ifexists) {
+ case "skip":
+ break;
+ case "overwrite-recrawl":
+ case "overwrite": {
+ const existingPrecrawledArchiveId = bookmark.assets
+ .filter((a) => a.assetType == "precrawledArchive")
+ .at(-1)?.id;
+ if (existingPrecrawledArchiveId) {
+ await c.var.api.assets.replaceAsset({
+ bookmarkId: bookmark.id,
+ oldAssetId: existingPrecrawledArchiveId,
+ newAssetId: up.assetId,
+ });
+ } else {
+ await c.var.api.assets.attachAsset({
+ bookmarkId: bookmark.id,
+ asset: {
+ id: up.assetId,
+ assetType: "precrawledArchive",
+ },
+ });
+ }
+ if (ifexists == "overwrite-recrawl") {
+ await c.var.api.bookmarks.recrawlBookmark({
+ bookmarkId: bookmark.id,
+ });
+ }
+ break;
+ }
+ case "append-recrawl":
+ case "append": {
+ await c.var.api.assets.attachAsset({
+ bookmarkId: bookmark.id,
+ asset: {
+ id: up.assetId,
+ assetType: "precrawledArchive",
+ },
+ });
+ if (ifexists == "append-recrawl") {
+ await c.var.api.bookmarks.recrawlBookmark({
+ bookmarkId: bookmark.id,
+ });
+ }
+ break;
+ }
+ }
+ return c.json(bookmark, 200);
+ } else {
+ return c.json(bookmark, 201);
+ }
+ },
+ )
+
+ // GET /bookmarks/[bookmarkId]
+ .get(
+ "/:bookmarkId",
+ zValidator("query", zIncludeContentSearchParamsSchema),
+ async (c) => {
+ const bookmarkId = c.req.param("bookmarkId");
+ const searchParams = c.req.valid("query");
+ const bookmark = await c.var.api.bookmarks.getBookmark({
+ bookmarkId,
+ includeContent: searchParams.includeContent,
+ });
+ return c.json(bookmark, 200);
+ },
+ )
+
+ // PATCH /bookmarks/[bookmarkId]
+ .patch(
+ "/:bookmarkId",
+ zValidator(
+ "json",
+ zUpdateBookmarksRequestSchema.omit({ bookmarkId: true }),
+ ),
+ async (c) => {
+ const bookmarkId = c.req.param("bookmarkId");
+ const body = c.req.valid("json");
+ const bookmark = await c.var.api.bookmarks.updateBookmark({
+ bookmarkId,
+ ...body,
+ });
+ return c.json(bookmark, 200);
+ },
+ )
+
+ // DELETE /bookmarks/[bookmarkId]
+ .delete("/:bookmarkId", async (c) => {
+ const bookmarkId = c.req.param("bookmarkId");
+ await c.var.api.bookmarks.deleteBookmark({ bookmarkId });
+ return c.body(null, 204);
+ })
+
+ // GET /bookmarks/[bookmarkId]/lists
+ .get("/:bookmarkId/lists", async (c) => {
+ const bookmarkId = c.req.param("bookmarkId");
+ const resp = await c.var.api.lists.getListsOfBookmark({ bookmarkId });
+ return c.json(resp, 200);
+ })
+
+ // GET /bookmarks/[bookmarkId]/assets
+ .get("/:bookmarkId/assets", async (c) => {
+ const bookmarkId = c.req.param("bookmarkId");
+ const resp = await c.var.api.bookmarks.getBookmark({ bookmarkId });
+ return c.json({ assets: resp.assets }, 200);
+ })
+
+ // POST /bookmarks/[bookmarkId]/assets
+ .post("/:bookmarkId/assets", zValidator("json", zAssetSchema), async (c) => {
+ const bookmarkId = c.req.param("bookmarkId");
+ const body = c.req.valid("json");
+ const asset = await c.var.api.assets.attachAsset({
+ bookmarkId,
+ asset: body,
+ });
+ return c.json(asset, 201);
+ })
+
+ // PUT /bookmarks/[bookmarkId]/assets/[assetId]
+ .put(
+ "/:bookmarkId/assets/:assetId",
+ zValidator("json", z.object({ assetId: z.string() })),
+ async (c) => {
+ const bookmarkId = c.req.param("bookmarkId");
+ const assetId = c.req.param("assetId");
+ const body = c.req.valid("json");
+ await c.var.api.assets.replaceAsset({
+ bookmarkId,
+ oldAssetId: assetId,
+ newAssetId: body.assetId,
+ });
+ return c.body(null, 204);
+ },
+ )
+
+ // DELETE /bookmarks/[bookmarkId]/assets/[assetId]
+ .delete("/:bookmarkId/assets/:assetId", async (c) => {
+ const bookmarkId = c.req.param("bookmarkId");
+ const assetId = c.req.param("assetId");
+ await c.var.api.assets.detachAsset({ bookmarkId, assetId });
+ return c.body(null, 204);
+ })
+
+ // POST /bookmarks/[bookmarkId]/tags
+ .post(
+ "/:bookmarkId/tags",
+ zValidator("json", z.object({ tags: z.array(zManipulatedTagSchema) })),
+ async (c) => {
+ const bookmarkId = c.req.param("bookmarkId");
+ const body = c.req.valid("json");
+ const resp = await c.var.api.bookmarks.updateTags({
+ bookmarkId,
+ attach: body.tags,
+ detach: [],
+ });
+ return c.json({ attached: resp.attached }, 200);
+ },
+ )
+
+ // DELETE /bookmarks/[bookmarkId]/tags
+ .delete(
+ "/:bookmarkId/tags",
+ zValidator("json", z.object({ tags: z.array(zManipulatedTagSchema) })),
+ async (c) => {
+ const bookmarkId = c.req.param("bookmarkId");
+ const body = c.req.valid("json");
+ const resp = await c.var.api.bookmarks.updateTags({
+ bookmarkId,
+ detach: body.tags,
+ attach: [],
+ });
+ return c.json({ detached: resp.detached }, 200);
+ },
+ )
+
+ // POST /bookmarks/[bookmarkId]/summarize
+ .post("/:bookmarkId/summarize", async (c) => {
+ const bookmarkId = c.req.param("bookmarkId");
+ const bookmark = await c.var.api.bookmarks.summarizeBookmark({
+ bookmarkId,
+ });
+ return c.json(bookmark, 200);
+ })
+
+ // GET /bookmarks/[bookmarkId]/highlights
+ .get("/:bookmarkId/highlights", async (c) => {
+ const bookmarkId = c.req.param("bookmarkId");
+ const resp = await c.var.api.highlights.getForBookmark({ bookmarkId });
+ return c.json(resp, 200);
+ });
+
+export default app;
diff --git a/packages/api/routes/highlights.ts b/packages/api/routes/highlights.ts
new file mode 100644
index 00000000..d381f7e2
--- /dev/null
+++ b/packages/api/routes/highlights.ts
@@ -0,0 +1,54 @@
+import { zValidator } from "@hono/zod-validator";
+import { Hono } from "hono";
+
+import {
+ zNewHighlightSchema,
+ zUpdateHighlightSchema,
+} from "@karakeep/shared/types/highlights";
+
+import { authMiddleware } from "../middlewares/auth";
+import { adaptPagination, zPagination } from "../utils/pagination";
+
+const app = new Hono()
+ .use(authMiddleware)
+ .get("/", zValidator("query", zPagination), async (c) => {
+ const searchParams = c.req.valid("query");
+ const resp = await c.var.api.highlights.getAll({
+ ...searchParams,
+ });
+ return c.json(adaptPagination(resp));
+ })
+ .post("/", zValidator("json", zNewHighlightSchema), async (c) => {
+ const body = c.req.valid("json");
+ const resp = await c.var.api.highlights.create(body);
+ return c.json(resp, 201);
+ })
+ .get("/:highlightId", async (c) => {
+ const highlightId = c.req.param("highlightId");
+ const highlight = await c.var.api.highlights.get({
+ highlightId,
+ });
+ return c.json(highlight, 200);
+ })
+ .patch(
+ "/:highlightId",
+ zValidator("json", zUpdateHighlightSchema.omit({ highlightId: true })),
+ async (c) => {
+ const highlightId = c.req.param("highlightId");
+ const body = c.req.valid("json");
+ const highlight = await c.var.api.highlights.update({
+ highlightId,
+ ...body,
+ });
+ return c.json(highlight, 200);
+ },
+ )
+ .delete("/:highlightId", async (c) => {
+ const highlightId = c.req.param("highlightId");
+ const highlight = await c.var.api.highlights.delete({
+ highlightId,
+ });
+ return c.json(highlight, 200);
+ });
+
+export default app;
diff --git a/packages/api/routes/lists.ts b/packages/api/routes/lists.ts
new file mode 100644
index 00000000..33908629
--- /dev/null
+++ b/packages/api/routes/lists.ts
@@ -0,0 +1,70 @@
+import { zValidator } from "@hono/zod-validator";
+import { Hono } from "hono";
+
+import {
+ zEditBookmarkListSchema,
+ zNewBookmarkListSchema,
+} from "@karakeep/shared/types/lists";
+
+import { authMiddleware } from "../middlewares/auth";
+import { adaptPagination, zPagination } from "../utils/pagination";
+import { zGetBookmarkQueryParamsSchema } from "../utils/types";
+
+const app = new Hono()
+ .use(authMiddleware)
+ .get("/", async (c) => {
+ const lists = await c.var.api.lists.list();
+ return c.json(lists, 200);
+ })
+ .post("/", zValidator("json", zNewBookmarkListSchema), async (c) => {
+ const body = c.req.valid("json");
+ const list = await c.var.api.lists.create(body);
+ return c.json(list, 201);
+ })
+ .get("/:listId", async (c) => {
+ const listId = c.req.param("listId");
+ const list = await c.var.api.lists.get({ listId });
+ return c.json(list, 200);
+ })
+ .patch(
+ "/:listId",
+ zValidator("json", zEditBookmarkListSchema.omit({ listId: true })),
+ async (c) => {
+ const listId = c.req.param("listId");
+ const body = c.req.valid("json");
+ const list = await c.var.api.lists.edit({ ...body, listId });
+ return c.json(list, 200);
+ },
+ )
+ .delete("/:listId", async (c) => {
+ const listId = c.req.param("listId");
+ await c.var.api.lists.delete({ listId });
+ return c.body(null, 204);
+ })
+ .get(
+ "/:listId/bookmarks",
+ zValidator("query", zPagination.and(zGetBookmarkQueryParamsSchema)),
+ async (c) => {
+ const listId = c.req.param("listId");
+ const searchParams = c.req.valid("query");
+ const bookmarks = await c.var.api.bookmarks.getBookmarks({
+ listId,
+ ...searchParams,
+ });
+ return c.json(adaptPagination(bookmarks), 200);
+ },
+ )
+ .put("/:listId/bookmarks/:bookmarkId", async (c) => {
+ const listId = c.req.param("listId");
+ const bookmarkId = c.req.param("bookmarkId");
+ await c.var.api.lists.addToList({ listId, bookmarkId });
+ return c.body(null, 204);
+ })
+ .delete("/:listId/bookmarks/:bookmarkId", async (c) => {
+ const listId = c.req.param("listId");
+ const bookmarkId = c.req.param("bookmarkId");
+ await c.var.api.lists.removeFromList({ listId, bookmarkId });
+ return c.body(null, 204);
+ });
+
+export default app;
diff --git a/packages/api/routes/public.ts b/packages/api/routes/public.ts
new file mode 100644
index 00000000..d17049c4
--- /dev/null
+++ b/packages/api/routes/public.ts
@@ -0,0 +1,47 @@
+import { zValidator } from "@hono/zod-validator";
+import { and, eq } from "drizzle-orm";
+import { Hono } from "hono";
+import { z } from "zod";
+
+import { assets } from "@karakeep/db/schema";
+import { verifySignedToken } from "@karakeep/shared/signedTokens";
+import { zAssetSignedTokenSchema } from "@karakeep/shared/types/assets";
+
+import { unauthedMiddleware } from "../middlewares/auth";
+import { serveAsset } from "../utils/assets";
+
+const app = new Hono().get(
+ "/assets/:assetId",
+ unauthedMiddleware,
+ zValidator(
+ "query",
+ z.object({
+ token: z.string(),
+ }),
+ ),
+ async (c) => {
+ const assetId = c.req.param("assetId");
+ const tokenPayload = verifySignedToken(
+ c.req.valid("query").token,
+ zAssetSignedTokenSchema,
+ );
+ if (!tokenPayload) {
+ return c.json({ error: "Invalid or expired token" }, { status: 403 });
+ }
+ if (tokenPayload.assetId !== assetId) {
+ return c.json({ error: "Invalid or expired token" }, { status: 403 });
+ }
+ const userId = tokenPayload.userId;
+
+ const assetDb = await c.var.ctx.db.query.assets.findFirst({
+ where: and(eq(assets.id, assetId), eq(assets.userId, userId)),
+ });
+
+ if (!assetDb) {
+ return c.json({ error: "Asset not found" }, { status: 404 });
+ }
+ return await serveAsset(c, assetId, userId);
+ },
+);
+
+export default app;
diff --git a/packages/api/routes/rss.ts b/packages/api/routes/rss.ts
new file mode 100644
index 00000000..88b943ad
--- /dev/null
+++ b/packages/api/routes/rss.ts
@@ -0,0 +1,53 @@
+import { zValidator } from "@hono/zod-validator";
+import { Hono } from "hono";
+import { z } from "zod";
+
+import serverConfig from "@karakeep/shared/config";
+import { MAX_NUM_BOOKMARKS_PER_PAGE } from "@karakeep/shared/types/bookmarks";
+import { List } from "@karakeep/trpc/models/lists";
+
+import { unauthedMiddleware } from "../middlewares/auth";
+import { toRSS } from "../utils/rss";
+
+const app = new Hono().get(
+ "/lists/:listId",
+ zValidator(
+ "query",
+ z.object({
+ token: z.string().min(1),
+ limit: z.coerce
+ .number()
+ .min(1)
+ .max(MAX_NUM_BOOKMARKS_PER_PAGE)
+ .optional(),
+ }),
+ ),
+ unauthedMiddleware,
+ async (c) => {
+ const listId = c.req.param("listId");
+ const searchParams = c.req.valid("query");
+ const token = searchParams.token;
+
+ const res = await List.getPublicListContents(c.var.ctx, listId, token, {
+ limit: searchParams.limit ?? 20,
+ order: "desc",
+ cursor: null,
+ });
+ const list = res.list;
+
+ const rssFeed = toRSS(
+ {
+ title: `Bookmarks from ${list.icon} ${list.name}`,
+ feedUrl: `${serverConfig.publicApiUrl}/v1/rss/lists/${listId}`,
+ siteUrl: `${serverConfig.publicUrl}/dashboard/lists/${listId}`,
+ description: list.description ?? undefined,
+ },
+ res.bookmarks,
+ );
+
+ c.header("Content-Type", "application/rss+xml");
+ return c.body(rssFeed);
+ },
+);
+
+export default app;
diff --git a/packages/api/routes/tags.ts b/packages/api/routes/tags.ts
new file mode 100644
index 00000000..816e58b4
--- /dev/null
+++ b/packages/api/routes/tags.ts
@@ -0,0 +1,70 @@
+import { zValidator } from "@hono/zod-validator";
+import { Hono } from "hono";
+
+import {
+ zCreateTagRequestSchema,
+ zUpdateTagRequestSchema,
+} from "@karakeep/shared/types/tags";
+
+import { authMiddleware } from "../middlewares/auth";
+import { adaptPagination, zPagination } from "../utils/pagination";
+import { zGetBookmarkQueryParamsSchema } from "../utils/types";
+
+const app = new Hono()
+ .use(authMiddleware)
+
+ // GET /tags
+ .get("/", async (c) => {
+ const tags = await c.var.api.tags.list();
+ return c.json(tags, 200);
+ })
+
+ // POST /tags
+ .post("/", zValidator("json", zCreateTagRequestSchema), async (c) => {
+ const body = c.req.valid("json");
+ const tags = await c.var.api.tags.create(body);
+ return c.json(tags, 201);
+ })
+
+ // GET /tags/[tagId]
+ .get("/:tagId", async (c) => {
+ const tagId = c.req.param("tagId");
+ const tag = await c.var.api.tags.get({ tagId });
+ return c.json(tag, 200);
+ })
+
+ // PATCH /tags/[tagId]
+ .patch(
+ "/:tagId",
+ zValidator("json", zUpdateTagRequestSchema.omit({ tagId: true })),
+ async (c) => {
+ const tagId = c.req.param("tagId");
+ const body = c.req.valid("json");
+ const tag = await c.var.api.tags.update({ tagId, ...body });
+ return c.json(tag, 200);
+ },
+ )
+
+ // DELETE /tags/[tagId]
+ .delete("/:tagId", async (c) => {
+ const tagId = c.req.param("tagId");
+ await c.var.api.tags.delete({ tagId });
+ return c.body(null, 204);
+ })
+
+ // GET /tags/[tagId]/bookmarks
+ .get(
+ "/:tagId/bookmarks",
+ zValidator("query", zPagination.and(zGetBookmarkQueryParamsSchema)),
+ async (c) => {
+ const tagId = c.req.param("tagId");
+ const searchParams = c.req.valid("query");
+ const bookmarks = await c.var.api.bookmarks.getBookmarks({
+ tagId,
+ ...searchParams,
+ });
+ return c.json(adaptPagination(bookmarks), 200);
+ },
+ );
+
+export default app;
diff --git a/packages/api/routes/users.ts b/packages/api/routes/users.ts
new file mode 100644
index 00000000..81177fe3
--- /dev/null
+++ b/packages/api/routes/users.ts
@@ -0,0 +1,20 @@
+import { Hono } from "hono";
+
+import { authMiddleware } from "../middlewares/auth";
+
+const app = new Hono()
+ .use(authMiddleware)
+
+ // GET /users/me
+ .get("/me", async (c) => {
+ const user = await c.var.api.users.whoami();
+ return c.json(user, 200);
+ })
+
+ // GET /users/me/stats
+ .get("/me/stats", async (c) => {
+ const stats = await c.var.api.users.stats();
+ return c.json(stats, 200);
+ });
+
+export default app;
diff --git a/packages/api/tsconfig.json b/packages/api/tsconfig.json
new file mode 100644
index 00000000..0036ccfa
--- /dev/null
+++ b/packages/api/tsconfig.json
@@ -0,0 +1,13 @@
+{
+ "$schema": "https://json.schemastore.org/tsconfig",
+ "extends": "@karakeep/tsconfig/node.json",
+ "include": [
+ "**/*.ts"
+ ],
+ "exclude": [
+ "node_modules"
+ ],
+ "compilerOptions": {
+ "tsBuildInfoFile": "node_modules/.cache/tsbuildinfo.json"
+ }
+}
diff --git a/packages/api/utils/assets.ts b/packages/api/utils/assets.ts
new file mode 100644
index 00000000..d8a726a6
--- /dev/null
+++ b/packages/api/utils/assets.ts
@@ -0,0 +1,57 @@
+import { Context } from "hono";
+import { stream } from "hono/streaming";
+
+import {
+ createAssetReadStream,
+ getAssetSize,
+ readAssetMetadata,
+} from "@karakeep/shared/assetdb";
+
+import { toWebReadableStream } from "./upload";
+
+export async function serveAsset(c: Context, assetId: string, userId: string) {
+ const [metadata, size] = await Promise.all([
+ readAssetMetadata({
+ userId,
+ assetId,
+ }),
+
+ getAssetSize({
+ userId,
+ assetId,
+ }),
+ ]);
+
+ const range = c.req.header("Range");
+ if (range) {
+ const parts = range.replace(/bytes=/, "").split("-");
+ const start = parseInt(parts[0], 10);
+ const end = parts[1] ? parseInt(parts[1], 10) : size - 1;
+
+ const fStream = createAssetReadStream({
+ userId,
+ assetId,
+ start,
+ end,
+ });
+ c.status(206); // Partial Content
+ c.header("Content-Range", `bytes ${start}-${end}/${size}`);
+ c.header("Accept-Ranges", "bytes");
+ c.header("Content-Length", (end - start + 1).toString());
+ c.header("Content-type", metadata.contentType);
+ return stream(c, async (stream) => {
+ await stream.pipe(toWebReadableStream(fStream));
+ });
+ } else {
+ const fStream = createAssetReadStream({
+ userId,
+ assetId,
+ });
+ c.status(200);
+ c.header("Content-Length", size.toString());
+ c.header("Content-type", metadata.contentType);
+ return stream(c, async (stream) => {
+ await stream.pipe(toWebReadableStream(fStream));
+ });
+ }
+}
diff --git a/packages/api/utils/pagination.ts b/packages/api/utils/pagination.ts
new file mode 100644
index 00000000..12a0b950
--- /dev/null
+++ b/packages/api/utils/pagination.ts
@@ -0,0 +1,30 @@
+import { z } from "zod";
+
+import { MAX_NUM_BOOKMARKS_PER_PAGE } from "@karakeep/shared/types/bookmarks";
+import { zCursorV2 } from "@karakeep/shared/types/pagination";
+
+export const zPagination = z.object({
+ limit: z.coerce.number().max(MAX_NUM_BOOKMARKS_PER_PAGE).optional(),
+ cursor: z
+ .string()
+ .refine((val) => val.includes("_"), "Must be a valid cursor")
+ .transform((val) => {
+ const [id, createdAt] = val.split("_");
+ return { id, createdAt };
+ })
+ .pipe(z.object({ id: z.string(), createdAt: z.coerce.date() }))
+ .optional(),
+});
+
+export function adaptPagination<
+ T extends { nextCursor: z.infer<typeof zCursorV2> | null },
+>(input: T) {
+ const { nextCursor, ...rest } = input;
+ if (!nextCursor) {
+ return input;
+ }
+ return {
+ ...rest,
+ nextCursor: `${nextCursor.id}_${nextCursor.createdAt.toISOString()}`,
+ };
+}
diff --git a/packages/api/utils/rss.ts b/packages/api/utils/rss.ts
new file mode 100644
index 00000000..079b3f5a
--- /dev/null
+++ b/packages/api/utils/rss.ts
@@ -0,0 +1,54 @@
+import RSS from "rss";
+
+import serverConfig from "@karakeep/shared/config";
+import {
+ BookmarkTypes,
+ ZPublicBookmark,
+} from "@karakeep/shared/types/bookmarks";
+import { getAssetUrl } from "@karakeep/shared/utils/assetUtils";
+
+export function toRSS(
+ params: {
+ title: string;
+ description?: string;
+ feedUrl: string;
+ siteUrl: string;
+ },
+ bookmarks: ZPublicBookmark[],
+) {
+ const feed = new RSS({
+ title: params.title,
+ feed_url: params.feedUrl,
+ site_url: params.siteUrl,
+ description: params.description,
+ generator: "Karakeep",
+ });
+
+ bookmarks
+ .filter(
+ (b) =>
+ b.content.type === BookmarkTypes.LINK ||
+ b.content.type === BookmarkTypes.ASSET,
+ )
+ .forEach((bookmark) => {
+ feed.item({
+ date: bookmark.createdAt,
+ title: bookmark.title ?? "",
+ url:
+ bookmark.content.type === BookmarkTypes.LINK
+ ? bookmark.content.url
+ : bookmark.content.type === BookmarkTypes.ASSET
+ ? `${serverConfig.publicUrl}${getAssetUrl(bookmark.content.assetId)}`
+ : "",
+ guid: bookmark.id,
+ author:
+ bookmark.content.type === BookmarkTypes.LINK
+ ? (bookmark.content.author ?? undefined)
+ : undefined,
+ categories: bookmark.tags,
+ description: bookmark.description ?? "",
+ });
+ });
+
+ return feed.xml({ indent: true });
+}
diff --git a/packages/api/utils/types.ts b/packages/api/utils/types.ts
new file mode 100644
index 00000000..bdaf815f
--- /dev/null
+++ b/packages/api/utils/types.ts
@@ -0,0 +1,28 @@
+import { z } from "zod";
+
+import { zSortOrder } from "@karakeep/shared/types/bookmarks";
+
+export const zStringBool = z
+ .string()
+ .refine((val) => val === "true" || val === "false", "Must be true or false")
+ .transform((val) => val === "true");
+
+export const zIncludeContentSearchParamsSchema = z.object({
+ // TODO: Change the default to false in a couple of releases.
+ includeContent: zStringBool.optional().default("true"),
+});
+
+export const zGetBookmarkQueryParamsSchema = z
+ .object({
+ sortOrder: zSortOrder
+ .exclude([zSortOrder.Enum.relevance])
+ .optional()
+ .default(zSortOrder.Enum.desc),
+ })
+ .merge(zIncludeContentSearchParamsSchema);
+
+export const zGetBookmarkSearchParamsSchema = z
+ .object({
+ sortOrder: zSortOrder.optional().default(zSortOrder.Enum.relevance),
+ })
+ .merge(zIncludeContentSearchParamsSchema);
diff --git a/packages/api/utils/upload.ts b/packages/api/utils/upload.ts
new file mode 100644
index 00000000..d96a0f60
--- /dev/null
+++ b/packages/api/utils/upload.ts
@@ -0,0 +1,110 @@
+import * as fs from "fs";
+import * as os from "os";
+import * as path from "path";
+import { Readable } from "stream";
+import { pipeline } from "stream/promises";
+
+import { assets, AssetTypes } from "@karakeep/db/schema";
+import {
+ newAssetId,
+ saveAssetFromFile,
+ SUPPORTED_UPLOAD_ASSET_TYPES,
+} from "@karakeep/shared/assetdb";
+import serverConfig from "@karakeep/shared/config";
+import { AuthedContext } from "@karakeep/trpc";
+
+const MAX_UPLOAD_SIZE_BYTES = serverConfig.maxAssetSizeMb * 1024 * 1024;
+
+// Helper to convert Web Stream to Node Stream (requires Node >= 16.5 / 14.18)
+export function webStreamToNode(
+ webStream: ReadableStream<Uint8Array>,
+): Readable {
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-explicit-any
+ return Readable.fromWeb(webStream as any); // Type assertion might be needed
+}
+
+export function toWebReadableStream(
+ nodeStream: fs.ReadStream,
+): ReadableStream<Uint8Array> {
+ const reader = nodeStream as unknown as Readable;
+
+ return new ReadableStream({
+ start(controller) {
+ reader.on("data", (chunk) => controller.enqueue(new Uint8Array(chunk)));
+ reader.on("end", () => controller.close());
+ reader.on("error", (err) => controller.error(err));
+ },
+ });
+}
+
+export async function uploadAsset(
+ user: AuthedContext["user"],
+ db: AuthedContext["db"],
+ formData: { file: File } | { image: File },
+): Promise<
+ | { error: string; status: 400 | 413 }
+ | {
+ assetId: string;
+ contentType: string;
+ fileName: string;
+ size: number;
+ }
+> {
+ let data: File;
+ if ("file" in formData) {
+ data = formData.file;
+ } else {
+ data = formData.image;
+ }
+
+ const contentType = data.type;
+ const fileName = data.name;
+ if (!SUPPORTED_UPLOAD_ASSET_TYPES.has(contentType)) {
+ return { error: "Unsupported asset type", status: 400 };
+ }
+ if (data.size > MAX_UPLOAD_SIZE_BYTES) {
+ return { error: "Asset is too big", status: 413 };
+ }
+
+ let tempFilePath: string | undefined;
+
+ try {
+ tempFilePath = path.join(os.tmpdir(), `karakeep-upload-${Date.now()}`);
+ await pipeline(
+ webStreamToNode(data.stream()),
+ fs.createWriteStream(tempFilePath),
+ );
+ const [assetDb] = await db
+ .insert(assets)
+ .values({
+ id: newAssetId(),
+ // Initially, uploads are uploaded for unknown purpose
+ // And without an attached bookmark.
+ assetType: AssetTypes.UNKNOWN,
+ bookmarkId: null,
+ userId: user.id,
+ contentType,
+ size: data.size,
+ fileName,
+ })
+ .returning();
+
+ await saveAssetFromFile({
+ userId: user.id,
+ assetId: assetDb.id,
+ assetPath: tempFilePath,
+ metadata: { contentType, fileName },
+ });
+
+ return {
+ assetId: assetDb.id,
+ contentType,
+ size: data.size,
+ fileName,
+ };
+ } finally {
+ if (tempFilePath) {
+ await fs.promises.unlink(tempFilePath).catch(() => ({}));
+ }
+ }
+}
diff --git a/packages/db/drizzle/0046_add_rss_feed_enabled_col.sql b/packages/db/drizzle/0046_add_rss_feed_enabled_col.sql
new file mode 100644
index 00000000..3352b5b0
--- /dev/null
+++ b/packages/db/drizzle/0046_add_rss_feed_enabled_col.sql
@@ -0,0 +1 @@
+ALTER TABLE `rssFeeds` ADD `enabled` integer DEFAULT true NOT NULL; \ No newline at end of file
diff --git a/packages/db/drizzle/0047_add_summarization_status.sql b/packages/db/drizzle/0047_add_summarization_status.sql
new file mode 100644
index 00000000..6e8b40d0
--- /dev/null
+++ b/packages/db/drizzle/0047_add_summarization_status.sql
@@ -0,0 +1 @@
+ALTER TABLE `bookmarks` ADD `summarizationStatus` text DEFAULT 'pending'; \ No newline at end of file
diff --git a/packages/db/drizzle/0048_add_user_settings.sql b/packages/db/drizzle/0048_add_user_settings.sql
new file mode 100644
index 00000000..8d55c1e1
--- /dev/null
+++ b/packages/db/drizzle/0048_add_user_settings.sql
@@ -0,0 +1,6 @@
+CREATE TABLE `userSettings` (
+ `userId` text PRIMARY KEY NOT NULL,
+ `bookmarkClickAction` text DEFAULT 'open_original_link' NOT NULL,
+ FOREIGN KEY (`userId`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE cascade
+);--> statement-breakpoint
+INSERT INTO `userSettings` (`userId`, `bookmarkClickAction`) SELECT `id`, 'open_original_link' FROM `user`;
diff --git a/packages/db/drizzle/0049_add_rss_token.sql b/packages/db/drizzle/0049_add_rss_token.sql
new file mode 100644
index 00000000..c256f859
--- /dev/null
+++ b/packages/db/drizzle/0049_add_rss_token.sql
@@ -0,0 +1 @@
+ALTER TABLE `bookmarkLists` ADD `rssToken` text; \ No newline at end of file
diff --git a/packages/db/drizzle/0050_add_user_settings_archive_display_behaviour.sql b/packages/db/drizzle/0050_add_user_settings_archive_display_behaviour.sql
new file mode 100644
index 00000000..6d9936b6
--- /dev/null
+++ b/packages/db/drizzle/0050_add_user_settings_archive_display_behaviour.sql
@@ -0,0 +1 @@
+ALTER TABLE `userSettings` ADD `archiveDisplayBehaviour` text DEFAULT 'show' NOT NULL;
diff --git a/packages/db/drizzle/0051_public_lists.sql b/packages/db/drizzle/0051_public_lists.sql
new file mode 100644
index 00000000..6f9714e4
--- /dev/null
+++ b/packages/db/drizzle/0051_public_lists.sql
@@ -0,0 +1 @@
+ALTER TABLE `bookmarkLists` ADD `public` integer DEFAULT false NOT NULL; \ No newline at end of file
diff --git a/packages/db/drizzle/meta/0046_snapshot.json b/packages/db/drizzle/meta/0046_snapshot.json
new file mode 100644
index 00000000..7015f5db
--- /dev/null
+++ b/packages/db/drizzle/meta/0046_snapshot.json
@@ -0,0 +1,1959 @@
+{
+ "version": "6",
+ "dialect": "sqlite",
+ "id": "7a950001-03e7-43f2-8ef5-272dfe82d223",
+ "prevId": "dce61b2b-d896-425a-a9cd-6a79f596d7b2",
+ "tables": {
+ "account": {
+ "name": "account",
+ "columns": {
+ "userId": {
+ "name": "userId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "type": {
+ "name": "type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "provider": {
+ "name": "provider",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "providerAccountId": {
+ "name": "providerAccountId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "refresh_token": {
+ "name": "refresh_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "access_token": {
+ "name": "access_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "token_type": {
+ "name": "token_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "scope": {
+ "name": "scope",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "id_token": {
+ "name": "id_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "session_state": {
+ "name": "session_state",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "account_userId_user_id_fk": {
+ "name": "account_userId_user_id_fk",
+ "tableFrom": "account",
+ "tableTo": "user",
+ "columnsFrom": [
+ "userId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "account_provider_providerAccountId_pk": {
+ "columns": [
+ "provider",
+ "providerAccountId"
+ ],
+ "name": "account_provider_providerAccountId_pk"
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "apiKey": {
+ "name": "apiKey",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "keyId": {
+ "name": "keyId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "keyHash": {
+ "name": "keyHash",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "userId": {
+ "name": "userId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "apiKey_keyId_unique": {
+ "name": "apiKey_keyId_unique",
+ "columns": [
+ "keyId"
+ ],
+ "isUnique": true
+ },
+ "apiKey_name_userId_unique": {
+ "name": "apiKey_name_userId_unique",
+ "columns": [
+ "name",
+ "userId"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {
+ "apiKey_userId_user_id_fk": {
+ "name": "apiKey_userId_user_id_fk",
+ "tableFrom": "apiKey",
+ "tableTo": "user",
+ "columnsFrom": [
+ "userId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "assets": {
+ "name": "assets",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "assetType": {
+ "name": "assetType",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "size": {
+ "name": "size",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 0
+ },
+ "contentType": {
+ "name": "contentType",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "fileName": {
+ "name": "fileName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "bookmarkId": {
+ "name": "bookmarkId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "userId": {
+ "name": "userId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "assets_bookmarkId_idx": {
+ "name": "assets_bookmarkId_idx",
+ "columns": [
+ "bookmarkId"
+ ],
+ "isUnique": false
+ },
+ "assets_assetType_idx": {
+ "name": "assets_assetType_idx",
+ "columns": [
+ "assetType"
+ ],
+ "isUnique": false
+ },
+ "assets_userId_idx": {
+ "name": "assets_userId_idx",
+ "columns": [
+ "userId"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "assets_bookmarkId_bookmarks_id_fk": {
+ "name": "assets_bookmarkId_bookmarks_id_fk",
+ "tableFrom": "assets",
+ "tableTo": "bookmarks",
+ "columnsFrom": [
+ "bookmarkId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "assets_userId_user_id_fk": {
+ "name": "assets_userId_user_id_fk",
+ "tableFrom": "assets",
+ "tableTo": "user",
+ "columnsFrom": [
+ "userId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "bookmarkAssets": {
+ "name": "bookmarkAssets",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "assetType": {
+ "name": "assetType",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "assetId": {
+ "name": "assetId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "content": {
+ "name": "content",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "metadata": {
+ "name": "metadata",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "fileName": {
+ "name": "fileName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "sourceUrl": {
+ "name": "sourceUrl",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "bookmarkAssets_id_bookmarks_id_fk": {
+ "name": "bookmarkAssets_id_bookmarks_id_fk",
+ "tableFrom": "bookmarkAssets",
+ "tableTo": "bookmarks",
+ "columnsFrom": [
+ "id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "bookmarkLinks": {
+ "name": "bookmarkLinks",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "url": {
+ "name": "url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "author": {
+ "name": "author",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "publisher": {
+ "name": "publisher",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "datePublished": {
+ "name": "datePublished",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "dateModified": {
+ "name": "dateModified",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "imageUrl": {
+ "name": "imageUrl",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "favicon": {
+ "name": "favicon",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "content": {
+ "name": "content",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "htmlContent": {
+ "name": "htmlContent",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "crawledAt": {
+ "name": "crawledAt",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "crawlStatus": {
+ "name": "crawlStatus",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'pending'"
+ },
+ "crawlStatusCode": {
+ "name": "crawlStatusCode",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": 200
+ }
+ },
+ "indexes": {
+ "bookmarkLinks_url_idx": {
+ "name": "bookmarkLinks_url_idx",
+ "columns": [
+ "url"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "bookmarkLinks_id_bookmarks_id_fk": {
+ "name": "bookmarkLinks_id_bookmarks_id_fk",
+ "tableFrom": "bookmarkLinks",
+ "tableTo": "bookmarks",
+ "columnsFrom": [
+ "id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "bookmarkLists": {
+ "name": "bookmarkLists",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "icon": {
+ "name": "icon",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "userId": {
+ "name": "userId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "type": {
+ "name": "type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "query": {
+ "name": "query",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "parentId": {
+ "name": "parentId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "bookmarkLists_userId_idx": {
+ "name": "bookmarkLists_userId_idx",
+ "columns": [
+ "userId"
+ ],
+ "isUnique": false
+ },
+ "bookmarkLists_userId_id_idx": {
+ "name": "bookmarkLists_userId_id_idx",
+ "columns": [
+ "userId",
+ "id"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {
+ "bookmarkLists_userId_user_id_fk": {
+ "name": "bookmarkLists_userId_user_id_fk",
+ "tableFrom": "bookmarkLists",
+ "tableTo": "user",
+ "columnsFrom": [
+ "userId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "bookmarkLists_parentId_bookmarkLists_id_fk": {
+ "name": "bookmarkLists_parentId_bookmarkLists_id_fk",
+ "tableFrom": "bookmarkLists",
+ "tableTo": "bookmarkLists",
+ "columnsFrom": [
+ "parentId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "bookmarkTags": {
+ "name": "bookmarkTags",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "userId": {
+ "name": "userId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "bookmarkTags_name_idx": {
+ "name": "bookmarkTags_name_idx",
+ "columns": [
+ "name"
+ ],
+ "isUnique": false
+ },
+ "bookmarkTags_userId_idx": {
+ "name": "bookmarkTags_userId_idx",
+ "columns": [
+ "userId"
+ ],
+ "isUnique": false
+ },
+ "bookmarkTags_userId_name_unique": {
+ "name": "bookmarkTags_userId_name_unique",
+ "columns": [
+ "userId",
+ "name"
+ ],
+ "isUnique": true
+ },
+ "bookmarkTags_userId_id_idx": {
+ "name": "bookmarkTags_userId_id_idx",
+ "columns": [
+ "userId",
+ "id"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {
+ "bookmarkTags_userId_user_id_fk": {
+ "name": "bookmarkTags_userId_user_id_fk",
+ "tableFrom": "bookmarkTags",
+ "tableTo": "user",
+ "columnsFrom": [
+ "userId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "bookmarkTexts": {
+ "name": "bookmarkTexts",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "text": {
+ "name": "text",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "sourceUrl": {
+ "name": "sourceUrl",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "bookmarkTexts_id_bookmarks_id_fk": {
+ "name": "bookmarkTexts_id_bookmarks_id_fk",
+ "tableFrom": "bookmarkTexts",
+ "tableTo": "bookmarks",
+ "columnsFrom": [
+ "id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "bookmarks": {
+ "name": "bookmarks",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "modifiedAt": {
+ "name": "modifiedAt",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "archived": {
+ "name": "archived",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": false
+ },
+ "favourited": {
+ "name": "favourited",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": false
+ },
+ "userId": {
+ "name": "userId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "taggingStatus": {
+ "name": "taggingStatus",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'pending'"
+ },
+ "summary": {
+ "name": "summary",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "note": {
+ "name": "note",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "type": {
+ "name": "type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "bookmarks_userId_idx": {
+ "name": "bookmarks_userId_idx",
+ "columns": [
+ "userId"
+ ],
+ "isUnique": false
+ },
+ "bookmarks_archived_idx": {
+ "name": "bookmarks_archived_idx",
+ "columns": [
+ "archived"
+ ],
+ "isUnique": false
+ },
+ "bookmarks_favourited_idx": {
+ "name": "bookmarks_favourited_idx",
+ "columns": [
+ "favourited"
+ ],
+ "isUnique": false
+ },
+ "bookmarks_createdAt_idx": {
+ "name": "bookmarks_createdAt_idx",
+ "columns": [
+ "createdAt"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "bookmarks_userId_user_id_fk": {
+ "name": "bookmarks_userId_user_id_fk",
+ "tableFrom": "bookmarks",
+ "tableTo": "user",
+ "columnsFrom": [
+ "userId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "bookmarksInLists": {
+ "name": "bookmarksInLists",
+ "columns": {
+ "bookmarkId": {
+ "name": "bookmarkId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "listId": {
+ "name": "listId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "addedAt": {
+ "name": "addedAt",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "bookmarksInLists_bookmarkId_idx": {
+ "name": "bookmarksInLists_bookmarkId_idx",
+ "columns": [
+ "bookmarkId"
+ ],
+ "isUnique": false
+ },
+ "bookmarksInLists_listId_idx": {
+ "name": "bookmarksInLists_listId_idx",
+ "columns": [
+ "listId"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "bookmarksInLists_bookmarkId_bookmarks_id_fk": {
+ "name": "bookmarksInLists_bookmarkId_bookmarks_id_fk",
+ "tableFrom": "bookmarksInLists",
+ "tableTo": "bookmarks",
+ "columnsFrom": [
+ "bookmarkId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "bookmarksInLists_listId_bookmarkLists_id_fk": {
+ "name": "bookmarksInLists_listId_bookmarkLists_id_fk",
+ "tableFrom": "bookmarksInLists",
+ "tableTo": "bookmarkLists",
+ "columnsFrom": [
+ "listId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "bookmarksInLists_bookmarkId_listId_pk": {
+ "columns": [
+ "bookmarkId",
+ "listId"
+ ],
+ "name": "bookmarksInLists_bookmarkId_listId_pk"
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "config": {
+ "name": "config",
+ "columns": {
+ "key": {
+ "name": "key",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "value": {
+ "name": "value",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "customPrompts": {
+ "name": "customPrompts",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "text": {
+ "name": "text",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "appliesTo": {
+ "name": "appliesTo",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "userId": {
+ "name": "userId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "customPrompts_userId_idx": {
+ "name": "customPrompts_userId_idx",
+ "columns": [
+ "userId"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "customPrompts_userId_user_id_fk": {
+ "name": "customPrompts_userId_user_id_fk",
+ "tableFrom": "customPrompts",
+ "tableTo": "user",
+ "columnsFrom": [
+ "userId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "highlights": {
+ "name": "highlights",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "bookmarkId": {
+ "name": "bookmarkId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "userId": {
+ "name": "userId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "startOffset": {
+ "name": "startOffset",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "endOffset": {
+ "name": "endOffset",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "color": {
+ "name": "color",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'yellow'"
+ },
+ "text": {
+ "name": "text",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "note": {
+ "name": "note",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "highlights_bookmarkId_idx": {
+ "name": "highlights_bookmarkId_idx",
+ "columns": [
+ "bookmarkId"
+ ],
+ "isUnique": false
+ },
+ "highlights_userId_idx": {
+ "name": "highlights_userId_idx",
+ "columns": [
+ "userId"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "highlights_bookmarkId_bookmarks_id_fk": {
+ "name": "highlights_bookmarkId_bookmarks_id_fk",
+ "tableFrom": "highlights",
+ "tableTo": "bookmarks",
+ "columnsFrom": [
+ "bookmarkId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "highlights_userId_user_id_fk": {
+ "name": "highlights_userId_user_id_fk",
+ "tableFrom": "highlights",
+ "tableTo": "user",
+ "columnsFrom": [
+ "userId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "rssFeedImports": {
+ "name": "rssFeedImports",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "entryId": {
+ "name": "entryId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "rssFeedId": {
+ "name": "rssFeedId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "bookmarkId": {
+ "name": "bookmarkId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "rssFeedImports_feedIdIdx_idx": {
+ "name": "rssFeedImports_feedIdIdx_idx",
+ "columns": [
+ "rssFeedId"
+ ],
+ "isUnique": false
+ },
+ "rssFeedImports_entryIdIdx_idx": {
+ "name": "rssFeedImports_entryIdIdx_idx",
+ "columns": [
+ "entryId"
+ ],
+ "isUnique": false
+ },
+ "rssFeedImports_rssFeedId_entryId_unique": {
+ "name": "rssFeedImports_rssFeedId_entryId_unique",
+ "columns": [
+ "rssFeedId",
+ "entryId"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {
+ "rssFeedImports_rssFeedId_rssFeeds_id_fk": {
+ "name": "rssFeedImports_rssFeedId_rssFeeds_id_fk",
+ "tableFrom": "rssFeedImports",
+ "tableTo": "rssFeeds",
+ "columnsFrom": [
+ "rssFeedId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "rssFeedImports_bookmarkId_bookmarks_id_fk": {
+ "name": "rssFeedImports_bookmarkId_bookmarks_id_fk",
+ "tableFrom": "rssFeedImports",
+ "tableTo": "bookmarks",
+ "columnsFrom": [
+ "bookmarkId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "rssFeeds": {
+ "name": "rssFeeds",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "url": {
+ "name": "url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": true
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "lastFetchedAt": {
+ "name": "lastFetchedAt",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "lastFetchedStatus": {
+ "name": "lastFetchedStatus",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'pending'"
+ },
+ "userId": {
+ "name": "userId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "rssFeeds_userId_idx": {
+ "name": "rssFeeds_userId_idx",
+ "columns": [
+ "userId"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "rssFeeds_userId_user_id_fk": {
+ "name": "rssFeeds_userId_user_id_fk",
+ "tableFrom": "rssFeeds",
+ "tableTo": "user",
+ "columnsFrom": [
+ "userId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "ruleEngineActions": {
+ "name": "ruleEngineActions",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "userId": {
+ "name": "userId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "ruleId": {
+ "name": "ruleId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "action": {
+ "name": "action",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "listId": {
+ "name": "listId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "tagId": {
+ "name": "tagId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "ruleEngineActions_userId_idx": {
+ "name": "ruleEngineActions_userId_idx",
+ "columns": [
+ "userId"
+ ],
+ "isUnique": false
+ },
+ "ruleEngineActions_ruleId_idx": {
+ "name": "ruleEngineActions_ruleId_idx",
+ "columns": [
+ "ruleId"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "ruleEngineActions_userId_user_id_fk": {
+ "name": "ruleEngineActions_userId_user_id_fk",
+ "tableFrom": "ruleEngineActions",
+ "tableTo": "user",
+ "columnsFrom": [
+ "userId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "ruleEngineActions_ruleId_ruleEngineRules_id_fk": {
+ "name": "ruleEngineActions_ruleId_ruleEngineRules_id_fk",
+ "tableFrom": "ruleEngineActions",
+ "tableTo": "ruleEngineRules",
+ "columnsFrom": [
+ "ruleId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "ruleEngineActions_userId_tagId_fk": {
+ "name": "ruleEngineActions_userId_tagId_fk",
+ "tableFrom": "ruleEngineActions",
+ "tableTo": "bookmarkTags",
+ "columnsFrom": [
+ "userId",
+ "tagId"
+ ],
+ "columnsTo": [
+ "userId",
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "ruleEngineActions_userId_listId_fk": {
+ "name": "ruleEngineActions_userId_listId_fk",
+ "tableFrom": "ruleEngineActions",
+ "tableTo": "bookmarkLists",
+ "columnsFrom": [
+ "userId",
+ "listId"
+ ],
+ "columnsTo": [
+ "userId",
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "ruleEngineRules": {
+ "name": "ruleEngineRules",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "event": {
+ "name": "event",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "condition": {
+ "name": "condition",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "userId": {
+ "name": "userId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "listId": {
+ "name": "listId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "tagId": {
+ "name": "tagId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "ruleEngine_userId_idx": {
+ "name": "ruleEngine_userId_idx",
+ "columns": [
+ "userId"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "ruleEngineRules_userId_user_id_fk": {
+ "name": "ruleEngineRules_userId_user_id_fk",
+ "tableFrom": "ruleEngineRules",
+ "tableTo": "user",
+ "columnsFrom": [
+ "userId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "ruleEngineRules_userId_tagId_fk": {
+ "name": "ruleEngineRules_userId_tagId_fk",
+ "tableFrom": "ruleEngineRules",
+ "tableTo": "bookmarkTags",
+ "columnsFrom": [
+ "userId",
+ "tagId"
+ ],
+ "columnsTo": [
+ "userId",
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "ruleEngineRules_userId_listId_fk": {
+ "name": "ruleEngineRules_userId_listId_fk",
+ "tableFrom": "ruleEngineRules",
+ "tableTo": "bookmarkLists",
+ "columnsFrom": [
+ "userId",
+ "listId"
+ ],
+ "columnsTo": [
+ "userId",
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "session": {
+ "name": "session",
+ "columns": {
+ "sessionToken": {
+ "name": "sessionToken",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "userId": {
+ "name": "userId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "expires": {
+ "name": "expires",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "session_userId_user_id_fk": {
+ "name": "session_userId_user_id_fk",
+ "tableFrom": "session",
+ "tableTo": "user",
+ "columnsFrom": [
+ "userId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "tagsOnBookmarks": {
+ "name": "tagsOnBookmarks",
+ "columns": {
+ "bookmarkId": {
+ "name": "bookmarkId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "tagId": {
+ "name": "tagId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "attachedAt": {
+ "name": "attachedAt",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "attachedBy": {
+ "name": "attachedBy",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "tagsOnBookmarks_tagId_idx": {
+ "name": "tagsOnBookmarks_tagId_idx",
+ "columns": [
+ "tagId"
+ ],
+ "isUnique": false
+ },
+ "tagsOnBookmarks_bookmarkId_idx": {
+ "name": "tagsOnBookmarks_bookmarkId_idx",
+ "columns": [
+ "bookmarkId"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "tagsOnBookmarks_bookmarkId_bookmarks_id_fk": {
+ "name": "tagsOnBookmarks_bookmarkId_bookmarks_id_fk",
+ "tableFrom": "tagsOnBookmarks",
+ "tableTo": "bookmarks",
+ "columnsFrom": [
+ "bookmarkId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "tagsOnBookmarks_tagId_bookmarkTags_id_fk": {
+ "name": "tagsOnBookmarks_tagId_bookmarkTags_id_fk",
+ "tableFrom": "tagsOnBookmarks",
+ "tableTo": "bookmarkTags",
+ "columnsFrom": [
+ "tagId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "tagsOnBookmarks_bookmarkId_tagId_pk": {
+ "columns": [
+ "bookmarkId",
+ "tagId"
+ ],
+ "name": "tagsOnBookmarks_bookmarkId_tagId_pk"
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "user": {
+ "name": "user",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "email": {
+ "name": "email",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "emailVerified": {
+ "name": "emailVerified",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "image": {
+ "name": "image",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "password": {
+ "name": "password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "salt": {
+ "name": "salt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "''"
+ },
+ "role": {
+ "name": "role",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'user'"
+ }
+ },
+ "indexes": {
+ "user_email_unique": {
+ "name": "user_email_unique",
+ "columns": [
+ "email"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "verificationToken": {
+ "name": "verificationToken",
+ "columns": {
+ "identifier": {
+ "name": "identifier",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "token": {
+ "name": "token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "expires": {
+ "name": "expires",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "verificationToken_identifier_token_pk": {
+ "columns": [
+ "identifier",
+ "token"
+ ],
+ "name": "verificationToken_identifier_token_pk"
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "webhooks": {
+ "name": "webhooks",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "url": {
+ "name": "url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "userId": {
+ "name": "userId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "events": {
+ "name": "events",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "token": {
+ "name": "token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "webhooks_userId_idx": {
+ "name": "webhooks_userId_idx",
+ "columns": [
+ "userId"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "webhooks_userId_user_id_fk": {
+ "name": "webhooks_userId_user_id_fk",
+ "tableFrom": "webhooks",
+ "tableTo": "user",
+ "columnsFrom": [
+ "userId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ }
+ },
+ "views": {},
+ "enums": {},
+ "_meta": {
+ "schemas": {},
+ "tables": {},
+ "columns": {}
+ },
+ "internal": {
+ "indexes": {}
+ }
+} \ No newline at end of file
diff --git a/packages/db/drizzle/meta/0047_snapshot.json b/packages/db/drizzle/meta/0047_snapshot.json
new file mode 100644
index 00000000..f6908043
--- /dev/null
+++ b/packages/db/drizzle/meta/0047_snapshot.json
@@ -0,0 +1,1967 @@
+{
+ "version": "6",
+ "dialect": "sqlite",
+ "id": "cd8ff1a8-c7bb-4576-9bec-2644785c894e",
+ "prevId": "7a950001-03e7-43f2-8ef5-272dfe82d223",
+ "tables": {
+ "account": {
+ "name": "account",
+ "columns": {
+ "userId": {
+ "name": "userId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "type": {
+ "name": "type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "provider": {
+ "name": "provider",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "providerAccountId": {
+ "name": "providerAccountId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "refresh_token": {
+ "name": "refresh_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "access_token": {
+ "name": "access_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "token_type": {
+ "name": "token_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "scope": {
+ "name": "scope",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "id_token": {
+ "name": "id_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "session_state": {
+ "name": "session_state",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "account_userId_user_id_fk": {
+ "name": "account_userId_user_id_fk",
+ "tableFrom": "account",
+ "tableTo": "user",
+ "columnsFrom": [
+ "userId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "account_provider_providerAccountId_pk": {
+ "columns": [
+ "provider",
+ "providerAccountId"
+ ],
+ "name": "account_provider_providerAccountId_pk"
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "apiKey": {
+ "name": "apiKey",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "keyId": {
+ "name": "keyId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "keyHash": {
+ "name": "keyHash",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "userId": {
+ "name": "userId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "apiKey_keyId_unique": {
+ "name": "apiKey_keyId_unique",
+ "columns": [
+ "keyId"
+ ],
+ "isUnique": true
+ },
+ "apiKey_name_userId_unique": {
+ "name": "apiKey_name_userId_unique",
+ "columns": [
+ "name",
+ "userId"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {
+ "apiKey_userId_user_id_fk": {
+ "name": "apiKey_userId_user_id_fk",
+ "tableFrom": "apiKey",
+ "tableTo": "user",
+ "columnsFrom": [
+ "userId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "assets": {
+ "name": "assets",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "assetType": {
+ "name": "assetType",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "size": {
+ "name": "size",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 0
+ },
+ "contentType": {
+ "name": "contentType",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "fileName": {
+ "name": "fileName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "bookmarkId": {
+ "name": "bookmarkId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "userId": {
+ "name": "userId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "assets_bookmarkId_idx": {
+ "name": "assets_bookmarkId_idx",
+ "columns": [
+ "bookmarkId"
+ ],
+ "isUnique": false
+ },
+ "assets_assetType_idx": {
+ "name": "assets_assetType_idx",
+ "columns": [
+ "assetType"
+ ],
+ "isUnique": false
+ },
+ "assets_userId_idx": {
+ "name": "assets_userId_idx",
+ "columns": [
+ "userId"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "assets_bookmarkId_bookmarks_id_fk": {
+ "name": "assets_bookmarkId_bookmarks_id_fk",
+ "tableFrom": "assets",
+ "tableTo": "bookmarks",
+ "columnsFrom": [
+ "bookmarkId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "assets_userId_user_id_fk": {
+ "name": "assets_userId_user_id_fk",
+ "tableFrom": "assets",
+ "tableTo": "user",
+ "columnsFrom": [
+ "userId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "bookmarkAssets": {
+ "name": "bookmarkAssets",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "assetType": {
+ "name": "assetType",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "assetId": {
+ "name": "assetId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "content": {
+ "name": "content",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "metadata": {
+ "name": "metadata",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "fileName": {
+ "name": "fileName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "sourceUrl": {
+ "name": "sourceUrl",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "bookmarkAssets_id_bookmarks_id_fk": {
+ "name": "bookmarkAssets_id_bookmarks_id_fk",
+ "tableFrom": "bookmarkAssets",
+ "tableTo": "bookmarks",
+ "columnsFrom": [
+ "id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "bookmarkLinks": {
+ "name": "bookmarkLinks",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "url": {
+ "name": "url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "author": {
+ "name": "author",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "publisher": {
+ "name": "publisher",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "datePublished": {
+ "name": "datePublished",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "dateModified": {
+ "name": "dateModified",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "imageUrl": {
+ "name": "imageUrl",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "favicon": {
+ "name": "favicon",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "content": {
+ "name": "content",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "htmlContent": {
+ "name": "htmlContent",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "crawledAt": {
+ "name": "crawledAt",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "crawlStatus": {
+ "name": "crawlStatus",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'pending'"
+ },
+ "crawlStatusCode": {
+ "name": "crawlStatusCode",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": 200
+ }
+ },
+ "indexes": {
+ "bookmarkLinks_url_idx": {
+ "name": "bookmarkLinks_url_idx",
+ "columns": [
+ "url"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "bookmarkLinks_id_bookmarks_id_fk": {
+ "name": "bookmarkLinks_id_bookmarks_id_fk",
+ "tableFrom": "bookmarkLinks",
+ "tableTo": "bookmarks",
+ "columnsFrom": [
+ "id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "bookmarkLists": {
+ "name": "bookmarkLists",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "icon": {
+ "name": "icon",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "userId": {
+ "name": "userId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "type": {
+ "name": "type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "query": {
+ "name": "query",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "parentId": {
+ "name": "parentId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "bookmarkLists_userId_idx": {
+ "name": "bookmarkLists_userId_idx",
+ "columns": [
+ "userId"
+ ],
+ "isUnique": false
+ },
+ "bookmarkLists_userId_id_idx": {
+ "name": "bookmarkLists_userId_id_idx",
+ "columns": [
+ "userId",
+ "id"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {
+ "bookmarkLists_userId_user_id_fk": {
+ "name": "bookmarkLists_userId_user_id_fk",
+ "tableFrom": "bookmarkLists",
+ "tableTo": "user",
+ "columnsFrom": [
+ "userId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "bookmarkLists_parentId_bookmarkLists_id_fk": {
+ "name": "bookmarkLists_parentId_bookmarkLists_id_fk",
+ "tableFrom": "bookmarkLists",
+ "tableTo": "bookmarkLists",
+ "columnsFrom": [
+ "parentId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "bookmarkTags": {
+ "name": "bookmarkTags",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "userId": {
+ "name": "userId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "bookmarkTags_name_idx": {
+ "name": "bookmarkTags_name_idx",
+ "columns": [
+ "name"
+ ],
+ "isUnique": false
+ },
+ "bookmarkTags_userId_idx": {
+ "name": "bookmarkTags_userId_idx",
+ "columns": [
+ "userId"
+ ],
+ "isUnique": false
+ },
+ "bookmarkTags_userId_name_unique": {
+ "name": "bookmarkTags_userId_name_unique",
+ "columns": [
+ "userId",
+ "name"
+ ],
+ "isUnique": true
+ },
+ "bookmarkTags_userId_id_idx": {
+ "name": "bookmarkTags_userId_id_idx",
+ "columns": [
+ "userId",
+ "id"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {
+ "bookmarkTags_userId_user_id_fk": {
+ "name": "bookmarkTags_userId_user_id_fk",
+ "tableFrom": "bookmarkTags",
+ "tableTo": "user",
+ "columnsFrom": [
+ "userId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "bookmarkTexts": {
+ "name": "bookmarkTexts",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "text": {
+ "name": "text",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "sourceUrl": {
+ "name": "sourceUrl",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "bookmarkTexts_id_bookmarks_id_fk": {
+ "name": "bookmarkTexts_id_bookmarks_id_fk",
+ "tableFrom": "bookmarkTexts",
+ "tableTo": "bookmarks",
+ "columnsFrom": [
+ "id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "bookmarks": {
+ "name": "bookmarks",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "modifiedAt": {
+ "name": "modifiedAt",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "archived": {
+ "name": "archived",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": false
+ },
+ "favourited": {
+ "name": "favourited",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": false
+ },
+ "userId": {
+ "name": "userId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "taggingStatus": {
+ "name": "taggingStatus",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'pending'"
+ },
+ "summarizationStatus": {
+ "name": "summarizationStatus",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'pending'"
+ },
+ "summary": {
+ "name": "summary",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "note": {
+ "name": "note",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "type": {
+ "name": "type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "bookmarks_userId_idx": {
+ "name": "bookmarks_userId_idx",
+ "columns": [
+ "userId"
+ ],
+ "isUnique": false
+ },
+ "bookmarks_archived_idx": {
+ "name": "bookmarks_archived_idx",
+ "columns": [
+ "archived"
+ ],
+ "isUnique": false
+ },
+ "bookmarks_favourited_idx": {
+ "name": "bookmarks_favourited_idx",
+ "columns": [
+ "favourited"
+ ],
+ "isUnique": false
+ },
+ "bookmarks_createdAt_idx": {
+ "name": "bookmarks_createdAt_idx",
+ "columns": [
+ "createdAt"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "bookmarks_userId_user_id_fk": {
+ "name": "bookmarks_userId_user_id_fk",
+ "tableFrom": "bookmarks",
+ "tableTo": "user",
+ "columnsFrom": [
+ "userId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "bookmarksInLists": {
+ "name": "bookmarksInLists",
+ "columns": {
+ "bookmarkId": {
+ "name": "bookmarkId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "listId": {
+ "name": "listId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "addedAt": {
+ "name": "addedAt",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "bookmarksInLists_bookmarkId_idx": {
+ "name": "bookmarksInLists_bookmarkId_idx",
+ "columns": [
+ "bookmarkId"
+ ],
+ "isUnique": false
+ },
+ "bookmarksInLists_listId_idx": {
+ "name": "bookmarksInLists_listId_idx",
+ "columns": [
+ "listId"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "bookmarksInLists_bookmarkId_bookmarks_id_fk": {
+ "name": "bookmarksInLists_bookmarkId_bookmarks_id_fk",
+ "tableFrom": "bookmarksInLists",
+ "tableTo": "bookmarks",
+ "columnsFrom": [
+ "bookmarkId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "bookmarksInLists_listId_bookmarkLists_id_fk": {
+ "name": "bookmarksInLists_listId_bookmarkLists_id_fk",
+ "tableFrom": "bookmarksInLists",
+ "tableTo": "bookmarkLists",
+ "columnsFrom": [
+ "listId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "bookmarksInLists_bookmarkId_listId_pk": {
+ "columns": [
+ "bookmarkId",
+ "listId"
+ ],
+ "name": "bookmarksInLists_bookmarkId_listId_pk"
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "config": {
+ "name": "config",
+ "columns": {
+ "key": {
+ "name": "key",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "value": {
+ "name": "value",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "customPrompts": {
+ "name": "customPrompts",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "text": {
+ "name": "text",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "appliesTo": {
+ "name": "appliesTo",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "userId": {
+ "name": "userId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "customPrompts_userId_idx": {
+ "name": "customPrompts_userId_idx",
+ "columns": [
+ "userId"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "customPrompts_userId_user_id_fk": {
+ "name": "customPrompts_userId_user_id_fk",
+ "tableFrom": "customPrompts",
+ "tableTo": "user",
+ "columnsFrom": [
+ "userId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "highlights": {
+ "name": "highlights",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "bookmarkId": {
+ "name": "bookmarkId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "userId": {
+ "name": "userId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "startOffset": {
+ "name": "startOffset",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "endOffset": {
+ "name": "endOffset",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "color": {
+ "name": "color",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'yellow'"
+ },
+ "text": {
+ "name": "text",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "note": {
+ "name": "note",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "highlights_bookmarkId_idx": {
+ "name": "highlights_bookmarkId_idx",
+ "columns": [
+ "bookmarkId"
+ ],
+ "isUnique": false
+ },
+ "highlights_userId_idx": {
+ "name": "highlights_userId_idx",
+ "columns": [
+ "userId"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "highlights_bookmarkId_bookmarks_id_fk": {
+ "name": "highlights_bookmarkId_bookmarks_id_fk",
+ "tableFrom": "highlights",
+ "tableTo": "bookmarks",
+ "columnsFrom": [
+ "bookmarkId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "highlights_userId_user_id_fk": {
+ "name": "highlights_userId_user_id_fk",
+ "tableFrom": "highlights",
+ "tableTo": "user",
+ "columnsFrom": [
+ "userId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "rssFeedImports": {
+ "name": "rssFeedImports",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "entryId": {
+ "name": "entryId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "rssFeedId": {
+ "name": "rssFeedId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "bookmarkId": {
+ "name": "bookmarkId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "rssFeedImports_feedIdIdx_idx": {
+ "name": "rssFeedImports_feedIdIdx_idx",
+ "columns": [
+ "rssFeedId"
+ ],
+ "isUnique": false
+ },
+ "rssFeedImports_entryIdIdx_idx": {
+ "name": "rssFeedImports_entryIdIdx_idx",
+ "columns": [
+ "entryId"
+ ],
+ "isUnique": false
+ },
+ "rssFeedImports_rssFeedId_entryId_unique": {
+ "name": "rssFeedImports_rssFeedId_entryId_unique",
+ "columns": [
+ "rssFeedId",
+ "entryId"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {
+ "rssFeedImports_rssFeedId_rssFeeds_id_fk": {
+ "name": "rssFeedImports_rssFeedId_rssFeeds_id_fk",
+ "tableFrom": "rssFeedImports",
+ "tableTo": "rssFeeds",
+ "columnsFrom": [
+ "rssFeedId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "rssFeedImports_bookmarkId_bookmarks_id_fk": {
+ "name": "rssFeedImports_bookmarkId_bookmarks_id_fk",
+ "tableFrom": "rssFeedImports",
+ "tableTo": "bookmarks",
+ "columnsFrom": [
+ "bookmarkId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "rssFeeds": {
+ "name": "rssFeeds",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "url": {
+ "name": "url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": true
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "lastFetchedAt": {
+ "name": "lastFetchedAt",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "lastFetchedStatus": {
+ "name": "lastFetchedStatus",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'pending'"
+ },
+ "userId": {
+ "name": "userId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "rssFeeds_userId_idx": {
+ "name": "rssFeeds_userId_idx",
+ "columns": [
+ "userId"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "rssFeeds_userId_user_id_fk": {
+ "name": "rssFeeds_userId_user_id_fk",
+ "tableFrom": "rssFeeds",
+ "tableTo": "user",
+ "columnsFrom": [
+ "userId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "ruleEngineActions": {
+ "name": "ruleEngineActions",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "userId": {
+ "name": "userId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "ruleId": {
+ "name": "ruleId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "action": {
+ "name": "action",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "listId": {
+ "name": "listId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "tagId": {
+ "name": "tagId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "ruleEngineActions_userId_idx": {
+ "name": "ruleEngineActions_userId_idx",
+ "columns": [
+ "userId"
+ ],
+ "isUnique": false
+ },
+ "ruleEngineActions_ruleId_idx": {
+ "name": "ruleEngineActions_ruleId_idx",
+ "columns": [
+ "ruleId"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "ruleEngineActions_userId_user_id_fk": {
+ "name": "ruleEngineActions_userId_user_id_fk",
+ "tableFrom": "ruleEngineActions",
+ "tableTo": "user",
+ "columnsFrom": [
+ "userId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "ruleEngineActions_ruleId_ruleEngineRules_id_fk": {
+ "name": "ruleEngineActions_ruleId_ruleEngineRules_id_fk",
+ "tableFrom": "ruleEngineActions",
+ "tableTo": "ruleEngineRules",
+ "columnsFrom": [
+ "ruleId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "ruleEngineActions_userId_tagId_fk": {
+ "name": "ruleEngineActions_userId_tagId_fk",
+ "tableFrom": "ruleEngineActions",
+ "tableTo": "bookmarkTags",
+ "columnsFrom": [
+ "userId",
+ "tagId"
+ ],
+ "columnsTo": [
+ "userId",
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "ruleEngineActions_userId_listId_fk": {
+ "name": "ruleEngineActions_userId_listId_fk",
+ "tableFrom": "ruleEngineActions",
+ "tableTo": "bookmarkLists",
+ "columnsFrom": [
+ "userId",
+ "listId"
+ ],
+ "columnsTo": [
+ "userId",
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "ruleEngineRules": {
+ "name": "ruleEngineRules",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "event": {
+ "name": "event",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "condition": {
+ "name": "condition",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "userId": {
+ "name": "userId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "listId": {
+ "name": "listId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "tagId": {
+ "name": "tagId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "ruleEngine_userId_idx": {
+ "name": "ruleEngine_userId_idx",
+ "columns": [
+ "userId"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "ruleEngineRules_userId_user_id_fk": {
+ "name": "ruleEngineRules_userId_user_id_fk",
+ "tableFrom": "ruleEngineRules",
+ "tableTo": "user",
+ "columnsFrom": [
+ "userId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "ruleEngineRules_userId_tagId_fk": {
+ "name": "ruleEngineRules_userId_tagId_fk",
+ "tableFrom": "ruleEngineRules",
+ "tableTo": "bookmarkTags",
+ "columnsFrom": [
+ "userId",
+ "tagId"
+ ],
+ "columnsTo": [
+ "userId",
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "ruleEngineRules_userId_listId_fk": {
+ "name": "ruleEngineRules_userId_listId_fk",
+ "tableFrom": "ruleEngineRules",
+ "tableTo": "bookmarkLists",
+ "columnsFrom": [
+ "userId",
+ "listId"
+ ],
+ "columnsTo": [
+ "userId",
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "session": {
+ "name": "session",
+ "columns": {
+ "sessionToken": {
+ "name": "sessionToken",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "userId": {
+ "name": "userId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "expires": {
+ "name": "expires",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "session_userId_user_id_fk": {
+ "name": "session_userId_user_id_fk",
+ "tableFrom": "session",
+ "tableTo": "user",
+ "columnsFrom": [
+ "userId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "tagsOnBookmarks": {
+ "name": "tagsOnBookmarks",
+ "columns": {
+ "bookmarkId": {
+ "name": "bookmarkId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "tagId": {
+ "name": "tagId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "attachedAt": {
+ "name": "attachedAt",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "attachedBy": {
+ "name": "attachedBy",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "tagsOnBookmarks_tagId_idx": {
+ "name": "tagsOnBookmarks_tagId_idx",
+ "columns": [
+ "tagId"
+ ],
+ "isUnique": false
+ },
+ "tagsOnBookmarks_bookmarkId_idx": {
+ "name": "tagsOnBookmarks_bookmarkId_idx",
+ "columns": [
+ "bookmarkId"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "tagsOnBookmarks_bookmarkId_bookmarks_id_fk": {
+ "name": "tagsOnBookmarks_bookmarkId_bookmarks_id_fk",
+ "tableFrom": "tagsOnBookmarks",
+ "tableTo": "bookmarks",
+ "columnsFrom": [
+ "bookmarkId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "tagsOnBookmarks_tagId_bookmarkTags_id_fk": {
+ "name": "tagsOnBookmarks_tagId_bookmarkTags_id_fk",
+ "tableFrom": "tagsOnBookmarks",
+ "tableTo": "bookmarkTags",
+ "columnsFrom": [
+ "tagId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "tagsOnBookmarks_bookmarkId_tagId_pk": {
+ "columns": [
+ "bookmarkId",
+ "tagId"
+ ],
+ "name": "tagsOnBookmarks_bookmarkId_tagId_pk"
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "user": {
+ "name": "user",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "email": {
+ "name": "email",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "emailVerified": {
+ "name": "emailVerified",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "image": {
+ "name": "image",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "password": {
+ "name": "password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "salt": {
+ "name": "salt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "''"
+ },
+ "role": {
+ "name": "role",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'user'"
+ }
+ },
+ "indexes": {
+ "user_email_unique": {
+ "name": "user_email_unique",
+ "columns": [
+ "email"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "verificationToken": {
+ "name": "verificationToken",
+ "columns": {
+ "identifier": {
+ "name": "identifier",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "token": {
+ "name": "token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "expires": {
+ "name": "expires",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "verificationToken_identifier_token_pk": {
+ "columns": [
+ "identifier",
+ "token"
+ ],
+ "name": "verificationToken_identifier_token_pk"
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "webhooks": {
+ "name": "webhooks",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "url": {
+ "name": "url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "userId": {
+ "name": "userId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "events": {
+ "name": "events",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "token": {
+ "name": "token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "webhooks_userId_idx": {
+ "name": "webhooks_userId_idx",
+ "columns": [
+ "userId"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "webhooks_userId_user_id_fk": {
+ "name": "webhooks_userId_user_id_fk",
+ "tableFrom": "webhooks",
+ "tableTo": "user",
+ "columnsFrom": [
+ "userId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ }
+ },
+ "views": {},
+ "enums": {},
+ "_meta": {
+ "schemas": {},
+ "tables": {},
+ "columns": {}
+ },
+ "internal": {
+ "indexes": {}
+ }
+} \ No newline at end of file
diff --git a/packages/db/drizzle/meta/0048_snapshot.json b/packages/db/drizzle/meta/0048_snapshot.json
new file mode 100644
index 00000000..ec098d85
--- /dev/null
+++ b/packages/db/drizzle/meta/0048_snapshot.json
@@ -0,0 +1,2006 @@
+{
+ "version": "6",
+ "dialect": "sqlite",
+ "id": "d3ffc2ee-399e-4652-813d-652b56d649f6",
+ "prevId": "cd8ff1a8-c7bb-4576-9bec-2644785c894e",
+ "tables": {
+ "account": {
+ "name": "account",
+ "columns": {
+ "userId": {
+ "name": "userId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "type": {
+ "name": "type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "provider": {
+ "name": "provider",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "providerAccountId": {
+ "name": "providerAccountId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "refresh_token": {
+ "name": "refresh_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "access_token": {
+ "name": "access_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "token_type": {
+ "name": "token_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "scope": {
+ "name": "scope",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "id_token": {
+ "name": "id_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "session_state": {
+ "name": "session_state",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "account_userId_user_id_fk": {
+ "name": "account_userId_user_id_fk",
+ "tableFrom": "account",
+ "tableTo": "user",
+ "columnsFrom": [
+ "userId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "account_provider_providerAccountId_pk": {
+ "columns": [
+ "provider",
+ "providerAccountId"
+ ],
+ "name": "account_provider_providerAccountId_pk"
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "apiKey": {
+ "name": "apiKey",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "keyId": {
+ "name": "keyId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "keyHash": {
+ "name": "keyHash",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "userId": {
+ "name": "userId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "apiKey_keyId_unique": {
+ "name": "apiKey_keyId_unique",
+ "columns": [
+ "keyId"
+ ],
+ "isUnique": true
+ },
+ "apiKey_name_userId_unique": {
+ "name": "apiKey_name_userId_unique",
+ "columns": [
+ "name",
+ "userId"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {
+ "apiKey_userId_user_id_fk": {
+ "name": "apiKey_userId_user_id_fk",
+ "tableFrom": "apiKey",
+ "tableTo": "user",
+ "columnsFrom": [
+ "userId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "assets": {
+ "name": "assets",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "assetType": {
+ "name": "assetType",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "size": {
+ "name": "size",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 0
+ },
+ "contentType": {
+ "name": "contentType",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "fileName": {
+ "name": "fileName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "bookmarkId": {
+ "name": "bookmarkId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "userId": {
+ "name": "userId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "assets_bookmarkId_idx": {
+ "name": "assets_bookmarkId_idx",
+ "columns": [
+ "bookmarkId"
+ ],
+ "isUnique": false
+ },
+ "assets_assetType_idx": {
+ "name": "assets_assetType_idx",
+ "columns": [
+ "assetType"
+ ],
+ "isUnique": false
+ },
+ "assets_userId_idx": {
+ "name": "assets_userId_idx",
+ "columns": [
+ "userId"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "assets_bookmarkId_bookmarks_id_fk": {
+ "name": "assets_bookmarkId_bookmarks_id_fk",
+ "tableFrom": "assets",
+ "tableTo": "bookmarks",
+ "columnsFrom": [
+ "bookmarkId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "assets_userId_user_id_fk": {
+ "name": "assets_userId_user_id_fk",
+ "tableFrom": "assets",
+ "tableTo": "user",
+ "columnsFrom": [
+ "userId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "bookmarkAssets": {
+ "name": "bookmarkAssets",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "assetType": {
+ "name": "assetType",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "assetId": {
+ "name": "assetId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "content": {
+ "name": "content",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "metadata": {
+ "name": "metadata",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "fileName": {
+ "name": "fileName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "sourceUrl": {
+ "name": "sourceUrl",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "bookmarkAssets_id_bookmarks_id_fk": {
+ "name": "bookmarkAssets_id_bookmarks_id_fk",
+ "tableFrom": "bookmarkAssets",
+ "tableTo": "bookmarks",
+ "columnsFrom": [
+ "id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "bookmarkLinks": {
+ "name": "bookmarkLinks",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "url": {
+ "name": "url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "author": {
+ "name": "author",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "publisher": {
+ "name": "publisher",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "datePublished": {
+ "name": "datePublished",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "dateModified": {
+ "name": "dateModified",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "imageUrl": {
+ "name": "imageUrl",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "favicon": {
+ "name": "favicon",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "content": {
+ "name": "content",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "htmlContent": {
+ "name": "htmlContent",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "crawledAt": {
+ "name": "crawledAt",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "crawlStatus": {
+ "name": "crawlStatus",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'pending'"
+ },
+ "crawlStatusCode": {
+ "name": "crawlStatusCode",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": 200
+ }
+ },
+ "indexes": {
+ "bookmarkLinks_url_idx": {
+ "name": "bookmarkLinks_url_idx",
+ "columns": [
+ "url"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "bookmarkLinks_id_bookmarks_id_fk": {
+ "name": "bookmarkLinks_id_bookmarks_id_fk",
+ "tableFrom": "bookmarkLinks",
+ "tableTo": "bookmarks",
+ "columnsFrom": [
+ "id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "bookmarkLists": {
+ "name": "bookmarkLists",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "icon": {
+ "name": "icon",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "userId": {
+ "name": "userId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "type": {
+ "name": "type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "query": {
+ "name": "query",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "parentId": {
+ "name": "parentId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "bookmarkLists_userId_idx": {
+ "name": "bookmarkLists_userId_idx",
+ "columns": [
+ "userId"
+ ],
+ "isUnique": false
+ },
+ "bookmarkLists_userId_id_idx": {
+ "name": "bookmarkLists_userId_id_idx",
+ "columns": [
+ "userId",
+ "id"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {
+ "bookmarkLists_userId_user_id_fk": {
+ "name": "bookmarkLists_userId_user_id_fk",
+ "tableFrom": "bookmarkLists",
+ "tableTo": "user",
+ "columnsFrom": [
+ "userId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "bookmarkLists_parentId_bookmarkLists_id_fk": {
+ "name": "bookmarkLists_parentId_bookmarkLists_id_fk",
+ "tableFrom": "bookmarkLists",
+ "tableTo": "bookmarkLists",
+ "columnsFrom": [
+ "parentId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "bookmarkTags": {
+ "name": "bookmarkTags",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "userId": {
+ "name": "userId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "bookmarkTags_name_idx": {
+ "name": "bookmarkTags_name_idx",
+ "columns": [
+ "name"
+ ],
+ "isUnique": false
+ },
+ "bookmarkTags_userId_idx": {
+ "name": "bookmarkTags_userId_idx",
+ "columns": [
+ "userId"
+ ],
+ "isUnique": false
+ },
+ "bookmarkTags_userId_name_unique": {
+ "name": "bookmarkTags_userId_name_unique",
+ "columns": [
+ "userId",
+ "name"
+ ],
+ "isUnique": true
+ },
+ "bookmarkTags_userId_id_idx": {
+ "name": "bookmarkTags_userId_id_idx",
+ "columns": [
+ "userId",
+ "id"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {
+ "bookmarkTags_userId_user_id_fk": {
+ "name": "bookmarkTags_userId_user_id_fk",
+ "tableFrom": "bookmarkTags",
+ "tableTo": "user",
+ "columnsFrom": [
+ "userId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "bookmarkTexts": {
+ "name": "bookmarkTexts",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "text": {
+ "name": "text",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "sourceUrl": {
+ "name": "sourceUrl",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "bookmarkTexts_id_bookmarks_id_fk": {
+ "name": "bookmarkTexts_id_bookmarks_id_fk",
+ "tableFrom": "bookmarkTexts",
+ "tableTo": "bookmarks",
+ "columnsFrom": [
+ "id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "bookmarks": {
+ "name": "bookmarks",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "modifiedAt": {
+ "name": "modifiedAt",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "archived": {
+ "name": "archived",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": false
+ },
+ "favourited": {
+ "name": "favourited",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": false
+ },
+ "userId": {
+ "name": "userId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "taggingStatus": {
+ "name": "taggingStatus",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'pending'"
+ },
+ "summarizationStatus": {
+ "name": "summarizationStatus",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'pending'"
+ },
+ "summary": {
+ "name": "summary",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "note": {
+ "name": "note",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "type": {
+ "name": "type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "bookmarks_userId_idx": {
+ "name": "bookmarks_userId_idx",
+ "columns": [
+ "userId"
+ ],
+ "isUnique": false
+ },
+ "bookmarks_archived_idx": {
+ "name": "bookmarks_archived_idx",
+ "columns": [
+ "archived"
+ ],
+ "isUnique": false
+ },
+ "bookmarks_favourited_idx": {
+ "name": "bookmarks_favourited_idx",
+ "columns": [
+ "favourited"
+ ],
+ "isUnique": false
+ },
+ "bookmarks_createdAt_idx": {
+ "name": "bookmarks_createdAt_idx",
+ "columns": [
+ "createdAt"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "bookmarks_userId_user_id_fk": {
+ "name": "bookmarks_userId_user_id_fk",
+ "tableFrom": "bookmarks",
+ "tableTo": "user",
+ "columnsFrom": [
+ "userId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "bookmarksInLists": {
+ "name": "bookmarksInLists",
+ "columns": {
+ "bookmarkId": {
+ "name": "bookmarkId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "listId": {
+ "name": "listId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "addedAt": {
+ "name": "addedAt",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "bookmarksInLists_bookmarkId_idx": {
+ "name": "bookmarksInLists_bookmarkId_idx",
+ "columns": [
+ "bookmarkId"
+ ],
+ "isUnique": false
+ },
+ "bookmarksInLists_listId_idx": {
+ "name": "bookmarksInLists_listId_idx",
+ "columns": [
+ "listId"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "bookmarksInLists_bookmarkId_bookmarks_id_fk": {
+ "name": "bookmarksInLists_bookmarkId_bookmarks_id_fk",
+ "tableFrom": "bookmarksInLists",
+ "tableTo": "bookmarks",
+ "columnsFrom": [
+ "bookmarkId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "bookmarksInLists_listId_bookmarkLists_id_fk": {
+ "name": "bookmarksInLists_listId_bookmarkLists_id_fk",
+ "tableFrom": "bookmarksInLists",
+ "tableTo": "bookmarkLists",
+ "columnsFrom": [
+ "listId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "bookmarksInLists_bookmarkId_listId_pk": {
+ "columns": [
+ "bookmarkId",
+ "listId"
+ ],
+ "name": "bookmarksInLists_bookmarkId_listId_pk"
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "config": {
+ "name": "config",
+ "columns": {
+ "key": {
+ "name": "key",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "value": {
+ "name": "value",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "customPrompts": {
+ "name": "customPrompts",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "text": {
+ "name": "text",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "appliesTo": {
+ "name": "appliesTo",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "userId": {
+ "name": "userId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "customPrompts_userId_idx": {
+ "name": "customPrompts_userId_idx",
+ "columns": [
+ "userId"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "customPrompts_userId_user_id_fk": {
+ "name": "customPrompts_userId_user_id_fk",
+ "tableFrom": "customPrompts",
+ "tableTo": "user",
+ "columnsFrom": [
+ "userId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "highlights": {
+ "name": "highlights",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "bookmarkId": {
+ "name": "bookmarkId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "userId": {
+ "name": "userId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "startOffset": {
+ "name": "startOffset",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "endOffset": {
+ "name": "endOffset",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "color": {
+ "name": "color",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'yellow'"
+ },
+ "text": {
+ "name": "text",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "note": {
+ "name": "note",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "highlights_bookmarkId_idx": {
+ "name": "highlights_bookmarkId_idx",
+ "columns": [
+ "bookmarkId"
+ ],
+ "isUnique": false
+ },
+ "highlights_userId_idx": {
+ "name": "highlights_userId_idx",
+ "columns": [
+ "userId"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "highlights_bookmarkId_bookmarks_id_fk": {
+ "name": "highlights_bookmarkId_bookmarks_id_fk",
+ "tableFrom": "highlights",
+ "tableTo": "bookmarks",
+ "columnsFrom": [
+ "bookmarkId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "highlights_userId_user_id_fk": {
+ "name": "highlights_userId_user_id_fk",
+ "tableFrom": "highlights",
+ "tableTo": "user",
+ "columnsFrom": [
+ "userId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "rssFeedImports": {
+ "name": "rssFeedImports",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "entryId": {
+ "name": "entryId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "rssFeedId": {
+ "name": "rssFeedId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "bookmarkId": {
+ "name": "bookmarkId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "rssFeedImports_feedIdIdx_idx": {
+ "name": "rssFeedImports_feedIdIdx_idx",
+ "columns": [
+ "rssFeedId"
+ ],
+ "isUnique": false
+ },
+ "rssFeedImports_entryIdIdx_idx": {
+ "name": "rssFeedImports_entryIdIdx_idx",
+ "columns": [
+ "entryId"
+ ],
+ "isUnique": false
+ },
+ "rssFeedImports_rssFeedId_entryId_unique": {
+ "name": "rssFeedImports_rssFeedId_entryId_unique",
+ "columns": [
+ "rssFeedId",
+ "entryId"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {
+ "rssFeedImports_rssFeedId_rssFeeds_id_fk": {
+ "name": "rssFeedImports_rssFeedId_rssFeeds_id_fk",
+ "tableFrom": "rssFeedImports",
+ "tableTo": "rssFeeds",
+ "columnsFrom": [
+ "rssFeedId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "rssFeedImports_bookmarkId_bookmarks_id_fk": {
+ "name": "rssFeedImports_bookmarkId_bookmarks_id_fk",
+ "tableFrom": "rssFeedImports",
+ "tableTo": "bookmarks",
+ "columnsFrom": [
+ "bookmarkId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "rssFeeds": {
+ "name": "rssFeeds",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "url": {
+ "name": "url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": true
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "lastFetchedAt": {
+ "name": "lastFetchedAt",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "lastFetchedStatus": {
+ "name": "lastFetchedStatus",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'pending'"
+ },
+ "userId": {
+ "name": "userId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "rssFeeds_userId_idx": {
+ "name": "rssFeeds_userId_idx",
+ "columns": [
+ "userId"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "rssFeeds_userId_user_id_fk": {
+ "name": "rssFeeds_userId_user_id_fk",
+ "tableFrom": "rssFeeds",
+ "tableTo": "user",
+ "columnsFrom": [
+ "userId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "ruleEngineActions": {
+ "name": "ruleEngineActions",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "userId": {
+ "name": "userId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "ruleId": {
+ "name": "ruleId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "action": {
+ "name": "action",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "listId": {
+ "name": "listId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "tagId": {
+ "name": "tagId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "ruleEngineActions_userId_idx": {
+ "name": "ruleEngineActions_userId_idx",
+ "columns": [
+ "userId"
+ ],
+ "isUnique": false
+ },
+ "ruleEngineActions_ruleId_idx": {
+ "name": "ruleEngineActions_ruleId_idx",
+ "columns": [
+ "ruleId"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "ruleEngineActions_userId_user_id_fk": {
+ "name": "ruleEngineActions_userId_user_id_fk",
+ "tableFrom": "ruleEngineActions",
+ "tableTo": "user",
+ "columnsFrom": [
+ "userId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "ruleEngineActions_ruleId_ruleEngineRules_id_fk": {
+ "name": "ruleEngineActions_ruleId_ruleEngineRules_id_fk",
+ "tableFrom": "ruleEngineActions",
+ "tableTo": "ruleEngineRules",
+ "columnsFrom": [
+ "ruleId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "ruleEngineActions_userId_tagId_fk": {
+ "name": "ruleEngineActions_userId_tagId_fk",
+ "tableFrom": "ruleEngineActions",
+ "tableTo": "bookmarkTags",
+ "columnsFrom": [
+ "userId",
+ "tagId"
+ ],
+ "columnsTo": [
+ "userId",
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "ruleEngineActions_userId_listId_fk": {
+ "name": "ruleEngineActions_userId_listId_fk",
+ "tableFrom": "ruleEngineActions",
+ "tableTo": "bookmarkLists",
+ "columnsFrom": [
+ "userId",
+ "listId"
+ ],
+ "columnsTo": [
+ "userId",
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "ruleEngineRules": {
+ "name": "ruleEngineRules",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "event": {
+ "name": "event",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "condition": {
+ "name": "condition",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "userId": {
+ "name": "userId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "listId": {
+ "name": "listId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "tagId": {
+ "name": "tagId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "ruleEngine_userId_idx": {
+ "name": "ruleEngine_userId_idx",
+ "columns": [
+ "userId"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "ruleEngineRules_userId_user_id_fk": {
+ "name": "ruleEngineRules_userId_user_id_fk",
+ "tableFrom": "ruleEngineRules",
+ "tableTo": "user",
+ "columnsFrom": [
+ "userId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "ruleEngineRules_userId_tagId_fk": {
+ "name": "ruleEngineRules_userId_tagId_fk",
+ "tableFrom": "ruleEngineRules",
+ "tableTo": "bookmarkTags",
+ "columnsFrom": [
+ "userId",
+ "tagId"
+ ],
+ "columnsTo": [
+ "userId",
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "ruleEngineRules_userId_listId_fk": {
+ "name": "ruleEngineRules_userId_listId_fk",
+ "tableFrom": "ruleEngineRules",
+ "tableTo": "bookmarkLists",
+ "columnsFrom": [
+ "userId",
+ "listId"
+ ],
+ "columnsTo": [
+ "userId",
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "session": {
+ "name": "session",
+ "columns": {
+ "sessionToken": {
+ "name": "sessionToken",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "userId": {
+ "name": "userId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "expires": {
+ "name": "expires",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "session_userId_user_id_fk": {
+ "name": "session_userId_user_id_fk",
+ "tableFrom": "session",
+ "tableTo": "user",
+ "columnsFrom": [
+ "userId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "tagsOnBookmarks": {
+ "name": "tagsOnBookmarks",
+ "columns": {
+ "bookmarkId": {
+ "name": "bookmarkId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "tagId": {
+ "name": "tagId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "attachedAt": {
+ "name": "attachedAt",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "attachedBy": {
+ "name": "attachedBy",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "tagsOnBookmarks_tagId_idx": {
+ "name": "tagsOnBookmarks_tagId_idx",
+ "columns": [
+ "tagId"
+ ],
+ "isUnique": false
+ },
+ "tagsOnBookmarks_bookmarkId_idx": {
+ "name": "tagsOnBookmarks_bookmarkId_idx",
+ "columns": [
+ "bookmarkId"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "tagsOnBookmarks_bookmarkId_bookmarks_id_fk": {
+ "name": "tagsOnBookmarks_bookmarkId_bookmarks_id_fk",
+ "tableFrom": "tagsOnBookmarks",
+ "tableTo": "bookmarks",
+ "columnsFrom": [
+ "bookmarkId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "tagsOnBookmarks_tagId_bookmarkTags_id_fk": {
+ "name": "tagsOnBookmarks_tagId_bookmarkTags_id_fk",
+ "tableFrom": "tagsOnBookmarks",
+ "tableTo": "bookmarkTags",
+ "columnsFrom": [
+ "tagId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "tagsOnBookmarks_bookmarkId_tagId_pk": {
+ "columns": [
+ "bookmarkId",
+ "tagId"
+ ],
+ "name": "tagsOnBookmarks_bookmarkId_tagId_pk"
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "userSettings": {
+ "name": "userSettings",
+ "columns": {
+ "userId": {
+ "name": "userId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "bookmarkClickAction": {
+ "name": "bookmarkClickAction",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'open_original_link'"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "userSettings_userId_user_id_fk": {
+ "name": "userSettings_userId_user_id_fk",
+ "tableFrom": "userSettings",
+ "tableTo": "user",
+ "columnsFrom": [
+ "userId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "user": {
+ "name": "user",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "email": {
+ "name": "email",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "emailVerified": {
+ "name": "emailVerified",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "image": {
+ "name": "image",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "password": {
+ "name": "password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "salt": {
+ "name": "salt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "''"
+ },
+ "role": {
+ "name": "role",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'user'"
+ }
+ },
+ "indexes": {
+ "user_email_unique": {
+ "name": "user_email_unique",
+ "columns": [
+ "email"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "verificationToken": {
+ "name": "verificationToken",
+ "columns": {
+ "identifier": {
+ "name": "identifier",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "token": {
+ "name": "token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "expires": {
+ "name": "expires",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "verificationToken_identifier_token_pk": {
+ "columns": [
+ "identifier",
+ "token"
+ ],
+ "name": "verificationToken_identifier_token_pk"
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "webhooks": {
+ "name": "webhooks",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "url": {
+ "name": "url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "userId": {
+ "name": "userId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "events": {
+ "name": "events",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "token": {
+ "name": "token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "webhooks_userId_idx": {
+ "name": "webhooks_userId_idx",
+ "columns": [
+ "userId"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "webhooks_userId_user_id_fk": {
+ "name": "webhooks_userId_user_id_fk",
+ "tableFrom": "webhooks",
+ "tableTo": "user",
+ "columnsFrom": [
+ "userId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ }
+ },
+ "views": {},
+ "enums": {},
+ "_meta": {
+ "schemas": {},
+ "tables": {},
+ "columns": {}
+ },
+ "internal": {
+ "indexes": {}
+ }
+} \ No newline at end of file
diff --git a/packages/db/drizzle/meta/0049_snapshot.json b/packages/db/drizzle/meta/0049_snapshot.json
new file mode 100644
index 00000000..4dc9f149
--- /dev/null
+++ b/packages/db/drizzle/meta/0049_snapshot.json
@@ -0,0 +1,2013 @@
+{
+ "version": "6",
+ "dialect": "sqlite",
+ "id": "98a6c743-e257-4126-a95f-40850e03ee4e",
+ "prevId": "d3ffc2ee-399e-4652-813d-652b56d649f6",
+ "tables": {
+ "account": {
+ "name": "account",
+ "columns": {
+ "userId": {
+ "name": "userId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "type": {
+ "name": "type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "provider": {
+ "name": "provider",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "providerAccountId": {
+ "name": "providerAccountId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "refresh_token": {
+ "name": "refresh_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "access_token": {
+ "name": "access_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "token_type": {
+ "name": "token_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "scope": {
+ "name": "scope",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "id_token": {
+ "name": "id_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "session_state": {
+ "name": "session_state",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "account_userId_user_id_fk": {
+ "name": "account_userId_user_id_fk",
+ "tableFrom": "account",
+ "tableTo": "user",
+ "columnsFrom": [
+ "userId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "account_provider_providerAccountId_pk": {
+ "columns": [
+ "provider",
+ "providerAccountId"
+ ],
+ "name": "account_provider_providerAccountId_pk"
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "apiKey": {
+ "name": "apiKey",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "keyId": {
+ "name": "keyId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "keyHash": {
+ "name": "keyHash",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "userId": {
+ "name": "userId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "apiKey_keyId_unique": {
+ "name": "apiKey_keyId_unique",
+ "columns": [
+ "keyId"
+ ],
+ "isUnique": true
+ },
+ "apiKey_name_userId_unique": {
+ "name": "apiKey_name_userId_unique",
+ "columns": [
+ "name",
+ "userId"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {
+ "apiKey_userId_user_id_fk": {
+ "name": "apiKey_userId_user_id_fk",
+ "tableFrom": "apiKey",
+ "tableTo": "user",
+ "columnsFrom": [
+ "userId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "assets": {
+ "name": "assets",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "assetType": {
+ "name": "assetType",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "size": {
+ "name": "size",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 0
+ },
+ "contentType": {
+ "name": "contentType",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "fileName": {
+ "name": "fileName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "bookmarkId": {
+ "name": "bookmarkId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "userId": {
+ "name": "userId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "assets_bookmarkId_idx": {
+ "name": "assets_bookmarkId_idx",
+ "columns": [
+ "bookmarkId"
+ ],
+ "isUnique": false
+ },
+ "assets_assetType_idx": {
+ "name": "assets_assetType_idx",
+ "columns": [
+ "assetType"
+ ],
+ "isUnique": false
+ },
+ "assets_userId_idx": {
+ "name": "assets_userId_idx",
+ "columns": [
+ "userId"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "assets_bookmarkId_bookmarks_id_fk": {
+ "name": "assets_bookmarkId_bookmarks_id_fk",
+ "tableFrom": "assets",
+ "tableTo": "bookmarks",
+ "columnsFrom": [
+ "bookmarkId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "assets_userId_user_id_fk": {
+ "name": "assets_userId_user_id_fk",
+ "tableFrom": "assets",
+ "tableTo": "user",
+ "columnsFrom": [
+ "userId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "bookmarkAssets": {
+ "name": "bookmarkAssets",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "assetType": {
+ "name": "assetType",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "assetId": {
+ "name": "assetId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "content": {
+ "name": "content",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "metadata": {
+ "name": "metadata",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "fileName": {
+ "name": "fileName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "sourceUrl": {
+ "name": "sourceUrl",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "bookmarkAssets_id_bookmarks_id_fk": {
+ "name": "bookmarkAssets_id_bookmarks_id_fk",
+ "tableFrom": "bookmarkAssets",
+ "tableTo": "bookmarks",
+ "columnsFrom": [
+ "id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "bookmarkLinks": {
+ "name": "bookmarkLinks",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "url": {
+ "name": "url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "author": {
+ "name": "author",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "publisher": {
+ "name": "publisher",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "datePublished": {
+ "name": "datePublished",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "dateModified": {
+ "name": "dateModified",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "imageUrl": {
+ "name": "imageUrl",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "favicon": {
+ "name": "favicon",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "content": {
+ "name": "content",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "htmlContent": {
+ "name": "htmlContent",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "crawledAt": {
+ "name": "crawledAt",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "crawlStatus": {
+ "name": "crawlStatus",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'pending'"
+ },
+ "crawlStatusCode": {
+ "name": "crawlStatusCode",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": 200
+ }
+ },
+ "indexes": {
+ "bookmarkLinks_url_idx": {
+ "name": "bookmarkLinks_url_idx",
+ "columns": [
+ "url"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "bookmarkLinks_id_bookmarks_id_fk": {
+ "name": "bookmarkLinks_id_bookmarks_id_fk",
+ "tableFrom": "bookmarkLinks",
+ "tableTo": "bookmarks",
+ "columnsFrom": [
+ "id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "bookmarkLists": {
+ "name": "bookmarkLists",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "icon": {
+ "name": "icon",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "userId": {
+ "name": "userId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "type": {
+ "name": "type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "query": {
+ "name": "query",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "parentId": {
+ "name": "parentId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "rssToken": {
+ "name": "rssToken",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "bookmarkLists_userId_idx": {
+ "name": "bookmarkLists_userId_idx",
+ "columns": [
+ "userId"
+ ],
+ "isUnique": false
+ },
+ "bookmarkLists_userId_id_idx": {
+ "name": "bookmarkLists_userId_id_idx",
+ "columns": [
+ "userId",
+ "id"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {
+ "bookmarkLists_userId_user_id_fk": {
+ "name": "bookmarkLists_userId_user_id_fk",
+ "tableFrom": "bookmarkLists",
+ "tableTo": "user",
+ "columnsFrom": [
+ "userId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "bookmarkLists_parentId_bookmarkLists_id_fk": {
+ "name": "bookmarkLists_parentId_bookmarkLists_id_fk",
+ "tableFrom": "bookmarkLists",
+ "tableTo": "bookmarkLists",
+ "columnsFrom": [
+ "parentId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "bookmarkTags": {
+ "name": "bookmarkTags",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "userId": {
+ "name": "userId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "bookmarkTags_name_idx": {
+ "name": "bookmarkTags_name_idx",
+ "columns": [
+ "name"
+ ],
+ "isUnique": false
+ },
+ "bookmarkTags_userId_idx": {
+ "name": "bookmarkTags_userId_idx",
+ "columns": [
+ "userId"
+ ],
+ "isUnique": false
+ },
+ "bookmarkTags_userId_name_unique": {
+ "name": "bookmarkTags_userId_name_unique",
+ "columns": [
+ "userId",
+ "name"
+ ],
+ "isUnique": true
+ },
+ "bookmarkTags_userId_id_idx": {
+ "name": "bookmarkTags_userId_id_idx",
+ "columns": [
+ "userId",
+ "id"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {
+ "bookmarkTags_userId_user_id_fk": {
+ "name": "bookmarkTags_userId_user_id_fk",
+ "tableFrom": "bookmarkTags",
+ "tableTo": "user",
+ "columnsFrom": [
+ "userId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "bookmarkTexts": {
+ "name": "bookmarkTexts",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "text": {
+ "name": "text",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "sourceUrl": {
+ "name": "sourceUrl",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "bookmarkTexts_id_bookmarks_id_fk": {
+ "name": "bookmarkTexts_id_bookmarks_id_fk",
+ "tableFrom": "bookmarkTexts",
+ "tableTo": "bookmarks",
+ "columnsFrom": [
+ "id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "bookmarks": {
+ "name": "bookmarks",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "modifiedAt": {
+ "name": "modifiedAt",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "archived": {
+ "name": "archived",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": false
+ },
+ "favourited": {
+ "name": "favourited",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": false
+ },
+ "userId": {
+ "name": "userId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "taggingStatus": {
+ "name": "taggingStatus",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'pending'"
+ },
+ "summarizationStatus": {
+ "name": "summarizationStatus",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'pending'"
+ },
+ "summary": {
+ "name": "summary",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "note": {
+ "name": "note",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "type": {
+ "name": "type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "bookmarks_userId_idx": {
+ "name": "bookmarks_userId_idx",
+ "columns": [
+ "userId"
+ ],
+ "isUnique": false
+ },
+ "bookmarks_archived_idx": {
+ "name": "bookmarks_archived_idx",
+ "columns": [
+ "archived"
+ ],
+ "isUnique": false
+ },
+ "bookmarks_favourited_idx": {
+ "name": "bookmarks_favourited_idx",
+ "columns": [
+ "favourited"
+ ],
+ "isUnique": false
+ },
+ "bookmarks_createdAt_idx": {
+ "name": "bookmarks_createdAt_idx",
+ "columns": [
+ "createdAt"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "bookmarks_userId_user_id_fk": {
+ "name": "bookmarks_userId_user_id_fk",
+ "tableFrom": "bookmarks",
+ "tableTo": "user",
+ "columnsFrom": [
+ "userId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "bookmarksInLists": {
+ "name": "bookmarksInLists",
+ "columns": {
+ "bookmarkId": {
+ "name": "bookmarkId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "listId": {
+ "name": "listId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "addedAt": {
+ "name": "addedAt",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "bookmarksInLists_bookmarkId_idx": {
+ "name": "bookmarksInLists_bookmarkId_idx",
+ "columns": [
+ "bookmarkId"
+ ],
+ "isUnique": false
+ },
+ "bookmarksInLists_listId_idx": {
+ "name": "bookmarksInLists_listId_idx",
+ "columns": [
+ "listId"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "bookmarksInLists_bookmarkId_bookmarks_id_fk": {
+ "name": "bookmarksInLists_bookmarkId_bookmarks_id_fk",
+ "tableFrom": "bookmarksInLists",
+ "tableTo": "bookmarks",
+ "columnsFrom": [
+ "bookmarkId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "bookmarksInLists_listId_bookmarkLists_id_fk": {
+ "name": "bookmarksInLists_listId_bookmarkLists_id_fk",
+ "tableFrom": "bookmarksInLists",
+ "tableTo": "bookmarkLists",
+ "columnsFrom": [
+ "listId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "bookmarksInLists_bookmarkId_listId_pk": {
+ "columns": [
+ "bookmarkId",
+ "listId"
+ ],
+ "name": "bookmarksInLists_bookmarkId_listId_pk"
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "config": {
+ "name": "config",
+ "columns": {
+ "key": {
+ "name": "key",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "value": {
+ "name": "value",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "customPrompts": {
+ "name": "customPrompts",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "text": {
+ "name": "text",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "appliesTo": {
+ "name": "appliesTo",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "userId": {
+ "name": "userId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "customPrompts_userId_idx": {
+ "name": "customPrompts_userId_idx",
+ "columns": [
+ "userId"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "customPrompts_userId_user_id_fk": {
+ "name": "customPrompts_userId_user_id_fk",
+ "tableFrom": "customPrompts",
+ "tableTo": "user",
+ "columnsFrom": [
+ "userId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "highlights": {
+ "name": "highlights",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "bookmarkId": {
+ "name": "bookmarkId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "userId": {
+ "name": "userId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "startOffset": {
+ "name": "startOffset",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "endOffset": {
+ "name": "endOffset",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "color": {
+ "name": "color",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'yellow'"
+ },
+ "text": {
+ "name": "text",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "note": {
+ "name": "note",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "highlights_bookmarkId_idx": {
+ "name": "highlights_bookmarkId_idx",
+ "columns": [
+ "bookmarkId"
+ ],
+ "isUnique": false
+ },
+ "highlights_userId_idx": {
+ "name": "highlights_userId_idx",
+ "columns": [
+ "userId"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "highlights_bookmarkId_bookmarks_id_fk": {
+ "name": "highlights_bookmarkId_bookmarks_id_fk",
+ "tableFrom": "highlights",
+ "tableTo": "bookmarks",
+ "columnsFrom": [
+ "bookmarkId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "highlights_userId_user_id_fk": {
+ "name": "highlights_userId_user_id_fk",
+ "tableFrom": "highlights",
+ "tableTo": "user",
+ "columnsFrom": [
+ "userId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "rssFeedImports": {
+ "name": "rssFeedImports",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "entryId": {
+ "name": "entryId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "rssFeedId": {
+ "name": "rssFeedId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "bookmarkId": {
+ "name": "bookmarkId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "rssFeedImports_feedIdIdx_idx": {
+ "name": "rssFeedImports_feedIdIdx_idx",
+ "columns": [
+ "rssFeedId"
+ ],
+ "isUnique": false
+ },
+ "rssFeedImports_entryIdIdx_idx": {
+ "name": "rssFeedImports_entryIdIdx_idx",
+ "columns": [
+ "entryId"
+ ],
+ "isUnique": false
+ },
+ "rssFeedImports_rssFeedId_entryId_unique": {
+ "name": "rssFeedImports_rssFeedId_entryId_unique",
+ "columns": [
+ "rssFeedId",
+ "entryId"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {
+ "rssFeedImports_rssFeedId_rssFeeds_id_fk": {
+ "name": "rssFeedImports_rssFeedId_rssFeeds_id_fk",
+ "tableFrom": "rssFeedImports",
+ "tableTo": "rssFeeds",
+ "columnsFrom": [
+ "rssFeedId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "rssFeedImports_bookmarkId_bookmarks_id_fk": {
+ "name": "rssFeedImports_bookmarkId_bookmarks_id_fk",
+ "tableFrom": "rssFeedImports",
+ "tableTo": "bookmarks",
+ "columnsFrom": [
+ "bookmarkId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "rssFeeds": {
+ "name": "rssFeeds",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "url": {
+ "name": "url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": true
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "lastFetchedAt": {
+ "name": "lastFetchedAt",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "lastFetchedStatus": {
+ "name": "lastFetchedStatus",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'pending'"
+ },
+ "userId": {
+ "name": "userId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "rssFeeds_userId_idx": {
+ "name": "rssFeeds_userId_idx",
+ "columns": [
+ "userId"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "rssFeeds_userId_user_id_fk": {
+ "name": "rssFeeds_userId_user_id_fk",
+ "tableFrom": "rssFeeds",
+ "tableTo": "user",
+ "columnsFrom": [
+ "userId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "ruleEngineActions": {
+ "name": "ruleEngineActions",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "userId": {
+ "name": "userId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "ruleId": {
+ "name": "ruleId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "action": {
+ "name": "action",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "listId": {
+ "name": "listId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "tagId": {
+ "name": "tagId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "ruleEngineActions_userId_idx": {
+ "name": "ruleEngineActions_userId_idx",
+ "columns": [
+ "userId"
+ ],
+ "isUnique": false
+ },
+ "ruleEngineActions_ruleId_idx": {
+ "name": "ruleEngineActions_ruleId_idx",
+ "columns": [
+ "ruleId"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "ruleEngineActions_userId_user_id_fk": {
+ "name": "ruleEngineActions_userId_user_id_fk",
+ "tableFrom": "ruleEngineActions",
+ "tableTo": "user",
+ "columnsFrom": [
+ "userId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "ruleEngineActions_ruleId_ruleEngineRules_id_fk": {
+ "name": "ruleEngineActions_ruleId_ruleEngineRules_id_fk",
+ "tableFrom": "ruleEngineActions",
+ "tableTo": "ruleEngineRules",
+ "columnsFrom": [
+ "ruleId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "ruleEngineActions_userId_tagId_fk": {
+ "name": "ruleEngineActions_userId_tagId_fk",
+ "tableFrom": "ruleEngineActions",
+ "tableTo": "bookmarkTags",
+ "columnsFrom": [
+ "userId",
+ "tagId"
+ ],
+ "columnsTo": [
+ "userId",
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "ruleEngineActions_userId_listId_fk": {
+ "name": "ruleEngineActions_userId_listId_fk",
+ "tableFrom": "ruleEngineActions",
+ "tableTo": "bookmarkLists",
+ "columnsFrom": [
+ "userId",
+ "listId"
+ ],
+ "columnsTo": [
+ "userId",
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "ruleEngineRules": {
+ "name": "ruleEngineRules",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "event": {
+ "name": "event",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "condition": {
+ "name": "condition",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "userId": {
+ "name": "userId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "listId": {
+ "name": "listId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "tagId": {
+ "name": "tagId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "ruleEngine_userId_idx": {
+ "name": "ruleEngine_userId_idx",
+ "columns": [
+ "userId"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "ruleEngineRules_userId_user_id_fk": {
+ "name": "ruleEngineRules_userId_user_id_fk",
+ "tableFrom": "ruleEngineRules",
+ "tableTo": "user",
+ "columnsFrom": [
+ "userId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "ruleEngineRules_userId_tagId_fk": {
+ "name": "ruleEngineRules_userId_tagId_fk",
+ "tableFrom": "ruleEngineRules",
+ "tableTo": "bookmarkTags",
+ "columnsFrom": [
+ "userId",
+ "tagId"
+ ],
+ "columnsTo": [
+ "userId",
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "ruleEngineRules_userId_listId_fk": {
+ "name": "ruleEngineRules_userId_listId_fk",
+ "tableFrom": "ruleEngineRules",
+ "tableTo": "bookmarkLists",
+ "columnsFrom": [
+ "userId",
+ "listId"
+ ],
+ "columnsTo": [
+ "userId",
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "session": {
+ "name": "session",
+ "columns": {
+ "sessionToken": {
+ "name": "sessionToken",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "userId": {
+ "name": "userId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "expires": {
+ "name": "expires",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "session_userId_user_id_fk": {
+ "name": "session_userId_user_id_fk",
+ "tableFrom": "session",
+ "tableTo": "user",
+ "columnsFrom": [
+ "userId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "tagsOnBookmarks": {
+ "name": "tagsOnBookmarks",
+ "columns": {
+ "bookmarkId": {
+ "name": "bookmarkId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "tagId": {
+ "name": "tagId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "attachedAt": {
+ "name": "attachedAt",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "attachedBy": {
+ "name": "attachedBy",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "tagsOnBookmarks_tagId_idx": {
+ "name": "tagsOnBookmarks_tagId_idx",
+ "columns": [
+ "tagId"
+ ],
+ "isUnique": false
+ },
+ "tagsOnBookmarks_bookmarkId_idx": {
+ "name": "tagsOnBookmarks_bookmarkId_idx",
+ "columns": [
+ "bookmarkId"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "tagsOnBookmarks_bookmarkId_bookmarks_id_fk": {
+ "name": "tagsOnBookmarks_bookmarkId_bookmarks_id_fk",
+ "tableFrom": "tagsOnBookmarks",
+ "tableTo": "bookmarks",
+ "columnsFrom": [
+ "bookmarkId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "tagsOnBookmarks_tagId_bookmarkTags_id_fk": {
+ "name": "tagsOnBookmarks_tagId_bookmarkTags_id_fk",
+ "tableFrom": "tagsOnBookmarks",
+ "tableTo": "bookmarkTags",
+ "columnsFrom": [
+ "tagId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "tagsOnBookmarks_bookmarkId_tagId_pk": {
+ "columns": [
+ "bookmarkId",
+ "tagId"
+ ],
+ "name": "tagsOnBookmarks_bookmarkId_tagId_pk"
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "userSettings": {
+ "name": "userSettings",
+ "columns": {
+ "userId": {
+ "name": "userId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "bookmarkClickAction": {
+ "name": "bookmarkClickAction",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'open_original_link'"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "userSettings_userId_user_id_fk": {
+ "name": "userSettings_userId_user_id_fk",
+ "tableFrom": "userSettings",
+ "tableTo": "user",
+ "columnsFrom": [
+ "userId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "user": {
+ "name": "user",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "email": {
+ "name": "email",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "emailVerified": {
+ "name": "emailVerified",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "image": {
+ "name": "image",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "password": {
+ "name": "password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "salt": {
+ "name": "salt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "''"
+ },
+ "role": {
+ "name": "role",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'user'"
+ }
+ },
+ "indexes": {
+ "user_email_unique": {
+ "name": "user_email_unique",
+ "columns": [
+ "email"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "verificationToken": {
+ "name": "verificationToken",
+ "columns": {
+ "identifier": {
+ "name": "identifier",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "token": {
+ "name": "token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "expires": {
+ "name": "expires",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "verificationToken_identifier_token_pk": {
+ "columns": [
+ "identifier",
+ "token"
+ ],
+ "name": "verificationToken_identifier_token_pk"
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "webhooks": {
+ "name": "webhooks",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "url": {
+ "name": "url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "userId": {
+ "name": "userId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "events": {
+ "name": "events",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "token": {
+ "name": "token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "webhooks_userId_idx": {
+ "name": "webhooks_userId_idx",
+ "columns": [
+ "userId"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "webhooks_userId_user_id_fk": {
+ "name": "webhooks_userId_user_id_fk",
+ "tableFrom": "webhooks",
+ "tableTo": "user",
+ "columnsFrom": [
+ "userId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ }
+ },
+ "views": {},
+ "enums": {},
+ "_meta": {
+ "schemas": {},
+ "tables": {},
+ "columns": {}
+ },
+ "internal": {
+ "indexes": {}
+ }
+} \ No newline at end of file
diff --git a/packages/db/drizzle/meta/0050_snapshot.json b/packages/db/drizzle/meta/0050_snapshot.json
new file mode 100644
index 00000000..860b0f54
--- /dev/null
+++ b/packages/db/drizzle/meta/0050_snapshot.json
@@ -0,0 +1,2021 @@
+{
+ "version": "6",
+ "dialect": "sqlite",
+ "id": "a92cdc19-e420-4f05-bfac-9fbbb2f5b8a3",
+ "prevId": "98a6c743-e257-4126-a95f-40850e03ee4e",
+ "tables": {
+ "account": {
+ "name": "account",
+ "columns": {
+ "userId": {
+ "name": "userId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "type": {
+ "name": "type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "provider": {
+ "name": "provider",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "providerAccountId": {
+ "name": "providerAccountId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "refresh_token": {
+ "name": "refresh_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "access_token": {
+ "name": "access_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "token_type": {
+ "name": "token_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "scope": {
+ "name": "scope",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "id_token": {
+ "name": "id_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "session_state": {
+ "name": "session_state",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "account_userId_user_id_fk": {
+ "name": "account_userId_user_id_fk",
+ "tableFrom": "account",
+ "tableTo": "user",
+ "columnsFrom": [
+ "userId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "account_provider_providerAccountId_pk": {
+ "columns": [
+ "provider",
+ "providerAccountId"
+ ],
+ "name": "account_provider_providerAccountId_pk"
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "apiKey": {
+ "name": "apiKey",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "keyId": {
+ "name": "keyId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "keyHash": {
+ "name": "keyHash",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "userId": {
+ "name": "userId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "apiKey_keyId_unique": {
+ "name": "apiKey_keyId_unique",
+ "columns": [
+ "keyId"
+ ],
+ "isUnique": true
+ },
+ "apiKey_name_userId_unique": {
+ "name": "apiKey_name_userId_unique",
+ "columns": [
+ "name",
+ "userId"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {
+ "apiKey_userId_user_id_fk": {
+ "name": "apiKey_userId_user_id_fk",
+ "tableFrom": "apiKey",
+ "tableTo": "user",
+ "columnsFrom": [
+ "userId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "assets": {
+ "name": "assets",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "assetType": {
+ "name": "assetType",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "size": {
+ "name": "size",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 0
+ },
+ "contentType": {
+ "name": "contentType",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "fileName": {
+ "name": "fileName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "bookmarkId": {
+ "name": "bookmarkId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "userId": {
+ "name": "userId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "assets_bookmarkId_idx": {
+ "name": "assets_bookmarkId_idx",
+ "columns": [
+ "bookmarkId"
+ ],
+ "isUnique": false
+ },
+ "assets_assetType_idx": {
+ "name": "assets_assetType_idx",
+ "columns": [
+ "assetType"
+ ],
+ "isUnique": false
+ },
+ "assets_userId_idx": {
+ "name": "assets_userId_idx",
+ "columns": [
+ "userId"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "assets_bookmarkId_bookmarks_id_fk": {
+ "name": "assets_bookmarkId_bookmarks_id_fk",
+ "tableFrom": "assets",
+ "tableTo": "bookmarks",
+ "columnsFrom": [
+ "bookmarkId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "assets_userId_user_id_fk": {
+ "name": "assets_userId_user_id_fk",
+ "tableFrom": "assets",
+ "tableTo": "user",
+ "columnsFrom": [
+ "userId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "bookmarkAssets": {
+ "name": "bookmarkAssets",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "assetType": {
+ "name": "assetType",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "assetId": {
+ "name": "assetId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "content": {
+ "name": "content",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "metadata": {
+ "name": "metadata",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "fileName": {
+ "name": "fileName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "sourceUrl": {
+ "name": "sourceUrl",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "bookmarkAssets_id_bookmarks_id_fk": {
+ "name": "bookmarkAssets_id_bookmarks_id_fk",
+ "tableFrom": "bookmarkAssets",
+ "tableTo": "bookmarks",
+ "columnsFrom": [
+ "id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "bookmarkLinks": {
+ "name": "bookmarkLinks",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "url": {
+ "name": "url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "author": {
+ "name": "author",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "publisher": {
+ "name": "publisher",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "datePublished": {
+ "name": "datePublished",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "dateModified": {
+ "name": "dateModified",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "imageUrl": {
+ "name": "imageUrl",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "favicon": {
+ "name": "favicon",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "content": {
+ "name": "content",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "htmlContent": {
+ "name": "htmlContent",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "crawledAt": {
+ "name": "crawledAt",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "crawlStatus": {
+ "name": "crawlStatus",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'pending'"
+ },
+ "crawlStatusCode": {
+ "name": "crawlStatusCode",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": 200
+ }
+ },
+ "indexes": {
+ "bookmarkLinks_url_idx": {
+ "name": "bookmarkLinks_url_idx",
+ "columns": [
+ "url"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "bookmarkLinks_id_bookmarks_id_fk": {
+ "name": "bookmarkLinks_id_bookmarks_id_fk",
+ "tableFrom": "bookmarkLinks",
+ "tableTo": "bookmarks",
+ "columnsFrom": [
+ "id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "bookmarkLists": {
+ "name": "bookmarkLists",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "icon": {
+ "name": "icon",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "userId": {
+ "name": "userId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "type": {
+ "name": "type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "query": {
+ "name": "query",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "parentId": {
+ "name": "parentId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "rssToken": {
+ "name": "rssToken",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "bookmarkLists_userId_idx": {
+ "name": "bookmarkLists_userId_idx",
+ "columns": [
+ "userId"
+ ],
+ "isUnique": false
+ },
+ "bookmarkLists_userId_id_idx": {
+ "name": "bookmarkLists_userId_id_idx",
+ "columns": [
+ "userId",
+ "id"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {
+ "bookmarkLists_userId_user_id_fk": {
+ "name": "bookmarkLists_userId_user_id_fk",
+ "tableFrom": "bookmarkLists",
+ "tableTo": "user",
+ "columnsFrom": [
+ "userId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "bookmarkLists_parentId_bookmarkLists_id_fk": {
+ "name": "bookmarkLists_parentId_bookmarkLists_id_fk",
+ "tableFrom": "bookmarkLists",
+ "tableTo": "bookmarkLists",
+ "columnsFrom": [
+ "parentId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "bookmarkTags": {
+ "name": "bookmarkTags",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "userId": {
+ "name": "userId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "bookmarkTags_name_idx": {
+ "name": "bookmarkTags_name_idx",
+ "columns": [
+ "name"
+ ],
+ "isUnique": false
+ },
+ "bookmarkTags_userId_idx": {
+ "name": "bookmarkTags_userId_idx",
+ "columns": [
+ "userId"
+ ],
+ "isUnique": false
+ },
+ "bookmarkTags_userId_name_unique": {
+ "name": "bookmarkTags_userId_name_unique",
+ "columns": [
+ "userId",
+ "name"
+ ],
+ "isUnique": true
+ },
+ "bookmarkTags_userId_id_idx": {
+ "name": "bookmarkTags_userId_id_idx",
+ "columns": [
+ "userId",
+ "id"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {
+ "bookmarkTags_userId_user_id_fk": {
+ "name": "bookmarkTags_userId_user_id_fk",
+ "tableFrom": "bookmarkTags",
+ "tableTo": "user",
+ "columnsFrom": [
+ "userId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "bookmarkTexts": {
+ "name": "bookmarkTexts",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "text": {
+ "name": "text",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "sourceUrl": {
+ "name": "sourceUrl",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "bookmarkTexts_id_bookmarks_id_fk": {
+ "name": "bookmarkTexts_id_bookmarks_id_fk",
+ "tableFrom": "bookmarkTexts",
+ "tableTo": "bookmarks",
+ "columnsFrom": [
+ "id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "bookmarks": {
+ "name": "bookmarks",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "modifiedAt": {
+ "name": "modifiedAt",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "archived": {
+ "name": "archived",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": false
+ },
+ "favourited": {
+ "name": "favourited",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": false
+ },
+ "userId": {
+ "name": "userId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "taggingStatus": {
+ "name": "taggingStatus",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'pending'"
+ },
+ "summarizationStatus": {
+ "name": "summarizationStatus",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'pending'"
+ },
+ "summary": {
+ "name": "summary",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "note": {
+ "name": "note",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "type": {
+ "name": "type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "bookmarks_userId_idx": {
+ "name": "bookmarks_userId_idx",
+ "columns": [
+ "userId"
+ ],
+ "isUnique": false
+ },
+ "bookmarks_archived_idx": {
+ "name": "bookmarks_archived_idx",
+ "columns": [
+ "archived"
+ ],
+ "isUnique": false
+ },
+ "bookmarks_favourited_idx": {
+ "name": "bookmarks_favourited_idx",
+ "columns": [
+ "favourited"
+ ],
+ "isUnique": false
+ },
+ "bookmarks_createdAt_idx": {
+ "name": "bookmarks_createdAt_idx",
+ "columns": [
+ "createdAt"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "bookmarks_userId_user_id_fk": {
+ "name": "bookmarks_userId_user_id_fk",
+ "tableFrom": "bookmarks",
+ "tableTo": "user",
+ "columnsFrom": [
+ "userId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "bookmarksInLists": {
+ "name": "bookmarksInLists",
+ "columns": {
+ "bookmarkId": {
+ "name": "bookmarkId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "listId": {
+ "name": "listId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "addedAt": {
+ "name": "addedAt",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "bookmarksInLists_bookmarkId_idx": {
+ "name": "bookmarksInLists_bookmarkId_idx",
+ "columns": [
+ "bookmarkId"
+ ],
+ "isUnique": false
+ },
+ "bookmarksInLists_listId_idx": {
+ "name": "bookmarksInLists_listId_idx",
+ "columns": [
+ "listId"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "bookmarksInLists_bookmarkId_bookmarks_id_fk": {
+ "name": "bookmarksInLists_bookmarkId_bookmarks_id_fk",
+ "tableFrom": "bookmarksInLists",
+ "tableTo": "bookmarks",
+ "columnsFrom": [
+ "bookmarkId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "bookmarksInLists_listId_bookmarkLists_id_fk": {
+ "name": "bookmarksInLists_listId_bookmarkLists_id_fk",
+ "tableFrom": "bookmarksInLists",
+ "tableTo": "bookmarkLists",
+ "columnsFrom": [
+ "listId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "bookmarksInLists_bookmarkId_listId_pk": {
+ "columns": [
+ "bookmarkId",
+ "listId"
+ ],
+ "name": "bookmarksInLists_bookmarkId_listId_pk"
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "config": {
+ "name": "config",
+ "columns": {
+ "key": {
+ "name": "key",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "value": {
+ "name": "value",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "customPrompts": {
+ "name": "customPrompts",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "text": {
+ "name": "text",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "appliesTo": {
+ "name": "appliesTo",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "userId": {
+ "name": "userId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "customPrompts_userId_idx": {
+ "name": "customPrompts_userId_idx",
+ "columns": [
+ "userId"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "customPrompts_userId_user_id_fk": {
+ "name": "customPrompts_userId_user_id_fk",
+ "tableFrom": "customPrompts",
+ "tableTo": "user",
+ "columnsFrom": [
+ "userId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "highlights": {
+ "name": "highlights",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "bookmarkId": {
+ "name": "bookmarkId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "userId": {
+ "name": "userId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "startOffset": {
+ "name": "startOffset",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "endOffset": {
+ "name": "endOffset",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "color": {
+ "name": "color",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'yellow'"
+ },
+ "text": {
+ "name": "text",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "note": {
+ "name": "note",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "highlights_bookmarkId_idx": {
+ "name": "highlights_bookmarkId_idx",
+ "columns": [
+ "bookmarkId"
+ ],
+ "isUnique": false
+ },
+ "highlights_userId_idx": {
+ "name": "highlights_userId_idx",
+ "columns": [
+ "userId"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "highlights_bookmarkId_bookmarks_id_fk": {
+ "name": "highlights_bookmarkId_bookmarks_id_fk",
+ "tableFrom": "highlights",
+ "tableTo": "bookmarks",
+ "columnsFrom": [
+ "bookmarkId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "highlights_userId_user_id_fk": {
+ "name": "highlights_userId_user_id_fk",
+ "tableFrom": "highlights",
+ "tableTo": "user",
+ "columnsFrom": [
+ "userId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "rssFeedImports": {
+ "name": "rssFeedImports",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "entryId": {
+ "name": "entryId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "rssFeedId": {
+ "name": "rssFeedId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "bookmarkId": {
+ "name": "bookmarkId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "rssFeedImports_feedIdIdx_idx": {
+ "name": "rssFeedImports_feedIdIdx_idx",
+ "columns": [
+ "rssFeedId"
+ ],
+ "isUnique": false
+ },
+ "rssFeedImports_entryIdIdx_idx": {
+ "name": "rssFeedImports_entryIdIdx_idx",
+ "columns": [
+ "entryId"
+ ],
+ "isUnique": false
+ },
+ "rssFeedImports_rssFeedId_entryId_unique": {
+ "name": "rssFeedImports_rssFeedId_entryId_unique",
+ "columns": [
+ "rssFeedId",
+ "entryId"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {
+ "rssFeedImports_rssFeedId_rssFeeds_id_fk": {
+ "name": "rssFeedImports_rssFeedId_rssFeeds_id_fk",
+ "tableFrom": "rssFeedImports",
+ "tableTo": "rssFeeds",
+ "columnsFrom": [
+ "rssFeedId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "rssFeedImports_bookmarkId_bookmarks_id_fk": {
+ "name": "rssFeedImports_bookmarkId_bookmarks_id_fk",
+ "tableFrom": "rssFeedImports",
+ "tableTo": "bookmarks",
+ "columnsFrom": [
+ "bookmarkId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "rssFeeds": {
+ "name": "rssFeeds",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "url": {
+ "name": "url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": true
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "lastFetchedAt": {
+ "name": "lastFetchedAt",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "lastFetchedStatus": {
+ "name": "lastFetchedStatus",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'pending'"
+ },
+ "userId": {
+ "name": "userId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "rssFeeds_userId_idx": {
+ "name": "rssFeeds_userId_idx",
+ "columns": [
+ "userId"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "rssFeeds_userId_user_id_fk": {
+ "name": "rssFeeds_userId_user_id_fk",
+ "tableFrom": "rssFeeds",
+ "tableTo": "user",
+ "columnsFrom": [
+ "userId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "ruleEngineActions": {
+ "name": "ruleEngineActions",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "userId": {
+ "name": "userId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "ruleId": {
+ "name": "ruleId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "action": {
+ "name": "action",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "listId": {
+ "name": "listId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "tagId": {
+ "name": "tagId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "ruleEngineActions_userId_idx": {
+ "name": "ruleEngineActions_userId_idx",
+ "columns": [
+ "userId"
+ ],
+ "isUnique": false
+ },
+ "ruleEngineActions_ruleId_idx": {
+ "name": "ruleEngineActions_ruleId_idx",
+ "columns": [
+ "ruleId"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "ruleEngineActions_userId_user_id_fk": {
+ "name": "ruleEngineActions_userId_user_id_fk",
+ "tableFrom": "ruleEngineActions",
+ "tableTo": "user",
+ "columnsFrom": [
+ "userId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "ruleEngineActions_ruleId_ruleEngineRules_id_fk": {
+ "name": "ruleEngineActions_ruleId_ruleEngineRules_id_fk",
+ "tableFrom": "ruleEngineActions",
+ "tableTo": "ruleEngineRules",
+ "columnsFrom": [
+ "ruleId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "ruleEngineActions_userId_tagId_fk": {
+ "name": "ruleEngineActions_userId_tagId_fk",
+ "tableFrom": "ruleEngineActions",
+ "tableTo": "bookmarkTags",
+ "columnsFrom": [
+ "userId",
+ "tagId"
+ ],
+ "columnsTo": [
+ "userId",
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "ruleEngineActions_userId_listId_fk": {
+ "name": "ruleEngineActions_userId_listId_fk",
+ "tableFrom": "ruleEngineActions",
+ "tableTo": "bookmarkLists",
+ "columnsFrom": [
+ "userId",
+ "listId"
+ ],
+ "columnsTo": [
+ "userId",
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "ruleEngineRules": {
+ "name": "ruleEngineRules",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "event": {
+ "name": "event",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "condition": {
+ "name": "condition",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "userId": {
+ "name": "userId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "listId": {
+ "name": "listId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "tagId": {
+ "name": "tagId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "ruleEngine_userId_idx": {
+ "name": "ruleEngine_userId_idx",
+ "columns": [
+ "userId"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "ruleEngineRules_userId_user_id_fk": {
+ "name": "ruleEngineRules_userId_user_id_fk",
+ "tableFrom": "ruleEngineRules",
+ "tableTo": "user",
+ "columnsFrom": [
+ "userId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "ruleEngineRules_userId_tagId_fk": {
+ "name": "ruleEngineRules_userId_tagId_fk",
+ "tableFrom": "ruleEngineRules",
+ "tableTo": "bookmarkTags",
+ "columnsFrom": [
+ "userId",
+ "tagId"
+ ],
+ "columnsTo": [
+ "userId",
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "ruleEngineRules_userId_listId_fk": {
+ "name": "ruleEngineRules_userId_listId_fk",
+ "tableFrom": "ruleEngineRules",
+ "tableTo": "bookmarkLists",
+ "columnsFrom": [
+ "userId",
+ "listId"
+ ],
+ "columnsTo": [
+ "userId",
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "session": {
+ "name": "session",
+ "columns": {
+ "sessionToken": {
+ "name": "sessionToken",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "userId": {
+ "name": "userId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "expires": {
+ "name": "expires",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "session_userId_user_id_fk": {
+ "name": "session_userId_user_id_fk",
+ "tableFrom": "session",
+ "tableTo": "user",
+ "columnsFrom": [
+ "userId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "tagsOnBookmarks": {
+ "name": "tagsOnBookmarks",
+ "columns": {
+ "bookmarkId": {
+ "name": "bookmarkId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "tagId": {
+ "name": "tagId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "attachedAt": {
+ "name": "attachedAt",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "attachedBy": {
+ "name": "attachedBy",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "tagsOnBookmarks_tagId_idx": {
+ "name": "tagsOnBookmarks_tagId_idx",
+ "columns": [
+ "tagId"
+ ],
+ "isUnique": false
+ },
+ "tagsOnBookmarks_bookmarkId_idx": {
+ "name": "tagsOnBookmarks_bookmarkId_idx",
+ "columns": [
+ "bookmarkId"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "tagsOnBookmarks_bookmarkId_bookmarks_id_fk": {
+ "name": "tagsOnBookmarks_bookmarkId_bookmarks_id_fk",
+ "tableFrom": "tagsOnBookmarks",
+ "tableTo": "bookmarks",
+ "columnsFrom": [
+ "bookmarkId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "tagsOnBookmarks_tagId_bookmarkTags_id_fk": {
+ "name": "tagsOnBookmarks_tagId_bookmarkTags_id_fk",
+ "tableFrom": "tagsOnBookmarks",
+ "tableTo": "bookmarkTags",
+ "columnsFrom": [
+ "tagId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "tagsOnBookmarks_bookmarkId_tagId_pk": {
+ "columns": [
+ "bookmarkId",
+ "tagId"
+ ],
+ "name": "tagsOnBookmarks_bookmarkId_tagId_pk"
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "userSettings": {
+ "name": "userSettings",
+ "columns": {
+ "userId": {
+ "name": "userId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "bookmarkClickAction": {
+ "name": "bookmarkClickAction",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'open_original_link'"
+ },
+ "archiveDisplayBehaviour": {
+ "name": "archiveDisplayBehaviour",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'show'"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "userSettings_userId_user_id_fk": {
+ "name": "userSettings_userId_user_id_fk",
+ "tableFrom": "userSettings",
+ "tableTo": "user",
+ "columnsFrom": [
+ "userId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "user": {
+ "name": "user",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "email": {
+ "name": "email",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "emailVerified": {
+ "name": "emailVerified",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "image": {
+ "name": "image",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "password": {
+ "name": "password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "salt": {
+ "name": "salt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "''"
+ },
+ "role": {
+ "name": "role",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'user'"
+ }
+ },
+ "indexes": {
+ "user_email_unique": {
+ "name": "user_email_unique",
+ "columns": [
+ "email"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "verificationToken": {
+ "name": "verificationToken",
+ "columns": {
+ "identifier": {
+ "name": "identifier",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "token": {
+ "name": "token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "expires": {
+ "name": "expires",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "verificationToken_identifier_token_pk": {
+ "columns": [
+ "identifier",
+ "token"
+ ],
+ "name": "verificationToken_identifier_token_pk"
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "webhooks": {
+ "name": "webhooks",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "url": {
+ "name": "url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "userId": {
+ "name": "userId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "events": {
+ "name": "events",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "token": {
+ "name": "token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "webhooks_userId_idx": {
+ "name": "webhooks_userId_idx",
+ "columns": [
+ "userId"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "webhooks_userId_user_id_fk": {
+ "name": "webhooks_userId_user_id_fk",
+ "tableFrom": "webhooks",
+ "tableTo": "user",
+ "columnsFrom": [
+ "userId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ }
+ },
+ "views": {},
+ "enums": {},
+ "_meta": {
+ "schemas": {},
+ "tables": {},
+ "columns": {}
+ },
+ "internal": {
+ "indexes": {}
+ }
+} \ No newline at end of file
diff --git a/packages/db/drizzle/meta/0051_snapshot.json b/packages/db/drizzle/meta/0051_snapshot.json
new file mode 100644
index 00000000..6db03ecf
--- /dev/null
+++ b/packages/db/drizzle/meta/0051_snapshot.json
@@ -0,0 +1,2029 @@
+{
+ "version": "6",
+ "dialect": "sqlite",
+ "id": "5a549719-8f7d-49ff-91cc-5ad0f3b5c4ef",
+ "prevId": "a92cdc19-e420-4f05-bfac-9fbbb2f5b8a3",
+ "tables": {
+ "account": {
+ "name": "account",
+ "columns": {
+ "userId": {
+ "name": "userId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "type": {
+ "name": "type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "provider": {
+ "name": "provider",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "providerAccountId": {
+ "name": "providerAccountId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "refresh_token": {
+ "name": "refresh_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "access_token": {
+ "name": "access_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "token_type": {
+ "name": "token_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "scope": {
+ "name": "scope",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "id_token": {
+ "name": "id_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "session_state": {
+ "name": "session_state",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "account_userId_user_id_fk": {
+ "name": "account_userId_user_id_fk",
+ "tableFrom": "account",
+ "tableTo": "user",
+ "columnsFrom": [
+ "userId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "account_provider_providerAccountId_pk": {
+ "columns": [
+ "provider",
+ "providerAccountId"
+ ],
+ "name": "account_provider_providerAccountId_pk"
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "apiKey": {
+ "name": "apiKey",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "keyId": {
+ "name": "keyId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "keyHash": {
+ "name": "keyHash",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "userId": {
+ "name": "userId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "apiKey_keyId_unique": {
+ "name": "apiKey_keyId_unique",
+ "columns": [
+ "keyId"
+ ],
+ "isUnique": true
+ },
+ "apiKey_name_userId_unique": {
+ "name": "apiKey_name_userId_unique",
+ "columns": [
+ "name",
+ "userId"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {
+ "apiKey_userId_user_id_fk": {
+ "name": "apiKey_userId_user_id_fk",
+ "tableFrom": "apiKey",
+ "tableTo": "user",
+ "columnsFrom": [
+ "userId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "assets": {
+ "name": "assets",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "assetType": {
+ "name": "assetType",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "size": {
+ "name": "size",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 0
+ },
+ "contentType": {
+ "name": "contentType",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "fileName": {
+ "name": "fileName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "bookmarkId": {
+ "name": "bookmarkId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "userId": {
+ "name": "userId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "assets_bookmarkId_idx": {
+ "name": "assets_bookmarkId_idx",
+ "columns": [
+ "bookmarkId"
+ ],
+ "isUnique": false
+ },
+ "assets_assetType_idx": {
+ "name": "assets_assetType_idx",
+ "columns": [
+ "assetType"
+ ],
+ "isUnique": false
+ },
+ "assets_userId_idx": {
+ "name": "assets_userId_idx",
+ "columns": [
+ "userId"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "assets_bookmarkId_bookmarks_id_fk": {
+ "name": "assets_bookmarkId_bookmarks_id_fk",
+ "tableFrom": "assets",
+ "tableTo": "bookmarks",
+ "columnsFrom": [
+ "bookmarkId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "assets_userId_user_id_fk": {
+ "name": "assets_userId_user_id_fk",
+ "tableFrom": "assets",
+ "tableTo": "user",
+ "columnsFrom": [
+ "userId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "bookmarkAssets": {
+ "name": "bookmarkAssets",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "assetType": {
+ "name": "assetType",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "assetId": {
+ "name": "assetId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "content": {
+ "name": "content",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "metadata": {
+ "name": "metadata",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "fileName": {
+ "name": "fileName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "sourceUrl": {
+ "name": "sourceUrl",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "bookmarkAssets_id_bookmarks_id_fk": {
+ "name": "bookmarkAssets_id_bookmarks_id_fk",
+ "tableFrom": "bookmarkAssets",
+ "tableTo": "bookmarks",
+ "columnsFrom": [
+ "id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "bookmarkLinks": {
+ "name": "bookmarkLinks",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "url": {
+ "name": "url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "author": {
+ "name": "author",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "publisher": {
+ "name": "publisher",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "datePublished": {
+ "name": "datePublished",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "dateModified": {
+ "name": "dateModified",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "imageUrl": {
+ "name": "imageUrl",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "favicon": {
+ "name": "favicon",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "content": {
+ "name": "content",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "htmlContent": {
+ "name": "htmlContent",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "crawledAt": {
+ "name": "crawledAt",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "crawlStatus": {
+ "name": "crawlStatus",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'pending'"
+ },
+ "crawlStatusCode": {
+ "name": "crawlStatusCode",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": 200
+ }
+ },
+ "indexes": {
+ "bookmarkLinks_url_idx": {
+ "name": "bookmarkLinks_url_idx",
+ "columns": [
+ "url"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "bookmarkLinks_id_bookmarks_id_fk": {
+ "name": "bookmarkLinks_id_bookmarks_id_fk",
+ "tableFrom": "bookmarkLinks",
+ "tableTo": "bookmarks",
+ "columnsFrom": [
+ "id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "bookmarkLists": {
+ "name": "bookmarkLists",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "icon": {
+ "name": "icon",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "userId": {
+ "name": "userId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "type": {
+ "name": "type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "query": {
+ "name": "query",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "parentId": {
+ "name": "parentId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "rssToken": {
+ "name": "rssToken",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "public": {
+ "name": "public",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": false
+ }
+ },
+ "indexes": {
+ "bookmarkLists_userId_idx": {
+ "name": "bookmarkLists_userId_idx",
+ "columns": [
+ "userId"
+ ],
+ "isUnique": false
+ },
+ "bookmarkLists_userId_id_idx": {
+ "name": "bookmarkLists_userId_id_idx",
+ "columns": [
+ "userId",
+ "id"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {
+ "bookmarkLists_userId_user_id_fk": {
+ "name": "bookmarkLists_userId_user_id_fk",
+ "tableFrom": "bookmarkLists",
+ "tableTo": "user",
+ "columnsFrom": [
+ "userId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "bookmarkLists_parentId_bookmarkLists_id_fk": {
+ "name": "bookmarkLists_parentId_bookmarkLists_id_fk",
+ "tableFrom": "bookmarkLists",
+ "tableTo": "bookmarkLists",
+ "columnsFrom": [
+ "parentId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "bookmarkTags": {
+ "name": "bookmarkTags",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "userId": {
+ "name": "userId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "bookmarkTags_name_idx": {
+ "name": "bookmarkTags_name_idx",
+ "columns": [
+ "name"
+ ],
+ "isUnique": false
+ },
+ "bookmarkTags_userId_idx": {
+ "name": "bookmarkTags_userId_idx",
+ "columns": [
+ "userId"
+ ],
+ "isUnique": false
+ },
+ "bookmarkTags_userId_name_unique": {
+ "name": "bookmarkTags_userId_name_unique",
+ "columns": [
+ "userId",
+ "name"
+ ],
+ "isUnique": true
+ },
+ "bookmarkTags_userId_id_idx": {
+ "name": "bookmarkTags_userId_id_idx",
+ "columns": [
+ "userId",
+ "id"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {
+ "bookmarkTags_userId_user_id_fk": {
+ "name": "bookmarkTags_userId_user_id_fk",
+ "tableFrom": "bookmarkTags",
+ "tableTo": "user",
+ "columnsFrom": [
+ "userId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "bookmarkTexts": {
+ "name": "bookmarkTexts",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "text": {
+ "name": "text",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "sourceUrl": {
+ "name": "sourceUrl",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "bookmarkTexts_id_bookmarks_id_fk": {
+ "name": "bookmarkTexts_id_bookmarks_id_fk",
+ "tableFrom": "bookmarkTexts",
+ "tableTo": "bookmarks",
+ "columnsFrom": [
+ "id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "bookmarks": {
+ "name": "bookmarks",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "modifiedAt": {
+ "name": "modifiedAt",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "archived": {
+ "name": "archived",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": false
+ },
+ "favourited": {
+ "name": "favourited",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": false
+ },
+ "userId": {
+ "name": "userId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "taggingStatus": {
+ "name": "taggingStatus",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'pending'"
+ },
+ "summarizationStatus": {
+ "name": "summarizationStatus",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'pending'"
+ },
+ "summary": {
+ "name": "summary",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "note": {
+ "name": "note",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "type": {
+ "name": "type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "bookmarks_userId_idx": {
+ "name": "bookmarks_userId_idx",
+ "columns": [
+ "userId"
+ ],
+ "isUnique": false
+ },
+ "bookmarks_archived_idx": {
+ "name": "bookmarks_archived_idx",
+ "columns": [
+ "archived"
+ ],
+ "isUnique": false
+ },
+ "bookmarks_favourited_idx": {
+ "name": "bookmarks_favourited_idx",
+ "columns": [
+ "favourited"
+ ],
+ "isUnique": false
+ },
+ "bookmarks_createdAt_idx": {
+ "name": "bookmarks_createdAt_idx",
+ "columns": [
+ "createdAt"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "bookmarks_userId_user_id_fk": {
+ "name": "bookmarks_userId_user_id_fk",
+ "tableFrom": "bookmarks",
+ "tableTo": "user",
+ "columnsFrom": [
+ "userId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "bookmarksInLists": {
+ "name": "bookmarksInLists",
+ "columns": {
+ "bookmarkId": {
+ "name": "bookmarkId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "listId": {
+ "name": "listId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "addedAt": {
+ "name": "addedAt",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "bookmarksInLists_bookmarkId_idx": {
+ "name": "bookmarksInLists_bookmarkId_idx",
+ "columns": [
+ "bookmarkId"
+ ],
+ "isUnique": false
+ },
+ "bookmarksInLists_listId_idx": {
+ "name": "bookmarksInLists_listId_idx",
+ "columns": [
+ "listId"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "bookmarksInLists_bookmarkId_bookmarks_id_fk": {
+ "name": "bookmarksInLists_bookmarkId_bookmarks_id_fk",
+ "tableFrom": "bookmarksInLists",
+ "tableTo": "bookmarks",
+ "columnsFrom": [
+ "bookmarkId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "bookmarksInLists_listId_bookmarkLists_id_fk": {
+ "name": "bookmarksInLists_listId_bookmarkLists_id_fk",
+ "tableFrom": "bookmarksInLists",
+ "tableTo": "bookmarkLists",
+ "columnsFrom": [
+ "listId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "bookmarksInLists_bookmarkId_listId_pk": {
+ "columns": [
+ "bookmarkId",
+ "listId"
+ ],
+ "name": "bookmarksInLists_bookmarkId_listId_pk"
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "config": {
+ "name": "config",
+ "columns": {
+ "key": {
+ "name": "key",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "value": {
+ "name": "value",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "customPrompts": {
+ "name": "customPrompts",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "text": {
+ "name": "text",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "appliesTo": {
+ "name": "appliesTo",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "userId": {
+ "name": "userId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "customPrompts_userId_idx": {
+ "name": "customPrompts_userId_idx",
+ "columns": [
+ "userId"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "customPrompts_userId_user_id_fk": {
+ "name": "customPrompts_userId_user_id_fk",
+ "tableFrom": "customPrompts",
+ "tableTo": "user",
+ "columnsFrom": [
+ "userId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "highlights": {
+ "name": "highlights",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "bookmarkId": {
+ "name": "bookmarkId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "userId": {
+ "name": "userId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "startOffset": {
+ "name": "startOffset",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "endOffset": {
+ "name": "endOffset",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "color": {
+ "name": "color",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'yellow'"
+ },
+ "text": {
+ "name": "text",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "note": {
+ "name": "note",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "highlights_bookmarkId_idx": {
+ "name": "highlights_bookmarkId_idx",
+ "columns": [
+ "bookmarkId"
+ ],
+ "isUnique": false
+ },
+ "highlights_userId_idx": {
+ "name": "highlights_userId_idx",
+ "columns": [
+ "userId"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "highlights_bookmarkId_bookmarks_id_fk": {
+ "name": "highlights_bookmarkId_bookmarks_id_fk",
+ "tableFrom": "highlights",
+ "tableTo": "bookmarks",
+ "columnsFrom": [
+ "bookmarkId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "highlights_userId_user_id_fk": {
+ "name": "highlights_userId_user_id_fk",
+ "tableFrom": "highlights",
+ "tableTo": "user",
+ "columnsFrom": [
+ "userId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "rssFeedImports": {
+ "name": "rssFeedImports",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "entryId": {
+ "name": "entryId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "rssFeedId": {
+ "name": "rssFeedId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "bookmarkId": {
+ "name": "bookmarkId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "rssFeedImports_feedIdIdx_idx": {
+ "name": "rssFeedImports_feedIdIdx_idx",
+ "columns": [
+ "rssFeedId"
+ ],
+ "isUnique": false
+ },
+ "rssFeedImports_entryIdIdx_idx": {
+ "name": "rssFeedImports_entryIdIdx_idx",
+ "columns": [
+ "entryId"
+ ],
+ "isUnique": false
+ },
+ "rssFeedImports_rssFeedId_entryId_unique": {
+ "name": "rssFeedImports_rssFeedId_entryId_unique",
+ "columns": [
+ "rssFeedId",
+ "entryId"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {
+ "rssFeedImports_rssFeedId_rssFeeds_id_fk": {
+ "name": "rssFeedImports_rssFeedId_rssFeeds_id_fk",
+ "tableFrom": "rssFeedImports",
+ "tableTo": "rssFeeds",
+ "columnsFrom": [
+ "rssFeedId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "rssFeedImports_bookmarkId_bookmarks_id_fk": {
+ "name": "rssFeedImports_bookmarkId_bookmarks_id_fk",
+ "tableFrom": "rssFeedImports",
+ "tableTo": "bookmarks",
+ "columnsFrom": [
+ "bookmarkId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "rssFeeds": {
+ "name": "rssFeeds",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "url": {
+ "name": "url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": true
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "lastFetchedAt": {
+ "name": "lastFetchedAt",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "lastFetchedStatus": {
+ "name": "lastFetchedStatus",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'pending'"
+ },
+ "userId": {
+ "name": "userId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "rssFeeds_userId_idx": {
+ "name": "rssFeeds_userId_idx",
+ "columns": [
+ "userId"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "rssFeeds_userId_user_id_fk": {
+ "name": "rssFeeds_userId_user_id_fk",
+ "tableFrom": "rssFeeds",
+ "tableTo": "user",
+ "columnsFrom": [
+ "userId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "ruleEngineActions": {
+ "name": "ruleEngineActions",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "userId": {
+ "name": "userId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "ruleId": {
+ "name": "ruleId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "action": {
+ "name": "action",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "listId": {
+ "name": "listId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "tagId": {
+ "name": "tagId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "ruleEngineActions_userId_idx": {
+ "name": "ruleEngineActions_userId_idx",
+ "columns": [
+ "userId"
+ ],
+ "isUnique": false
+ },
+ "ruleEngineActions_ruleId_idx": {
+ "name": "ruleEngineActions_ruleId_idx",
+ "columns": [
+ "ruleId"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "ruleEngineActions_userId_user_id_fk": {
+ "name": "ruleEngineActions_userId_user_id_fk",
+ "tableFrom": "ruleEngineActions",
+ "tableTo": "user",
+ "columnsFrom": [
+ "userId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "ruleEngineActions_ruleId_ruleEngineRules_id_fk": {
+ "name": "ruleEngineActions_ruleId_ruleEngineRules_id_fk",
+ "tableFrom": "ruleEngineActions",
+ "tableTo": "ruleEngineRules",
+ "columnsFrom": [
+ "ruleId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "ruleEngineActions_userId_tagId_fk": {
+ "name": "ruleEngineActions_userId_tagId_fk",
+ "tableFrom": "ruleEngineActions",
+ "tableTo": "bookmarkTags",
+ "columnsFrom": [
+ "userId",
+ "tagId"
+ ],
+ "columnsTo": [
+ "userId",
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "ruleEngineActions_userId_listId_fk": {
+ "name": "ruleEngineActions_userId_listId_fk",
+ "tableFrom": "ruleEngineActions",
+ "tableTo": "bookmarkLists",
+ "columnsFrom": [
+ "userId",
+ "listId"
+ ],
+ "columnsTo": [
+ "userId",
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "ruleEngineRules": {
+ "name": "ruleEngineRules",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "event": {
+ "name": "event",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "condition": {
+ "name": "condition",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "userId": {
+ "name": "userId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "listId": {
+ "name": "listId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "tagId": {
+ "name": "tagId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "ruleEngine_userId_idx": {
+ "name": "ruleEngine_userId_idx",
+ "columns": [
+ "userId"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "ruleEngineRules_userId_user_id_fk": {
+ "name": "ruleEngineRules_userId_user_id_fk",
+ "tableFrom": "ruleEngineRules",
+ "tableTo": "user",
+ "columnsFrom": [
+ "userId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "ruleEngineRules_userId_tagId_fk": {
+ "name": "ruleEngineRules_userId_tagId_fk",
+ "tableFrom": "ruleEngineRules",
+ "tableTo": "bookmarkTags",
+ "columnsFrom": [
+ "userId",
+ "tagId"
+ ],
+ "columnsTo": [
+ "userId",
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "ruleEngineRules_userId_listId_fk": {
+ "name": "ruleEngineRules_userId_listId_fk",
+ "tableFrom": "ruleEngineRules",
+ "tableTo": "bookmarkLists",
+ "columnsFrom": [
+ "userId",
+ "listId"
+ ],
+ "columnsTo": [
+ "userId",
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "session": {
+ "name": "session",
+ "columns": {
+ "sessionToken": {
+ "name": "sessionToken",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "userId": {
+ "name": "userId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "expires": {
+ "name": "expires",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "session_userId_user_id_fk": {
+ "name": "session_userId_user_id_fk",
+ "tableFrom": "session",
+ "tableTo": "user",
+ "columnsFrom": [
+ "userId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "tagsOnBookmarks": {
+ "name": "tagsOnBookmarks",
+ "columns": {
+ "bookmarkId": {
+ "name": "bookmarkId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "tagId": {
+ "name": "tagId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "attachedAt": {
+ "name": "attachedAt",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "attachedBy": {
+ "name": "attachedBy",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "tagsOnBookmarks_tagId_idx": {
+ "name": "tagsOnBookmarks_tagId_idx",
+ "columns": [
+ "tagId"
+ ],
+ "isUnique": false
+ },
+ "tagsOnBookmarks_bookmarkId_idx": {
+ "name": "tagsOnBookmarks_bookmarkId_idx",
+ "columns": [
+ "bookmarkId"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "tagsOnBookmarks_bookmarkId_bookmarks_id_fk": {
+ "name": "tagsOnBookmarks_bookmarkId_bookmarks_id_fk",
+ "tableFrom": "tagsOnBookmarks",
+ "tableTo": "bookmarks",
+ "columnsFrom": [
+ "bookmarkId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "tagsOnBookmarks_tagId_bookmarkTags_id_fk": {
+ "name": "tagsOnBookmarks_tagId_bookmarkTags_id_fk",
+ "tableFrom": "tagsOnBookmarks",
+ "tableTo": "bookmarkTags",
+ "columnsFrom": [
+ "tagId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "tagsOnBookmarks_bookmarkId_tagId_pk": {
+ "columns": [
+ "bookmarkId",
+ "tagId"
+ ],
+ "name": "tagsOnBookmarks_bookmarkId_tagId_pk"
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "userSettings": {
+ "name": "userSettings",
+ "columns": {
+ "userId": {
+ "name": "userId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "bookmarkClickAction": {
+ "name": "bookmarkClickAction",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'open_original_link'"
+ },
+ "archiveDisplayBehaviour": {
+ "name": "archiveDisplayBehaviour",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'show'"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "userSettings_userId_user_id_fk": {
+ "name": "userSettings_userId_user_id_fk",
+ "tableFrom": "userSettings",
+ "tableTo": "user",
+ "columnsFrom": [
+ "userId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "user": {
+ "name": "user",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "email": {
+ "name": "email",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "emailVerified": {
+ "name": "emailVerified",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "image": {
+ "name": "image",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "password": {
+ "name": "password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "salt": {
+ "name": "salt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "''"
+ },
+ "role": {
+ "name": "role",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'user'"
+ }
+ },
+ "indexes": {
+ "user_email_unique": {
+ "name": "user_email_unique",
+ "columns": [
+ "email"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "verificationToken": {
+ "name": "verificationToken",
+ "columns": {
+ "identifier": {
+ "name": "identifier",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "token": {
+ "name": "token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "expires": {
+ "name": "expires",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "verificationToken_identifier_token_pk": {
+ "columns": [
+ "identifier",
+ "token"
+ ],
+ "name": "verificationToken_identifier_token_pk"
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "webhooks": {
+ "name": "webhooks",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "url": {
+ "name": "url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "userId": {
+ "name": "userId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "events": {
+ "name": "events",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "token": {
+ "name": "token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "webhooks_userId_idx": {
+ "name": "webhooks_userId_idx",
+ "columns": [
+ "userId"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "webhooks_userId_user_id_fk": {
+ "name": "webhooks_userId_user_id_fk",
+ "tableFrom": "webhooks",
+ "tableTo": "user",
+ "columnsFrom": [
+ "userId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ }
+ },
+ "views": {},
+ "enums": {},
+ "_meta": {
+ "schemas": {},
+ "tables": {},
+ "columns": {}
+ },
+ "internal": {
+ "indexes": {}
+ }
+} \ No newline at end of file
diff --git a/packages/db/drizzle/meta/_journal.json b/packages/db/drizzle/meta/_journal.json
index 8281b126..765eba59 100644
--- a/packages/db/drizzle/meta/_journal.json
+++ b/packages/db/drizzle/meta/_journal.json
@@ -323,6 +323,48 @@
"when": 1745705657846,
"tag": "0045_add_rule_engine",
"breakpoints": true
+ },
+ {
+ "idx": 46,
+ "version": "6",
+ "when": 1746902541511,
+ "tag": "0046_add_rss_feed_enabled_col",
+ "breakpoints": true
+ },
+ {
+ "idx": 47,
+ "version": "6",
+ "when": 1747598543992,
+ "tag": "0047_add_summarization_status",
+ "breakpoints": true
+ },
+ {
+ "idx": 48,
+ "version": "6",
+ "when": 1748086734370,
+ "tag": "0048_add_user_settings",
+ "breakpoints": true
+ },
+ {
+ "idx": 49,
+ "version": "6",
+ "when": 1748699971545,
+ "tag": "0049_add_rss_token",
+ "breakpoints": true
+ },
+ {
+ "idx": 50,
+ "version": "6",
+ "when": 1748795265779,
+ "tag": "0050_add_user_settings_archive_display_behaviour",
+ "breakpoints": true
+ },
+ {
+ "idx": 51,
+ "version": "6",
+ "when": 1748804695561,
+ "tag": "0051_public_lists",
+ "breakpoints": true
}
]
} \ No newline at end of file
diff --git a/packages/db/package.json b/packages/db/package.json
index 74f4f84f..e7819457 100644
--- a/packages/db/package.json
+++ b/packages/db/package.json
@@ -7,6 +7,7 @@
"scripts": {
"typecheck": "tsc --noEmit",
"migrate": "tsx migrate.ts",
+ "generate": "drizzle-kit generate",
"studio": "drizzle-kit studio"
},
"dependencies": {
diff --git a/packages/db/schema.ts b/packages/db/schema.ts
index bedcf9ad..e79bd2c9 100644
--- a/packages/db/schema.ts
+++ b/packages/db/schema.ts
@@ -123,6 +123,9 @@ export const bookmarks = sqliteTable(
taggingStatus: text("taggingStatus", {
enum: ["pending", "failure", "success"],
}).default("pending"),
+ summarizationStatus: text("summarizationStatus", {
+ enum: ["pending", "failure", "success"],
+ }).default("pending"),
summary: text("summary"),
note: text("note"),
type: text("type", {
@@ -333,6 +336,9 @@ export const bookmarkLists = sqliteTable(
(): AnySQLiteColumn => bookmarkLists.id,
{ onDelete: "set null" },
),
+ // Whoever have access to this token can read the content of this list
+ rssToken: text("rssToken"),
+ public: integer("public", { mode: "boolean" }).notNull().default(false),
},
(bl) => [
index("bookmarkLists_userId_idx").on(bl.userId),
@@ -389,6 +395,7 @@ export const rssFeedsTable = sqliteTable(
.$defaultFn(() => createId()),
name: text("name").notNull(),
url: text("url").notNull(),
+ enabled: integer("enabled", { mode: "boolean" }).notNull().default(true),
createdAt: createdAtField(),
lastFetchedAt: integer("lastFetchedAt", { mode: "timestamp" }),
lastFetchedStatus: text("lastFetchedStatus", {
@@ -415,7 +422,7 @@ export const webhooksTable = sqliteTable(
.references(() => users.id, { onDelete: "cascade" }),
events: text("events", { mode: "json" })
.notNull()
- .$type<("created" | "edited" | "crawled" | "ai tagged")[]>(),
+ .$type<("created" | "edited" | "crawled" | "ai tagged" | "deleted")[]>(),
token: text("token"),
},
(bl) => [index("webhooks_userId_idx").on(bl.userId)],
@@ -523,13 +530,34 @@ export const ruleEngineActionsTable = sqliteTable(
],
);
+export const userSettings = sqliteTable("userSettings", {
+ userId: text("userId")
+ .notNull()
+ .primaryKey()
+ .references(() => users.id, { onDelete: "cascade" }),
+ bookmarkClickAction: text("bookmarkClickAction", {
+ enum: ["open_original_link", "expand_bookmark_preview"],
+ })
+ .notNull()
+ .default("open_original_link"),
+ archiveDisplayBehaviour: text("archiveDisplayBehaviour", {
+ enum: ["show", "hide"],
+ })
+ .notNull()
+ .default("show"),
+});
+
// Relations
-export const userRelations = relations(users, ({ many }) => ({
+export const userRelations = relations(users, ({ many, one }) => ({
tags: many(bookmarkTags),
bookmarks: many(bookmarks),
webhooks: many(webhooksTable),
rules: many(ruleEngineRulesTable),
+ settings: one(userSettings, {
+ fields: [users.id],
+ references: [userSettings.userId],
+ }),
}));
export const bookmarkRelations = relations(bookmarks, ({ many, one }) => ({
@@ -664,3 +692,10 @@ export const rssFeedImportsTableRelations = relations(
}),
}),
);
+
+export const userSettingsRelations = relations(userSettings, ({ one }) => ({
+ user: one(users, {
+ fields: [userSettings.userId],
+ references: [users.id],
+ }),
+}));
diff --git a/packages/e2e_tests/docker-compose.yml b/packages/e2e_tests/docker-compose.yml
index 201db154..e1fe46bb 100644
--- a/packages/e2e_tests/docker-compose.yml
+++ b/packages/e2e_tests/docker-compose.yml
@@ -10,6 +10,7 @@ services:
environment:
DATA_DIR: /tmp
NEXTAUTH_SECRET: secret
+ NEXTAUTH_URL: http://localhost:${KARAKEEP_PORT:-3000}
MEILI_MASTER_KEY: dummy
MEILI_ADDR: http://meilisearch:7700
BROWSER_WEB_URL: http://chrome:9222
diff --git a/packages/e2e_tests/tests/api/assets.test.ts b/packages/e2e_tests/tests/api/assets.test.ts
index 5c294929..78a5c7fe 100644
--- a/packages/e2e_tests/tests/api/assets.test.ts
+++ b/packages/e2e_tests/tests/api/assets.test.ts
@@ -39,7 +39,7 @@ describe("Assets API", () => {
// Retrieve the asset
const resp = await fetch(
- `http://localhost:${port}/api/assets/${uploadResponse.assetId}`,
+ `http://localhost:${port}/api/v1/assets/${uploadResponse.assetId}`,
{
headers: {
authorization: `Bearer ${apiKey}`,
@@ -123,7 +123,7 @@ describe("Assets API", () => {
// Verify asset is deleted
const assetResponse = await fetch(
- `http://localhost:${port}/api/assets/${uploadResponse.assetId}`,
+ `http://localhost:${port}/api/v1/assets/${uploadResponse.assetId}`,
{
headers: {
authorization: `Bearer ${apiKey}`,
diff --git a/packages/e2e_tests/tests/api/bookmarks.test.ts b/packages/e2e_tests/tests/api/bookmarks.test.ts
index 6c56f689..d40c1add 100644
--- a/packages/e2e_tests/tests/api/bookmarks.test.ts
+++ b/packages/e2e_tests/tests/api/bookmarks.test.ts
@@ -397,54 +397,258 @@ describe("Bookmarks API", () => {
expect(finalPage!.nextCursor).toBeNull();
});
- it("should support precrawling via singlefile", async () => {
- const file = new File(["<html>HELLO WORLD</html>"], "test.html", {
- type: "text/html",
- });
+ describe("singlefile", () => {
+ async function uploadSinglefileAsset(ifexists?: string) {
+ const file = new File(["<html>HELLO WORLD</html>"], "test.html", {
+ type: "text/html",
+ });
- const formData = new FormData();
- formData.append("url", "https://example.com");
- formData.append("file", file);
+ const formData = new FormData();
+ formData.append("url", "https://example.com");
+ formData.append("file", file);
- // OpenAPI typescript doesn't support multipart/form-data
- // Upload the singlefile archive
- const response = await fetch(
- `http://localhost:${port}/api/v1/bookmarks/singlefile`,
- {
+ const url = new URL(
+ `http://localhost:${port}/api/v1/bookmarks/singlefile`,
+ );
+ if (ifexists) {
+ url.searchParams.append("ifexists", ifexists);
+ }
+
+ const response = await fetch(url.toString(), {
method: "POST",
headers: {
authorization: `Bearer ${apiKey}`,
},
body: formData,
- },
- );
+ });
- if (!response.ok) {
- throw new Error(`Failed to upload asset: ${response.statusText}`);
+ if (!response.ok) {
+ return [null, response] as const;
+ }
+
+ const data = (await response.json()) as { id: string };
+ return [data, response] as const;
}
- expect(response.status).toBe(201);
+ it("should support precrawling via singlefile with ifexists=skip", async () => {
+ // First upload: create a bookmark
+ const [data, response] = await uploadSinglefileAsset();
+ expect(response?.status).toBe(201);
+ const bookmarkId = data?.id;
+ if (!bookmarkId) throw new Error("Bookmark ID not found");
- const { id: bookmarkId } = (await response.json()) as {
- id: string;
- };
+ // Get the bookmark and record the precrawled asset id
+ const { data: bookmark, response: getResponse1 } = await client.GET(
+ "/bookmarks/{bookmarkId}",
+ {
+ params: { path: { bookmarkId } },
+ },
+ );
+ expect(getResponse1.status).toBe(200);
+ const assetIds = bookmark!.assets
+ .filter((a) => a.assetType === "precrawledArchive")
+ .map((a) => a.id);
+ expect(assetIds.length).toBe(1);
+ const firstAssetId = assetIds[0];
- // Get the created bookmark
- const { data: retrievedBookmark, response: getResponse } = await client.GET(
- "/bookmarks/{bookmarkId}",
- {
- params: {
- path: {
- bookmarkId: bookmarkId,
- },
+ // Second upload with skip
+ const [data2, response2] = await uploadSinglefileAsset("skip");
+ expect(response2?.status).toBe(200);
+ expect(data2?.id).toBe(bookmarkId);
+
+ // Get the bookmark again
+ const { data: bookmark2, response: getResponse2 } = await client.GET(
+ "/bookmarks/{bookmarkId}",
+ {
+ params: { path: { bookmarkId } },
},
- },
- );
+ );
+ expect(getResponse2.status).toBe(200);
+ const assetIds2 = bookmark2!.assets
+ .filter((a) => a.assetType === "precrawledArchive")
+ .map((a) => a.id);
+ expect(assetIds2).toEqual([firstAssetId]); // same asset
+ });
- expect(getResponse.status).toBe(200);
- assert(retrievedBookmark!.content.type === "link");
- expect(retrievedBookmark!.assets.map((a) => a.assetType)).toContain(
- "precrawledArchive",
- );
+ it("should support precrawling via singlefile with ifexists=overwrite", async () => {
+ // First upload
+ const [data, response] = await uploadSinglefileAsset("overwrite");
+ expect(response?.status).toBe(201);
+ const bookmarkId = data?.id;
+ if (!bookmarkId) throw new Error("Bookmark ID not found");
+
+ // Record the asset
+ const { data: bookmark, response: getResponse1 } = await client.GET(
+ "/bookmarks/{bookmarkId}",
+ {
+ params: { path: { bookmarkId } },
+ },
+ );
+ expect(getResponse1.status).toBe(200);
+ const firstAssetId = bookmark!.assets.find(
+ (a) => a.assetType === "precrawledArchive",
+ )?.id;
+ expect(firstAssetId).toBeDefined();
+
+ // Second upload with overwrite
+ const [data2, response2] = await uploadSinglefileAsset("overwrite");
+ expect(response2?.status).toBe(200);
+ expect(data2?.id).toBe(bookmarkId);
+
+ // Get the bookmark again
+ const { data: bookmark2, response: getResponse2 } = await client.GET(
+ "/bookmarks/{bookmarkId}",
+ {
+ params: { path: { bookmarkId } },
+ },
+ );
+ expect(getResponse2.status).toBe(200);
+ const secondAssetId = bookmark2!.assets.find(
+ (a) => a.assetType === "precrawledArchive",
+ )?.id;
+ expect(secondAssetId).toBeDefined();
+ expect(secondAssetId).not.toBe(firstAssetId);
+ // There should be only one precrawledArchive asset
+ const precrawledAssets = bookmark2!.assets.filter(
+ (a) => a.assetType === "precrawledArchive",
+ );
+ expect(precrawledAssets.length).toBe(1);
+ });
+
+ it("should support precrawling via singlefile with ifexists=overwrite-recrawl", async () => {
+ // First upload
+ const [data, response] = await uploadSinglefileAsset("overwrite-recrawl");
+ expect(response?.status).toBe(201);
+ const bookmarkId = data?.id;
+ if (!bookmarkId) throw new Error("Bookmark ID not found");
+
+ // Record the asset
+ const { data: bookmark, response: getResponse1 } = await client.GET(
+ "/bookmarks/{bookmarkId}",
+ {
+ params: { path: { bookmarkId } },
+ },
+ );
+ expect(getResponse1.status).toBe(200);
+ const firstAssetId = bookmark!.assets.find(
+ (a) => a.assetType === "precrawledArchive",
+ )?.id;
+ expect(firstAssetId).toBeDefined();
+
+ // Second upload with overwrite-recrawl
+ const [data2, response2] =
+ await uploadSinglefileAsset("overwrite-recrawl");
+ expect(response2?.status).toBe(200);
+ expect(data2?.id).toBe(bookmarkId);
+
+ // Get the bookmark again
+ const { data: bookmark2, response: getResponse2 } = await client.GET(
+ "/bookmarks/{bookmarkId}",
+ {
+ params: { path: { bookmarkId } },
+ },
+ );
+ expect(getResponse2.status).toBe(200);
+ const secondAssetId = bookmark2!.assets.find(
+ (a) => a.assetType === "precrawledArchive",
+ )?.id;
+ expect(secondAssetId).toBeDefined();
+ expect(secondAssetId).not.toBe(firstAssetId);
+ // There should be only one precrawledArchive asset
+ const precrawledAssets = bookmark2!.assets.filter(
+ (a) => a.assetType === "precrawledArchive",
+ );
+ expect(precrawledAssets.length).toBe(1);
+ });
+
+ it("should support precrawling via singlefile with ifexists=append", async () => {
+ // First upload
+ const [data, response] = await uploadSinglefileAsset("append");
+ expect(response?.status).toBe(201);
+ const bookmarkId = data?.id;
+ if (!bookmarkId) throw new Error("Bookmark ID not found");
+
+ // Record the first asset
+ const { data: bookmark, response: getResponse1 } = await client.GET(
+ "/bookmarks/{bookmarkId}",
+ {
+ params: { path: { bookmarkId } },
+ },
+ );
+ expect(getResponse1.status).toBe(200);
+ const firstAssetId = bookmark!.assets.find(
+ (a) => a.assetType === "precrawledArchive",
+ )?.id;
+ expect(firstAssetId).toBeDefined();
+
+ // Second upload with append
+ const [data2, response2] = await uploadSinglefileAsset("append");
+ expect(response2?.status).toBe(200);
+ expect(data2?.id).toBe(bookmarkId);
+
+ // Get the bookmark again
+ const { data: bookmark2, response: getResponse2 } = await client.GET(
+ "/bookmarks/{bookmarkId}",
+ {
+ params: { path: { bookmarkId } },
+ },
+ );
+ expect(getResponse2.status).toBe(200);
+ const precrawledAssets = bookmark2!.assets.filter(
+ (a) => a.assetType === "precrawledArchive",
+ );
+ expect(precrawledAssets.length).toBe(2);
+ expect(precrawledAssets.map((a) => a.id)).toContain(firstAssetId);
+ // The second asset id should be different
+ const secondAssetId = precrawledAssets.find(
+ (asset) => asset.id !== firstAssetId,
+ )?.id;
+ expect(secondAssetId).toBeDefined();
+ });
+
+ it("should support precrawling via singlefile with ifexists=append-recrawl", async () => {
+ // First upload
+ const [data, response] = await uploadSinglefileAsset("append-recrawl");
+ expect(response?.status).toBe(201);
+ const bookmarkId = data?.id;
+ if (!bookmarkId) throw new Error("Bookmark ID not found");
+
+ // Record the first asset
+ const { data: bookmark, response: getResponse1 } = await client.GET(
+ "/bookmarks/{bookmarkId}",
+ {
+ params: { path: { bookmarkId } },
+ },
+ );
+ expect(getResponse1.status).toBe(200);
+ const firstAssetId = bookmark!.assets.find(
+ (a) => a.assetType === "precrawledArchive",
+ )?.id;
+ expect(firstAssetId).toBeDefined();
+
+ // Second upload with append-recrawl
+ const [data2, response2] = await uploadSinglefileAsset("append-recrawl");
+ expect(response2?.status).toBe(200);
+ expect(data2?.id).toBe(bookmarkId);
+
+ // Get the bookmark again
+ const { data: bookmark2, response: getResponse2 } = await client.GET(
+ "/bookmarks/{bookmarkId}",
+ {
+ params: { path: { bookmarkId } },
+ },
+ );
+ expect(getResponse2.status).toBe(200);
+ const precrawledAssets = bookmark2!.assets.filter(
+ (a) => a.assetType === "precrawledArchive",
+ );
+ expect(precrawledAssets.length).toBe(2);
+ expect(precrawledAssets.map((a) => a.id)).toContain(firstAssetId);
+ // The second asset id should be different
+ const secondAssetId = precrawledAssets.find(
+ (asset) => asset.id !== firstAssetId,
+ )?.id;
+ expect(secondAssetId).toBeDefined();
+ });
});
});
diff --git a/packages/e2e_tests/tests/api/public.test.ts b/packages/e2e_tests/tests/api/public.test.ts
new file mode 100644
index 00000000..54ef79ea
--- /dev/null
+++ b/packages/e2e_tests/tests/api/public.test.ts
@@ -0,0 +1,322 @@
+import { assert, beforeEach, describe, expect, inject, it } from "vitest";
+import { z } from "zod";
+
+import { createSignedToken } from "../../../shared/signedTokens";
+import { zAssetSignedTokenSchema } from "../../../shared/types/assets";
+import { BookmarkTypes } from "../../../shared/types/bookmarks";
+import { createTestUser, uploadTestAsset } from "../../utils/api";
+import { waitUntil } from "../../utils/general";
+import { getTrpcClient } from "../../utils/trpc";
+
+describe("Public API", () => {
+ const port = inject("karakeepPort");
+
+ if (!port) {
+ throw new Error("Missing required environment variables");
+ }
+
+ let apiKey: string; // For the primary test user
+
+ async function seedDatabase(currentApiKey: string) {
+ const trpcClient = getTrpcClient(currentApiKey);
+
+ // Create two lists
+ const publicList = await trpcClient.lists.create.mutate({
+ name: "Public List",
+ icon: "🚀",
+ type: "manual",
+ });
+
+ await trpcClient.lists.edit.mutate({
+ listId: publicList.id,
+ public: true,
+ });
+
+ // Create two bookmarks
+ const createBookmark1 = await trpcClient.bookmarks.createBookmark.mutate({
+ title: "Test Bookmark #1",
+ url: "http://nginx:80/hello.html",
+ type: BookmarkTypes.LINK,
+ });
+
+ // Create a second bookmark with an asset
+ const file = new File(["test content"], "test.pdf", {
+ type: "application/pdf",
+ });
+
+ const uploadResponse = await uploadTestAsset(currentApiKey, port, file);
+ const createBookmark2 = await trpcClient.bookmarks.createBookmark.mutate({
+ title: "Test Bookmark #2",
+ type: BookmarkTypes.ASSET,
+ assetType: "pdf",
+ assetId: uploadResponse.assetId,
+ });
+
+ await trpcClient.lists.addToList.mutate({
+ listId: publicList.id,
+ bookmarkId: createBookmark1.id,
+ });
+ await trpcClient.lists.addToList.mutate({
+ listId: publicList.id,
+ bookmarkId: createBookmark2.id,
+ });
+
+ return { publicList, createBookmark1, createBookmark2 };
+ }
+
+ beforeEach(async () => {
+ apiKey = await createTestUser();
+ });
+
+ it("should get public bookmarks", async () => {
+ const { publicList } = await seedDatabase(apiKey);
+ const trpcClient = getTrpcClient(apiKey);
+
+ const res = await trpcClient.publicBookmarks.getPublicBookmarksInList.query(
+ {
+ listId: publicList.id,
+ },
+ );
+
+ expect(res.bookmarks.length).toBe(2);
+ });
+
+ it("should be able to access the assets of the public bookmarks", async () => {
+ const { publicList, createBookmark1, createBookmark2 } =
+ await seedDatabase(apiKey);
+
+ const trpcClient = getTrpcClient(apiKey);
+ // Wait for link bookmark to be crawled and have a banner image (screenshot)
+ await waitUntil(
+ async () => {
+ const res = await trpcClient.bookmarks.getBookmark.query({
+ bookmarkId: createBookmark1.id,
+ });
+ assert(res.content.type === BookmarkTypes.LINK);
+ // Check for screenshotAssetId as bannerImageUrl might be derived from it or original imageUrl
+ return !!res.content.screenshotAssetId || !!res.content.imageUrl;
+ },
+ "Bookmark is crawled and has banner info",
+ 20000, // Increased timeout as crawling can take time
+ );
+
+ const res = await trpcClient.publicBookmarks.getPublicBookmarksInList.query(
+ {
+ listId: publicList.id,
+ },
+ );
+
+ const b1Resp = res.bookmarks.find((b) => b.id === createBookmark1.id);
+ expect(b1Resp).toBeDefined();
+ const b2Resp = res.bookmarks.find((b) => b.id === createBookmark2.id);
+ expect(b2Resp).toBeDefined();
+
+ assert(b1Resp!.content.type === BookmarkTypes.LINK);
+ assert(b2Resp!.content.type === BookmarkTypes.ASSET);
+
+ {
+ // Banner image fetch for link bookmark
+ assert(
+ b1Resp!.bannerImageUrl,
+ "Link bookmark should have a bannerImageUrl",
+ );
+ const assetFetch = await fetch(b1Resp!.bannerImageUrl);
+ expect(assetFetch.status).toBe(200);
+ }
+
+ {
+ // Actual asset fetch for asset bookmark
+ assert(
+ b2Resp!.content.assetUrl,
+ "Asset bookmark should have an assetUrl",
+ );
+ const assetFetch = await fetch(b2Resp!.content.assetUrl);
+ expect(assetFetch.status).toBe(200);
+ }
+ });
+
+ it("Accessing non public list should fail", async () => {
+ const trpcClient = getTrpcClient(apiKey);
+ const nonPublicList = await trpcClient.lists.create.mutate({
+ name: "Non Public List",
+ icon: "🚀",
+ type: "manual",
+ });
+
+ await expect(
+ trpcClient.publicBookmarks.getPublicBookmarksInList.query({
+ listId: nonPublicList.id,
+ }),
+ ).rejects.toThrow(/List not found/);
+ });
+
+ describe("Public asset token validation", () => {
+ let userId: string;
+ let assetId: string; // Asset belonging to the primary user (userId)
+
+ beforeEach(async () => {
+ const trpcClient = getTrpcClient(apiKey);
+ const whoami = await trpcClient.users.whoami.query();
+ userId = whoami.id;
+ const assetUpload = await uploadTestAsset(
+ apiKey,
+ port,
+ new File(["test content for token validation"], "token_test.pdf", {
+ type: "application/pdf",
+ }),
+ );
+ assetId = assetUpload.assetId;
+ });
+
+ it("should succeed with a valid token", async () => {
+ const token = createSignedToken(
+ {
+ assetId,
+ userId,
+ } as z.infer<typeof zAssetSignedTokenSchema>,
+ Date.now() + 60000, // Expires in 60 seconds
+ );
+ const res = await fetch(
+ `http://localhost:${port}/api/public/assets/${assetId}?token=${token}`,
+ );
+ expect(res.status).toBe(200);
+ expect((await res.blob()).type).toBe("application/pdf");
+ });
+
+ it("should fail without a token", async () => {
+ const res = await fetch(
+ `http://localhost:${port}/api/public/assets/${assetId}`,
+ );
+ expect(res.status).toBe(400); // Bad Request due to missing token query param
+ });
+
+ it("should fail with a malformed token string (e.g., not base64)", async () => {
+ const res = await fetch(
+ `http://localhost:${port}/api/public/assets/${assetId}?token=thisIsNotValidBase64!@#`,
+ );
+ expect(res.status).toBe(403);
+ expect(await res.json()).toEqual(
+ expect.objectContaining({ error: "Invalid or expired token" }),
+ );
+ });
+
+ it("should fail with a token having a structurally invalid inner payload", async () => {
+ // Payload that doesn't conform to zAssetSignedTokenSchema (e.g. misspelled key)
+ const malformedInnerPayload = {
+ asset_id_mispelled: assetId,
+ userId: userId,
+ };
+ const token = createSignedToken(
+ malformedInnerPayload,
+ Date.now() + 60000,
+ );
+ const res = await fetch(
+ `http://localhost:${port}/api/public/assets/${assetId}?token=${token}`,
+ );
+ expect(res.status).toBe(403);
+ expect(await res.json()).toEqual(
+ expect.objectContaining({ error: "Invalid or expired token" }),
+ );
+ });
+
+ it("should fail after token expiry", async () => {
+ const token = createSignedToken(
+ {
+ assetId,
+ userId,
+ } as z.infer<typeof zAssetSignedTokenSchema>,
+ Date.now() + 1000, // Expires in 1 second
+ );
+
+ // Wait for more than 1 second to ensure expiry
+ await new Promise((resolve) => setTimeout(resolve, 2000));
+
+ const res = await fetch(
+ `http://localhost:${port}/api/public/assets/${assetId}?token=${token}`,
+ );
+ expect(res.status).toBe(403);
+ expect(await res.json()).toEqual(
+ expect.objectContaining({ error: "Invalid or expired token" }),
+ );
+ });
+
+ it("should fail when using a valid token for a different asset", async () => {
+ const anotherAssetUpload = await uploadTestAsset(
+ apiKey, // Same user
+ port,
+ new File(["other content"], "other_asset.pdf", {
+ type: "application/pdf",
+ }),
+ );
+ const anotherAssetId = anotherAssetUpload.assetId;
+
+ // Token is valid for 'anotherAssetId'
+ const tokenForAnotherAsset = createSignedToken(
+ {
+ assetId: anotherAssetId,
+ userId,
+ } as z.infer<typeof zAssetSignedTokenSchema>,
+ Date.now() + 60000,
+ );
+
+ // Attempt to use this token to access the original 'assetId'
+ const res = await fetch(
+ `http://localhost:${port}/api/public/assets/${assetId}?token=${tokenForAnotherAsset}`,
+ );
+ expect(res.status).toBe(403);
+ expect(await res.json()).toEqual(
+ expect.objectContaining({ error: "Invalid or expired token" }),
+ );
+ });
+
+ it("should fail if token's userId does not own the requested assetId (expect 404)", async () => {
+ // User1 (primary, `apiKey`, `userId`) owns `assetId` (from beforeEach)
+
+ // Create User2 - ensure unique email for user creation
+ const apiKeyUser2 = await createTestUser();
+ const trpcClientUser2 = getTrpcClient(apiKeyUser2);
+ const whoamiUser2 = await trpcClientUser2.users.whoami.query();
+ const userIdUser2 = whoamiUser2.id;
+
+ // Generate a token where the payload claims assetId is being accessed by userIdUser2,
+ // but assetId actually belongs to the original userId.
+ const tokenForUser2AttemptingAsset1 = createSignedToken(
+ {
+ assetId: assetId, // assetId belongs to user1 (userId)
+ userId: userIdUser2, // token claims user2 is accessing it
+ } as z.infer<typeof zAssetSignedTokenSchema>,
+ Date.now() + 60000,
+ );
+
+ // User2 attempts to access assetId (owned by User1) using a token that has User2's ID in its payload.
+ // The API route will use userIdUser2 from the token to query the DB for assetId.
+ // Since assetId is not owned by userIdUser2, the DB query will find nothing.
+ const res = await fetch(
+ `http://localhost:${port}/api/public/assets/${assetId}?token=${tokenForUser2AttemptingAsset1}`,
+ );
+ expect(res.status).toBe(404);
+ expect(await res.json()).toEqual(
+ expect.objectContaining({ error: "Asset not found" }),
+ );
+ });
+
+ it("should fail for a token referencing a non-existent assetId (expect 404)", async () => {
+ const nonExistentAssetId = `nonexistent-asset-${Date.now()}`;
+ const token = createSignedToken(
+ {
+ assetId: nonExistentAssetId,
+ userId, // Valid userId from the primary user
+ } as z.infer<typeof zAssetSignedTokenSchema>,
+ Date.now() + 60000,
+ );
+
+ const res = await fetch(
+ `http://localhost:${port}/api/public/assets/${nonExistentAssetId}?token=${token}`,
+ );
+ expect(res.status).toBe(404);
+ expect(await res.json()).toEqual(
+ expect.objectContaining({ error: "Asset not found" }),
+ );
+ });
+ });
+});
diff --git a/packages/e2e_tests/tests/api/rss.test.ts b/packages/e2e_tests/tests/api/rss.test.ts
new file mode 100644
index 00000000..8a7447e6
--- /dev/null
+++ b/packages/e2e_tests/tests/api/rss.test.ts
@@ -0,0 +1,155 @@
+import { beforeEach, describe, expect, inject, it } from "vitest";
+
+import { BookmarkTypes } from "../../../shared/types/bookmarks";
+import { createTestUser } from "../../utils/api";
+import { getTrpcClient } from "../../utils/trpc";
+
+describe("RSS Feed API", () => {
+ const port = inject("karakeepPort");
+
+ if (!port) {
+ throw new Error("Missing required environment variables");
+ }
+
+ async function fetchRssFeed(listId: string, token: string) {
+ return await fetch(
+ `http://localhost:${port}/api/v1/rss/lists/${listId}?token=${token}`,
+ );
+ }
+
+ async function seedDatabase() {
+ const trpcClient = getTrpcClient(apiKey);
+
+ // Create two lists
+ const manualList = await trpcClient.lists.create.mutate({
+ name: "Test List #1",
+ icon: "🚀",
+ type: "manual",
+ });
+
+ const smartList = await trpcClient.lists.create.mutate({
+ name: "Test List #2",
+ icon: "🚀",
+ type: "smart",
+ query: "is:fav",
+ });
+
+ // Create two bookmarks
+ const createBookmark1 = await trpcClient.bookmarks.createBookmark.mutate({
+ title: "Test Bookmark #1",
+ url: "https://example.com",
+ type: BookmarkTypes.LINK,
+ });
+
+ const createBookmark2 = await trpcClient.bookmarks.createBookmark.mutate({
+ title: "Test Bookmark #2",
+ url: "https://example.com/2",
+ type: BookmarkTypes.LINK,
+ favourited: true,
+ });
+
+ await trpcClient.lists.addToList.mutate({
+ listId: manualList.id,
+ bookmarkId: createBookmark1.id,
+ });
+
+ return { manualList, smartList, createBookmark1, createBookmark2 };
+ }
+
+ let apiKey: string;
+
+ beforeEach(async () => {
+ apiKey = await createTestUser();
+ });
+
+ it("should generate rss feed for manual lists", async () => {
+ const { manualList } = await seedDatabase();
+ const trpcClient = getTrpcClient(apiKey);
+
+ // Enable rss feed
+ const token = await trpcClient.lists.regenRssToken.mutate({
+ listId: manualList.id,
+ });
+
+ const res = await fetchRssFeed(manualList.id, token.token);
+ expect(res.status).toBe(200);
+ expect(res.headers.get("Content-Type")).toBe("application/rss+xml");
+
+ const text = await res.text();
+ expect(text).toContain("Test Bookmark #1");
+ expect(text).not.toContain("Test Bookmark #2");
+ });
+
+ it("should generate rss feed for smart lists", async () => {
+ const { smartList } = await seedDatabase();
+ const trpcClient = getTrpcClient(apiKey);
+
+ // Enable rss feed
+ const token = await trpcClient.lists.regenRssToken.mutate({
+ listId: smartList.id,
+ });
+
+ const res = await fetchRssFeed(smartList.id, token.token);
+ expect(res.status).toBe(200);
+ expect(res.headers.get("Content-Type")).toBe("application/rss+xml");
+
+ const text = await res.text();
+ expect(text).not.toContain("Test Bookmark #1");
+ expect(text).toContain("Test Bookmark #2");
+ });
+
+ it("should fail when the token is invalid", async () => {
+ const { smartList } = await seedDatabase();
+ const trpcClient = getTrpcClient(apiKey);
+
+ // Enable rss feed
+ const token = await trpcClient.lists.regenRssToken.mutate({
+ listId: smartList.id,
+ });
+
+ let res = await fetchRssFeed(smartList.id, token.token);
+ expect(res.status).toBe(200);
+
+ // Invalidate the token
+ await trpcClient.lists.regenRssToken.mutate({
+ listId: smartList.id,
+ });
+
+ res = await fetchRssFeed(smartList.id, token.token);
+ expect(res.status).toBe(404);
+ });
+
+ it("should fail when rss gets disabled", async () => {
+ const { smartList } = await seedDatabase();
+ const trpcClient = getTrpcClient(apiKey);
+
+ // Enable rss feed
+ const token = await trpcClient.lists.regenRssToken.mutate({
+ listId: smartList.id,
+ });
+
+ const res = await fetchRssFeed(smartList.id, token.token);
+ expect(res.status).toBe(200);
+
+ // Disable rss feed
+ await trpcClient.lists.clearRssToken.mutate({
+ listId: smartList.id,
+ });
+
+ const res2 = await fetchRssFeed(smartList.id, token.token);
+ expect(res2.status).toBe(404);
+ });
+
+ it("should fail when no token is provided", async () => {
+ const { smartList } = await seedDatabase();
+ const trpcClient = getTrpcClient(apiKey);
+
+ // Enable rss feed
+ await trpcClient.lists.regenRssToken.mutate({
+ listId: smartList.id,
+ });
+
+ const res2 = await fetchRssFeed(smartList.id, "");
+ expect(res2.status).toBe(400);
+ });
+});
diff --git a/packages/e2e_tests/tests/api/tags.test.ts b/packages/e2e_tests/tests/api/tags.test.ts
index 3e3cacc0..6c387628 100644
--- a/packages/e2e_tests/tests/api/tags.test.ts
+++ b/packages/e2e_tests/tests/api/tags.test.ts
@@ -26,31 +26,16 @@ describe("Tags API", () => {
});
it("should get, update and delete a tag", async () => {
- // Create a bookmark first
- const { data: createdBookmark } = await client.POST("/bookmarks", {
+ // Create a tag by attaching it to the bookmark
+ const { data: tag } = await client.POST("/tags", {
body: {
- type: "text",
- title: "Test Bookmark",
- text: "This is a test bookmark",
+ name: "Test Tag",
},
});
+ expect(tag).toBeDefined();
+ expect(tag!.name).toBe("Test Tag");
- // Create a tag by attaching it to the bookmark
- const { data: addTagResponse } = await client.POST(
- "/bookmarks/{bookmarkId}/tags",
- {
- params: {
- path: {
- bookmarkId: createdBookmark!.id,
- },
- },
- body: {
- tags: [{ tagName: "Test Tag" }],
- },
- },
- );
-
- const tagId = addTagResponse!.attached[0];
+ const tagId = tag!.id;
// Get the tag
const { data: retrievedTag, response: getResponse } = await client.GET(
diff --git a/packages/e2e_tests/utils/api.ts b/packages/e2e_tests/utils/api.ts
index 84a6eb91..9f9052fc 100644
--- a/packages/e2e_tests/utils/api.ts
+++ b/packages/e2e_tests/utils/api.ts
@@ -15,7 +15,7 @@ export async function uploadTestAsset(
const formData = new FormData();
formData.append("file", file);
- const response = await fetch(`http://localhost:${port}/api/assets`, {
+ const response = await fetch(`http://localhost:${port}/api/v1/assets`, {
method: "POST",
headers: {
authorization: `Bearer ${apiKey}`,
diff --git a/packages/e2e_tests/vitest.config.ts b/packages/e2e_tests/vitest.config.ts
index bb1c7ea4..2735f1e2 100644
--- a/packages/e2e_tests/vitest.config.ts
+++ b/packages/e2e_tests/vitest.config.ts
@@ -14,5 +14,8 @@ export default defineConfig({
teardownTimeout: 30000,
include: ["tests/**/*.test.ts"],
testTimeout: 60000,
+ env: {
+ NEXTAUTH_SECRET: "secret",
+ },
},
});
diff --git a/packages/open-api/karakeep-openapi-spec.json b/packages/open-api/karakeep-openapi-spec.json
index 0f8074d4..a8eb2ac2 100644
--- a/packages/open-api/karakeep-openapi-spec.json
+++ b/packages/open-api/karakeep-openapi-spec.json
@@ -77,6 +77,15 @@
"pending"
]
},
+ "summarizationStatus": {
+ "type": "string",
+ "nullable": true,
+ "enum": [
+ "success",
+ "failure",
+ "pending"
+ ]
+ },
"note": {
"type": "string",
"nullable": true
@@ -307,6 +316,7 @@
"archived",
"favourited",
"taggingStatus",
+ "summarizationStatus",
"tags",
"content",
"assets"
@@ -416,13 +426,17 @@
"query": {
"type": "string",
"nullable": true
+ },
+ "public": {
+ "type": "boolean"
}
},
"required": [
"id",
"name",
"icon",
- "parentId"
+ "parentId",
+ "public"
]
},
"Tag": {
@@ -551,6 +565,19 @@
},
{
"schema": {
+ "type": "string",
+ "enum": [
+ "asc",
+ "desc"
+ ],
+ "default": "desc"
+ },
+ "required": false,
+ "name": "sortOrder",
+ "in": "query"
+ },
+ {
+ "schema": {
"type": "number"
},
"required": false,
@@ -776,6 +803,20 @@
},
{
"schema": {
+ "type": "string",
+ "enum": [
+ "asc",
+ "desc",
+ "relevance"
+ ],
+ "default": "relevance"
+ },
+ "required": false,
+ "name": "sortOrder",
+ "in": "query"
+ },
+ {
+ "schema": {
"type": "number"
},
"required": false,
@@ -1042,6 +1083,15 @@
"pending"
]
},
+ "summarizationStatus": {
+ "type": "string",
+ "nullable": true,
+ "enum": [
+ "success",
+ "failure",
+ "pending"
+ ]
+ },
"note": {
"type": "string",
"nullable": true
@@ -1057,7 +1107,8 @@
"modifiedAt",
"archived",
"favourited",
- "taggingStatus"
+ "taggingStatus",
+ "summarizationStatus"
]
}
}
@@ -1142,6 +1193,15 @@
"pending"
]
},
+ "summarizationStatus": {
+ "type": "string",
+ "nullable": true,
+ "enum": [
+ "success",
+ "failure",
+ "pending"
+ ]
+ },
"note": {
"type": "string",
"nullable": true
@@ -1157,7 +1217,8 @@
"modifiedAt",
"archived",
"favourited",
- "taggingStatus"
+ "taggingStatus",
+ "summarizationStatus"
]
}
}
@@ -1925,6 +1986,9 @@
"query": {
"type": "string",
"minLength": 1
+ },
+ "public": {
+ "type": "boolean"
}
}
}
@@ -1969,8 +2033,8 @@
},
"/lists/{listId}/bookmarks": {
"get": {
- "description": "Get the bookmarks in a list",
- "summary": "Get a bookmarks in a list",
+ "description": "Get bookmarks in the list",
+ "summary": "Get bookmarks in the list",
"tags": [
"Lists"
],
@@ -1985,6 +2049,19 @@
},
{
"schema": {
+ "type": "string",
+ "enum": [
+ "asc",
+ "desc"
+ ],
+ "default": "desc"
+ },
+ "required": false,
+ "name": "sortOrder",
+ "in": "query"
+ },
+ {
+ "schema": {
"type": "number"
},
"required": false,
@@ -2071,28 +2148,6 @@
"204": {
"description": "No content - the bookmark was added"
},
- "400": {
- "description": "Bookmark already in list",
- "content": {
- "application/json": {
- "schema": {
- "type": "object",
- "properties": {
- "code": {
- "type": "string"
- },
- "message": {
- "type": "string"
- }
- },
- "required": [
- "code",
- "message"
- ]
- }
- }
- }
- },
"404": {
"description": "List or bookmark not found",
"content": {
@@ -2222,6 +2277,61 @@
}
}
}
+ },
+ "post": {
+ "description": "Create a new tag",
+ "summary": "Create a new tag",
+ "tags": [
+ "Tags"
+ ],
+ "security": [
+ {
+ "bearerAuth": []
+ }
+ ],
+ "requestBody": {
+ "description": "The data to create the tag with.",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "minLength": 1
+ }
+ },
+ "required": [
+ "name"
+ ]
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "The created tag",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "type": "string"
+ },
+ "name": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "id",
+ "name"
+ ]
+ }
+ }
+ }
+ }
+ }
}
},
"/tags/{tagId}": {
@@ -2357,7 +2467,19 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/Tag"
+ "type": "object",
+ "properties": {
+ "id": {
+ "type": "string"
+ },
+ "name": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "id",
+ "name"
+ ]
}
}
}
@@ -2389,8 +2511,8 @@
},
"/tags/{tagId}/bookmarks": {
"get": {
- "description": "Get the bookmarks with the tag",
- "summary": "Get a bookmarks with the tag",
+ "description": "Get bookmarks with the tag",
+ "summary": "Get bookmarks with the tag",
"tags": [
"Tags"
],
@@ -2405,6 +2527,19 @@
},
{
"schema": {
+ "type": "string",
+ "enum": [
+ "asc",
+ "desc"
+ ],
+ "default": "desc"
+ },
+ "required": false,
+ "name": "sortOrder",
+ "in": "query"
+ },
+ {
+ "schema": {
"type": "number"
},
"required": false,
diff --git a/packages/open-api/lib/bookmarks.ts b/packages/open-api/lib/bookmarks.ts
index e344d656..8fb0eb8c 100644
--- a/packages/open-api/lib/bookmarks.ts
+++ b/packages/open-api/lib/bookmarks.ts
@@ -9,6 +9,7 @@ import {
zBareBookmarkSchema,
zManipulatedTagSchema,
zNewBookmarkRequestSchema,
+ zSortOrder,
zUpdateBookmarksRequestSchema,
} from "@karakeep/shared/types/bookmarks";
@@ -60,6 +61,10 @@ registry.registerPath({
.object({
archived: z.boolean().optional(),
favourited: z.boolean().optional(),
+ sortOrder: zSortOrder
+ .exclude(["relevance"])
+ .optional()
+ .default(zSortOrder.Enum.desc),
})
.merge(PaginationSchema)
.merge(IncludeContentSearchParamSchema),
@@ -87,6 +92,7 @@ registry.registerPath({
query: z
.object({
q: z.string(),
+ sortOrder: zSortOrder.optional().default(zSortOrder.Enum.relevance),
})
.merge(PaginationSchema)
.merge(IncludeContentSearchParamSchema),
diff --git a/packages/open-api/lib/lists.ts b/packages/open-api/lib/lists.ts
index 2273d33b..992b96c4 100644
--- a/packages/open-api/lib/lists.ts
+++ b/packages/open-api/lib/lists.ts
@@ -4,6 +4,7 @@ import {
} from "@asteasolutions/zod-to-openapi";
import { z } from "zod";
+import { zSortOrder } from "@karakeep/shared/types/bookmarks";
import {
zBookmarkListSchema,
zEditBookmarkListSchema,
@@ -190,13 +191,21 @@ registry.registerPath({
registry.registerPath({
method: "get",
path: "/lists/{listId}/bookmarks",
- description: "Get the bookmarks in a list",
- summary: "Get a bookmarks in a list",
+ description: "Get bookmarks in the list",
+ summary: "Get bookmarks in the list",
tags: ["Lists"],
security: [{ [BearerAuth.name]: [] }],
request: {
params: z.object({ listId: ListIdSchema }),
- query: PaginationSchema.merge(IncludeContentSearchParamSchema),
+ query: z
+ .object({
+ sortOrder: zSortOrder
+ .exclude(["relevance"])
+ .optional()
+ .default(zSortOrder.Enum.desc),
+ })
+ .merge(PaginationSchema)
+ .merge(IncludeContentSearchParamSchema),
},
responses: {
200: {
@@ -232,14 +241,6 @@ registry.registerPath({
204: {
description: "No content - the bookmark was added",
},
- 400: {
- description: "Bookmark already in list",
- content: {
- "application/json": {
- schema: ErrorSchema,
- },
- },
- },
404: {
description: "List or bookmark not found",
content: {
diff --git a/packages/open-api/lib/tags.ts b/packages/open-api/lib/tags.ts
index c51e3b84..0a4f62cb 100644
--- a/packages/open-api/lib/tags.ts
+++ b/packages/open-api/lib/tags.ts
@@ -4,8 +4,11 @@ import {
} from "@asteasolutions/zod-to-openapi";
import { z } from "zod";
+import { zSortOrder } from "@karakeep/shared/types/bookmarks";
import {
+ zCreateTagRequestSchema,
zGetTagResponseSchema,
+ zTagBasicSchema,
zUpdateTagRequestSchema,
} from "@karakeep/shared/types/tags";
@@ -56,6 +59,35 @@ registry.registerPath({
});
registry.registerPath({
+ method: "post",
+ path: "/tags",
+ description: "Create a new tag",
+ summary: "Create a new tag",
+ tags: ["Tags"],
+ security: [{ [BearerAuth.name]: [] }],
+ request: {
+ body: {
+ description: "The data to create the tag with.",
+ content: {
+ "application/json": {
+ schema: zCreateTagRequestSchema,
+ },
+ },
+ },
+ },
+ responses: {
+ 201: {
+ description: "The created tag",
+ content: {
+ "application/json": {
+ schema: zTagBasicSchema,
+ },
+ },
+ },
+ },
+});
+
+registry.registerPath({
method: "get",
path: "/tags/{tagId}",
description: "Get tag by its id",
@@ -134,7 +166,7 @@ registry.registerPath({
description: "The updated tag",
content: {
"application/json": {
- schema: TagSchema,
+ schema: zTagBasicSchema,
},
},
},
@@ -152,13 +184,21 @@ registry.registerPath({
registry.registerPath({
method: "get",
path: "/tags/{tagId}/bookmarks",
- description: "Get the bookmarks with the tag",
- summary: "Get a bookmarks with the tag",
+ description: "Get bookmarks with the tag",
+ summary: "Get bookmarks with the tag",
tags: ["Tags"],
security: [{ [BearerAuth.name]: [] }],
request: {
params: z.object({ tagId: TagIdSchema }),
- query: PaginationSchema.merge(IncludeContentSearchParamSchema),
+ query: z
+ .object({
+ sortOrder: zSortOrder
+ .exclude(["relevance"])
+ .optional()
+ .default(zSortOrder.Enum.desc),
+ })
+ .merge(PaginationSchema)
+ .merge(IncludeContentSearchParamSchema),
},
responses: {
200: {
diff --git a/packages/open-api/package.json b/packages/open-api/package.json
index dfbb0bb6..900af481 100644
--- a/packages/open-api/package.json
+++ b/packages/open-api/package.json
@@ -19,6 +19,7 @@
"typecheck": "tsc --noEmit",
"generate": "tsx index.ts",
"format": "prettier . --ignore-path ../../.prettierignore",
+ "format:fix": "prettier . --write --ignore-path ../../.prettierignore",
"lint": "eslint ."
},
"main": "index.ts",
diff --git a/packages/open-api/tsconfig.json b/packages/open-api/tsconfig.json
index d97c8ef4..a795b96a 100644
--- a/packages/open-api/tsconfig.json
+++ b/packages/open-api/tsconfig.json
@@ -5,5 +5,5 @@
"exclude": ["node_modules"],
"compilerOptions": {
"tsBuildInfoFile": "node_modules/.cache/tsbuildinfo.json"
- },
+ }
}
diff --git a/packages/sdk/src/karakeep-api.d.ts b/packages/sdk/src/karakeep-api.d.ts
index 0b434b3d..e8c048c4 100644
--- a/packages/sdk/src/karakeep-api.d.ts
+++ b/packages/sdk/src/karakeep-api.d.ts
@@ -20,6 +20,7 @@ export interface paths {
query?: {
archived?: boolean;
favourited?: boolean;
+ sortOrder?: "asc" | "desc";
limit?: number;
cursor?: components["schemas"]["Cursor"];
/** @description If set to true, bookmark's content will be included in the response. Note, this content can be large for some bookmarks. */
@@ -135,6 +136,7 @@ export interface paths {
parameters: {
query: {
q: string;
+ sortOrder?: "asc" | "desc" | "relevance";
limit?: number;
cursor?: components["schemas"]["Cursor"];
/** @description If set to true, bookmark's content will be included in the response. Note, this content can be large for some bookmarks. */
@@ -971,12 +973,13 @@ export interface paths {
cookie?: never;
};
/**
- * Get a bookmarks in a list
- * @description Get the bookmarks in a list
+ * Get bookmarks in the list
+ * @description Get bookmarks in the list
*/
get: {
parameters: {
query?: {
+ sortOrder?: "asc" | "desc";
limit?: number;
cursor?: components["schemas"]["Cursor"];
/** @description If set to true, bookmark's content will be included in the response. Note, this content can be large for some bookmarks. */
@@ -1052,18 +1055,6 @@ export interface paths {
};
content?: never;
};
- /** @description Bookmark already in list */
- 400: {
- headers: {
- [name: string]: unknown;
- };
- content: {
- "application/json": {
- code: string;
- message: string;
- };
- };
- };
/** @description List or bookmark not found */
404: {
headers: {
@@ -1167,7 +1158,40 @@ export interface paths {
};
};
put?: never;
- post?: never;
+ /**
+ * Create a new tag
+ * @description Create a new tag
+ */
+ post: {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ /** @description The data to create the tag with. */
+ requestBody?: {
+ content: {
+ "application/json": {
+ name: string;
+ };
+ };
+ };
+ responses: {
+ /** @description The created tag */
+ 201: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": {
+ id: string;
+ name: string;
+ };
+ };
+ };
+ };
+ };
delete?: never;
options?: never;
head?: never;
@@ -1287,7 +1311,10 @@ export interface paths {
[name: string]: unknown;
};
content: {
- "application/json": components["schemas"]["Tag"];
+ "application/json": {
+ id: string;
+ name: string;
+ };
};
};
/** @description Tag not found */
@@ -1314,12 +1341,13 @@ export interface paths {
cookie?: never;
};
/**
- * Get a bookmarks with the tag
- * @description Get the bookmarks with the tag
+ * Get bookmarks with the tag
+ * @description Get bookmarks with the tag
*/
get: {
parameters: {
query?: {
+ sortOrder?: "asc" | "desc";
limit?: number;
cursor?: components["schemas"]["Cursor"];
/** @description If set to true, bookmark's content will be included in the response. Note, this content can be large for some bookmarks. */
diff --git a/packages/shared-react/hooks/bookmarks.ts b/packages/shared-react/hooks/bookmarks.ts
index e06c7841..15b678cc 100644
--- a/packages/shared-react/hooks/bookmarks.ts
+++ b/packages/shared-react/hooks/bookmarks.ts
@@ -1,5 +1,6 @@
+import { isBookmarkStillLoading } from "@karakeep/shared/utils/bookmarkUtils";
+
import { api } from "../trpc";
-import { isBookmarkStillLoading } from "../utils/bookmarkUtils";
import { useBookmarkGridContext } from "./bookmark-grid-context";
import { useAddBookmarkToList } from "./lists";
diff --git a/packages/shared-react/hooks/users.ts b/packages/shared-react/hooks/users.ts
new file mode 100644
index 00000000..e896f8e4
--- /dev/null
+++ b/packages/shared-react/hooks/users.ts
@@ -0,0 +1,14 @@
+import { api } from "../trpc";
+
+export function useUpdateUserSettings(
+ ...opts: Parameters<typeof api.users.updateSettings.useMutation>
+) {
+ const apiUtils = api.useUtils();
+ return api.users.updateSettings.useMutation({
+ ...opts[0],
+ onSuccess: (res, req, meta) => {
+ apiUtils.users.settings.invalidate();
+ return opts[0]?.onSuccess?.(res, req, meta);
+ },
+ });
+}
diff --git a/packages/shared-react/utils/bookmarkUtils.ts b/packages/shared-react/utils/bookmarkUtils.ts
index 1f840f78..e69de29b 100644
--- a/packages/shared-react/utils/bookmarkUtils.ts
+++ b/packages/shared-react/utils/bookmarkUtils.ts
@@ -1,70 +0,0 @@
-import {
- BookmarkTypes,
- ZBookmark,
- ZBookmarkedLink,
-} from "@karakeep/shared/types/bookmarks";
-
-import { getAssetUrl } from "./assetUtils";
-
-const MAX_LOADING_MSEC = 30 * 1000;
-
-export function getBookmarkLinkImageUrl(bookmark: ZBookmarkedLink) {
- if (bookmark.imageAssetId) {
- return { url: getAssetUrl(bookmark.imageAssetId), localAsset: true };
- }
- if (bookmark.screenshotAssetId) {
- return { url: getAssetUrl(bookmark.screenshotAssetId), localAsset: true };
- }
- return bookmark.imageUrl
- ? { url: bookmark.imageUrl, localAsset: false }
- : null;
-}
-
-export function isBookmarkStillCrawling(bookmark: ZBookmark) {
- return (
- bookmark.content.type == BookmarkTypes.LINK &&
- !bookmark.content.crawledAt &&
- Date.now().valueOf() - bookmark.createdAt.valueOf() < MAX_LOADING_MSEC
- );
-}
-
-export function isBookmarkStillTagging(bookmark: ZBookmark) {
- return (
- bookmark.taggingStatus == "pending" &&
- Date.now().valueOf() - bookmark.createdAt.valueOf() < MAX_LOADING_MSEC
- );
-}
-
-export function isBookmarkStillLoading(bookmark: ZBookmark) {
- return isBookmarkStillTagging(bookmark) || isBookmarkStillCrawling(bookmark);
-}
-
-export function getSourceUrl(bookmark: ZBookmark) {
- if (bookmark.content.type === BookmarkTypes.LINK) {
- return bookmark.content.url;
- }
- if (bookmark.content.type === BookmarkTypes.ASSET) {
- return bookmark.content.sourceUrl ?? null;
- }
- if (bookmark.content.type === BookmarkTypes.TEXT) {
- return bookmark.content.sourceUrl ?? null;
- }
- return null;
-}
-
-export function getBookmarkTitle(bookmark: ZBookmark) {
- let title: string | null = null;
- switch (bookmark.content.type) {
- case BookmarkTypes.LINK:
- title = bookmark.content.title ?? bookmark.content.url;
- break;
- case BookmarkTypes.TEXT:
- title = null;
- break;
- case BookmarkTypes.ASSET:
- title = bookmark.content.fileName ?? null;
- break;
- }
-
- return bookmark.title ? bookmark.title : title;
-}
diff --git a/packages/shared/config.ts b/packages/shared/config.ts
index 046583c6..b899dbeb 100644
--- a/packages/shared/config.ts
+++ b/packages/shared/config.ts
@@ -17,6 +17,8 @@ const optionalStringBool = () =>
const allEnv = z.object({
API_URL: z.string().url().default("http://localhost:3000"),
+ NEXTAUTH_URL: z.string().url().default("http://localhost:3000"),
+ NEXTAUTH_SECRET: z.string().optional(),
DISABLE_SIGNUPS: stringBool("false"),
DISABLE_PASSWORD_AUTH: stringBool("false"),
OAUTH_ALLOW_DANGEROUS_EMAIL_ACCOUNT_LINKING: stringBool("false"),
@@ -40,6 +42,8 @@ const allEnv = z.object({
INFERENCE_OUTPUT_SCHEMA: z
.enum(["structured", "json", "plain"])
.default("structured"),
+ INFERENCE_ENABLE_AUTO_TAGGING: stringBool("true"),
+ INFERENCE_ENABLE_AUTO_SUMMARIZATION: stringBool("false"),
OCR_CACHE_DIR: z.string().optional(),
OCR_LANGS: z
.string()
@@ -89,6 +93,14 @@ const allEnv = z.object({
const serverConfigSchema = allEnv.transform((val) => {
return {
apiUrl: val.API_URL,
+ publicUrl: val.NEXTAUTH_URL,
+ publicApiUrl: `${val.NEXTAUTH_URL}/api`,
+ signingSecret: () => {
+ if (!val.NEXTAUTH_SECRET) {
+ throw new Error("NEXTAUTH_SECRET is not set");
+ }
+ return val.NEXTAUTH_SECRET;
+ },
auth: {
disableSignups: val.DISABLE_SIGNUPS,
disablePasswordAuth: val.DISABLE_PASSWORD_AUTH,
@@ -120,6 +132,8 @@ const serverConfigSchema = allEnv.transform((val) => {
? ("structured" as const)
: ("plain" as const)
: val.INFERENCE_OUTPUT_SCHEMA,
+ enableAutoTagging: val.INFERENCE_ENABLE_AUTO_TAGGING,
+ enableAutoSummarization: val.INFERENCE_ENABLE_AUTO_SUMMARIZATION,
},
embedding: {
textModel: val.EMBEDDING_TEXT_MODEL,
@@ -177,12 +191,17 @@ const serverConfigSchema = allEnv.transform((val) => {
const serverConfig = serverConfigSchema.parse(process.env);
// Always explicitly pick up stuff from server config to avoid accidentally leaking stuff
export const clientConfig = {
+ publicUrl: serverConfig.publicUrl,
+ publicApiUrl: serverConfig.publicApiUrl,
demoMode: serverConfig.demoMode,
auth: {
disableSignups: serverConfig.auth.disableSignups,
disablePasswordAuth: serverConfig.auth.disablePasswordAuth,
},
inference: {
+ isConfigured:
+ !!serverConfig.inference.openAIApiKey ||
+ !!serverConfig.inference.ollamaBaseUrl,
inferredTagLang: serverConfig.inference.inferredTagLang,
},
serverVersion: serverConfig.serverVersion,
diff --git a/packages/shared/inference.ts b/packages/shared/inference.ts
index e1f21dae..04fa8cfc 100644
--- a/packages/shared/inference.ts
+++ b/packages/shared/inference.ts
@@ -71,6 +71,10 @@ class OpenAIInferenceClient implements InferenceClient {
this.openAI = new OpenAI({
apiKey: serverConfig.inference.openAIApiKey,
baseURL: serverConfig.inference.openAIBaseUrl,
+ defaultHeaders: {
+ "X-Title": "Karakeep",
+ "HTTP-Referer": "https://karakeep.app",
+ },
});
}
diff --git a/packages/shared/langs.ts b/packages/shared/langs.ts
index 5cbe923c..74e1a42a 100644
--- a/packages/shared/langs.ts
+++ b/packages/shared/langs.ts
@@ -6,6 +6,7 @@ export const langNameMappings: Record<string, string> = {
da: "Danish",
nl: "Dutch",
en: "English",
+ en_US: "English (US)",
fr: "French",
gl: "Galician",
de: "German",
diff --git a/packages/shared/package.json b/packages/shared/package.json
index a262267a..478fb77d 100644
--- a/packages/shared/package.json
+++ b/packages/shared/package.json
@@ -6,6 +6,7 @@
"type": "module",
"dependencies": {
"glob": "^11.0.0",
+ "js-tiktoken": "^1.0.20",
"liteque": "^0.3.2",
"meilisearch": "^0.37.0",
"ollama": "^0.5.14",
diff --git a/packages/shared/prompts.ts b/packages/shared/prompts.ts
index 40e706b2..b7957dd4 100644
--- a/packages/shared/prompts.ts
+++ b/packages/shared/prompts.ts
@@ -1,15 +1,15 @@
-// TODO: Use a proper tokenizer
+import { getEncoding } from "js-tiktoken";
+
+const encoding = getEncoding("o200k_base");
+
function calculateNumTokens(text: string) {
- return text.split(" ").length;
+ return encoding.encode(text).length;
}
function truncateContent(content: string, length: number) {
- let words = content.split(" ");
- if (words.length > length) {
- words = words.slice(0, length);
- content = words.join(" ");
- }
- return content;
+ const tokens = encoding.encode(content);
+ const truncatedTokens = tokens.slice(0, length);
+ return encoding.decode(truncatedTokens);
}
export function buildImagePrompt(lang: string, customPrompts: string[]) {
diff --git a/packages/shared/queues.ts b/packages/shared/queues.ts
index 571df568..a434414c 100644
--- a/packages/shared/queues.ts
+++ b/packages/shared/queues.ts
@@ -32,9 +32,10 @@ export const LinkCrawlerQueue = new SqliteQueue<ZCrawlLinkRequest>(
},
);
-// OpenAI Worker
+// Inference Worker
export const zOpenAIRequestSchema = z.object({
bookmarkId: z.string(),
+ type: z.enum(["summarize", "tag"]).default("tag"),
});
export type ZOpenAIRequest = z.infer<typeof zOpenAIRequestSchema>;
@@ -171,7 +172,7 @@ export const AssetPreprocessingQueue =
// Webhook worker
export const zWebhookRequestSchema = z.object({
bookmarkId: z.string(),
- operation: z.enum(["crawled", "created", "edited", "ai tagged"]),
+ operation: z.enum(["crawled", "created", "edited", "ai tagged", "deleted"]),
});
export type ZWebhookRequest = z.infer<typeof zWebhookRequestSchema>;
export const WebhookQueue = new SqliteQueue<ZWebhookRequest>(
@@ -195,7 +196,7 @@ export async function triggerWebhook(
});
}
-// RuleEgine worker
+// RuleEngine worker
export const zRuleEngineRequestSchema = z.object({
bookmarkId: z.string(),
events: z.array(zRuleEngineEventSchema),
diff --git a/packages/shared/signedTokens.ts b/packages/shared/signedTokens.ts
new file mode 100644
index 00000000..b5e27f3e
--- /dev/null
+++ b/packages/shared/signedTokens.ts
@@ -0,0 +1,71 @@
+import crypto from "node:crypto";
+import { z } from "zod";
+
+import serverConfig from "./config";
+
+const zTokenPayload = z.object({
+ payload: z.unknown(),
+ expiresAt: z.number(),
+});
+
+const zSignedTokenPayload = z.object({
+ payload: zTokenPayload,
+ signature: z.string(),
+});
+
+export type SignedTokenPayload = z.infer<typeof zSignedTokenPayload>;
+
+export function createSignedToken(
+ payload: unknown,
+ expiryEpoch?: number,
+): string {
+ const expiresAt = expiryEpoch ?? Date.now() + 5 * 60 * 1000; // 5 minutes from now
+
+ const toBeSigned: z.infer<typeof zTokenPayload> = {
+ payload,
+ expiresAt,
+ };
+
+ const payloadString = JSON.stringify(toBeSigned);
+ const signature = crypto
+ .createHmac("sha256", serverConfig.signingSecret())
+ .update(payloadString)
+ .digest("hex");
+
+ const tokenData: z.infer<typeof zSignedTokenPayload> = {
+ payload: toBeSigned,
+ signature,
+ };
+
+ return Buffer.from(JSON.stringify(tokenData)).toString("base64");
+}
+
+export function verifySignedToken<T>(
+ token: string,
+ schema: z.ZodSchema<T>,
+): T | null {
+ try {
+ const tokenData = zSignedTokenPayload.parse(
+ JSON.parse(Buffer.from(token, "base64").toString()),
+ );
+ const { payload, signature } = tokenData;
+
+ // Verify signature
+ const expectedSignature = crypto
+ .createHmac("sha256", serverConfig.signingSecret())
+ .update(JSON.stringify(payload))
+ .digest("hex");
+
+ if (signature !== expectedSignature) {
+ return null;
+ }
+ // Check expiry
+ if (Date.now() > payload.expiresAt) {
+ return null;
+ }
+
+ return schema.parse(payload.payload);
+ } catch {
+ return null;
+ }
+}
diff --git a/packages/shared/types/assets.ts b/packages/shared/types/assets.ts
new file mode 100644
index 00000000..fe0adcfd
--- /dev/null
+++ b/packages/shared/types/assets.ts
@@ -0,0 +1,6 @@
+import { z } from "zod";
+
+export const zAssetSignedTokenSchema = z.object({
+ assetId: z.string(),
+ userId: z.string(),
+});
diff --git a/packages/shared/types/bookmarks.ts b/packages/shared/types/bookmarks.ts
index 3cac2845..ea1ab717 100644
--- a/packages/shared/types/bookmarks.ts
+++ b/packages/shared/types/bookmarks.ts
@@ -12,7 +12,7 @@ export const enum BookmarkTypes {
UNKNOWN = "unknown",
}
-export const zSortOrder = z.enum(["asc", "desc"]);
+export const zSortOrder = z.enum(["asc", "desc", "relevance"]);
export type ZSortOrder = z.infer<typeof zSortOrder>;
export const zAssetTypesSchema = z.enum([
@@ -87,6 +87,7 @@ export const zBareBookmarkSchema = z.object({
archived: z.boolean(),
favourited: z.boolean(),
taggingStatus: z.enum(["success", "failure", "pending"]).nullable(),
+ summarizationStatus: z.enum(["success", "failure", "pending"]).nullable(),
note: z.string().nullish(),
summary: z.string().nullish(),
});
@@ -178,7 +179,7 @@ export const zGetBookmarksRequestSchema = z.object({
// The value is currently not being used, but keeping it so that client can still set it to true for older
// servers.
useCursorV2: z.boolean().optional(),
- sortOrder: zSortOrder.optional().default("desc"),
+ sortOrder: zSortOrder.exclude(["relevance"]).optional().default("desc"),
includeContent: z.boolean().optional().default(false),
});
export type ZGetBookmarksRequest = z.infer<typeof zGetBookmarksRequestSchema>;
@@ -238,6 +239,37 @@ export const zSearchBookmarksRequestSchema = z.object({
text: z.string(),
limit: z.number().max(MAX_NUM_BOOKMARKS_PER_PAGE).optional(),
cursor: zSearchBookmarksCursor.nullish(),
- sortOrder: zSortOrder.optional().default("desc"),
+ sortOrder: zSortOrder.optional().default("relevance"),
includeContent: z.boolean().optional().default(false),
});
+
+export const zPublicBookmarkSchema = z.object({
+ id: z.string(),
+ createdAt: z.date(),
+ modifiedAt: z.date().nullable(),
+ title: z.string().nullish(),
+ tags: z.array(z.string()),
+ description: z.string().nullish(),
+ bannerImageUrl: z.string().nullable(),
+ content: z.discriminatedUnion("type", [
+ z.object({
+ type: z.literal(BookmarkTypes.LINK),
+ url: z.string(),
+ author: z.string().nullish(),
+ }),
+ z.object({
+ type: z.literal(BookmarkTypes.TEXT),
+ text: z.string(),
+ }),
+ z.object({
+ type: z.literal(BookmarkTypes.ASSET),
+ assetType: z.enum(["image", "pdf"]),
+ assetId: z.string(),
+ assetUrl: z.string(),
+ fileName: z.string().nullish(),
+ sourceUrl: z.string().nullish(),
+ }),
+ ]),
+});
+
+export type ZPublicBookmark = z.infer<typeof zPublicBookmarkSchema>;
diff --git a/packages/shared/types/feeds.ts b/packages/shared/types/feeds.ts
index 3771624b..060f6d10 100644
--- a/packages/shared/types/feeds.ts
+++ b/packages/shared/types/feeds.ts
@@ -9,6 +9,7 @@ export const zFeedSchema = z.object({
id: z.string(),
name: z.string().min(1).max(MAX_FEED_NAME_LENGTH),
url: z.string().url(),
+ enabled: z.boolean(),
lastFetchedStatus: z.enum(["success", "failure", "pending"]).nullable(),
lastFetchedAt: z.date().nullable(),
});
@@ -18,10 +19,12 @@ export type ZFeed = z.infer<typeof zFeedSchema>;
export const zNewFeedSchema = z.object({
name: z.string().min(1).max(MAX_FEED_NAME_LENGTH),
url: z.string().max(MAX_FEED_URL_LENGTH).url(),
+ enabled: z.boolean(),
});
export const zUpdateFeedSchema = z.object({
feedId: z.string(),
- name: z.string().min(1).max(MAX_FEED_NAME_LENGTH),
- url: z.string().max(MAX_FEED_URL_LENGTH).url(),
+ name: z.string().min(1).max(MAX_FEED_NAME_LENGTH).optional(),
+ url: z.string().max(MAX_FEED_URL_LENGTH).url().optional(),
+ enabled: z.boolean().optional(),
});
diff --git a/packages/shared/types/lists.ts b/packages/shared/types/lists.ts
index 7ef5687c..51fb458c 100644
--- a/packages/shared/types/lists.ts
+++ b/packages/shared/types/lists.ts
@@ -47,6 +47,7 @@ export const zBookmarkListSchema = z.object({
parentId: z.string().nullable(),
type: z.enum(["manual", "smart"]).default("manual"),
query: z.string().nullish(),
+ public: z.boolean(),
});
export type ZBookmarkList = z.infer<typeof zBookmarkListSchema>;
@@ -66,6 +67,7 @@ export const zEditBookmarkListSchema = z.object({
icon: z.string().optional(),
parentId: z.string().nullish(),
query: z.string().min(1).optional(),
+ public: z.boolean().optional(),
});
export const zEditBookmarkListSchemaWithValidation = zEditBookmarkListSchema
diff --git a/packages/shared/types/pagination.ts b/packages/shared/types/pagination.ts
index d2312982..3b9dead4 100644
--- a/packages/shared/types/pagination.ts
+++ b/packages/shared/types/pagination.ts
@@ -4,3 +4,5 @@ export const zCursorV2 = z.object({
createdAt: z.date(),
id: z.string(),
});
+
+export type ZCursor = z.infer<typeof zCursorV2>;
diff --git a/packages/shared/types/tags.ts b/packages/shared/types/tags.ts
index c2d8b28d..c7a0103e 100644
--- a/packages/shared/types/tags.ts
+++ b/packages/shared/types/tags.ts
@@ -1,5 +1,9 @@
import { z } from "zod";
+export const zCreateTagRequestSchema = z.object({
+ name: z.string().min(1),
+});
+
export const zAttachedByEnumSchema = z.enum(["ai", "human"]);
export type ZAttachedByEnum = z.infer<typeof zAttachedByEnumSchema>;
export const zBookmarkTagSchema = z.object({
diff --git a/packages/shared/types/users.ts b/packages/shared/types/users.ts
index 86c5a9ee..0abe13b1 100644
--- a/packages/shared/types/users.ts
+++ b/packages/shared/types/users.ts
@@ -39,3 +39,18 @@ export const zUserStatsResponseSchema = z.object({
numLists: z.number(),
numHighlights: z.number(),
});
+
+export const zUserSettingsSchema = z.object({
+ bookmarkClickAction: z.enum([
+ "open_original_link",
+ "expand_bookmark_preview",
+ ]),
+ archiveDisplayBehaviour: z.enum(["show", "hide"]),
+});
+
+export type ZUserSettings = z.infer<typeof zUserSettingsSchema>;
+
+export const zUpdateUserSettingsSchema = zUserSettingsSchema.partial().pick({
+ bookmarkClickAction: true,
+ archiveDisplayBehaviour: true,
+});
diff --git a/packages/shared/types/webhooks.ts b/packages/shared/types/webhooks.ts
index 819951ce..75980b70 100644
--- a/packages/shared/types/webhooks.ts
+++ b/packages/shared/types/webhooks.ts
@@ -8,6 +8,7 @@ export const zWebhookEventSchema = z.enum([
"edited",
"crawled",
"ai tagged",
+ "deleted",
]);
export type ZWebhookEvent = z.infer<typeof zWebhookEventSchema>;
diff --git a/packages/shared-react/utils/assetUtils.ts b/packages/shared/utils/assetUtils.ts
index 119451d9..119451d9 100644
--- a/packages/shared-react/utils/assetUtils.ts
+++ b/packages/shared/utils/assetUtils.ts
diff --git a/packages/shared/utils/bookmarkUtils.ts b/packages/shared/utils/bookmarkUtils.ts
new file mode 100644
index 00000000..97ef08fc
--- /dev/null
+++ b/packages/shared/utils/bookmarkUtils.ts
@@ -0,0 +1,90 @@
+import { BookmarkTypes, ZBookmark, ZBookmarkedLink } from "../types/bookmarks";
+import { getAssetUrl } from "./assetUtils";
+
+const MAX_LOADING_MSEC = 30 * 1000;
+
+export function getBookmarkLinkAssetIdOrUrl(bookmark: ZBookmarkedLink) {
+ if (bookmark.imageAssetId) {
+ return { assetId: bookmark.imageAssetId, localAsset: true as const };
+ }
+ if (bookmark.screenshotAssetId) {
+ return { assetId: bookmark.screenshotAssetId, localAsset: true as const };
+ }
+ return bookmark.imageUrl
+ ? { url: bookmark.imageUrl, localAsset: false as const }
+ : null;
+}
+
+export function getBookmarkLinkImageUrl(bookmark: ZBookmarkedLink) {
+ const assetOrUrl = getBookmarkLinkAssetIdOrUrl(bookmark);
+ if (!assetOrUrl) {
+ return null;
+ }
+ if (!assetOrUrl.localAsset) {
+ return assetOrUrl;
+ }
+ return {
+ url: getAssetUrl(assetOrUrl.assetId),
+ localAsset: true,
+ };
+}
+
+export function isBookmarkStillCrawling(bookmark: ZBookmark) {
+ return (
+ bookmark.content.type == BookmarkTypes.LINK &&
+ !bookmark.content.crawledAt &&
+ Date.now().valueOf() - bookmark.createdAt.valueOf() < MAX_LOADING_MSEC
+ );
+}
+
+export function isBookmarkStillTagging(bookmark: ZBookmark) {
+ return (
+ bookmark.taggingStatus == "pending" &&
+ Date.now().valueOf() - bookmark.createdAt.valueOf() < MAX_LOADING_MSEC
+ );
+}
+
+export function isBookmarkStillSummarizing(bookmark: ZBookmark) {
+ return (
+ bookmark.summarizationStatus == "pending" &&
+ Date.now().valueOf() - bookmark.createdAt.valueOf() < MAX_LOADING_MSEC
+ );
+}
+
+export function isBookmarkStillLoading(bookmark: ZBookmark) {
+ return (
+ isBookmarkStillTagging(bookmark) ||
+ isBookmarkStillCrawling(bookmark) ||
+ isBookmarkStillSummarizing(bookmark)
+ );
+}
+
+export function getSourceUrl(bookmark: ZBookmark) {
+ if (bookmark.content.type === BookmarkTypes.LINK) {
+ return bookmark.content.url;
+ }
+ if (bookmark.content.type === BookmarkTypes.ASSET) {
+ return bookmark.content.sourceUrl ?? null;
+ }
+ if (bookmark.content.type === BookmarkTypes.TEXT) {
+ return bookmark.content.sourceUrl ?? null;
+ }
+ return null;
+}
+
+export function getBookmarkTitle(bookmark: ZBookmark) {
+ let title: string | null = null;
+ switch (bookmark.content.type) {
+ case BookmarkTypes.LINK:
+ title = bookmark.content.title ?? bookmark.content.url;
+ break;
+ case BookmarkTypes.TEXT:
+ title = null;
+ break;
+ case BookmarkTypes.ASSET:
+ title = bookmark.content.fileName ?? null;
+ break;
+ }
+
+ return bookmark.title ? bookmark.title : title;
+}
diff --git a/packages/trpc/lib/__tests__/search.test.ts b/packages/trpc/lib/__tests__/search.test.ts
index 72b53368..9d9b39d7 100644
--- a/packages/trpc/lib/__tests__/search.test.ts
+++ b/packages/trpc/lib/__tests__/search.test.ts
@@ -34,12 +34,6 @@ beforeEach(async () => {
email: "test@example.com",
role: "user",
},
- {
- id: "another-user",
- name: "Another User",
- email: "another@example.com",
- role: "user",
- },
]);
// Setup test data
@@ -92,14 +86,6 @@ beforeEach(async () => {
favourited: false,
createdAt: new Date("2024-01-06"),
},
- {
- id: "b7",
- type: BookmarkTypes.ASSET,
- userId: "another-user",
- archived: true,
- favourited: false,
- createdAt: new Date("2024-01-06"),
- },
]);
await db.insert(bookmarkLinks).values([
@@ -157,21 +143,6 @@ beforeEach(async () => {
type: "manual",
},
{ id: "l4", userId: testUserId, name: "work", icon: "💼", type: "manual" },
- {
- id: "l5",
- userId: testUserId,
- name: "smartlist",
- icon: "🧠",
- type: "smart",
- query: "#tag1 or #tag2",
- },
- {
- id: "l6",
- userId: testUserId,
- name: "emptylist",
- icon: "∅",
- type: "manual",
- },
]);
await db.insert(bookmarksInLists).values([
@@ -253,26 +224,6 @@ describe("getBookmarkIdsFromMatcher", () => {
expect(result).toEqual(["b1", "b6"]);
});
- it("should handle listName matcher with smartList", async () => {
- const matcher: Matcher = {
- type: "listName",
- listName: "smartlist",
- inverse: false,
- };
- const result = await getBookmarkIdsFromMatcher(mockCtx, matcher);
- expect(result).toEqual(["b1", "b2"]);
- });
-
- it("should handle listName matcher with empty list", async () => {
- const matcher: Matcher = {
- type: "listName",
- listName: "emptylist",
- inverse: false,
- };
- const result = await getBookmarkIdsFromMatcher(mockCtx, matcher);
- expect(result).toEqual([]);
- });
-
it("should handle listName matcher with inverse=true", async () => {
const matcher: Matcher = {
type: "listName",
@@ -283,26 +234,6 @@ describe("getBookmarkIdsFromMatcher", () => {
expect(result.sort()).toEqual(["b2", "b3", "b4", "b5"]);
});
- it("should handle listName matcher with smartList with inverse=true", async () => {
- const matcher: Matcher = {
- type: "listName",
- listName: "smartlist",
- inverse: true,
- };
- const result = await getBookmarkIdsFromMatcher(mockCtx, matcher);
- expect(result).toEqual(["b3", "b4", "b5", "b6"]);
- });
-
- it("should handle listName matcher with empty list with inverse=true", async () => {
- const matcher: Matcher = {
- type: "listName",
- listName: "emptylist",
- inverse: true,
- };
- const result = await getBookmarkIdsFromMatcher(mockCtx, matcher);
- expect(result).toEqual(["b1", "b2", "b3", "b4", "b5", "b6"]);
- });
-
it("should handle archived matcher", async () => {
const matcher: Matcher = { type: "archived", archived: true };
const result = await getBookmarkIdsFromMatcher(mockCtx, matcher);
diff --git a/packages/trpc/lib/attachments.ts b/packages/trpc/lib/attachments.ts
index 15cbba74..739aa8f5 100644
--- a/packages/trpc/lib/attachments.ts
+++ b/packages/trpc/lib/attachments.ts
@@ -55,7 +55,7 @@ export function isAllowedToAttachAsset(type: ZAssetType) {
screenshot: true,
assetScreenshot: true,
fullPageArchive: false,
- precrawledArchive: false,
+ precrawledArchive: true,
bannerImage: true,
video: false,
bookmarkAsset: false,
diff --git a/packages/trpc/lib/impersonate.ts b/packages/trpc/lib/impersonate.ts
new file mode 100644
index 00000000..f44a2c70
--- /dev/null
+++ b/packages/trpc/lib/impersonate.ts
@@ -0,0 +1,30 @@
+import { eq } from "drizzle-orm";
+
+import { db } from "@karakeep/db";
+import { users } from "@karakeep/db/schema";
+
+import { AuthedContext } from "..";
+
+export async function buildImpersonatingAuthedContext(
+ userId: string,
+): Promise<AuthedContext> {
+ const user = await db.query.users.findFirst({
+ where: eq(users.id, userId),
+ });
+ if (!user) {
+ throw new Error("User not found");
+ }
+
+ return {
+ user: {
+ id: user.id,
+ name: user.name,
+ email: user.email,
+ role: user.role,
+ },
+ db,
+ req: {
+ ip: null,
+ },
+ };
+}
diff --git a/packages/trpc/lib/search.ts b/packages/trpc/lib/search.ts
index 7bb78a01..d4130798 100644
--- a/packages/trpc/lib/search.ts
+++ b/packages/trpc/lib/search.ts
@@ -4,21 +4,21 @@ import {
exists,
gt,
gte,
- inArray,
isNotNull,
like,
lt,
lte,
ne,
notExists,
- notInArray,
notLike,
} from "drizzle-orm";
import {
bookmarkAssets,
bookmarkLinks,
+ bookmarkLists,
bookmarks,
+ bookmarksInLists,
bookmarkTags,
rssFeedImportsTable,
rssFeedsTable,
@@ -28,7 +28,6 @@ import { Matcher } from "@karakeep/shared/types/search";
import { toAbsoluteDate } from "@karakeep/shared/utils/relativeDateUtils";
import { AuthedContext } from "..";
-import { List } from "../models/lists";
interface BookmarkQueryReturnType {
id: string;
@@ -88,20 +87,21 @@ function union(vals: BookmarkQueryReturnType[][]): BookmarkQueryReturnType[] {
}
async function getIds(
- ctx: AuthedContext,
+ db: AuthedContext["db"],
+ userId: string,
matcher: Matcher,
): Promise<BookmarkQueryReturnType[]> {
switch (matcher.type) {
case "tagName": {
const comp = matcher.inverse ? notExists : exists;
- return ctx.db
+ return db
.selectDistinct({ id: bookmarks.id })
.from(bookmarks)
.where(
and(
- eq(bookmarks.userId, ctx.user.id),
+ eq(bookmarks.userId, userId),
comp(
- ctx.db
+ db
.select()
.from(tagsOnBookmarks)
.innerJoin(
@@ -111,7 +111,7 @@ async function getIds(
.where(
and(
eq(tagsOnBookmarks.bookmarkId, bookmarks.id),
- eq(bookmarkTags.userId, ctx.user.id),
+ eq(bookmarkTags.userId, userId),
eq(bookmarkTags.name, matcher.tagName),
),
),
@@ -121,14 +121,14 @@ async function getIds(
}
case "tagged": {
const comp = matcher.tagged ? exists : notExists;
- return ctx.db
+ return db
.select({ id: bookmarks.id })
.from(bookmarks)
.where(
and(
- eq(bookmarks.userId, ctx.user.id),
+ eq(bookmarks.userId, userId),
comp(
- ctx.db
+ db
.select()
.from(tagsOnBookmarks)
.where(and(eq(tagsOnBookmarks.bookmarkId, bookmarks.id))),
@@ -137,43 +137,59 @@ async function getIds(
);
}
case "listName": {
- const lists = await List.fromName(ctx, matcher.listName);
- const ids = await Promise.all(lists.map((l) => l.getBookmarkIds()));
- const comp = matcher.inverse ? notInArray : inArray;
- return ctx.db
- .select({ id: bookmarks.id })
+ const comp = matcher.inverse ? notExists : exists;
+ return db
+ .selectDistinct({ id: bookmarks.id })
.from(bookmarks)
.where(
and(
- eq(bookmarks.userId, ctx.user.id),
- comp(bookmarks.id, ids.flat()),
+ eq(bookmarks.userId, userId),
+ comp(
+ db
+ .select()
+ .from(bookmarksInLists)
+ .innerJoin(
+ bookmarkLists,
+ eq(bookmarksInLists.listId, bookmarkLists.id),
+ )
+ .where(
+ and(
+ eq(bookmarksInLists.bookmarkId, bookmarks.id),
+ eq(bookmarkLists.userId, userId),
+ eq(bookmarkLists.name, matcher.listName),
+ ),
+ ),
+ ),
),
);
}
case "inlist": {
- const lists = await List.getAll(ctx);
- const ids = await Promise.all(lists.map((l) => l.getBookmarkIds()));
- const comp = matcher.inList ? inArray : notInArray;
- return ctx.db
+ const comp = matcher.inList ? exists : notExists;
+ return db
.select({ id: bookmarks.id })
.from(bookmarks)
.where(
and(
- eq(bookmarks.userId, ctx.user.id),
- comp(bookmarks.id, ids.flat()),
+ eq(bookmarks.userId, userId),
+ comp(
+ db
+ .select()
+ .from(bookmarksInLists)
+ .where(and(eq(bookmarksInLists.bookmarkId, bookmarks.id))),
+ ),
),
);
}
case "rssFeedName": {
const comp = matcher.inverse ? notExists : exists;
- return ctx.db
+ return db
.selectDistinct({ id: bookmarks.id })
.from(bookmarks)
.where(
and(
- eq(bookmarks.userId, ctx.user.id),
+ eq(bookmarks.userId, userId),
comp(
- ctx.db
+ db
.select()
.from(rssFeedImportsTable)
.innerJoin(
@@ -183,7 +199,7 @@ async function getIds(
.where(
and(
eq(rssFeedImportsTable.bookmarkId, bookmarks.id),
- eq(rssFeedsTable.userId, ctx.user.id),
+ eq(rssFeedsTable.userId, userId),
eq(rssFeedsTable.name, matcher.feedName),
),
),
@@ -192,36 +208,36 @@ async function getIds(
);
}
case "archived": {
- return ctx.db
+ return db
.select({ id: bookmarks.id })
.from(bookmarks)
.where(
and(
- eq(bookmarks.userId, ctx.user.id),
+ eq(bookmarks.userId, userId),
eq(bookmarks.archived, matcher.archived),
),
);
}
case "url": {
const comp = matcher.inverse ? notLike : like;
- return ctx.db
+ return db
.select({ id: bookmarkLinks.id })
.from(bookmarkLinks)
.leftJoin(bookmarks, eq(bookmarks.id, bookmarkLinks.id))
.where(
and(
- eq(bookmarks.userId, ctx.user.id),
+ eq(bookmarks.userId, userId),
comp(bookmarkLinks.url, `%${matcher.url}%`),
),
)
.union(
- ctx.db
+ db
.select({ id: bookmarkAssets.id })
.from(bookmarkAssets)
.leftJoin(bookmarks, eq(bookmarks.id, bookmarkAssets.id))
.where(
and(
- eq(bookmarks.userId, ctx.user.id),
+ eq(bookmarks.userId, userId),
// When a user is asking for a link, the inverse matcher should match only assets with URLs.
isNotNull(bookmarkAssets.sourceUrl),
comp(bookmarkAssets.sourceUrl, `%${matcher.url}%`),
@@ -230,73 +246,73 @@ async function getIds(
);
}
case "favourited": {
- return ctx.db
+ return db
.select({ id: bookmarks.id })
.from(bookmarks)
.where(
and(
- eq(bookmarks.userId, ctx.user.id),
+ eq(bookmarks.userId, userId),
eq(bookmarks.favourited, matcher.favourited),
),
);
}
case "dateAfter": {
const comp = matcher.inverse ? lt : gte;
- return ctx.db
+ return db
.select({ id: bookmarks.id })
.from(bookmarks)
.where(
and(
- eq(bookmarks.userId, ctx.user.id),
+ eq(bookmarks.userId, userId),
comp(bookmarks.createdAt, matcher.dateAfter),
),
);
}
case "dateBefore": {
const comp = matcher.inverse ? gt : lte;
- return ctx.db
+ return db
.select({ id: bookmarks.id })
.from(bookmarks)
.where(
and(
- eq(bookmarks.userId, ctx.user.id),
+ eq(bookmarks.userId, userId),
comp(bookmarks.createdAt, matcher.dateBefore),
),
);
}
case "age": {
const comp = matcher.relativeDate.direction === "newer" ? gte : lt;
- return ctx.db
+ return db
.select({ id: bookmarks.id })
.from(bookmarks)
.where(
and(
- eq(bookmarks.userId, ctx.user.id),
+ eq(bookmarks.userId, userId),
comp(bookmarks.createdAt, toAbsoluteDate(matcher.relativeDate)),
),
);
}
case "type": {
const comp = matcher.inverse ? ne : eq;
- return ctx.db
+ return db
.select({ id: bookmarks.id })
.from(bookmarks)
.where(
and(
- eq(bookmarks.userId, ctx.user.id),
+ eq(bookmarks.userId, userId),
comp(bookmarks.type, matcher.typeName),
),
);
}
case "and": {
const vals = await Promise.all(
- matcher.matchers.map((m) => getIds(ctx, m)),
+ matcher.matchers.map((m) => getIds(db, userId, m)),
);
return intersect(vals);
}
case "or": {
const vals = await Promise.all(
- matcher.matchers.map((m) => getIds(ctx, m)),
+ matcher.matchers.map((m) => getIds(db, userId, m)),
);
return union(vals);
}
@@ -311,6 +327,6 @@ export async function getBookmarkIdsFromMatcher(
ctx: AuthedContext,
matcher: Matcher,
): Promise<string[]> {
- const results = await getIds(ctx, matcher);
+ const results = await getIds(ctx.db, ctx.user.id, matcher);
return results.map((r) => r.id);
}
diff --git a/packages/trpc/models/bookmarks.ts b/packages/trpc/models/bookmarks.ts
new file mode 100644
index 00000000..6e9e5651
--- /dev/null
+++ b/packages/trpc/models/bookmarks.ts
@@ -0,0 +1,422 @@
+import { TRPCError } from "@trpc/server";
+import {
+ and,
+ asc,
+ desc,
+ eq,
+ exists,
+ gt,
+ gte,
+ inArray,
+ lt,
+ lte,
+ or,
+} from "drizzle-orm";
+import invariant from "tiny-invariant";
+import { z } from "zod";
+
+import {
+ assets,
+ AssetTypes,
+ bookmarkAssets,
+ bookmarkLinks,
+ bookmarks,
+ bookmarksInLists,
+ bookmarkTags,
+ bookmarkTexts,
+ rssFeedImportsTable,
+ tagsOnBookmarks,
+} from "@karakeep/db/schema";
+import serverConfig from "@karakeep/shared/config";
+import { createSignedToken } from "@karakeep/shared/signedTokens";
+import { zAssetSignedTokenSchema } from "@karakeep/shared/types/assets";
+import {
+ BookmarkTypes,
+ DEFAULT_NUM_BOOKMARKS_PER_PAGE,
+ ZBookmark,
+ ZBookmarkContent,
+ zGetBookmarksRequestSchema,
+ ZPublicBookmark,
+} from "@karakeep/shared/types/bookmarks";
+import { ZCursor } from "@karakeep/shared/types/pagination";
+import {
+ getBookmarkLinkAssetIdOrUrl,
+ getBookmarkTitle,
+} from "@karakeep/shared/utils/bookmarkUtils";
+
+import { AuthedContext } from "..";
+import { mapDBAssetTypeToUserType } from "../lib/attachments";
+import { List } from "./lists";
+import { PrivacyAware } from "./privacy";
+
+export class Bookmark implements PrivacyAware {
+ protected constructor(
+ protected ctx: AuthedContext,
+ public bookmark: ZBookmark & { userId: string },
+ ) {}
+
+ ensureCanAccess(ctx: AuthedContext): void {
+ if (this.bookmark.userId != ctx.user.id) {
+ throw new TRPCError({
+ code: "FORBIDDEN",
+ message: "User is not allowed to access resource",
+ });
+ }
+ }
+
+ static fromData(ctx: AuthedContext, data: ZBookmark) {
+ return new Bookmark(ctx, {
+ ...data,
+ userId: ctx.user.id,
+ });
+ }
+
+ static async loadMulti(
+ ctx: AuthedContext,
+ input: z.infer<typeof zGetBookmarksRequestSchema>,
+ ): Promise<{
+ bookmarks: Bookmark[];
+ nextCursor: ZCursor | null;
+ }> {
+ if (input.ids && input.ids.length == 0) {
+ return { bookmarks: [], nextCursor: null };
+ }
+ if (!input.limit) {
+ input.limit = DEFAULT_NUM_BOOKMARKS_PER_PAGE;
+ }
+ if (input.listId) {
+ const list = await List.fromId(ctx, input.listId);
+ if (list.type === "smart") {
+ input.ids = await list.getBookmarkIds();
+ delete input.listId;
+ }
+ }
+
+ const sq = ctx.db.$with("bookmarksSq").as(
+ ctx.db
+ .select()
+ .from(bookmarks)
+ .where(
+ and(
+ eq(bookmarks.userId, ctx.user.id),
+ input.archived !== undefined
+ ? eq(bookmarks.archived, input.archived)
+ : undefined,
+ input.favourited !== undefined
+ ? eq(bookmarks.favourited, input.favourited)
+ : undefined,
+ input.ids ? inArray(bookmarks.id, input.ids) : undefined,
+ input.tagId !== undefined
+ ? exists(
+ ctx.db
+ .select()
+ .from(tagsOnBookmarks)
+ .where(
+ and(
+ eq(tagsOnBookmarks.bookmarkId, bookmarks.id),
+ eq(tagsOnBookmarks.tagId, input.tagId),
+ ),
+ ),
+ )
+ : undefined,
+ input.rssFeedId !== undefined
+ ? exists(
+ ctx.db
+ .select()
+ .from(rssFeedImportsTable)
+ .where(
+ and(
+ eq(rssFeedImportsTable.bookmarkId, bookmarks.id),
+ eq(rssFeedImportsTable.rssFeedId, input.rssFeedId),
+ ),
+ ),
+ )
+ : undefined,
+ input.listId !== undefined
+ ? exists(
+ ctx.db
+ .select()
+ .from(bookmarksInLists)
+ .where(
+ and(
+ eq(bookmarksInLists.bookmarkId, bookmarks.id),
+ eq(bookmarksInLists.listId, input.listId),
+ ),
+ ),
+ )
+ : undefined,
+ input.cursor
+ ? input.sortOrder === "asc"
+ ? or(
+ gt(bookmarks.createdAt, input.cursor.createdAt),
+ and(
+ eq(bookmarks.createdAt, input.cursor.createdAt),
+ gte(bookmarks.id, input.cursor.id),
+ ),
+ )
+ : or(
+ lt(bookmarks.createdAt, input.cursor.createdAt),
+ and(
+ eq(bookmarks.createdAt, input.cursor.createdAt),
+ lte(bookmarks.id, input.cursor.id),
+ ),
+ )
+ : undefined,
+ ),
+ )
+ .limit(input.limit + 1)
+ .orderBy(
+ input.sortOrder === "asc"
+ ? asc(bookmarks.createdAt)
+ : desc(bookmarks.createdAt),
+ desc(bookmarks.id),
+ ),
+ );
+ // TODO: Consider not inlining the tags in the response of getBookmarks as this query is getting kinda expensive
+ const results = await ctx.db
+ .with(sq)
+ .select()
+ .from(sq)
+ .leftJoin(tagsOnBookmarks, eq(sq.id, tagsOnBookmarks.bookmarkId))
+ .leftJoin(bookmarkTags, eq(tagsOnBookmarks.tagId, bookmarkTags.id))
+ .leftJoin(bookmarkLinks, eq(bookmarkLinks.id, sq.id))
+ .leftJoin(bookmarkTexts, eq(bookmarkTexts.id, sq.id))
+ .leftJoin(bookmarkAssets, eq(bookmarkAssets.id, sq.id))
+ .leftJoin(assets, eq(assets.bookmarkId, sq.id))
+ .orderBy(desc(sq.createdAt), desc(sq.id));
+
+ const bookmarksRes = results.reduce<Record<string, ZBookmark>>(
+ (acc, row) => {
+ const bookmarkId = row.bookmarksSq.id;
+ if (!acc[bookmarkId]) {
+ let content: ZBookmarkContent;
+ if (row.bookmarkLinks) {
+ content = {
+ type: BookmarkTypes.LINK,
+ url: row.bookmarkLinks.url,
+ title: row.bookmarkLinks.title,
+ description: row.bookmarkLinks.description,
+ imageUrl: row.bookmarkLinks.imageUrl,
+ favicon: row.bookmarkLinks.favicon,
+ htmlContent: input.includeContent
+ ? row.bookmarkLinks.htmlContent
+ : null,
+ crawledAt: row.bookmarkLinks.crawledAt,
+ author: row.bookmarkLinks.author,
+ publisher: row.bookmarkLinks.publisher,
+ datePublished: row.bookmarkLinks.datePublished,
+ dateModified: row.bookmarkLinks.dateModified,
+ };
+ } else if (row.bookmarkTexts) {
+ content = {
+ type: BookmarkTypes.TEXT,
+ text: row.bookmarkTexts.text ?? "",
+ sourceUrl: row.bookmarkTexts.sourceUrl ?? null,
+ };
+ } else if (row.bookmarkAssets) {
+ content = {
+ type: BookmarkTypes.ASSET,
+ assetId: row.bookmarkAssets.assetId,
+ assetType: row.bookmarkAssets.assetType,
+ fileName: row.bookmarkAssets.fileName,
+ sourceUrl: row.bookmarkAssets.sourceUrl ?? null,
+ size: null, // This will get filled in the asset loop
+ content: input.includeContent
+ ? (row.bookmarkAssets.content ?? null)
+ : null,
+ };
+ } else {
+ content = {
+ type: BookmarkTypes.UNKNOWN,
+ };
+ }
+ acc[bookmarkId] = {
+ ...row.bookmarksSq,
+ content,
+ tags: [],
+ assets: [],
+ };
+ }
+
+ if (
+ row.bookmarkTags &&
+ // Duplicates may occur because of the join, so we need to make sure we're not adding the same tag twice
+ !acc[bookmarkId].tags.some((t) => t.id == row.bookmarkTags!.id)
+ ) {
+ invariant(
+ row.tagsOnBookmarks,
+ "if bookmark tag is set, its many-to-many relation must also be set",
+ );
+ acc[bookmarkId].tags.push({
+ ...row.bookmarkTags,
+ attachedBy: row.tagsOnBookmarks.attachedBy,
+ });
+ }
+
+ if (
+ row.assets &&
+ !acc[bookmarkId].assets.some((a) => a.id == row.assets!.id)
+ ) {
+ if (acc[bookmarkId].content.type == BookmarkTypes.LINK) {
+ const content = acc[bookmarkId].content;
+ invariant(content.type == BookmarkTypes.LINK);
+ if (row.assets.assetType == AssetTypes.LINK_SCREENSHOT) {
+ content.screenshotAssetId = row.assets.id;
+ }
+ if (row.assets.assetType == AssetTypes.LINK_FULL_PAGE_ARCHIVE) {
+ content.fullPageArchiveAssetId = row.assets.id;
+ }
+ if (row.assets.assetType == AssetTypes.LINK_BANNER_IMAGE) {
+ content.imageAssetId = row.assets.id;
+ }
+ if (row.assets.assetType == AssetTypes.LINK_VIDEO) {
+ content.videoAssetId = row.assets.id;
+ }
+ if (row.assets.assetType == AssetTypes.LINK_PRECRAWLED_ARCHIVE) {
+ content.precrawledArchiveAssetId = row.assets.id;
+ }
+ acc[bookmarkId].content = content;
+ }
+ if (acc[bookmarkId].content.type == BookmarkTypes.ASSET) {
+ const content = acc[bookmarkId].content;
+ if (row.assets.id == content.assetId) {
+ // If this is the bookmark's main aset, caputure its size.
+ content.size = row.assets.size;
+ }
+ }
+ acc[bookmarkId].assets.push({
+ id: row.assets.id,
+ assetType: mapDBAssetTypeToUserType(row.assets.assetType),
+ });
+ }
+
+ return acc;
+ },
+ {},
+ );
+
+ const bookmarksArr = Object.values(bookmarksRes);
+
+ bookmarksArr.sort((a, b) => {
+ if (a.createdAt != b.createdAt) {
+ return input.sortOrder === "asc"
+ ? a.createdAt.getTime() - b.createdAt.getTime()
+ : b.createdAt.getTime() - a.createdAt.getTime();
+ } else {
+ return b.id.localeCompare(a.id);
+ }
+ });
+
+ let nextCursor = null;
+ if (bookmarksArr.length > input.limit) {
+ const nextItem = bookmarksArr.pop()!;
+ nextCursor = {
+ id: nextItem.id,
+ createdAt: nextItem.createdAt,
+ };
+ }
+
+ return {
+ bookmarks: bookmarksArr.map((b) => Bookmark.fromData(ctx, b)),
+ nextCursor,
+ };
+ }
+
+ asZBookmark(): ZBookmark {
+ return this.bookmark;
+ }
+
+ asPublicBookmark(): ZPublicBookmark {
+ const getPublicSignedAssetUrl = (assetId: string) => {
+ const payload: z.infer<typeof zAssetSignedTokenSchema> = {
+ assetId,
+ userId: this.ctx.user.id,
+ };
+ const signedToken = createSignedToken(payload);
+ return `${serverConfig.publicApiUrl}/public/assets/${assetId}?token=${signedToken}`;
+ };
+ const getContent = (
+ content: ZBookmarkContent,
+ ): ZPublicBookmark["content"] => {
+ switch (content.type) {
+ case BookmarkTypes.LINK: {
+ return {
+ type: BookmarkTypes.LINK,
+ url: content.url,
+ };
+ }
+ case BookmarkTypes.TEXT: {
+ return {
+ type: BookmarkTypes.TEXT,
+ text: content.text,
+ };
+ }
+ case BookmarkTypes.ASSET: {
+ return {
+ type: BookmarkTypes.ASSET,
+ assetType: content.assetType,
+ assetId: content.assetId,
+ assetUrl: getPublicSignedAssetUrl(content.assetId),
+ fileName: content.fileName,
+ sourceUrl: content.sourceUrl,
+ };
+ }
+ default: {
+ throw new Error("Unknown bookmark content type");
+ }
+ }
+ };
+
+ const getBannerImageUrl = (content: ZBookmarkContent): string | null => {
+ switch (content.type) {
+ case BookmarkTypes.LINK: {
+ const assetIdOrUrl = getBookmarkLinkAssetIdOrUrl(content);
+ if (!assetIdOrUrl) {
+ return null;
+ }
+ if (assetIdOrUrl.localAsset) {
+ return getPublicSignedAssetUrl(assetIdOrUrl.assetId);
+ } else {
+ return assetIdOrUrl.url;
+ }
+ }
+ case BookmarkTypes.TEXT: {
+ return null;
+ }
+ case BookmarkTypes.ASSET: {
+ switch (content.assetType) {
+ case "image":
+ return `${getPublicSignedAssetUrl(content.assetId)}`;
+ case "pdf": {
+ const screenshotAssetId = this.bookmark.assets.find(
+ (r) => r.assetType === "assetScreenshot",
+ )?.id;
+ if (!screenshotAssetId) {
+ return null;
+ }
+ return getPublicSignedAssetUrl(screenshotAssetId);
+ }
+ default: {
+ const _exhaustiveCheck: never = content.assetType;
+ return null;
+ }
+ }
+ }
+ default: {
+ throw new Error("Unknown bookmark content type");
+ }
+ }
+ };
+
+ // WARNING: Everything below is exposed in the public APIs, don't use spreads!
+ return {
+ id: this.bookmark.id,
+ createdAt: this.bookmark.createdAt,
+ modifiedAt: this.bookmark.modifiedAt,
+ title: getBookmarkTitle(this.bookmark),
+ tags: this.bookmark.tags.map((t) => t.name),
+ content: getContent(this.bookmark.content),
+ bannerImageUrl: getBannerImageUrl(this.bookmark.content),
+ };
+ }
+}
diff --git a/packages/trpc/models/lists.ts b/packages/trpc/models/lists.ts
index d278f8d9..2631ca7e 100644
--- a/packages/trpc/models/lists.ts
+++ b/packages/trpc/models/lists.ts
@@ -1,5 +1,6 @@
+import crypto from "node:crypto";
import { TRPCError } from "@trpc/server";
-import { and, count, eq } from "drizzle-orm";
+import { and, count, eq, or } from "drizzle-orm";
import invariant from "tiny-invariant";
import { z } from "zod";
@@ -7,14 +8,18 @@ import { SqliteError } from "@karakeep/db";
import { bookmarkLists, bookmarksInLists } from "@karakeep/db/schema";
import { triggerRuleEngineOnEvent } from "@karakeep/shared/queues";
import { parseSearchQuery } from "@karakeep/shared/searchQueryParser";
+import { ZSortOrder } from "@karakeep/shared/types/bookmarks";
import {
ZBookmarkList,
zEditBookmarkListSchemaWithValidation,
zNewBookmarkListSchema,
} from "@karakeep/shared/types/lists";
+import { ZCursor } from "@karakeep/shared/types/pagination";
-import { AuthedContext } from "..";
+import { AuthedContext, Context } from "..";
+import { buildImpersonatingAuthedContext } from "../lib/impersonate";
import { getBookmarkIdsFromMatcher } from "../lib/search";
+import { Bookmark } from "./bookmarks";
import { PrivacyAware } from "./privacy";
export abstract class List implements PrivacyAware {
@@ -26,7 +31,7 @@ export abstract class List implements PrivacyAware {
private static fromData(
ctx: AuthedContext,
data: ZBookmarkList & { userId: string },
- ): ManualList | SmartList {
+ ) {
if (data.type === "smart") {
return new SmartList(ctx, data);
} else {
@@ -34,21 +39,6 @@ export abstract class List implements PrivacyAware {
}
}
- static async fromName(
- ctx: AuthedContext,
- name: string,
- ): Promise<(ManualList | SmartList)[]> {
- // Names are not unique, so we need to find all lists with the same name
- const lists = await ctx.db.query.bookmarkLists.findMany({
- where: and(
- eq(bookmarkLists.name, name),
- eq(bookmarkLists.userId, ctx.user.id),
- ),
- });
-
- return lists.map((l) => this.fromData(ctx, l));
- }
-
static async fromId(
ctx: AuthedContext,
id: string,
@@ -66,7 +56,64 @@ export abstract class List implements PrivacyAware {
message: "List not found",
});
}
- return this.fromData(ctx, list);
+ if (list.type === "smart") {
+ return new SmartList(ctx, list);
+ } else {
+ return new ManualList(ctx, list);
+ }
+ }
+
+ static async getPublicListContents(
+ ctx: Context,
+ listId: string,
+ token: string | null,
+ pagination: {
+ limit: number;
+ order: Exclude<ZSortOrder, "relevance">;
+ cursor: ZCursor | null | undefined;
+ },
+ ) {
+ const listdb = await ctx.db.query.bookmarkLists.findFirst({
+ where: and(
+ eq(bookmarkLists.id, listId),
+ or(
+ eq(bookmarkLists.public, true),
+ token !== null ? eq(bookmarkLists.rssToken, token) : undefined,
+ ),
+ ),
+ });
+ if (!listdb) {
+ throw new TRPCError({
+ code: "NOT_FOUND",
+ message: "List not found",
+ });
+ }
+
+ // The token here acts as an authed context, so we can create
+ // an impersonating context for the list owner as long as
+ // we don't leak the context.
+ const authedCtx = await buildImpersonatingAuthedContext(listdb.userId);
+ const list = List.fromData(authedCtx, listdb);
+ const bookmarkIds = await list.getBookmarkIds();
+
+ const bookmarks = await Bookmark.loadMulti(authedCtx, {
+ ids: bookmarkIds,
+ includeContent: false,
+ limit: pagination.limit,
+ sortOrder: pagination.order,
+ cursor: pagination.cursor,
+ });
+
+ return {
+ list: {
+ icon: list.list.icon,
+ name: list.list.name,
+ description: list.list.description,
+ numItems: bookmarkIds.length,
+ },
+ bookmarks: bookmarks.bookmarks.map((b) => b.asPublicBookmark()),
+ nextCursor: bookmarks.nextCursor,
+ };
}
static async create(
@@ -90,6 +137,9 @@ export abstract class List implements PrivacyAware {
static async getAll(ctx: AuthedContext): Promise<(ManualList | SmartList)[]> {
const lists = await ctx.db.query.bookmarkLists.findMany({
+ columns: {
+ rssToken: false,
+ },
where: and(eq(bookmarkLists.userId, ctx.user.id)),
});
return lists.map((l) => this.fromData(ctx, l));
@@ -99,7 +149,11 @@ export abstract class List implements PrivacyAware {
const lists = await ctx.db.query.bookmarksInLists.findMany({
where: and(eq(bookmarksInLists.bookmarkId, bookmarkId)),
with: {
- list: true,
+ list: {
+ columns: {
+ rssToken: false,
+ },
+ },
},
});
invariant(lists.map((l) => l.list.userId).every((id) => id == ctx.user.id));
@@ -140,6 +194,7 @@ export abstract class List implements PrivacyAware {
icon: input.icon,
parentId: input.parentId,
query: input.query,
+ public: input.public,
})
.where(
and(
@@ -154,6 +209,45 @@ export abstract class List implements PrivacyAware {
this.list = result[0];
}
+ private async setRssToken(token: string | null) {
+ const result = await this.ctx.db
+ .update(bookmarkLists)
+ .set({ rssToken: token })
+ .where(
+ and(
+ eq(bookmarkLists.id, this.list.id),
+ eq(bookmarkLists.userId, this.ctx.user.id),
+ ),
+ )
+ .returning();
+ if (result.length == 0) {
+ throw new TRPCError({ code: "NOT_FOUND" });
+ }
+ return result[0].rssToken;
+ }
+
+ async getRssToken(): Promise<string | null> {
+ const [result] = await this.ctx.db
+ .select({ rssToken: bookmarkLists.rssToken })
+ .from(bookmarkLists)
+ .where(
+ and(
+ eq(bookmarkLists.id, this.list.id),
+ eq(bookmarkLists.userId, this.ctx.user.id),
+ ),
+ )
+ .limit(1);
+ return result.rssToken ?? null;
+ }
+
+ async regenRssToken() {
+ return await this.setRssToken(crypto.randomBytes(32).toString("hex"));
+ }
+
+ async clearRssToken() {
+ await this.setRssToken(null);
+ }
+
abstract get type(): "manual" | "smart";
abstract getBookmarkIds(ctx: AuthedContext): Promise<string[]>;
abstract getSize(ctx: AuthedContext): Promise<number>;
@@ -271,10 +365,8 @@ export class ManualList extends List {
} catch (e) {
if (e instanceof SqliteError) {
if (e.code == "SQLITE_CONSTRAINT_PRIMARYKEY") {
- throw new TRPCError({
- code: "BAD_REQUEST",
- message: `Bookmark ${bookmarkId} is already in the list ${this.list.id}`,
- });
+ // this is fine, it just means the bookmark is already in the list
+ return;
}
}
throw new TRPCError({
diff --git a/packages/trpc/routers/_app.ts b/packages/trpc/routers/_app.ts
index 394e95e7..e09f959e 100644
--- a/packages/trpc/routers/_app.ts
+++ b/packages/trpc/routers/_app.ts
@@ -7,6 +7,7 @@ import { feedsAppRouter } from "./feeds";
import { highlightsAppRouter } from "./highlights";
import { listsAppRouter } from "./lists";
import { promptsAppRouter } from "./prompts";
+import { publicBookmarks } from "./publicBookmarks";
import { rulesAppRouter } from "./rules";
import { tagsAppRouter } from "./tags";
import { usersAppRouter } from "./users";
@@ -25,6 +26,7 @@ export const appRouter = router({
webhooks: webhooksAppRouter,
assets: assetsAppRouter,
rules: rulesAppRouter,
+ publicBookmarks: publicBookmarks,
});
// export type definition of API
export type AppRouter = typeof appRouter;
diff --git a/packages/trpc/routers/admin.ts b/packages/trpc/routers/admin.ts
index e4985b5c..91f4a34f 100644
--- a/packages/trpc/routers/admin.ts
+++ b/packages/trpc/routers/admin.ts
@@ -1,5 +1,5 @@
import { TRPCError } from "@trpc/server";
-import { count, eq, sum } from "drizzle-orm";
+import { count, eq, or, sum } from "drizzle-orm";
import { z } from "zod";
import { assets, bookmarkLinks, bookmarks, users } from "@karakeep/db/schema";
@@ -129,11 +129,21 @@ export const adminAppRouter = router({
ctx.db
.select({ value: count() })
.from(bookmarks)
- .where(eq(bookmarks.taggingStatus, "pending")),
+ .where(
+ or(
+ eq(bookmarks.taggingStatus, "pending"),
+ eq(bookmarks.summarizationStatus, "pending"),
+ ),
+ ),
ctx.db
.select({ value: count() })
.from(bookmarks)
- .where(eq(bookmarks.taggingStatus, "failure")),
+ .where(
+ or(
+ eq(bookmarks.taggingStatus, "failure"),
+ eq(bookmarks.summarizationStatus, "failure"),
+ ),
+ ),
// Tidy Assets
TidyAssetsQueue.stats(),
@@ -233,7 +243,8 @@ export const adminAppRouter = router({
reRunInferenceOnAllBookmarks: adminProcedure
.input(
z.object({
- taggingStatus: z.enum(["success", "failure", "all"]),
+ type: z.enum(["tag", "summarize"]),
+ status: z.enum(["success", "failure", "all"]),
}),
)
.mutation(async ({ input, ctx }) => {
@@ -241,13 +252,22 @@ export const adminAppRouter = router({
columns: {
id: true,
},
- ...(input.taggingStatus === "all"
- ? {}
- : { where: eq(bookmarks.taggingStatus, input.taggingStatus) }),
+ ...{
+ tag:
+ input.status === "all"
+ ? {}
+ : { where: eq(bookmarks.taggingStatus, input.status) },
+ summarize:
+ input.status === "all"
+ ? {}
+ : { where: eq(bookmarks.summarizationStatus, input.status) },
+ }[input.type],
});
await Promise.all(
- bookmarkIds.map((b) => OpenAIQueue.enqueue({ bookmarkId: b.id })),
+ bookmarkIds.map((b) =>
+ OpenAIQueue.enqueue({ bookmarkId: b.id, type: input.type }),
+ ),
);
}),
tidyAssets: adminProcedure.mutation(async () => {
diff --git a/packages/trpc/routers/bookmarks.test.ts b/packages/trpc/routers/bookmarks.test.ts
index e2179542..575b4d9a 100644
--- a/packages/trpc/routers/bookmarks.test.ts
+++ b/packages/trpc/routers/bookmarks.test.ts
@@ -25,7 +25,11 @@ describe("Bookmark Routes", () => {
feedUrl: string,
) {
// Create an RSS feed and return its ID
- const feed = await api.feeds.create({ name: feedName, url: feedUrl });
+ const feed = await api.feeds.create({
+ name: feedName,
+ url: feedUrl,
+ enabled: true,
+ });
return feed.id;
}
diff --git a/packages/trpc/routers/bookmarks.ts b/packages/trpc/routers/bookmarks.ts
index b9a21400..04d15d1f 100644
--- a/packages/trpc/routers/bookmarks.ts
+++ b/packages/trpc/routers/bookmarks.ts
@@ -1,17 +1,5 @@
import { experimental_trpcMiddleware, TRPCError } from "@trpc/server";
-import {
- and,
- asc,
- desc,
- eq,
- exists,
- gt,
- gte,
- inArray,
- lt,
- lte,
- or,
-} from "drizzle-orm";
+import { and, eq, gt, inArray, lt, or } from "drizzle-orm";
import invariant from "tiny-invariant";
import { z } from "zod";
@@ -27,11 +15,9 @@ import {
bookmarkAssets,
bookmarkLinks,
bookmarks,
- bookmarksInLists,
bookmarkTags,
bookmarkTexts,
customPrompts,
- rssFeedImportsTable,
tagsOnBookmarks,
} from "@karakeep/db/schema";
import {
@@ -69,7 +55,7 @@ import type { AuthedContext, Context } from "../index";
import { authedProcedure, router } from "../index";
import { mapDBAssetTypeToUserType } from "../lib/attachments";
import { getBookmarkIdsFromMatcher } from "../lib/search";
-import { List } from "../models/lists";
+import { Bookmark } from "../models/bookmarks";
import { ensureAssetOwnership } from "./assets";
export const ensureBookmarkOwnership = experimental_trpcMiddleware<{
@@ -420,6 +406,7 @@ export const bookmarksAppRouter = router({
case BookmarkTypes.TEXT: {
await OpenAIQueue.enqueue({
bookmarkId: bookmark.id,
+ type: "tag",
});
break;
}
@@ -659,6 +646,7 @@ export const bookmarksAppRouter = router({
),
);
await triggerSearchDeletion(input.bookmarkId);
+ await triggerWebhook(input.bookmarkId, "deleted");
if (deleted.changes > 0 && bookmark) {
await cleanupAssetForBookmark({
asset: bookmark.asset,
@@ -712,7 +700,7 @@ export const bookmarksAppRouter = router({
if (!input.limit) {
input.limit = DEFAULT_NUM_BOOKMARKS_PER_PAGE;
}
- const sortOrder = input.sortOrder || "desc";
+ const sortOrder = input.sortOrder || "relevance";
const client = await getSearchIdxClient();
if (!client) {
throw new TRPCError({
@@ -735,11 +723,16 @@ export const bookmarksAppRouter = router({
filter = [`userId = '${ctx.user.id}'`];
}
+ /**
+ * preserve legacy behaviour
+ */
+ const createdAtSortOrder = sortOrder === "relevance" ? "desc" : sortOrder;
+
const resp = await client.search(parsedQuery.text, {
filter,
showRankingScore: true,
attributesToRetrieve: ["id"],
- sort: [`createdAt:${sortOrder}`],
+ sort: [`createdAt:${createdAtSortOrder}`],
limit: input.limit,
...(input.cursor
? {
@@ -775,7 +768,18 @@ export const bookmarksAppRouter = router({
assets: true,
},
});
- results.sort((a, b) => idToRank[b.id] - idToRank[a.id]);
+
+ switch (true) {
+ case sortOrder === "relevance":
+ results.sort((a, b) => idToRank[b.id] - idToRank[a.id]);
+ break;
+ case sortOrder === "desc":
+ results.sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime());
+ break;
+ case sortOrder === "asc":
+ results.sort((a, b) => a.createdAt.getTime() - b.createdAt.getTime());
+ break;
+ }
return {
bookmarks: results.map((b) => toZodSchema(b, input.includeContent)),
@@ -792,245 +796,11 @@ export const bookmarksAppRouter = router({
.input(zGetBookmarksRequestSchema)
.output(zGetBookmarksResponseSchema)
.query(async ({ input, ctx }) => {
- if (input.ids && input.ids.length == 0) {
- return { bookmarks: [], nextCursor: null };
- }
- if (!input.limit) {
- input.limit = DEFAULT_NUM_BOOKMARKS_PER_PAGE;
- }
- if (input.listId) {
- const list = await List.fromId(ctx, input.listId);
- if (list.type === "smart") {
- input.ids = await list.getBookmarkIds();
- delete input.listId;
- }
- }
-
- const sq = ctx.db.$with("bookmarksSq").as(
- ctx.db
- .select()
- .from(bookmarks)
- .where(
- and(
- eq(bookmarks.userId, ctx.user.id),
- input.archived !== undefined
- ? eq(bookmarks.archived, input.archived)
- : undefined,
- input.favourited !== undefined
- ? eq(bookmarks.favourited, input.favourited)
- : undefined,
- input.ids ? inArray(bookmarks.id, input.ids) : undefined,
- input.tagId !== undefined
- ? exists(
- ctx.db
- .select()
- .from(tagsOnBookmarks)
- .where(
- and(
- eq(tagsOnBookmarks.bookmarkId, bookmarks.id),
- eq(tagsOnBookmarks.tagId, input.tagId),
- ),
- ),
- )
- : undefined,
- input.rssFeedId !== undefined
- ? exists(
- ctx.db
- .select()
- .from(rssFeedImportsTable)
- .where(
- and(
- eq(rssFeedImportsTable.bookmarkId, bookmarks.id),
- eq(rssFeedImportsTable.rssFeedId, input.rssFeedId),
- ),
- ),
- )
- : undefined,
- input.listId !== undefined
- ? exists(
- ctx.db
- .select()
- .from(bookmarksInLists)
- .where(
- and(
- eq(bookmarksInLists.bookmarkId, bookmarks.id),
- eq(bookmarksInLists.listId, input.listId),
- ),
- ),
- )
- : undefined,
- input.cursor
- ? input.sortOrder === "asc"
- ? or(
- gt(bookmarks.createdAt, input.cursor.createdAt),
- and(
- eq(bookmarks.createdAt, input.cursor.createdAt),
- gte(bookmarks.id, input.cursor.id),
- ),
- )
- : or(
- lt(bookmarks.createdAt, input.cursor.createdAt),
- and(
- eq(bookmarks.createdAt, input.cursor.createdAt),
- lte(bookmarks.id, input.cursor.id),
- ),
- )
- : undefined,
- ),
- )
- .limit(input.limit + 1)
- .orderBy(
- input.sortOrder === "asc"
- ? asc(bookmarks.createdAt)
- : desc(bookmarks.createdAt),
- desc(bookmarks.id),
- ),
- );
- // TODO: Consider not inlining the tags in the response of getBookmarks as this query is getting kinda expensive
- const results = await ctx.db
- .with(sq)
- .select()
- .from(sq)
- .leftJoin(tagsOnBookmarks, eq(sq.id, tagsOnBookmarks.bookmarkId))
- .leftJoin(bookmarkTags, eq(tagsOnBookmarks.tagId, bookmarkTags.id))
- .leftJoin(bookmarkLinks, eq(bookmarkLinks.id, sq.id))
- .leftJoin(bookmarkTexts, eq(bookmarkTexts.id, sq.id))
- .leftJoin(bookmarkAssets, eq(bookmarkAssets.id, sq.id))
- .leftJoin(assets, eq(assets.bookmarkId, sq.id))
- .orderBy(desc(sq.createdAt), desc(sq.id));
-
- const bookmarksRes = results.reduce<Record<string, ZBookmark>>(
- (acc, row) => {
- const bookmarkId = row.bookmarksSq.id;
- if (!acc[bookmarkId]) {
- let content: ZBookmarkContent;
- if (row.bookmarkLinks) {
- content = {
- type: BookmarkTypes.LINK,
- url: row.bookmarkLinks.url,
- title: row.bookmarkLinks.title,
- description: row.bookmarkLinks.description,
- imageUrl: row.bookmarkLinks.imageUrl,
- favicon: row.bookmarkLinks.favicon,
- htmlContent: input.includeContent
- ? row.bookmarkLinks.htmlContent
- : null,
- crawledAt: row.bookmarkLinks.crawledAt,
- author: row.bookmarkLinks.author,
- publisher: row.bookmarkLinks.publisher,
- datePublished: row.bookmarkLinks.datePublished,
- dateModified: row.bookmarkLinks.dateModified,
- };
- } else if (row.bookmarkTexts) {
- content = {
- type: BookmarkTypes.TEXT,
- text: row.bookmarkTexts.text ?? "",
- sourceUrl: row.bookmarkTexts.sourceUrl ?? null,
- };
- } else if (row.bookmarkAssets) {
- content = {
- type: BookmarkTypes.ASSET,
- assetId: row.bookmarkAssets.assetId,
- assetType: row.bookmarkAssets.assetType,
- fileName: row.bookmarkAssets.fileName,
- sourceUrl: row.bookmarkAssets.sourceUrl ?? null,
- size: null, // This will get filled in the asset loop
- content: input.includeContent
- ? (row.bookmarkAssets.content ?? null)
- : null,
- };
- } else {
- content = {
- type: BookmarkTypes.UNKNOWN,
- };
- }
- acc[bookmarkId] = {
- ...row.bookmarksSq,
- content,
- tags: [],
- assets: [],
- };
- }
-
- if (
- row.bookmarkTags &&
- // Duplicates may occur because of the join, so we need to make sure we're not adding the same tag twice
- !acc[bookmarkId].tags.some((t) => t.id == row.bookmarkTags!.id)
- ) {
- invariant(
- row.tagsOnBookmarks,
- "if bookmark tag is set, its many-to-many relation must also be set",
- );
- acc[bookmarkId].tags.push({
- ...row.bookmarkTags,
- attachedBy: row.tagsOnBookmarks.attachedBy,
- });
- }
-
- if (
- row.assets &&
- !acc[bookmarkId].assets.some((a) => a.id == row.assets!.id)
- ) {
- if (acc[bookmarkId].content.type == BookmarkTypes.LINK) {
- const content = acc[bookmarkId].content;
- invariant(content.type == BookmarkTypes.LINK);
- if (row.assets.assetType == AssetTypes.LINK_SCREENSHOT) {
- content.screenshotAssetId = row.assets.id;
- }
- if (row.assets.assetType == AssetTypes.LINK_FULL_PAGE_ARCHIVE) {
- content.fullPageArchiveAssetId = row.assets.id;
- }
- if (row.assets.assetType == AssetTypes.LINK_BANNER_IMAGE) {
- content.imageAssetId = row.assets.id;
- }
- if (row.assets.assetType == AssetTypes.LINK_VIDEO) {
- content.videoAssetId = row.assets.id;
- }
- if (row.assets.assetType == AssetTypes.LINK_PRECRAWLED_ARCHIVE) {
- content.precrawledArchiveAssetId = row.assets.id;
- }
- acc[bookmarkId].content = content;
- }
- if (acc[bookmarkId].content.type == BookmarkTypes.ASSET) {
- const content = acc[bookmarkId].content;
- if (row.assets.id == content.assetId) {
- // If this is the bookmark's main aset, caputure its size.
- content.size = row.assets.size;
- }
- }
- acc[bookmarkId].assets.push({
- id: row.assets.id,
- assetType: mapDBAssetTypeToUserType(row.assets.assetType),
- });
- }
-
- return acc;
- },
- {},
- );
-
- const bookmarksArr = Object.values(bookmarksRes);
-
- bookmarksArr.sort((a, b) => {
- if (a.createdAt != b.createdAt) {
- return input.sortOrder === "asc"
- ? a.createdAt.getTime() - b.createdAt.getTime()
- : b.createdAt.getTime() - a.createdAt.getTime();
- } else {
- return b.id.localeCompare(a.id);
- }
- });
-
- let nextCursor = null;
- if (bookmarksArr.length > input.limit) {
- const nextItem = bookmarksArr.pop()!;
- nextCursor = {
- id: nextItem.id,
- createdAt: nextItem.createdAt,
- };
- }
-
- return { bookmarks: bookmarksArr, nextCursor };
+ const res = await Bookmark.loadMulti(ctx, input);
+ return {
+ bookmarks: res.bookmarks.map((b) => b.asZBookmark()),
+ nextCursor: res.nextCursor,
+ };
}),
updateTags: authedProcedure
diff --git a/packages/trpc/routers/feeds.ts b/packages/trpc/routers/feeds.ts
index 819ba8ef..07c48f0a 100644
--- a/packages/trpc/routers/feeds.ts
+++ b/packages/trpc/routers/feeds.ts
@@ -55,6 +55,7 @@ export const feedsAppRouter = router({
name: input.name,
url: input.url,
userId: ctx.user.id,
+ enabled: input.enabled,
})
.returning();
return feed;
@@ -69,6 +70,7 @@ export const feedsAppRouter = router({
.set({
name: input.name,
url: input.url,
+ enabled: input.enabled,
})
.where(
and(
diff --git a/packages/trpc/routers/lists.ts b/packages/trpc/routers/lists.ts
index 65cffd2d..bb949962 100644
--- a/packages/trpc/routers/lists.ts
+++ b/packages/trpc/routers/lists.ts
@@ -131,4 +131,47 @@ export const listsAppRouter = router({
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(),
+ }),
+ )
+ .mutation(async ({ input, ctx }) => {
+ const list = await List.fromId(ctx, input.listId);
+ const token = await list.regenRssToken();
+ return { token: token! };
+ }),
+ clearRssToken: authedProcedure
+ .input(
+ z.object({
+ listId: z.string(),
+ }),
+ )
+ .mutation(async ({ input, ctx }) => {
+ const list = await List.fromId(ctx, input.listId);
+ await list.clearRssToken();
+ }),
+ getRssToken: authedProcedure
+ .input(
+ z.object({
+ listId: z.string(),
+ }),
+ )
+ .output(
+ z.object({
+ token: z.string().nullable(),
+ }),
+ )
+ .query(async ({ input, ctx }) => {
+ const list = await List.fromId(ctx, input.listId);
+ return { token: await list.getRssToken() };
+ }),
});
diff --git a/packages/trpc/routers/publicBookmarks.ts b/packages/trpc/routers/publicBookmarks.ts
new file mode 100644
index 00000000..6b643354
--- /dev/null
+++ b/packages/trpc/routers/publicBookmarks.ts
@@ -0,0 +1,49 @@
+import { z } from "zod";
+
+import {
+ MAX_NUM_BOOKMARKS_PER_PAGE,
+ zPublicBookmarkSchema,
+ zSortOrder,
+} from "@karakeep/shared/types/bookmarks";
+import { zBookmarkListSchema } from "@karakeep/shared/types/lists";
+import { zCursorV2 } from "@karakeep/shared/types/pagination";
+
+import { publicProcedure, router } from "../index";
+import { List } from "../models/lists";
+
+export const publicBookmarks = router({
+ getPublicBookmarksInList: publicProcedure
+ .input(
+ z.object({
+ listId: z.string(),
+ cursor: zCursorV2.nullish(),
+ limit: z.number().max(MAX_NUM_BOOKMARKS_PER_PAGE).default(20),
+ sortOrder: zSortOrder.exclude(["relevance"]).optional().default("desc"),
+ }),
+ )
+ .output(
+ z.object({
+ list: zBookmarkListSchema
+ .pick({
+ name: true,
+ description: true,
+ icon: true,
+ })
+ .merge(z.object({ numItems: z.number() })),
+ bookmarks: z.array(zPublicBookmarkSchema),
+ nextCursor: zCursorV2.nullable(),
+ }),
+ )
+ .query(async ({ input, ctx }) => {
+ return await List.getPublicListContents(
+ ctx,
+ input.listId,
+ /* token */ null,
+ {
+ limit: input.limit,
+ order: input.sortOrder,
+ cursor: input.cursor,
+ },
+ );
+ }),
+});
diff --git a/packages/trpc/routers/tags.ts b/packages/trpc/routers/tags.ts
index 7f75c16e..cade4b45 100644
--- a/packages/trpc/routers/tags.ts
+++ b/packages/trpc/routers/tags.ts
@@ -7,7 +7,9 @@ import { SqliteError } from "@karakeep/db";
import { bookmarkTags, tagsOnBookmarks } from "@karakeep/db/schema";
import { triggerSearchReindex } from "@karakeep/shared/queues";
import {
+ zCreateTagRequestSchema,
zGetTagResponseSchema,
+ zTagBasicSchema,
zUpdateTagRequestSchema,
} from "@karakeep/shared/types/tags";
@@ -53,19 +55,8 @@ export const ensureTagOwnership = experimental_trpcMiddleware<{
export const tagsAppRouter = router({
create: authedProcedure
- .input(
- z.object({
- name: z.string().min(1), // Ensure the name is provided and not empty
- }),
- )
- .output(
- z.object({
- id: z.string(),
- name: z.string(),
- userId: z.string(),
- createdAt: z.date(),
- }),
- )
+ .input(zCreateTagRequestSchema)
+ .output(zTagBasicSchema)
.mutation(async ({ input, ctx }) => {
try {
const [newTag] = await ctx.db
@@ -76,7 +67,10 @@ export const tagsAppRouter = router({
})
.returning();
- return newTag;
+ return {
+ id: newTag.id,
+ name: newTag.name,
+ };
} catch (e) {
if (e instanceof SqliteError && e.code === "SQLITE_CONSTRAINT_UNIQUE") {
throw new TRPCError({
@@ -195,14 +189,7 @@ export const tagsAppRouter = router({
}),
update: authedProcedure
.input(zUpdateTagRequestSchema)
- .output(
- z.object({
- id: z.string(),
- name: z.string(),
- userId: z.string(),
- createdAt: z.date(),
- }),
- )
+ .output(zTagBasicSchema)
.use(ensureTagOwnership)
.mutation(async ({ input, ctx }) => {
try {
@@ -242,7 +229,10 @@ export const tagsAppRouter = router({
console.error("Failed to reindex affected bookmarks", e);
}
- return res[0];
+ return {
+ id: res[0].id,
+ name: res[0].name,
+ };
} catch (e) {
if (e instanceof SqliteError) {
if (e.code == "SQLITE_CONSTRAINT_UNIQUE") {
diff --git a/packages/trpc/routers/users.test.ts b/packages/trpc/routers/users.test.ts
index ea342d33..3fd939b1 100644
--- a/packages/trpc/routers/users.test.ts
+++ b/packages/trpc/routers/users.test.ts
@@ -94,4 +94,41 @@ describe("User Routes", () => {
// A normal user can't list all users
await expect(() => user2Caller.users.list()).rejects.toThrow(/FORBIDDEN/);
});
+
+ test<CustomTestContext>("get/update user settings", async ({
+ db,
+ unauthedAPICaller,
+ }) => {
+ const user = await unauthedAPICaller.users.create({
+ name: "Test User",
+ email: "testupdate@test.com",
+ password: "pass1234",
+ confirmPassword: "pass1234",
+ });
+ const caller = getApiCaller(db, user.id);
+
+ const settings = await caller.users.settings();
+ // The default settings
+ expect(settings).toEqual({
+ bookmarkClickAction: "open_original_link",
+ archiveDisplayBehaviour: "show",
+ });
+
+ // Update settings
+ await caller.users.updateSettings({
+ bookmarkClickAction: "expand_bookmark_preview",
+ });
+
+ // Verify updated settings
+ const updatedSettings = await caller.users.settings();
+ expect(updatedSettings).toEqual({
+ bookmarkClickAction: "expand_bookmark_preview",
+ archiveDisplayBehaviour: "show",
+ });
+
+ // Test invalid update (e.g., empty input, if schema enforces it)
+ await expect(() => caller.users.updateSettings({})).rejects.toThrow(
+ /No settings provided/,
+ );
+ });
});
diff --git a/packages/trpc/routers/users.ts b/packages/trpc/routers/users.ts
index c56daaee..3813387f 100644
--- a/packages/trpc/routers/users.ts
+++ b/packages/trpc/routers/users.ts
@@ -10,11 +10,14 @@ import {
bookmarkTags,
highlights,
users,
+ userSettings,
} from "@karakeep/db/schema";
import { deleteUserAssets } from "@karakeep/shared/assetdb";
import serverConfig from "@karakeep/shared/config";
import {
zSignUpSchema,
+ zUpdateUserSettingsSchema,
+ zUserSettingsSchema,
zUserStatsResponseSchema,
zWhoAmIResponseSchema,
} from "@karakeep/shared/types/users";
@@ -59,6 +62,12 @@ export async function createUser(
email: users.email,
role: users.role,
});
+
+ // Insert user settings for the new user
+ await trx.insert(userSettings).values({
+ userId: result[0].id,
+ });
+
return result[0];
} catch (e) {
if (e instanceof SqliteError) {
@@ -242,4 +251,38 @@ export const usersAppRouter = router({
numHighlights,
};
}),
+ settings: authedProcedure
+ .output(zUserSettingsSchema)
+ .query(async ({ ctx }) => {
+ const settings = await ctx.db.query.userSettings.findFirst({
+ where: eq(userSettings.userId, ctx.user.id),
+ });
+ if (!settings) {
+ throw new TRPCError({
+ code: "NOT_FOUND",
+ message: "User settings not found",
+ });
+ }
+ return {
+ bookmarkClickAction: settings.bookmarkClickAction,
+ archiveDisplayBehaviour: settings.archiveDisplayBehaviour,
+ };
+ }),
+ updateSettings: authedProcedure
+ .input(zUpdateUserSettingsSchema)
+ .mutation(async ({ input, ctx }) => {
+ if (Object.keys(input).length === 0) {
+ throw new TRPCError({
+ code: "BAD_REQUEST",
+ message: "No settings provided",
+ });
+ }
+ await ctx.db
+ .update(userSettings)
+ .set({
+ bookmarkClickAction: input.bookmarkClickAction,
+ archiveDisplayBehaviour: input.archiveDisplayBehaviour,
+ })
+ .where(eq(userSettings.userId, ctx.user.id));
+ }),
});