0.4.0 convex及界面修改

This commit is contained in:
liaibo
2026-02-01 08:47:40 +08:00
parent d1f055f51a
commit af92c4b149
636 changed files with 7522 additions and 1815 deletions
+70 -1
View File
@@ -1,14 +1,24 @@
import { internalQuery, mutation, query } from "./_generated/server";
import { internalMutation, internalQuery, mutation, query } from "./_generated/server";
import { v } from "convex/values";
import { getAuthUserId } from "@convex-dev/auth/server";
import { nowIso } from "./_utils/time";
import { enqueueIngestMindmapJob } from "./_utils/ingestJobs";
import { internal } from "./_generated/api";
const defaultMindmapData = {
data: { text: "中心主题" },
children: [],
};
function resolveGraceSeconds(): number {
const raw = process.env.DELETE_GRACE_SECONDS ?? process.env.NEXT_PUBLIC_DELETE_GRACE_SECONDS ?? "600";
const parsed = Number(raw);
if (!Number.isFinite(parsed) || parsed <= 0) {
return 600;
}
return Math.floor(parsed);
}
async function requireUserId(ctx: any): Promise<string> {
const userId = await getAuthUserId(ctx);
if (userId === null) {
@@ -195,12 +205,71 @@ export const softDelete = mutation({
return { ok: true, moved: 0 };
}
if (existing.deleted_at != null) {
// 已在垃圾桶:保持幂等,避免重复删除导致 updated_at 抖动/重复调度
return { ok: true, moved: 0, deleted_at: existing.deleted_at };
}
const ts = nowIso();
await ctx.db.patch(existing._id, { deleted_at: ts, deleted_by: userId, updated_at: ts });
// 10 分钟宽限(可恢复);到期自动清理(彻底删除)。
// 说明:这里用 scheduler.runAfter 做“按条目调度”,无需全局 cron。
const graceMs = (resolveGraceSeconds() + 5) * 1000;
await ctx.scheduler.runAfter(graceMs, internal.mindmaps.purgeIfExpired, {
docId: args.docId,
mindmapId,
deletedAt: ts,
});
return { ok: true, moved: 1, deleted_at: ts };
},
});
export const purgeIfExpired = internalMutation({
args: { docId: v.string(), mindmapId: v.string(), deletedAt: v.string() },
handler: async (ctx, args) => {
const mindmapId = normalizeMindmapId(args.docId, args.mindmapId);
const existing = await ctx.db
.query("mindmaps")
.withIndex("by_doc_mindmap", (q) => q.eq("document_id", args.docId).eq("mindmap_id", mindmapId))
.first();
if (!existing) {
return { ok: true, deleted: false, reason: "not_found" as const };
}
if (existing.deleted_at == null) {
return { ok: true, deleted: false, reason: "restored" as const };
}
if (existing.deleted_at !== args.deletedAt) {
// 删除时间不一致:说明已被恢复/重新删除过,旧调度作废
return { ok: true, deleted: false, reason: "changed" as const };
}
const graceSeconds = resolveGraceSeconds();
const deletedTs = Date.parse(existing.deleted_at);
if (Number.isFinite(deletedTs)) {
const elapsedMs = Date.now() - deletedTs;
const graceMs = graceSeconds * 1000;
if (elapsedMs < graceMs) {
// 兜底:调度延迟/时钟偏差时,重新排一次剩余时间
const remainingMs = Math.max(1000, graceMs - elapsedMs + 5000);
await ctx.scheduler.runAfter(remainingMs, internal.mindmaps.purgeIfExpired, {
docId: args.docId,
mindmapId,
deletedAt: args.deletedAt,
});
return { ok: true, deleted: false, rescheduled: true, remainingMs };
}
}
await ctx.db.delete(existing._id);
return { ok: true, deleted: true };
},
});
export const restore = mutation({
args: { docId: v.string(), mindmapId: v.string() },
handler: async (ctx, args) => {