aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--packages/shared-server/src/services/quotaService.ts5
-rw-r--r--packages/trpc/routers/bookmarks.ts254
2 files changed, 133 insertions, 126 deletions
diff --git a/packages/shared-server/src/services/quotaService.ts b/packages/shared-server/src/services/quotaService.ts
index a09b76bf..bedda881 100644
--- a/packages/shared-server/src/services/quotaService.ts
+++ b/packages/shared-server/src/services/quotaService.ts
@@ -22,7 +22,10 @@ export class StorageQuotaError extends Error {
export class QuotaService {
// TODO: Use quota approval tokens for bookmark creation when
// bookmark creation logic is in the model.
- static async canCreateBookmark(db: DB, userId: string) {
+ static async canCreateBookmark(
+ db: DB | KarakeepDBTransaction,
+ userId: string,
+ ) {
const user = await db.query.users.findFirst({
where: eq(users.id, userId),
columns: {
diff --git a/packages/trpc/routers/bookmarks.ts b/packages/trpc/routers/bookmarks.ts
index 4f18e11b..65d401e2 100644
--- a/packages/trpc/routers/bookmarks.ts
+++ b/packages/trpc/routers/bookmarks.ts
@@ -131,149 +131,153 @@ export const bookmarksAppRouter = router({
}
}
- // Check user quota
- const quotaResult = await QuotaService.canCreateBookmark(
- ctx.db,
- ctx.user.id,
- );
- if (!quotaResult.result) {
- throw new TRPCError({
- code: "FORBIDDEN",
- message: quotaResult.error,
- });
- }
-
- const bookmark = await ctx.db.transaction(async (tx) => {
- const bookmark = (
- await tx
- .insert(bookmarks)
- .values({
- userId: ctx.user.id,
- title: input.title,
- type: input.type,
- archived: input.archived,
- favourited: input.favourited,
- note: input.note,
- summary: input.summary,
- createdAt: input.createdAt,
- source: input.source,
- // Only links currently support summarization. Let's set the status to null for other types for now.
- summarizationStatus:
- input.type === BookmarkTypes.LINK ? "pending" : null,
- })
- .returning()
- )[0];
-
- let content: ZBookmarkContent;
-
- switch (input.type) {
- case BookmarkTypes.LINK: {
- const link = (
- await tx
- .insert(bookmarkLinks)
+ const bookmark = await ctx.db.transaction(
+ async (tx) => {
+ // Check user quota
+ const quotaResult = await QuotaService.canCreateBookmark(
+ tx,
+ ctx.user.id,
+ );
+ if (!quotaResult.result) {
+ throw new TRPCError({
+ code: "FORBIDDEN",
+ message: quotaResult.error,
+ });
+ }
+ const bookmark = (
+ await tx
+ .insert(bookmarks)
+ .values({
+ userId: ctx.user.id,
+ title: input.title,
+ type: input.type,
+ archived: input.archived,
+ favourited: input.favourited,
+ note: input.note,
+ summary: input.summary,
+ createdAt: input.createdAt,
+ source: input.source,
+ // Only links currently support summarization. Let's set the status to null for other types for now.
+ summarizationStatus:
+ input.type === BookmarkTypes.LINK ? "pending" : null,
+ })
+ .returning()
+ )[0];
+
+ let content: ZBookmarkContent;
+
+ switch (input.type) {
+ case BookmarkTypes.LINK: {
+ const link = (
+ await tx
+ .insert(bookmarkLinks)
+ .values({
+ id: bookmark.id,
+ url: input.url.trim(),
+ })
+ .returning()
+ )[0];
+ if (input.precrawledArchiveId) {
+ await ensureAssetOwnership({
+ ctx,
+ assetId: input.precrawledArchiveId,
+ });
+ await tx
+ .update(assets)
+ .set({
+ bookmarkId: bookmark.id,
+ assetType: AssetTypes.LINK_PRECRAWLED_ARCHIVE,
+ })
+ .where(
+ and(
+ eq(assets.id, input.precrawledArchiveId),
+ eq(assets.userId, ctx.user.id),
+ ),
+ );
+ }
+ content = {
+ type: BookmarkTypes.LINK,
+ ...link,
+ };
+ break;
+ }
+ case BookmarkTypes.TEXT: {
+ const text = (
+ await tx
+ .insert(bookmarkTexts)
+ .values({
+ id: bookmark.id,
+ text: input.text,
+ sourceUrl: input.sourceUrl,
+ })
+ .returning()
+ )[0];
+ content = {
+ type: BookmarkTypes.TEXT,
+ text: text.text ?? "",
+ sourceUrl: text.sourceUrl,
+ };
+ break;
+ }
+ case BookmarkTypes.ASSET: {
+ const [asset] = await tx
+ .insert(bookmarkAssets)
.values({
id: bookmark.id,
- url: input.url.trim(),
+ assetType: input.assetType,
+ assetId: input.assetId,
+ content: null,
+ metadata: null,
+ fileName: input.fileName ?? null,
+ sourceUrl: null,
})
- .returning()
- )[0];
- if (input.precrawledArchiveId) {
- await ensureAssetOwnership({
+ .returning();
+ const uploadedAsset = await ensureAssetOwnership({
ctx,
- assetId: input.precrawledArchiveId,
+ assetId: input.assetId,
});
+ if (
+ !uploadedAsset.contentType ||
+ !SUPPORTED_BOOKMARK_ASSET_TYPES.has(uploadedAsset.contentType)
+ ) {
+ throw new TRPCError({
+ code: "BAD_REQUEST",
+ message: "Unsupported asset type",
+ });
+ }
await tx
.update(assets)
.set({
bookmarkId: bookmark.id,
- assetType: AssetTypes.LINK_PRECRAWLED_ARCHIVE,
+ assetType: AssetTypes.BOOKMARK_ASSET,
})
.where(
and(
- eq(assets.id, input.precrawledArchiveId),
+ eq(assets.id, input.assetId),
eq(assets.userId, ctx.user.id),
),
);
+ content = {
+ type: BookmarkTypes.ASSET,
+ assetType: asset.assetType,
+ assetId: asset.assetId,
+ };
+ break;
}
- content = {
- type: BookmarkTypes.LINK,
- ...link,
- };
- break;
- }
- case BookmarkTypes.TEXT: {
- const text = (
- await tx
- .insert(bookmarkTexts)
- .values({
- id: bookmark.id,
- text: input.text,
- sourceUrl: input.sourceUrl,
- })
- .returning()
- )[0];
- content = {
- type: BookmarkTypes.TEXT,
- text: text.text ?? "",
- sourceUrl: text.sourceUrl,
- };
- break;
- }
- case BookmarkTypes.ASSET: {
- const [asset] = await tx
- .insert(bookmarkAssets)
- .values({
- id: bookmark.id,
- assetType: input.assetType,
- assetId: input.assetId,
- content: null,
- metadata: null,
- fileName: input.fileName ?? null,
- sourceUrl: null,
- })
- .returning();
- const uploadedAsset = await ensureAssetOwnership({
- ctx,
- assetId: input.assetId,
- });
- if (
- !uploadedAsset.contentType ||
- !SUPPORTED_BOOKMARK_ASSET_TYPES.has(uploadedAsset.contentType)
- ) {
- throw new TRPCError({
- code: "BAD_REQUEST",
- message: "Unsupported asset type",
- });
- }
- await tx
- .update(assets)
- .set({
- bookmarkId: bookmark.id,
- assetType: AssetTypes.BOOKMARK_ASSET,
- })
- .where(
- and(
- eq(assets.id, input.assetId),
- eq(assets.userId, ctx.user.id),
- ),
- );
- content = {
- type: BookmarkTypes.ASSET,
- assetType: asset.assetType,
- assetId: asset.assetId,
- };
- break;
}
- }
- return {
- alreadyExists: false,
- tags: [] as ZBookmarkTags[],
- assets: [],
- content,
- ...bookmark,
- };
- });
+ return {
+ alreadyExists: false,
+ tags: [] as ZBookmarkTags[],
+ assets: [],
+ content,
+ ...bookmark,
+ };
+ },
+ {
+ behavior: "immediate",
+ },
+ );
if (input.importSessionId) {
const session = await ImportSession.fromId(ctx, input.importSessionId);