0.3.5 共享功能修复

This commit is contained in:
liaibo
2026-01-24 12:32:51 +08:00
parent 25923f308c
commit 3c3f407f4b
44 changed files with 3754 additions and 420 deletions
+137 -3
View File
@@ -1,7 +1,7 @@
import { mutation, query } from "./_generated/server";
import { v } from "convex/values";
import { nowIso } from "./_utils/time";
import { enqueueIngestMediaAssetJob } from "./_utils/ingestJobs";
import { enqueueExtractMediaAssetTextJob, enqueueIngestMediaAssetJob } from "./_utils/ingestJobs";
import type { MutationCtx, QueryCtx } from "./_generated/server";
async function assertWorkspaceMember(ctx: QueryCtx | MutationCtx, userId: string, workspaceId: string) {
@@ -15,6 +15,26 @@ async function assertWorkspaceMember(ctx: QueryCtx | MutationCtx, userId: string
return membership;
}
function shouldExtractAttachmentText(args: {
assetType?: string | null;
mimeType?: string | null;
fileName?: string | null;
}): boolean {
if (String(args.assetType ?? "") !== "file") return false;
const mime = String(args.mimeType ?? "").toLowerCase().trim();
if (mime === "application/pdf") return true;
if (mime === "application/vnd.openxmlformats-officedocument.wordprocessingml.document") return true;
if (mime === "application/vnd.openxmlformats-officedocument.presentationml.presentation") return true;
if (mime === "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet") return true;
const name = String(args.fileName ?? "").toLowerCase().trim();
if (name.endsWith(".pdf")) return true;
if (name.endsWith(".docx")) return true;
if (name.endsWith(".pptx")) return true;
if (name.endsWith(".xlsx")) return true;
return false;
}
export const getById = query({
args: { userId: v.string(), id: v.string() },
handler: async (ctx, args) => {
@@ -76,6 +96,44 @@ export const listByWorkspace = query({
},
});
export const listSearchDataByWorkspace = query({
args: {
userId: v.string(),
workspaceId: v.string(),
includeDeleted: v.optional(v.boolean()),
limit: v.optional(v.number()),
},
handler: async (ctx, args) => {
await assertWorkspaceMember(ctx, args.userId, args.workspaceId);
const includeDeleted = Boolean(args.includeDeleted);
const limit = Math.max(1, Math.min(5000, Math.floor(args.limit ?? 5000)));
const rows = await ctx.db
.query("media_assets")
.withIndex("by_workspace", (q) => q.eq("workspace_id", args.workspaceId))
.order("desc")
.take(limit * 2);
return rows
.filter((r) => (includeDeleted ? true : !r.deleted_at))
.filter((r) => !r.purged_at)
.slice(0, limit)
.map((r) => ({
id: r.id,
workspace_id: r.workspace_id,
document_id: r.document_id,
asset_type: r.asset_type,
file_name: r.file_name ?? null,
mime_type: r.mime_type ?? null,
ocr_text: r.ocr_text ?? null,
ocr_status: r.ocr_status ?? null,
created_at: r.created_at ?? null,
updated_at: r.updated_at ?? null,
}));
},
});
export const listByDocument = query({
args: {
userId: v.string(),
@@ -162,7 +220,13 @@ export const create = mutation({
...args.asset,
storage_id: args.asset.storage_id ?? null,
ocr_text: null,
ocr_status: null,
ocr_status: shouldExtractAttachmentText({
assetType: args.asset.asset_type,
mimeType: args.asset.mime_type,
fileName: args.asset.file_name,
})
? "queued"
: null,
deleted_at: null,
deleted_by: null,
purged_at: null,
@@ -171,6 +235,16 @@ export const create = mutation({
updated_at: ts,
});
if (
shouldExtractAttachmentText({
assetType: args.asset.asset_type,
mimeType: args.asset.mime_type,
fileName: args.asset.file_name,
})
) {
await enqueueExtractMediaAssetTextJob(ctx, { userId: args.userId, assetId: args.asset.id, debounceMs: 800 });
}
return args.asset;
},
});
@@ -222,7 +296,13 @@ export const createWithStorage = mutation({
mime_type: args.asset.mime_type,
ocr_text: null,
ocr_status: null,
ocr_status: shouldExtractAttachmentText({
assetType: args.asset.asset_type,
mimeType: args.asset.mime_type,
fileName: args.asset.file_name,
})
? "queued"
: null,
deleted_at: null,
deleted_by: null,
purged_at: null,
@@ -232,6 +312,16 @@ export const createWithStorage = mutation({
};
await ctx.db.insert("media_assets", row);
if (
shouldExtractAttachmentText({
assetType: args.asset.asset_type,
mimeType: args.asset.mime_type,
fileName: args.asset.file_name,
})
) {
await enqueueExtractMediaAssetTextJob(ctx, { userId: args.userId, assetId: args.asset.id, debounceMs: 800 });
}
return row;
},
});
@@ -312,13 +402,57 @@ export const replaceStorageFromUpload = mutation({
thumbnail_url: url,
bucket: null,
storage_path: null,
ocr_text: null,
ocr_status: shouldExtractAttachmentText({
assetType: row.asset_type,
mimeType: row.mime_type ?? null,
fileName: row.file_name ?? null,
})
? "queued"
: row.ocr_status ?? null,
updated_at: nowIso(),
});
if (
shouldExtractAttachmentText({
assetType: row.asset_type,
mimeType: row.mime_type ?? null,
fileName: row.file_name ?? null,
})
) {
await enqueueExtractMediaAssetTextJob(ctx, { userId: args.userId, assetId: args.id, debounceMs: 800 });
}
return { ok: true, fileUrl: url };
},
});
export const enqueueExtractText = mutation({
args: { userId: v.string(), id: v.string() },
handler: async (ctx, args) => {
const row = await ctx.db
.query("media_assets")
.withIndex("by_asset_id", (q) => q.eq("id", args.id))
.first();
if (!row) throw new Error("资源不存在");
await assertWorkspaceMember(ctx, args.userId, row.workspace_id);
if (
!shouldExtractAttachmentText({
assetType: row.asset_type,
mimeType: row.mime_type ?? null,
fileName: row.file_name ?? null,
})
) {
return { ok: false, reason: "unsupported" as const };
}
await ctx.db.patch(row._id, { ocr_status: "queued", updated_at: nowIso() });
await enqueueExtractMediaAssetTextJob(ctx, { userId: args.userId, assetId: args.id, debounceMs: 0 });
return { ok: true };
},
});
export const purgeById = mutation({
args: { userId: v.string(), id: v.string(), expiredDeletedAt: v.string() },
handler: async (ctx, args) => {