0.6 rust重构01
This commit is contained in:
+6
@@ -9,6 +9,7 @@
|
||||
*/
|
||||
|
||||
import type * as _utils_attachmentExtract from "../_utils/attachmentExtract.js";
|
||||
import type * as _utils_auth from "../_utils/auth.js";
|
||||
import type * as _utils_documentTree from "../_utils/documentTree.js";
|
||||
import type * as _utils_id from "../_utils/id.js";
|
||||
import type * as _utils_ingestJobs from "../_utils/ingestJobs.js";
|
||||
@@ -16,8 +17,10 @@ import type * as _utils_lightrag from "../_utils/lightrag.js";
|
||||
import type * as _utils_text from "../_utils/text.js";
|
||||
import type * as _utils_time from "../_utils/time.js";
|
||||
import type * as agentActions from "../agentActions.js";
|
||||
import type * as audit from "../audit.js";
|
||||
import type * as auth from "../auth.js";
|
||||
import type * as blocks from "../blocks.js";
|
||||
import type * as bridgeLogs from "../bridgeLogs.js";
|
||||
import type * as comments from "../comments.js";
|
||||
import type * as crons from "../crons.js";
|
||||
import type * as documentGroupShares from "../documentGroupShares.js";
|
||||
@@ -49,6 +52,7 @@ import type {
|
||||
|
||||
declare const fullApi: ApiFromModules<{
|
||||
"_utils/attachmentExtract": typeof _utils_attachmentExtract;
|
||||
"_utils/auth": typeof _utils_auth;
|
||||
"_utils/documentTree": typeof _utils_documentTree;
|
||||
"_utils/id": typeof _utils_id;
|
||||
"_utils/ingestJobs": typeof _utils_ingestJobs;
|
||||
@@ -56,8 +60,10 @@ declare const fullApi: ApiFromModules<{
|
||||
"_utils/text": typeof _utils_text;
|
||||
"_utils/time": typeof _utils_time;
|
||||
agentActions: typeof agentActions;
|
||||
audit: typeof audit;
|
||||
auth: typeof auth;
|
||||
blocks: typeof blocks;
|
||||
bridgeLogs: typeof bridgeLogs;
|
||||
comments: typeof comments;
|
||||
crons: typeof crons;
|
||||
documentGroupShares: typeof documentGroupShares;
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
import { getAuthUserId } from "@convex-dev/auth/server";
|
||||
|
||||
function readEnv(key: string): string | undefined {
|
||||
const raw = process.env[key];
|
||||
if (!raw) return undefined;
|
||||
const trimmed = raw.trim();
|
||||
return trimmed ? trimmed : undefined;
|
||||
}
|
||||
|
||||
function isDevAuthEnabled(): boolean {
|
||||
return readEnv("MNOTE_DEV_AUTH") === "1" || readEnv("NEXT_PUBLIC_MNOTE_DEV_AUTH") === "1";
|
||||
}
|
||||
|
||||
export async function requireUserId(ctx: any): Promise<string> {
|
||||
const userId = await getAuthUserId(ctx);
|
||||
if (userId !== null) {
|
||||
return String(userId);
|
||||
}
|
||||
|
||||
if (isDevAuthEnabled()) {
|
||||
return readEnv("DEV_USER_ID") ?? "dev-user";
|
||||
}
|
||||
|
||||
throw new Error("未登录");
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
type DocumentRecordLike = {
|
||||
_id: unknown;
|
||||
id?: string | null;
|
||||
user_id?: string | null;
|
||||
parent_id?: string | null;
|
||||
deleted_at?: string | null;
|
||||
created_at?: string | null;
|
||||
updated_at?: string | null;
|
||||
};
|
||||
|
||||
export function compareDocumentCanonicalOrder(a: DocumentRecordLike, b: DocumentRecordLike): number {
|
||||
const aDeleted = a?.deleted_at != null;
|
||||
const bDeleted = b?.deleted_at != null;
|
||||
if (aDeleted !== bDeleted) {
|
||||
return aDeleted ? 1 : -1;
|
||||
}
|
||||
|
||||
const updatedA = String(a?.updated_at ?? "");
|
||||
const updatedB = String(b?.updated_at ?? "");
|
||||
if (updatedA !== updatedB) {
|
||||
return updatedB.localeCompare(updatedA);
|
||||
}
|
||||
|
||||
const createdA = String(a?.created_at ?? "");
|
||||
const createdB = String(b?.created_at ?? "");
|
||||
if (createdA !== createdB) {
|
||||
return createdB.localeCompare(createdA);
|
||||
}
|
||||
|
||||
return String(a?._id ?? "").localeCompare(String(b?._id ?? ""));
|
||||
}
|
||||
|
||||
export function pickCanonicalDocumentRecord<T extends DocumentRecordLike>(records: T[]): T | null {
|
||||
if (records.length === 0) return null;
|
||||
return [...records].sort(compareDocumentCanonicalOrder)[0] ?? null;
|
||||
}
|
||||
|
||||
export async function getCanonicalDocumentByBusinessId<T extends DocumentRecordLike>(
|
||||
ctx: any,
|
||||
documentId: string,
|
||||
): Promise<T | null> {
|
||||
const records = await ctx.db
|
||||
.query("documents")
|
||||
.withIndex("by_document_id", (q: any) => q.eq("id", documentId))
|
||||
.collect();
|
||||
return pickCanonicalDocumentRecord(records) as T | null;
|
||||
}
|
||||
|
||||
export async function getCanonicalParentDocumentId(
|
||||
ctx: any,
|
||||
documentId: string | null | undefined,
|
||||
): Promise<string | null> {
|
||||
const normalizedId = String(documentId ?? "").trim();
|
||||
if (!normalizedId) return null;
|
||||
|
||||
const doc = await getCanonicalDocumentByBusinessId<DocumentRecordLike>(ctx, normalizedId);
|
||||
return doc?.parent_id ?? null;
|
||||
}
|
||||
|
||||
export async function requireCanonicalOwnedDocument<T extends DocumentRecordLike & { user_id?: string | null }>(
|
||||
ctx: any,
|
||||
documentId: string,
|
||||
userId: string,
|
||||
): Promise<T> {
|
||||
const doc = await getCanonicalDocumentByBusinessId<T>(ctx, documentId);
|
||||
if (!doc) {
|
||||
throw new Error("页面不存在");
|
||||
}
|
||||
if (String(doc.user_id ?? "") !== String(userId)) {
|
||||
throw new Error("无权限");
|
||||
}
|
||||
return doc;
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
import { query } from "./_generated/server";
|
||||
import { v } from "convex/values";
|
||||
import { requireUserId } from "./_utils/auth";
|
||||
import { nowIso } from "./_utils/time";
|
||||
|
||||
async function requireWorkspaceMember(ctx: any, workspaceId: string, userId: string) {
|
||||
const member = await ctx.db
|
||||
.query("workspace_members")
|
||||
.withIndex("by_workspace_user", (q: any) => q.eq("workspace_id", workspaceId).eq("user_id", userId))
|
||||
.first();
|
||||
if (!member) {
|
||||
throw new Error("无权限");
|
||||
}
|
||||
return member;
|
||||
}
|
||||
|
||||
export const listByTrace = query({
|
||||
args: { workspaceId: v.string(), traceId: v.string() },
|
||||
handler: async (ctx, args) => {
|
||||
const userId = await requireUserId(ctx);
|
||||
await requireWorkspaceMember(ctx, args.workspaceId, userId);
|
||||
|
||||
const commandLogs = await ctx.db
|
||||
.query("command_logs")
|
||||
.withIndex("by_workspace_trace", (q) => q.eq("workspace_id", args.workspaceId).eq("trace_id", args.traceId))
|
||||
.collect();
|
||||
const domainEvents = await ctx.db
|
||||
.query("domain_events")
|
||||
.withIndex("by_workspace_trace", (q) => q.eq("workspace_id", args.workspaceId).eq("trace_id", args.traceId))
|
||||
.collect();
|
||||
|
||||
return {
|
||||
trace_id: args.traceId,
|
||||
command_logs: commandLogs,
|
||||
domain_events: domainEvents,
|
||||
generated_at: nowIso(),
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
export const listByRequest = query({
|
||||
args: { workspaceId: v.string(), requestId: v.string() },
|
||||
handler: async (ctx, args) => {
|
||||
const userId = await requireUserId(ctx);
|
||||
await requireWorkspaceMember(ctx, args.workspaceId, userId);
|
||||
|
||||
const commandLogs = await ctx.db
|
||||
.query("command_logs")
|
||||
.withIndex("by_workspace_request", (q) => q.eq("workspace_id", args.workspaceId).eq("request_id", args.requestId))
|
||||
.collect();
|
||||
const domainEvents = await ctx.db
|
||||
.query("domain_events")
|
||||
.withIndex("by_workspace_request", (q) => q.eq("workspace_id", args.workspaceId).eq("request_id", args.requestId))
|
||||
.collect();
|
||||
|
||||
return {
|
||||
request_id: args.requestId,
|
||||
command_logs: commandLogs,
|
||||
domain_events: domainEvents,
|
||||
generated_at: nowIso(),
|
||||
};
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,173 @@
|
||||
import { mutation, query } from "./_generated/server";
|
||||
import { v } from "convex/values";
|
||||
import { requireUserId } from "./_utils/auth";
|
||||
import { nowIso } from "./_utils/time";
|
||||
|
||||
async function requireWorkspaceMember(ctx: any, workspaceId: string, userId: string) {
|
||||
const member = await ctx.db
|
||||
.query("workspace_members")
|
||||
.withIndex("by_workspace_user", (q: any) => q.eq("workspace_id", workspaceId).eq("user_id", userId))
|
||||
.first();
|
||||
if (!member) {
|
||||
throw new Error("无权限");
|
||||
}
|
||||
return member;
|
||||
}
|
||||
|
||||
export const recordCommandLog = mutation({
|
||||
args: {
|
||||
workspaceId: v.string(),
|
||||
id: v.string(),
|
||||
requestId: v.string(),
|
||||
traceId: v.string(),
|
||||
commandId: v.string(),
|
||||
commandName: v.string(),
|
||||
actorId: v.string(),
|
||||
actorType: v.string(),
|
||||
sourceChannel: v.string(),
|
||||
sourceClient: v.string(),
|
||||
status: v.union(v.literal("pending"), v.literal("succeeded"), v.literal("failed"), v.literal("rolled_back")),
|
||||
targetPageId: v.optional(v.union(v.string(), v.null())),
|
||||
targetBlockId: v.optional(v.union(v.string(), v.null())),
|
||||
payload: v.any(),
|
||||
payloadSummary: v.string(),
|
||||
refs: v.array(v.string()),
|
||||
idempotencyKey: v.optional(v.union(v.string(), v.null())),
|
||||
error: v.optional(v.union(v.string(), v.null())),
|
||||
createdAt: v.string(),
|
||||
finishedAt: v.optional(v.union(v.string(), v.null())),
|
||||
},
|
||||
handler: async (ctx, args) => {
|
||||
const userId = await requireUserId(ctx);
|
||||
await requireWorkspaceMember(ctx, args.workspaceId, userId);
|
||||
|
||||
const existing = await ctx.db
|
||||
.query("command_logs")
|
||||
.withIndex("by_command_log_id", (q) => q.eq("id", args.id))
|
||||
.first();
|
||||
if (existing) {
|
||||
return { ok: true, id: existing.id, duplicated: true };
|
||||
}
|
||||
|
||||
await ctx.db.insert("command_logs", {
|
||||
id: args.id,
|
||||
workspace_id: args.workspaceId,
|
||||
request_id: args.requestId,
|
||||
trace_id: args.traceId,
|
||||
command_id: args.commandId,
|
||||
command_name: args.commandName,
|
||||
actor_id: args.actorId,
|
||||
actor_type: args.actorType,
|
||||
source_channel: args.sourceChannel,
|
||||
source_client: args.sourceClient,
|
||||
status: args.status,
|
||||
target_page_id: args.targetPageId ?? null,
|
||||
target_block_id: args.targetBlockId ?? null,
|
||||
payload: args.payload,
|
||||
payload_summary: args.payloadSummary,
|
||||
refs: args.refs,
|
||||
idempotency_key: args.idempotencyKey ?? null,
|
||||
error: args.error ?? null,
|
||||
created_at: args.createdAt,
|
||||
finished_at: args.finishedAt ?? null,
|
||||
});
|
||||
return { ok: true, id: args.id, duplicated: false };
|
||||
},
|
||||
});
|
||||
|
||||
export const recordDomainEvent = mutation({
|
||||
args: {
|
||||
workspaceId: v.string(),
|
||||
id: v.string(),
|
||||
requestId: v.string(),
|
||||
traceId: v.string(),
|
||||
commandId: v.string(),
|
||||
commandLogId: v.string(),
|
||||
eventType: v.string(),
|
||||
aggregateType: v.string(),
|
||||
aggregateId: v.string(),
|
||||
eventVersion: v.number(),
|
||||
status: v.union(v.literal("pending"), v.literal("committed"), v.literal("rejected"), v.literal("failed")),
|
||||
actorType: v.string(),
|
||||
payload: v.any(),
|
||||
createdAt: v.string(),
|
||||
},
|
||||
handler: async (ctx, args) => {
|
||||
const userId = await requireUserId(ctx);
|
||||
await requireWorkspaceMember(ctx, args.workspaceId, userId);
|
||||
|
||||
const existing = await ctx.db
|
||||
.query("domain_events")
|
||||
.withIndex("by_domain_event_id", (q) => q.eq("id", args.id))
|
||||
.first();
|
||||
if (existing) {
|
||||
return { ok: true, id: existing.id, duplicated: true };
|
||||
}
|
||||
|
||||
await ctx.db.insert("domain_events", {
|
||||
id: args.id,
|
||||
workspace_id: args.workspaceId,
|
||||
request_id: args.requestId,
|
||||
trace_id: args.traceId,
|
||||
command_id: args.commandId,
|
||||
command_log_id: args.commandLogId,
|
||||
event_type: args.eventType,
|
||||
aggregate_type: args.aggregateType,
|
||||
aggregate_id: args.aggregateId,
|
||||
event_version: args.eventVersion,
|
||||
status: args.status,
|
||||
actor_type: args.actorType,
|
||||
payload: args.payload,
|
||||
created_at: args.createdAt,
|
||||
});
|
||||
return { ok: true, id: args.id, duplicated: false };
|
||||
},
|
||||
});
|
||||
|
||||
export const listByTrace = query({
|
||||
args: { workspaceId: v.string(), traceId: v.string() },
|
||||
handler: async (ctx, args) => {
|
||||
const userId = await requireUserId(ctx);
|
||||
await requireWorkspaceMember(ctx, args.workspaceId, userId);
|
||||
|
||||
const commandLogs = await ctx.db
|
||||
.query("command_logs")
|
||||
.withIndex("by_workspace_trace", (q) => q.eq("workspace_id", args.workspaceId).eq("trace_id", args.traceId))
|
||||
.collect();
|
||||
const domainEvents = await ctx.db
|
||||
.query("domain_events")
|
||||
.withIndex("by_workspace_trace", (q) => q.eq("workspace_id", args.workspaceId).eq("trace_id", args.traceId))
|
||||
.collect();
|
||||
|
||||
return {
|
||||
trace_id: args.traceId,
|
||||
command_logs: commandLogs,
|
||||
domain_events: domainEvents,
|
||||
generated_at: nowIso(),
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
export const listByRequest = query({
|
||||
args: { workspaceId: v.string(), requestId: v.string() },
|
||||
handler: async (ctx, args) => {
|
||||
const userId = await requireUserId(ctx);
|
||||
await requireWorkspaceMember(ctx, args.workspaceId, userId);
|
||||
|
||||
const commandLogs = await ctx.db
|
||||
.query("command_logs")
|
||||
.withIndex("by_workspace_request", (q) => q.eq("workspace_id", args.workspaceId).eq("request_id", args.requestId))
|
||||
.collect();
|
||||
const domainEvents = await ctx.db
|
||||
.query("domain_events")
|
||||
.withIndex("by_workspace_request", (q) => q.eq("workspace_id", args.workspaceId).eq("request_id", args.requestId))
|
||||
.collect();
|
||||
|
||||
return {
|
||||
request_id: args.requestId,
|
||||
command_logs: commandLogs,
|
||||
domain_events: domainEvents,
|
||||
generated_at: nowIso(),
|
||||
};
|
||||
},
|
||||
});
|
||||
@@ -2,6 +2,7 @@ import { mutation, query } from "./_generated/server";
|
||||
import { v } from "convex/values";
|
||||
import { getAuthUserId } from "@convex-dev/auth/server";
|
||||
import { nowIso } from "./_utils/time";
|
||||
import { getCanonicalDocumentByBusinessId, getCanonicalParentDocumentId } from "./_utils/documentRecord";
|
||||
|
||||
async function requireUserId(ctx: any): Promise<string> {
|
||||
const userId = await getAuthUserId(ctx);
|
||||
@@ -43,11 +44,7 @@ async function resolveSharePermission(ctx: any, doc: any, userId: string): Promi
|
||||
return parentShare.permission as SharePermission;
|
||||
}
|
||||
|
||||
const parent = await ctx.db
|
||||
.query("documents")
|
||||
.withIndex("by_document_id", (q: any) => q.eq("id", parentId))
|
||||
.first();
|
||||
parentId = parent?.parent_id ?? null;
|
||||
parentId = await getCanonicalParentDocumentId(ctx, parentId);
|
||||
}
|
||||
|
||||
return null;
|
||||
@@ -83,11 +80,7 @@ async function resolveGroupSharePermission(ctx: any, doc: any, userId: string):
|
||||
return "read";
|
||||
}
|
||||
}
|
||||
const parent = await ctx.db
|
||||
.query("documents")
|
||||
.withIndex("by_document_id", (q: any) => q.eq("id", parentId))
|
||||
.first();
|
||||
parentId = parent?.parent_id ?? null;
|
||||
parentId = await getCanonicalParentDocumentId(ctx, parentId);
|
||||
}
|
||||
|
||||
return null;
|
||||
@@ -132,10 +125,7 @@ export const listThreadsByDocument = query({
|
||||
handler: async (ctx, args) => {
|
||||
const userId = await requireUserId(ctx);
|
||||
|
||||
const doc = await ctx.db
|
||||
.query("documents")
|
||||
.withIndex("by_document_id", (q: any) => q.eq("id", args.documentId))
|
||||
.first();
|
||||
const doc = await getCanonicalDocumentByBusinessId<any>(ctx, args.documentId);
|
||||
if (!doc) throw new Error("页面不存在");
|
||||
if (doc.deleted_at != null) throw new Error("页面不存在");
|
||||
await requireCanViewDocument(ctx, doc, userId);
|
||||
@@ -190,10 +180,7 @@ export const listMessagesByThread = query({
|
||||
.first();
|
||||
if (!thread) throw new Error("评论线程不存在");
|
||||
|
||||
const doc = await ctx.db
|
||||
.query("documents")
|
||||
.withIndex("by_document_id", (q: any) => q.eq("id", thread.document_id))
|
||||
.first();
|
||||
const doc = await getCanonicalDocumentByBusinessId<any>(ctx, String(thread.document_id));
|
||||
if (!doc) throw new Error("页面不存在");
|
||||
if (doc.deleted_at != null) throw new Error("页面不存在");
|
||||
await requireCanViewDocument(ctx, doc, userId);
|
||||
@@ -241,10 +228,7 @@ export const createThread = mutation({
|
||||
handler: async (ctx, args) => {
|
||||
const userId = await requireUserId(ctx);
|
||||
|
||||
const doc = await ctx.db
|
||||
.query("documents")
|
||||
.withIndex("by_document_id", (q: any) => q.eq("id", args.documentId))
|
||||
.first();
|
||||
const doc = await getCanonicalDocumentByBusinessId<any>(ctx, args.documentId);
|
||||
if (!doc) throw new Error("页面不存在");
|
||||
if (doc.deleted_at != null) throw new Error("页面不存在");
|
||||
await requireCanViewDocument(ctx, doc, userId);
|
||||
@@ -305,10 +289,7 @@ export const reply = mutation({
|
||||
.first();
|
||||
if (!thread) throw new Error("评论线程不存在");
|
||||
|
||||
const doc = await ctx.db
|
||||
.query("documents")
|
||||
.withIndex("by_document_id", (q: any) => q.eq("id", thread.document_id))
|
||||
.first();
|
||||
const doc = await getCanonicalDocumentByBusinessId<any>(ctx, String(thread.document_id));
|
||||
if (!doc) throw new Error("页面不存在");
|
||||
if (doc.deleted_at != null) throw new Error("页面不存在");
|
||||
await requireCanViewDocument(ctx, doc, userId);
|
||||
@@ -354,10 +335,7 @@ export const setResolved = mutation({
|
||||
.first();
|
||||
if (!thread) throw new Error("评论线程不存在");
|
||||
|
||||
const doc = await ctx.db
|
||||
.query("documents")
|
||||
.withIndex("by_document_id", (q: any) => q.eq("id", thread.document_id))
|
||||
.first();
|
||||
const doc = await getCanonicalDocumentByBusinessId<any>(ctx, String(thread.document_id));
|
||||
if (!doc) throw new Error("页面不存在");
|
||||
if (doc.deleted_at != null) throw new Error("页面不存在");
|
||||
|
||||
@@ -393,10 +371,7 @@ export const editMessage = mutation({
|
||||
.first();
|
||||
if (!thread) throw new Error("评论线程不存在");
|
||||
|
||||
const doc = await ctx.db
|
||||
.query("documents")
|
||||
.withIndex("by_document_id", (q: any) => q.eq("id", message.document_id))
|
||||
.first();
|
||||
const doc = await getCanonicalDocumentByBusinessId<any>(ctx, String(message.document_id));
|
||||
if (!doc) throw new Error("页面不存在");
|
||||
if (doc.deleted_at != null) throw new Error("页面不存在");
|
||||
await requireCanViewDocument(ctx, doc, userId);
|
||||
@@ -444,10 +419,7 @@ export const deleteMessage = mutation({
|
||||
.first();
|
||||
if (!thread) throw new Error("评论线程不存在");
|
||||
|
||||
const doc = await ctx.db
|
||||
.query("documents")
|
||||
.withIndex("by_document_id", (q: any) => q.eq("id", message.document_id))
|
||||
.first();
|
||||
const doc = await getCanonicalDocumentByBusinessId<any>(ctx, String(message.document_id));
|
||||
if (!doc) throw new Error("页面不存在");
|
||||
if (doc.deleted_at != null) throw new Error("页面不存在");
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import { mutation, query } from "./_generated/server";
|
||||
import { v } from "convex/values";
|
||||
import { getAuthUserId } from "@convex-dev/auth/server";
|
||||
import { nowIso } from "./_utils/time";
|
||||
import { requireCanonicalOwnedDocument } from "./_utils/documentRecord";
|
||||
|
||||
type Permission = "read" | "edit";
|
||||
|
||||
@@ -11,16 +12,6 @@ async function requireUserId(ctx: any): Promise<string> {
|
||||
return String(userId);
|
||||
}
|
||||
|
||||
async function requireOwnedDocument(ctx: any, documentId: string, userId: string) {
|
||||
const doc = await ctx.db
|
||||
.query("documents")
|
||||
.withIndex("by_document_id", (q: any) => q.eq("id", documentId))
|
||||
.first();
|
||||
if (!doc) throw new Error("页面不存在");
|
||||
if (doc.user_id !== userId) throw new Error("无权限");
|
||||
return doc;
|
||||
}
|
||||
|
||||
async function requireGroup(ctx: any, groupId: string) {
|
||||
const group = await ctx.db
|
||||
.query("groups")
|
||||
@@ -43,7 +34,7 @@ export const listByDocument = query({
|
||||
args: { documentId: v.string() },
|
||||
handler: async (ctx, args) => {
|
||||
const userId = await requireUserId(ctx);
|
||||
const doc = await requireOwnedDocument(ctx, args.documentId, userId);
|
||||
const doc = await requireCanonicalOwnedDocument(ctx, args.documentId, userId);
|
||||
|
||||
const shares = await ctx.db
|
||||
.query("document_group_shares")
|
||||
@@ -94,7 +85,7 @@ export const upsert = mutation({
|
||||
},
|
||||
handler: async (ctx, args) => {
|
||||
const userId = await requireUserId(ctx);
|
||||
const doc = await requireOwnedDocument(ctx, args.documentId, userId);
|
||||
const doc = await requireCanonicalOwnedDocument(ctx, args.documentId, userId);
|
||||
const group = await requireGroup(ctx, args.groupId);
|
||||
if (group.workspace_id !== doc.workspace_id) throw new Error("群组不属于当前工作空间");
|
||||
await requireGroupMember(ctx, args.groupId, userId);
|
||||
@@ -178,7 +169,7 @@ export const remove = mutation({
|
||||
args: { documentId: v.string(), groupId: v.string() },
|
||||
handler: async (ctx, args) => {
|
||||
const userId = await requireUserId(ctx);
|
||||
const doc = await requireOwnedDocument(ctx, args.documentId, userId);
|
||||
const doc = await requireCanonicalOwnedDocument(ctx, args.documentId, userId);
|
||||
|
||||
const existingShare = await ctx.db
|
||||
.query("document_group_shares")
|
||||
|
||||
@@ -3,6 +3,7 @@ import { v } from "convex/values";
|
||||
import { getAuthUserId } from "@convex-dev/auth/server";
|
||||
import { nowIso } from "./_utils/time";
|
||||
import type { Id } from "./_generated/dataModel";
|
||||
import { requireCanonicalOwnedDocument, getCanonicalDocumentByBusinessId } from "./_utils/documentRecord";
|
||||
|
||||
const permission = v.union(v.literal("read"), v.literal("edit"));
|
||||
|
||||
@@ -20,16 +21,6 @@ function normalizeUsername(raw: string): string {
|
||||
return username;
|
||||
}
|
||||
|
||||
async function requireOwnedDocument(ctx: any, documentId: string, userId: string) {
|
||||
const doc = await ctx.db
|
||||
.query("documents")
|
||||
.withIndex("by_document_id", (q: any) => q.eq("id", documentId))
|
||||
.first();
|
||||
if (!doc) throw new Error("页面不存在");
|
||||
if (doc.user_id !== userId) throw new Error("无权限");
|
||||
return doc;
|
||||
}
|
||||
|
||||
async function resolveUserIdByUsername(ctx: any, rawUsername: string): Promise<{ userId: string; username: string }> {
|
||||
const username = normalizeUsername(rawUsername);
|
||||
|
||||
@@ -47,7 +38,7 @@ export const listByDocument = query({
|
||||
args: { documentId: v.string() },
|
||||
handler: async (ctx, args) => {
|
||||
const userId = await requireUserId(ctx);
|
||||
const doc = await requireOwnedDocument(ctx, args.documentId, userId);
|
||||
const doc = await requireCanonicalOwnedDocument(ctx, args.documentId, userId);
|
||||
|
||||
const shares = await ctx.db
|
||||
.query("document_shares")
|
||||
@@ -84,7 +75,7 @@ export const upsert = mutation({
|
||||
},
|
||||
handler: async (ctx, args) => {
|
||||
const userId = await requireUserId(ctx);
|
||||
const doc = await requireOwnedDocument(ctx, args.documentId, userId);
|
||||
const doc = await requireCanonicalOwnedDocument(ctx, args.documentId, userId);
|
||||
const { userId: sharedWithUserId } = await resolveUserIdByUsername(ctx, args.username);
|
||||
if (sharedWithUserId === userId) throw new Error("不能共享给自己");
|
||||
|
||||
@@ -149,7 +140,7 @@ export const remove = mutation({
|
||||
args: { documentId: v.string(), sharedWithUserId: v.string() },
|
||||
handler: async (ctx, args) => {
|
||||
const userId = await requireUserId(ctx);
|
||||
const doc = await requireOwnedDocument(ctx, args.documentId, userId);
|
||||
const doc = await requireCanonicalOwnedDocument(ctx, args.documentId, userId);
|
||||
|
||||
const existingShare = await ctx.db
|
||||
.query("document_shares")
|
||||
@@ -294,10 +285,7 @@ export const listMyShareRoots = query({
|
||||
if (documentCache.has(documentId)) {
|
||||
return cached ?? { exists: false, deleted: true, title: null };
|
||||
}
|
||||
const doc = await ctx.db
|
||||
.query("documents")
|
||||
.withIndex("by_document_id", (q: any) => q.eq("id", documentId))
|
||||
.first();
|
||||
const doc = await getCanonicalDocumentByBusinessId<any>(ctx, documentId);
|
||||
const info = doc
|
||||
? { exists: true, deleted: doc.deleted_at != null, title: (doc.title ?? null) as string | null }
|
||||
: { exists: false, deleted: true, title: null };
|
||||
|
||||
@@ -2,6 +2,7 @@ import { mutation, query } from "./_generated/server";
|
||||
import { v } from "convex/values";
|
||||
import { getAuthUserId } from "@convex-dev/auth/server";
|
||||
import { nowIso } from "./_utils/time";
|
||||
import { getCanonicalDocumentByBusinessId, getCanonicalParentDocumentId } from "./_utils/documentRecord";
|
||||
|
||||
type SharePermission = "read" | "edit";
|
||||
|
||||
@@ -34,12 +35,7 @@ async function resolveSharePermission(ctx: any, doc: any, userId: string): Promi
|
||||
.withIndex("by_doc_user", (q: any) => q.eq("document_id", parentId).eq("shared_with_user_id", userId))
|
||||
.first();
|
||||
if (parentShare && parentShare.include_descendants) return parentShare.permission as SharePermission;
|
||||
|
||||
const parent = await ctx.db
|
||||
.query("documents")
|
||||
.withIndex("by_document_id", (q: any) => q.eq("id", parentId))
|
||||
.first();
|
||||
parentId = parent?.parent_id ?? null;
|
||||
parentId = await getCanonicalParentDocumentId(ctx, parentId);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -89,12 +85,7 @@ async function resolveGroupSharePermission(ctx: any, doc: any, userId: string):
|
||||
let parentId: string | null = doc.parent_id ?? null;
|
||||
for (let depth = 0; depth < 60 && parentId; depth += 1) {
|
||||
if (await checkDocId(parentId, true)) return "edit";
|
||||
|
||||
const parent = await ctx.db
|
||||
.query("documents")
|
||||
.withIndex("by_document_id", (q: any) => q.eq("id", parentId))
|
||||
.first();
|
||||
parentId = parent?.parent_id ?? null;
|
||||
parentId = await getCanonicalParentDocumentId(ctx, parentId);
|
||||
}
|
||||
|
||||
return best;
|
||||
@@ -109,10 +100,7 @@ export const isStarred = query({
|
||||
if (authUserId === null) return false;
|
||||
const userId = String(authUserId);
|
||||
|
||||
const doc = await ctx.db
|
||||
.query("documents")
|
||||
.withIndex("by_document_id", (q: any) => q.eq("id", args.documentId))
|
||||
.first();
|
||||
const doc = await getCanonicalDocumentByBusinessId<any>(ctx, args.documentId);
|
||||
if (!doc) return false;
|
||||
if (doc.deleted_at != null) return false;
|
||||
|
||||
@@ -137,10 +125,7 @@ export const toggle = mutation({
|
||||
handler: async (ctx, args) => {
|
||||
const userId = await requireUserId(ctx);
|
||||
|
||||
const doc = await ctx.db
|
||||
.query("documents")
|
||||
.withIndex("by_document_id", (q: any) => q.eq("id", args.documentId))
|
||||
.first();
|
||||
const doc = await getCanonicalDocumentByBusinessId<any>(ctx, args.documentId);
|
||||
if (!doc) throw new Error("页面不存在");
|
||||
if (doc.deleted_at != null) throw new Error("页面不存在");
|
||||
|
||||
|
||||
@@ -1,21 +1,14 @@
|
||||
import { internalQuery, mutation, query } from "./_generated/server";
|
||||
import { v } from "convex/values";
|
||||
import { getAuthUserId } from "@convex-dev/auth/server";
|
||||
import { requireUserId } from "./_utils/auth";
|
||||
import { nowIso } from "./_utils/time";
|
||||
import { collectSubtree } from "./_utils/documentTree";
|
||||
import { enqueueIngestDocumentJob } from "./_utils/ingestJobs";
|
||||
import { extractTextFromDocumentContent } from "./_utils/text";
|
||||
import { getCanonicalDocumentByBusinessId, getCanonicalParentDocumentId } from "./_utils/documentRecord";
|
||||
|
||||
const accessScope = v.union(v.literal("private"), v.literal("shared"), v.literal("public"));
|
||||
|
||||
async function requireUserId(ctx: any): Promise<string> {
|
||||
const userId = await getAuthUserId(ctx);
|
||||
if (userId === null) {
|
||||
throw new Error("未登录");
|
||||
}
|
||||
return userId;
|
||||
}
|
||||
|
||||
type SharePermission = "read" | "edit";
|
||||
|
||||
type SharePolicy = { permission: SharePermission; disableDownload: boolean; disableCopy: boolean };
|
||||
@@ -58,11 +51,7 @@ async function resolveSharePolicy(ctx: any, doc: any, userId: string): Promise<S
|
||||
};
|
||||
}
|
||||
|
||||
const parent = await ctx.db
|
||||
.query("documents")
|
||||
.withIndex("by_document_id", (q: any) => q.eq("id", parentId))
|
||||
.first();
|
||||
parentId = parent?.parent_id ?? null;
|
||||
parentId = await getCanonicalParentDocumentId(ctx, parentId);
|
||||
}
|
||||
|
||||
return null;
|
||||
@@ -127,11 +116,7 @@ async function resolveGroupSharePolicy(ctx: any, doc: any, userId: string): Prom
|
||||
for (let depth = 0; depth < 60 && parentId; depth += 1) {
|
||||
await checkDocId(parentId, true);
|
||||
|
||||
const parent = await ctx.db
|
||||
.query("documents")
|
||||
.withIndex("by_document_id", (q: any) => q.eq("id", parentId))
|
||||
.first();
|
||||
parentId = parent?.parent_id ?? null;
|
||||
parentId = await getCanonicalParentDocumentId(ctx, parentId);
|
||||
}
|
||||
|
||||
if (!best) return null;
|
||||
@@ -331,10 +316,7 @@ export const getMeta = query({
|
||||
handler: async (ctx, args) => {
|
||||
const userId = await requireUserId(ctx);
|
||||
|
||||
const doc = await ctx.db
|
||||
.query("documents")
|
||||
.withIndex("by_document_id", (q) => q.eq("id", args.id))
|
||||
.first();
|
||||
const doc = await getCanonicalDocumentByBusinessId(ctx, args.id);
|
||||
if (!doc) return null;
|
||||
if (doc.deleted_at != null) return null;
|
||||
try {
|
||||
@@ -394,10 +376,7 @@ export const getMeta = query({
|
||||
export const getPermissionForUser = query({
|
||||
args: { userId: v.string(), id: v.string() },
|
||||
handler: async (ctx, args) => {
|
||||
const doc = await ctx.db
|
||||
.query("documents")
|
||||
.withIndex("by_document_id", (q) => q.eq("id", args.id))
|
||||
.first();
|
||||
const doc = await getCanonicalDocumentByBusinessId(ctx, args.id);
|
||||
if (!doc) return null;
|
||||
if (doc.deleted_at != null) return null;
|
||||
|
||||
@@ -425,10 +404,7 @@ export const getPermissionForUser = query({
|
||||
export const getMetaForIngest = internalQuery({
|
||||
args: { userId: v.string(), id: v.string() },
|
||||
handler: async (ctx, args) => {
|
||||
const doc = await ctx.db
|
||||
.query("documents")
|
||||
.withIndex("by_document_id", (q) => q.eq("id", args.id))
|
||||
.first();
|
||||
const doc = await getCanonicalDocumentByBusinessId(ctx, args.id);
|
||||
if (!doc) return null;
|
||||
if (doc.user_id !== args.userId) return null;
|
||||
return {
|
||||
@@ -459,10 +435,7 @@ export const getContent = query({
|
||||
handler: async (ctx, args) => {
|
||||
const userId = await requireUserId(ctx);
|
||||
|
||||
const doc = await ctx.db
|
||||
.query("documents")
|
||||
.withIndex("by_document_id", (q) => q.eq("id", args.id))
|
||||
.first();
|
||||
const doc = await getCanonicalDocumentByBusinessId(ctx, args.id);
|
||||
if (!doc) return null;
|
||||
if (doc.deleted_at != null) return null;
|
||||
try {
|
||||
@@ -486,10 +459,7 @@ export const getContent = query({
|
||||
export const getContentForIngest = internalQuery({
|
||||
args: { userId: v.string(), id: v.string() },
|
||||
handler: async (ctx, args) => {
|
||||
const doc = await ctx.db
|
||||
.query("documents")
|
||||
.withIndex("by_document_id", (q) => q.eq("id", args.id))
|
||||
.first();
|
||||
const doc = await getCanonicalDocumentByBusinessId(ctx, args.id);
|
||||
if (!doc) return null;
|
||||
if (doc.user_id !== args.userId) return null;
|
||||
return { content: doc.content ?? null };
|
||||
@@ -797,6 +767,10 @@ export const create = mutation({
|
||||
},
|
||||
handler: async (ctx, args) => {
|
||||
const userId = await requireUserId(ctx);
|
||||
const existing = await getCanonicalDocumentByBusinessId(ctx, args.id);
|
||||
if (existing && existing.deleted_at == null) {
|
||||
throw new Error("页面已存在");
|
||||
}
|
||||
|
||||
const siblings = await ctx.db
|
||||
.query("documents")
|
||||
@@ -867,10 +841,7 @@ export const updateContent = mutation({
|
||||
handler: async (ctx, args) => {
|
||||
const userId = await requireUserId(ctx);
|
||||
|
||||
const doc = await ctx.db
|
||||
.query("documents")
|
||||
.withIndex("by_document_id", (q) => q.eq("id", args.id))
|
||||
.first();
|
||||
const doc = await getCanonicalDocumentByBusinessId(ctx, args.id);
|
||||
if (!doc) throw new Error("页面不存在");
|
||||
if (doc.deleted_at != null) throw new Error("页面不存在");
|
||||
await requireWorkspaceMember(ctx, doc.workspace_id, userId);
|
||||
@@ -896,10 +867,7 @@ export const updateTitle = mutation({
|
||||
handler: async (ctx, args) => {
|
||||
const userId = await requireUserId(ctx);
|
||||
|
||||
const doc = await ctx.db
|
||||
.query("documents")
|
||||
.withIndex("by_document_id", (q) => q.eq("id", args.id))
|
||||
.first();
|
||||
const doc = await getCanonicalDocumentByBusinessId(ctx, args.id);
|
||||
if (!doc) throw new Error("页面不存在");
|
||||
if (doc.deleted_at != null) throw new Error("页面不存在");
|
||||
await requireWorkspaceMember(ctx, doc.workspace_id, userId);
|
||||
@@ -920,10 +888,7 @@ export const setTemplate = mutation({
|
||||
handler: async (ctx, args) => {
|
||||
const userId = await requireUserId(ctx);
|
||||
|
||||
const doc = await ctx.db
|
||||
.query("documents")
|
||||
.withIndex("by_document_id", (q) => q.eq("id", args.id))
|
||||
.first();
|
||||
const doc = await getCanonicalDocumentByBusinessId(ctx, args.id);
|
||||
if (!doc) throw new Error("页面不存在");
|
||||
if (doc.deleted_at != null) throw new Error("页面不存在");
|
||||
await requireWorkspaceMember(ctx, doc.workspace_id, userId);
|
||||
@@ -950,10 +915,7 @@ export const move = mutation({
|
||||
handler: async (ctx, args) => {
|
||||
const userId = await requireUserId(ctx);
|
||||
|
||||
const doc = await ctx.db
|
||||
.query("documents")
|
||||
.withIndex("by_document_id", (q) => q.eq("id", args.id))
|
||||
.first();
|
||||
const doc = await getCanonicalDocumentByBusinessId(ctx, args.id);
|
||||
if (!doc) throw new Error("页面不存在");
|
||||
if (doc.user_id !== userId) throw new Error("无权限");
|
||||
|
||||
@@ -1040,10 +1002,7 @@ export const softDelete = mutation({
|
||||
handler: async (ctx, args) => {
|
||||
const userId = await requireUserId(ctx);
|
||||
|
||||
const doc = await ctx.db
|
||||
.query("documents")
|
||||
.withIndex("by_document_id", (q) => q.eq("id", args.id))
|
||||
.first();
|
||||
const doc = await getCanonicalDocumentByBusinessId(ctx, args.id);
|
||||
if (!doc) throw new Error("页面不存在");
|
||||
if (doc.user_id !== userId) throw new Error("无权限");
|
||||
const ts = nowIso();
|
||||
@@ -1075,10 +1034,7 @@ export const restore = mutation({
|
||||
handler: async (ctx, args) => {
|
||||
const userId = await requireUserId(ctx);
|
||||
|
||||
const doc = await ctx.db
|
||||
.query("documents")
|
||||
.withIndex("by_document_id", (q) => q.eq("id", args.id))
|
||||
.first();
|
||||
const doc = await getCanonicalDocumentByBusinessId(ctx, args.id);
|
||||
if (!doc) throw new Error("页面不存在");
|
||||
if (doc.user_id !== userId) throw new Error("无权限");
|
||||
const ts = nowIso();
|
||||
@@ -1115,10 +1071,7 @@ export const purge = mutation({
|
||||
handler: async (ctx, args) => {
|
||||
const userId = await requireUserId(ctx);
|
||||
|
||||
const doc = await ctx.db
|
||||
.query("documents")
|
||||
.withIndex("by_document_id", (q) => q.eq("id", args.id))
|
||||
.first();
|
||||
const doc = await getCanonicalDocumentByBusinessId(ctx, args.id);
|
||||
if (!doc) throw new Error("页面不存在");
|
||||
if (doc.user_id !== userId) throw new Error("无权限");
|
||||
|
||||
@@ -1194,10 +1147,7 @@ export const updateOptions = mutation({
|
||||
handler: async (ctx, args) => {
|
||||
const userId = await requireUserId(ctx);
|
||||
|
||||
const doc = await ctx.db
|
||||
.query("documents")
|
||||
.withIndex("by_document_id", (q) => q.eq("id", args.id))
|
||||
.first();
|
||||
const doc = await getCanonicalDocumentByBusinessId(ctx, args.id);
|
||||
if (!doc) throw new Error("页面不存在");
|
||||
if (doc.deleted_at != null) throw new Error("页面不存在");
|
||||
await requireWorkspaceMember(ctx, doc.workspace_id, userId);
|
||||
@@ -1249,10 +1199,7 @@ export const updateStats = mutation({
|
||||
handler: async (ctx, args) => {
|
||||
const userId = await requireUserId(ctx);
|
||||
|
||||
const doc = await ctx.db
|
||||
.query("documents")
|
||||
.withIndex("by_document_id", (q) => q.eq("id", args.id))
|
||||
.first();
|
||||
const doc = await getCanonicalDocumentByBusinessId(ctx, args.id);
|
||||
if (!doc) throw new Error("页面不存在");
|
||||
if (doc.deleted_at != null) throw new Error("页面不存在");
|
||||
await requireWorkspaceMember(ctx, doc.workspace_id, userId);
|
||||
@@ -1284,13 +1231,15 @@ export const duplicate = mutation({
|
||||
handler: async (ctx, args) => {
|
||||
const userId = await requireUserId(ctx);
|
||||
|
||||
const source = await ctx.db
|
||||
.query("documents")
|
||||
.withIndex("by_document_id", (q) => q.eq("id", args.sourceId))
|
||||
.first();
|
||||
const source = await getCanonicalDocumentByBusinessId(ctx, args.sourceId);
|
||||
if (!source) throw new Error("页面不存在或无权限访问");
|
||||
if (source.user_id !== userId) throw new Error("页面不存在或无权限访问");
|
||||
|
||||
const existingTarget = await getCanonicalDocumentByBusinessId(ctx, args.newId);
|
||||
if (existingTarget && existingTarget.deleted_at == null) {
|
||||
throw new Error("目标页面已存在");
|
||||
}
|
||||
|
||||
const siblings = await ctx.db
|
||||
.query("documents")
|
||||
.withIndex("by_workspace_parent", (q) =>
|
||||
|
||||
@@ -4,6 +4,7 @@ import { nowIso } from "./_utils/time";
|
||||
import { enqueueExtractMediaAssetTextJob, enqueueIngestMediaAssetJob } from "./_utils/ingestJobs";
|
||||
import type { MutationCtx, QueryCtx } from "./_generated/server";
|
||||
import { internal } from "./_generated/api";
|
||||
import { getCanonicalDocumentByBusinessId } from "./_utils/documentRecord";
|
||||
|
||||
async function assertWorkspaceMember(ctx: QueryCtx | MutationCtx, userId: string, workspaceId: string) {
|
||||
const membership = await ctx.db
|
||||
@@ -302,10 +303,7 @@ export const createWithStorage = mutation({
|
||||
handler: async (ctx, args) => {
|
||||
await assertWorkspaceMember(ctx, args.userId, args.asset.workspace_id);
|
||||
|
||||
const doc = await ctx.db
|
||||
.query("documents")
|
||||
.withIndex("by_document_id", (q) => q.eq("id", args.asset.document_id))
|
||||
.first();
|
||||
const doc = await getCanonicalDocumentByBusinessId<any>(ctx, args.asset.document_id);
|
||||
|
||||
if (!doc || doc.workspace_id !== args.asset.workspace_id) {
|
||||
throw new Error("目标页面不存在或不属于该工作空间");
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { internalMutation, internalQuery, mutation, query } from "./_generated/server";
|
||||
import { v } from "convex/values";
|
||||
import { getAuthUserId } from "@convex-dev/auth/server";
|
||||
import { requireUserId } from "./_utils/auth";
|
||||
import { nowIso } from "./_utils/time";
|
||||
import { enqueueIngestMindmapJob } from "./_utils/ingestJobs";
|
||||
import { internal } from "./_generated/api";
|
||||
import { requireCanonicalOwnedDocument } from "./_utils/documentRecord";
|
||||
|
||||
const defaultMindmapData = {
|
||||
data: { text: "中心主题" },
|
||||
@@ -19,14 +20,6 @@ function resolveGraceSeconds(): number {
|
||||
return Math.floor(parsed);
|
||||
}
|
||||
|
||||
async function requireUserId(ctx: any): Promise<string> {
|
||||
const userId = await getAuthUserId(ctx);
|
||||
if (userId === null) {
|
||||
throw new Error("未登录");
|
||||
}
|
||||
return userId;
|
||||
}
|
||||
|
||||
function normalizeMindmapId(docId: string, mindmapId: string): string {
|
||||
const raw = String(mindmapId ?? "").trim();
|
||||
if (raw) return raw;
|
||||
@@ -35,17 +28,7 @@ function normalizeMindmapId(docId: string, mindmapId: string): string {
|
||||
}
|
||||
|
||||
async function requireOwnedDocument(ctx: any, userId: string, docId: string) {
|
||||
const doc = await ctx.db
|
||||
.query("documents")
|
||||
.withIndex("by_document_id", (q: any) => q.eq("id", docId))
|
||||
.first();
|
||||
if (!doc) {
|
||||
throw new Error("页面不存在");
|
||||
}
|
||||
if (doc.user_id !== userId) {
|
||||
throw new Error("无权限");
|
||||
}
|
||||
return doc;
|
||||
return await requireCanonicalOwnedDocument<any>(ctx, docId, userId);
|
||||
}
|
||||
|
||||
export const get = query({
|
||||
|
||||
@@ -2,6 +2,7 @@ import { mutation, query } from "./_generated/server";
|
||||
import { v } from "convex/values";
|
||||
import { nowIso } from "./_utils/time";
|
||||
import type { MutationCtx, QueryCtx } from "./_generated/server";
|
||||
import { getCanonicalDocumentByBusinessId } from "./_utils/documentRecord";
|
||||
|
||||
async function assertWorkspaceMember(ctx: QueryCtx | MutationCtx, userId: string, workspaceId: string) {
|
||||
const membership = await ctx.db
|
||||
@@ -120,7 +121,7 @@ export const listBacklinks = query({
|
||||
|
||||
const sourceTitleById = new Map<string, string | null>();
|
||||
for (const sid of sourceIds) {
|
||||
const doc = await ctx.db.query("documents").withIndex("by_document_id", (q) => q.eq("id", sid)).first();
|
||||
const doc = await getCanonicalDocumentByBusinessId<any>(ctx, sid);
|
||||
sourceTitleById.set(sid, doc ? (doc.title ?? null) : null);
|
||||
}
|
||||
|
||||
|
||||
@@ -446,4 +446,52 @@ export default defineSchema({
|
||||
.index("by_workspace_session", ["workspace_id", "session_id"])
|
||||
.index("by_session", ["session_id"])
|
||||
.index("by_workspace_status", ["workspace_id", "status"]),
|
||||
|
||||
command_logs: defineTable({
|
||||
id: v.string(),
|
||||
workspace_id: v.string(),
|
||||
request_id: v.string(),
|
||||
trace_id: v.string(),
|
||||
command_id: v.string(),
|
||||
command_name: v.string(),
|
||||
actor_id: v.string(),
|
||||
actor_type: v.string(),
|
||||
source_channel: v.string(),
|
||||
source_client: v.string(),
|
||||
status: v.union(v.literal("pending"), v.literal("succeeded"), v.literal("failed"), v.literal("rolled_back")),
|
||||
target_page_id: v.optional(v.union(v.string(), v.null())),
|
||||
target_block_id: v.optional(v.union(v.string(), v.null())),
|
||||
payload: v.any(),
|
||||
payload_summary: v.string(),
|
||||
refs: v.array(v.string()),
|
||||
idempotency_key: v.optional(v.union(v.string(), v.null())),
|
||||
error: v.optional(v.union(v.string(), v.null())),
|
||||
created_at: v.string(),
|
||||
finished_at: v.optional(v.union(v.string(), v.null())),
|
||||
})
|
||||
.index("by_command_log_id", ["id"])
|
||||
.index("by_workspace_request", ["workspace_id", "request_id"])
|
||||
.index("by_workspace_trace", ["workspace_id", "trace_id"])
|
||||
.index("by_workspace_command", ["workspace_id", "command_id"]),
|
||||
|
||||
domain_events: defineTable({
|
||||
id: v.string(),
|
||||
workspace_id: v.string(),
|
||||
request_id: v.string(),
|
||||
trace_id: v.string(),
|
||||
command_id: v.string(),
|
||||
command_log_id: v.string(),
|
||||
event_type: v.string(),
|
||||
aggregate_type: v.string(),
|
||||
aggregate_id: v.string(),
|
||||
event_version: v.number(),
|
||||
status: v.union(v.literal("pending"), v.literal("committed"), v.literal("rejected"), v.literal("failed")),
|
||||
actor_type: v.string(),
|
||||
payload: v.any(),
|
||||
created_at: v.string(),
|
||||
})
|
||||
.index("by_domain_event_id", ["id"])
|
||||
.index("by_workspace_request", ["workspace_id", "request_id"])
|
||||
.index("by_workspace_trace", ["workspace_id", "trace_id"])
|
||||
.index("by_workspace_command", ["workspace_id", "command_id"]),
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { mutation, query } from "./_generated/server";
|
||||
import { v } from "convex/values";
|
||||
import { getAuthUserId } from "@convex-dev/auth/server";
|
||||
import { requireUserId } from "./_utils/auth";
|
||||
import { nowIso } from "./_utils/time";
|
||||
import type { MutationCtx, QueryCtx } from "./_generated/server";
|
||||
|
||||
@@ -13,14 +13,6 @@ type WorkspaceSummary = {
|
||||
isDefault: boolean;
|
||||
};
|
||||
|
||||
async function requireUserId(ctx: any): Promise<string> {
|
||||
const userId = await getAuthUserId(ctx);
|
||||
if (userId === null) {
|
||||
throw new Error("未登录");
|
||||
}
|
||||
return userId;
|
||||
}
|
||||
|
||||
async function findWorkspaceById(ctx: QueryCtx | MutationCtx, workspaceId: string) {
|
||||
return await ctx.db
|
||||
.query("workspaces")
|
||||
|
||||
Reference in New Issue
Block a user