@open-mercato/documents 0.10.1-develop.8180.1.c6789f43f3 → 0.10.1-develop.8182.1.0acd6a564c

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -19,6 +19,7 @@ import {
19
19
  resolveDocumentsCommandBus
20
20
  } from "../../_commands.js";
21
21
  import {
22
+ assertDocumentId,
22
23
  assertDocumentNotArchived,
23
24
  handleDocumentsRouteError,
24
25
  readBody,
@@ -188,6 +189,7 @@ async function GET(request, context) {
188
189
  try {
189
190
  const documentId = await resolveId(context);
190
191
  const ctx = await resolveDocumentsContext(request, ["documents.view"]);
192
+ assertDocumentId(documentId);
191
193
  const tier = await assertTier(ctx.em, documentId, ctx.auth, "viewer");
192
194
  const userId = resolveActorUserId(ctx.auth);
193
195
  const url = new URL(request.url);
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../src/modules/documents/api/%5Bid%5D/comments/route.ts"],
4
- "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { randomUUID } from 'node:crypto'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { enforceCommandOptimisticLockWithGuards } from '@open-mercato/shared/lib/crud/optimistic-lock-command'\nimport { findOneWithDecryption, findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { DocumentComment } from '../../../data/entities'\nimport { documentCommentCreateSchema } from '../../../data/validators'\nimport { DOCUMENTS_ENTITY_IDS } from '../../../lib/constants'\nimport {\n DOCUMENTS_COMMENT_LIST_PAGE_SIZE,\n DOCUMENTS_MAX_COMMENTS_PER_DOCUMENT,\n} from '../../../lib/historyLimits'\nimport { assertTier, hasTier } from '../../../lib/permissions'\nimport { resolveViewerSafeUserLabels } from '../../../lib/userLabels'\nimport type {\n CommentCreateCommandInput,\n CommentCreateCommandResult,\n CommentResolveCommandInput,\n CommentResolveCommandResult,\n} from '../../../commands/comments'\nimport {\n attachDocumentsOperationMetadata,\n buildDocumentsCommandRuntimeContext,\n resolveDocumentsCommandBus,\n} from '../../_commands'\nimport {\n assertDocumentNotArchived,\n handleDocumentsRouteError,\n readBody,\n resolveActorUserId,\n resolveDocumentsContext,\n routeErrorSchema,\n runMutationGuardAfterSuccess,\n validateMutationGuard,\n withDocumentsContextErrors,\n} from '../../_shared'\n\ntype RouteContext = {\n params: Promise<{ id: string }> | { id: string }\n}\n\ntype SerializedComment = {\n id: string\n documentId: string\n parentCommentId: string | null\n authorUserId: string\n body: string\n anchor: Record<string, unknown> | null\n mentions: { userId: string }[]\n resolvedAt: string | null\n resolvedByUserId: string | null\n createdAt: string\n updatedAt: string\n canResolve: boolean\n replies: SerializedComment[]\n}\n\nconst commentResolveSchema = z.object({\n id: z.string().uuid(),\n resolved: z.boolean(),\n})\n\nexport const commentListQuerySchema = z.object({\n page: z.coerce.number().int().min(1).max(10_000).default(1),\n pageSize: z.coerce.number().int().min(1).default(DOCUMENTS_COMMENT_LIST_PAGE_SIZE)\n .transform((value) => Math.min(value, DOCUMENTS_COMMENT_LIST_PAGE_SIZE)),\n})\n\nconst commentMentionSchema = z.object({\n userId: z.string().uuid(),\n})\n\nconst userLabelSchema = z.object({\n label: z.string(),\n})\n\nconst commentNodeSchema: z.ZodType<SerializedComment> = z.lazy(() =>\n z.object({\n id: z.string().uuid(),\n documentId: z.string().uuid(),\n parentCommentId: z.string().uuid().nullable(),\n authorUserId: z.string().uuid(),\n body: z.string(),\n anchor: z.record(z.string(), z.unknown()).nullable(),\n mentions: z.array(commentMentionSchema),\n resolvedAt: z.string().nullable(),\n resolvedByUserId: z.string().uuid().nullable(),\n createdAt: z.string(),\n updatedAt: z.string(),\n canResolve: z.boolean(),\n replies: z.array(commentNodeSchema),\n }),\n)\n\nconst commentListResponseSchema = z.object({\n items: z.array(commentNodeSchema),\n userLabels: z.record(z.string(), userLabelSchema),\n page: z.number().int().positive(),\n pageSize: z.number().int().positive(),\n total: z.number().int().nonnegative(),\n totalPages: z.number().int().positive(),\n totalComments: z.number().int().nonnegative(),\n truncated: z.boolean(),\n})\n\nconst commentCreateResponseSchema = z.object({\n id: z.string().uuid(),\n updatedAt: z.string(),\n})\n\nconst commentResolveResponseSchema = z.object({\n id: z.string().uuid(),\n resolvedAt: z.string().nullable(),\n resolvedByUserId: z.string().uuid().nullable(),\n updatedAt: z.string(),\n})\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['documents.view'] },\n POST: { requireAuth: true, requireFeatures: ['documents.view'] },\n PATCH: { requireAuth: true, requireFeatures: ['documents.view'] },\n}\n\nasync function resolveId(context: RouteContext): Promise<string> {\n const params = await context.params\n return params.id\n}\n\nfunction serializeComment(comment: DocumentComment, canResolve: boolean): SerializedComment {\n return {\n id: comment.id,\n documentId: comment.documentId,\n parentCommentId: comment.parentCommentId ?? null,\n authorUserId: comment.authorUserId,\n body: comment.body,\n anchor: comment.anchor ?? null,\n mentions: comment.mentions ?? [],\n resolvedAt: comment.resolvedAt ? comment.resolvedAt.toISOString() : null,\n resolvedByUserId: comment.resolvedByUserId ?? null,\n createdAt: comment.createdAt.toISOString(),\n updatedAt: comment.updatedAt.toISOString(),\n canResolve,\n replies: [],\n }\n}\n\nfunction buildThreadedComments(\n comments: DocumentComment[],\n options: { tier: Awaited<ReturnType<typeof assertTier>>; userId: string },\n): SerializedComment[] {\n const nodes = new Map<string, SerializedComment>()\n for (const comment of comments) {\n nodes.set(comment.id, serializeComment(\n comment,\n hasTier(options.tier, 'commenter') || comment.authorUserId === options.userId,\n ))\n }\n\n const roots: SerializedComment[] = []\n for (const comment of comments) {\n const node = nodes.get(comment.id)\n if (!node) continue\n const parentId = comment.parentCommentId ?? null\n const parent = parentId ? nodes.get(parentId) : null\n if (parent) {\n parent.replies.push(node)\n } else {\n roots.push(node)\n }\n }\n return roots\n}\n\nexport function paginateNewestThreadRoots<T>(roots: T[], page: number, pageSize: number): T[] {\n const end = Math.max(0, roots.length - ((page - 1) * pageSize))\n const start = Math.max(0, end - pageSize)\n return roots.slice(start, end)\n}\n\nfunction collectSerializedCommentIds(comments: SerializedComment[]): Set<string> {\n const ids = new Set<string>()\n const visit = (nodes: SerializedComment[]) => {\n for (const comment of nodes) {\n ids.add(comment.id)\n visit(comment.replies)\n }\n }\n visit(comments)\n return ids\n}\n\nfunction extractMentionedUserIds(body: string): string[] {\n const mentionTokenPattern =\n /@\\[([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\\]/gi\n return Array.from(\n new Set(\n Array.from(body.matchAll(mentionTokenPattern))\n .map((match) => match[1])\n .filter((value): value is string => typeof value === 'string' && value.length > 0)\n .map((value) => value.toLowerCase()),\n ),\n )\n}\n\nfunction dedupeUserIds(userIds: string[]): string[] {\n return Array.from(new Set(userIds.map((userId) => userId.toLowerCase())))\n}\n\nfunction collectCommentLabelUserIds(comments: DocumentComment[]): string[] {\n const userIds: string[] = []\n for (const comment of comments) {\n userIds.push(comment.authorUserId)\n if (comment.resolvedByUserId) userIds.push(comment.resolvedByUserId)\n for (const mention of comment.mentions ?? []) {\n userIds.push(mention.userId)\n }\n userIds.push(...extractMentionedUserIds(comment.body))\n }\n return dedupeUserIds(userIds)\n}\n\nasync function loadScopedComment(documentId: string, commentId: string, ctx: Awaited<ReturnType<typeof resolveDocumentsContext>>): Promise<DocumentComment> {\n const comment = await findOneWithDecryption(\n ctx.em,\n DocumentComment,\n {\n id: commentId,\n documentId,\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId,\n deletedAt: null,\n },\n undefined,\n { tenantId: ctx.tenantId, organizationId: ctx.organizationId },\n )\n if (!comment) throw new CrudHttpError(404, { error: 'Comment not found' })\n return comment\n}\n\nexport async function GET(request: Request, context: RouteContext): Promise<Response> {\n try {\n const documentId = await resolveId(context)\n const ctx = await resolveDocumentsContext(request, ['documents.view'])\n const tier = await assertTier(ctx.em, documentId, ctx.auth, 'viewer')\n const userId = resolveActorUserId(ctx.auth)\n const url = new URL(request.url)\n const query = commentListQuerySchema.parse(Object.fromEntries(url.searchParams.entries()))\n\n const loadedComments = await findWithDecryption(\n ctx.em,\n DocumentComment,\n {\n documentId,\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId,\n deletedAt: null,\n },\n {\n orderBy: { createdAt: 'DESC', id: 'DESC' },\n limit: DOCUMENTS_MAX_COMMENTS_PER_DOCUMENT + 1,\n },\n { tenantId: ctx.tenantId, organizationId: ctx.organizationId },\n )\n const truncated = loadedComments.length > DOCUMENTS_MAX_COMMENTS_PER_DOCUMENT\n const comments = loadedComments.slice(0, DOCUMENTS_MAX_COMMENTS_PER_DOCUMENT).reverse()\n const roots = buildThreadedComments(comments, { tier, userId })\n const items = paginateNewestThreadRoots(roots, query.page, query.pageSize)\n const visibleCommentIds = collectSerializedCommentIds(items)\n const userLabels = await resolveViewerSafeUserLabels(\n ctx.container,\n { tenantId: ctx.tenantId, organizationId: ctx.organizationId },\n collectCommentLabelUserIds(comments.filter((comment) => visibleCommentIds.has(comment.id))),\n )\n\n return NextResponse.json({\n items,\n userLabels: Object.fromEntries(userLabels.entries()),\n page: query.page,\n pageSize: query.pageSize,\n total: roots.length,\n totalPages: Math.max(1, Math.ceil(roots.length / query.pageSize)),\n totalComments: comments.length,\n truncated,\n })\n } catch (error) {\n return handleDocumentsRouteError(error, 'documents.comments.list')\n }\n}\n\nexport async function POST(request: Request, context: RouteContext): Promise<Response> {\n try {\n const documentId = await resolveId(context)\n const ctx = await resolveDocumentsContext(request, ['documents.view'])\n await assertTier(ctx.em, documentId, ctx.auth, 'commenter')\n await assertDocumentNotArchived(ctx, documentId)\n const input = documentCommentCreateSchema.parse(await readBody(request))\n const userId = resolveActorUserId(ctx.auth)\n const guardResult = await validateMutationGuard(ctx, {\n resourceKind: DOCUMENTS_ENTITY_IDS.documentComment,\n resourceId: documentId,\n operation: 'create',\n mutationPayload: input,\n mutationPayloadSchema: documentCommentCreateSchema,\n })\n\n const commandInput: CommentCreateCommandInput = {\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId,\n documentId,\n commentId: randomUUID(),\n actorUserId: userId,\n comment: input,\n grantShares: dedupeUserIds(input.grantAccessTo ?? []).map((mentionedUserId) => ({\n userId: mentionedUserId,\n shareId: randomUUID(),\n })),\n }\n const execution = await resolveDocumentsCommandBus(ctx).execute<CommentCreateCommandInput, CommentCreateCommandResult>(\n 'documents.comment.create',\n { input: commandInput, ctx: buildDocumentsCommandRuntimeContext(ctx) },\n )\n await runMutationGuardAfterSuccess(ctx, guardResult, {\n resourceKind: DOCUMENTS_ENTITY_IDS.documentComment,\n resourceId: documentId,\n operation: 'create',\n })\n\n return attachDocumentsOperationMetadata(\n NextResponse.json({ id: execution.result.id, updatedAt: execution.result.updatedAt }, { status: 201 }),\n execution.logEntry,\n { resourceKind: DOCUMENTS_ENTITY_IDS.documentComment, resourceId: execution.result.id },\n )\n } catch (error) {\n return handleDocumentsRouteError(error, 'documents.comments.create')\n }\n}\n\nexport async function PATCH(request: Request, context: RouteContext): Promise<Response> {\n try {\n const documentId = await resolveId(context)\n const ctx = await resolveDocumentsContext(request, ['documents.view'])\n const tier = await assertTier(ctx.em, documentId, ctx.auth, 'viewer')\n await assertDocumentNotArchived(ctx, documentId)\n const input = commentResolveSchema.parse(await readBody(request))\n const comment = await loadScopedComment(documentId, input.id, ctx)\n const userId = resolveActorUserId(ctx.auth)\n if (!hasTier(tier, 'commenter') && comment.authorUserId !== userId) {\n throw new CrudHttpError(403, { error: 'Forbidden' })\n }\n await enforceCommandOptimisticLockWithGuards(ctx.container, {\n resourceKind: DOCUMENTS_ENTITY_IDS.documentComment,\n resourceId: comment.id,\n current: comment.updatedAt,\n request,\n })\n\n const guardResult = await validateMutationGuard(ctx, {\n resourceKind: DOCUMENTS_ENTITY_IDS.documentComment,\n resourceId: comment.id,\n operation: 'update',\n mutationPayload: input,\n mutationPayloadSchema: commentResolveSchema,\n })\n\n const commandInput: CommentResolveCommandInput = {\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId,\n documentId,\n actorUserId: userId,\n expectedUpdatedAt: comment.updatedAt.toISOString(),\n comment: input,\n }\n const execution = await resolveDocumentsCommandBus(ctx).execute<CommentResolveCommandInput, CommentResolveCommandResult>(\n 'documents.comment.resolve',\n { input: commandInput, ctx: buildDocumentsCommandRuntimeContext(ctx) },\n )\n await runMutationGuardAfterSuccess(ctx, guardResult, {\n resourceKind: DOCUMENTS_ENTITY_IDS.documentComment,\n resourceId: comment.id,\n operation: 'update',\n })\n\n return attachDocumentsOperationMetadata(\n NextResponse.json({\n id: execution.result.id,\n resolvedAt: execution.result.resolvedAt,\n resolvedByUserId: execution.result.resolvedByUserId,\n updatedAt: execution.result.updatedAt,\n }),\n execution.logEntry,\n { resourceKind: DOCUMENTS_ENTITY_IDS.documentComment, resourceId: execution.result.id },\n )\n } catch (error) {\n return handleDocumentsRouteError(error, 'documents.comments.resolve')\n }\n}\n\nexport const openApi: OpenApiRouteDoc = withDocumentsContextErrors({\n tag: 'Documents',\n summary: 'Document comments',\n pathParams: z.object({ id: z.string().uuid() }),\n methods: {\n GET: {\n summary: 'List document comments',\n query: commentListQuerySchema,\n responses: [{ status: 200, description: 'Threaded document comments', schema: commentListResponseSchema }],\n errors: [\n { status: 401, description: 'Unauthorized', schema: routeErrorSchema },\n { status: 403, description: 'Forbidden', schema: routeErrorSchema },\n ],\n },\n POST: {\n summary: 'Create document comment',\n description: 'Mentions may be sent out-of-band as mentions: [{ userId }]. Legacy bracketed @[user-uuid] tokens in the comment body are still honored.',\n requestBody: { contentType: 'application/json', schema: documentCommentCreateSchema },\n responses: [{ status: 201, description: 'Comment created', schema: commentCreateResponseSchema }],\n errors: [\n { status: 400, description: 'Validation failed', schema: routeErrorSchema },\n { status: 401, description: 'Unauthorized', schema: routeErrorSchema },\n { status: 403, description: 'Forbidden', schema: routeErrorSchema },\n { status: 413, description: 'Request body or document comment limit exceeds the safe resource bound', schema: routeErrorSchema },\n ],\n },\n PATCH: {\n summary: 'Resolve or unresolve document comment',\n requestBody: { contentType: 'application/json', schema: commentResolveSchema },\n responses: [{ status: 200, description: 'Comment resolution updated', schema: commentResolveResponseSchema }],\n errors: [\n { status: 400, description: 'Validation failed', schema: routeErrorSchema },\n { status: 401, description: 'Unauthorized', schema: routeErrorSchema },\n { status: 403, description: 'Forbidden', schema: routeErrorSchema },\n { status: 404, description: 'Comment not found', schema: routeErrorSchema },\n { status: 409, description: 'Optimistic lock conflict', schema: routeErrorSchema },\n { status: 413, description: 'Request body exceeds the safe resource bound', schema: routeErrorSchema },\n ],\n },\n },\n})\n\nexport default { GET, POST, PATCH }\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { randomUUID } from 'node:crypto'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { enforceCommandOptimisticLockWithGuards } from '@open-mercato/shared/lib/crud/optimistic-lock-command'\nimport { findOneWithDecryption, findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { DocumentComment } from '../../../data/entities'\nimport { documentCommentCreateSchema } from '../../../data/validators'\nimport { DOCUMENTS_ENTITY_IDS } from '../../../lib/constants'\nimport {\n DOCUMENTS_COMMENT_LIST_PAGE_SIZE,\n DOCUMENTS_MAX_COMMENTS_PER_DOCUMENT,\n} from '../../../lib/historyLimits'\nimport { assertTier, hasTier } from '../../../lib/permissions'\nimport { resolveViewerSafeUserLabels } from '../../../lib/userLabels'\nimport type {\n CommentCreateCommandInput,\n CommentCreateCommandResult,\n CommentResolveCommandInput,\n CommentResolveCommandResult,\n} from '../../../commands/comments'\nimport {\n attachDocumentsOperationMetadata,\n buildDocumentsCommandRuntimeContext,\n resolveDocumentsCommandBus,\n} from '../../_commands'\nimport {\n assertDocumentId,\n assertDocumentNotArchived,\n handleDocumentsRouteError,\n readBody,\n resolveActorUserId,\n resolveDocumentsContext,\n routeErrorSchema,\n runMutationGuardAfterSuccess,\n validateMutationGuard,\n withDocumentsContextErrors,\n} from '../../_shared'\n\ntype RouteContext = {\n params: Promise<{ id: string }> | { id: string }\n}\n\ntype SerializedComment = {\n id: string\n documentId: string\n parentCommentId: string | null\n authorUserId: string\n body: string\n anchor: Record<string, unknown> | null\n mentions: { userId: string }[]\n resolvedAt: string | null\n resolvedByUserId: string | null\n createdAt: string\n updatedAt: string\n canResolve: boolean\n replies: SerializedComment[]\n}\n\nconst commentResolveSchema = z.object({\n id: z.string().uuid(),\n resolved: z.boolean(),\n})\n\nexport const commentListQuerySchema = z.object({\n page: z.coerce.number().int().min(1).max(10_000).default(1),\n pageSize: z.coerce.number().int().min(1).default(DOCUMENTS_COMMENT_LIST_PAGE_SIZE)\n .transform((value) => Math.min(value, DOCUMENTS_COMMENT_LIST_PAGE_SIZE)),\n})\n\nconst commentMentionSchema = z.object({\n userId: z.string().uuid(),\n})\n\nconst userLabelSchema = z.object({\n label: z.string(),\n})\n\nconst commentNodeSchema: z.ZodType<SerializedComment> = z.lazy(() =>\n z.object({\n id: z.string().uuid(),\n documentId: z.string().uuid(),\n parentCommentId: z.string().uuid().nullable(),\n authorUserId: z.string().uuid(),\n body: z.string(),\n anchor: z.record(z.string(), z.unknown()).nullable(),\n mentions: z.array(commentMentionSchema),\n resolvedAt: z.string().nullable(),\n resolvedByUserId: z.string().uuid().nullable(),\n createdAt: z.string(),\n updatedAt: z.string(),\n canResolve: z.boolean(),\n replies: z.array(commentNodeSchema),\n }),\n)\n\nconst commentListResponseSchema = z.object({\n items: z.array(commentNodeSchema),\n userLabels: z.record(z.string(), userLabelSchema),\n page: z.number().int().positive(),\n pageSize: z.number().int().positive(),\n total: z.number().int().nonnegative(),\n totalPages: z.number().int().positive(),\n totalComments: z.number().int().nonnegative(),\n truncated: z.boolean(),\n})\n\nconst commentCreateResponseSchema = z.object({\n id: z.string().uuid(),\n updatedAt: z.string(),\n})\n\nconst commentResolveResponseSchema = z.object({\n id: z.string().uuid(),\n resolvedAt: z.string().nullable(),\n resolvedByUserId: z.string().uuid().nullable(),\n updatedAt: z.string(),\n})\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['documents.view'] },\n POST: { requireAuth: true, requireFeatures: ['documents.view'] },\n PATCH: { requireAuth: true, requireFeatures: ['documents.view'] },\n}\n\nasync function resolveId(context: RouteContext): Promise<string> {\n const params = await context.params\n return params.id\n}\n\nfunction serializeComment(comment: DocumentComment, canResolve: boolean): SerializedComment {\n return {\n id: comment.id,\n documentId: comment.documentId,\n parentCommentId: comment.parentCommentId ?? null,\n authorUserId: comment.authorUserId,\n body: comment.body,\n anchor: comment.anchor ?? null,\n mentions: comment.mentions ?? [],\n resolvedAt: comment.resolvedAt ? comment.resolvedAt.toISOString() : null,\n resolvedByUserId: comment.resolvedByUserId ?? null,\n createdAt: comment.createdAt.toISOString(),\n updatedAt: comment.updatedAt.toISOString(),\n canResolve,\n replies: [],\n }\n}\n\nfunction buildThreadedComments(\n comments: DocumentComment[],\n options: { tier: Awaited<ReturnType<typeof assertTier>>; userId: string },\n): SerializedComment[] {\n const nodes = new Map<string, SerializedComment>()\n for (const comment of comments) {\n nodes.set(comment.id, serializeComment(\n comment,\n hasTier(options.tier, 'commenter') || comment.authorUserId === options.userId,\n ))\n }\n\n const roots: SerializedComment[] = []\n for (const comment of comments) {\n const node = nodes.get(comment.id)\n if (!node) continue\n const parentId = comment.parentCommentId ?? null\n const parent = parentId ? nodes.get(parentId) : null\n if (parent) {\n parent.replies.push(node)\n } else {\n roots.push(node)\n }\n }\n return roots\n}\n\nexport function paginateNewestThreadRoots<T>(roots: T[], page: number, pageSize: number): T[] {\n const end = Math.max(0, roots.length - ((page - 1) * pageSize))\n const start = Math.max(0, end - pageSize)\n return roots.slice(start, end)\n}\n\nfunction collectSerializedCommentIds(comments: SerializedComment[]): Set<string> {\n const ids = new Set<string>()\n const visit = (nodes: SerializedComment[]) => {\n for (const comment of nodes) {\n ids.add(comment.id)\n visit(comment.replies)\n }\n }\n visit(comments)\n return ids\n}\n\nfunction extractMentionedUserIds(body: string): string[] {\n const mentionTokenPattern =\n /@\\[([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\\]/gi\n return Array.from(\n new Set(\n Array.from(body.matchAll(mentionTokenPattern))\n .map((match) => match[1])\n .filter((value): value is string => typeof value === 'string' && value.length > 0)\n .map((value) => value.toLowerCase()),\n ),\n )\n}\n\nfunction dedupeUserIds(userIds: string[]): string[] {\n return Array.from(new Set(userIds.map((userId) => userId.toLowerCase())))\n}\n\nfunction collectCommentLabelUserIds(comments: DocumentComment[]): string[] {\n const userIds: string[] = []\n for (const comment of comments) {\n userIds.push(comment.authorUserId)\n if (comment.resolvedByUserId) userIds.push(comment.resolvedByUserId)\n for (const mention of comment.mentions ?? []) {\n userIds.push(mention.userId)\n }\n userIds.push(...extractMentionedUserIds(comment.body))\n }\n return dedupeUserIds(userIds)\n}\n\nasync function loadScopedComment(documentId: string, commentId: string, ctx: Awaited<ReturnType<typeof resolveDocumentsContext>>): Promise<DocumentComment> {\n const comment = await findOneWithDecryption(\n ctx.em,\n DocumentComment,\n {\n id: commentId,\n documentId,\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId,\n deletedAt: null,\n },\n undefined,\n { tenantId: ctx.tenantId, organizationId: ctx.organizationId },\n )\n if (!comment) throw new CrudHttpError(404, { error: 'Comment not found' })\n return comment\n}\n\nexport async function GET(request: Request, context: RouteContext): Promise<Response> {\n try {\n const documentId = await resolveId(context)\n const ctx = await resolveDocumentsContext(request, ['documents.view'])\n assertDocumentId(documentId)\n const tier = await assertTier(ctx.em, documentId, ctx.auth, 'viewer')\n const userId = resolveActorUserId(ctx.auth)\n const url = new URL(request.url)\n const query = commentListQuerySchema.parse(Object.fromEntries(url.searchParams.entries()))\n\n const loadedComments = await findWithDecryption(\n ctx.em,\n DocumentComment,\n {\n documentId,\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId,\n deletedAt: null,\n },\n {\n orderBy: { createdAt: 'DESC', id: 'DESC' },\n limit: DOCUMENTS_MAX_COMMENTS_PER_DOCUMENT + 1,\n },\n { tenantId: ctx.tenantId, organizationId: ctx.organizationId },\n )\n const truncated = loadedComments.length > DOCUMENTS_MAX_COMMENTS_PER_DOCUMENT\n const comments = loadedComments.slice(0, DOCUMENTS_MAX_COMMENTS_PER_DOCUMENT).reverse()\n const roots = buildThreadedComments(comments, { tier, userId })\n const items = paginateNewestThreadRoots(roots, query.page, query.pageSize)\n const visibleCommentIds = collectSerializedCommentIds(items)\n const userLabels = await resolveViewerSafeUserLabels(\n ctx.container,\n { tenantId: ctx.tenantId, organizationId: ctx.organizationId },\n collectCommentLabelUserIds(comments.filter((comment) => visibleCommentIds.has(comment.id))),\n )\n\n return NextResponse.json({\n items,\n userLabels: Object.fromEntries(userLabels.entries()),\n page: query.page,\n pageSize: query.pageSize,\n total: roots.length,\n totalPages: Math.max(1, Math.ceil(roots.length / query.pageSize)),\n totalComments: comments.length,\n truncated,\n })\n } catch (error) {\n return handleDocumentsRouteError(error, 'documents.comments.list')\n }\n}\n\nexport async function POST(request: Request, context: RouteContext): Promise<Response> {\n try {\n const documentId = await resolveId(context)\n const ctx = await resolveDocumentsContext(request, ['documents.view'])\n await assertTier(ctx.em, documentId, ctx.auth, 'commenter')\n await assertDocumentNotArchived(ctx, documentId)\n const input = documentCommentCreateSchema.parse(await readBody(request))\n const userId = resolveActorUserId(ctx.auth)\n const guardResult = await validateMutationGuard(ctx, {\n resourceKind: DOCUMENTS_ENTITY_IDS.documentComment,\n resourceId: documentId,\n operation: 'create',\n mutationPayload: input,\n mutationPayloadSchema: documentCommentCreateSchema,\n })\n\n const commandInput: CommentCreateCommandInput = {\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId,\n documentId,\n commentId: randomUUID(),\n actorUserId: userId,\n comment: input,\n grantShares: dedupeUserIds(input.grantAccessTo ?? []).map((mentionedUserId) => ({\n userId: mentionedUserId,\n shareId: randomUUID(),\n })),\n }\n const execution = await resolveDocumentsCommandBus(ctx).execute<CommentCreateCommandInput, CommentCreateCommandResult>(\n 'documents.comment.create',\n { input: commandInput, ctx: buildDocumentsCommandRuntimeContext(ctx) },\n )\n await runMutationGuardAfterSuccess(ctx, guardResult, {\n resourceKind: DOCUMENTS_ENTITY_IDS.documentComment,\n resourceId: documentId,\n operation: 'create',\n })\n\n return attachDocumentsOperationMetadata(\n NextResponse.json({ id: execution.result.id, updatedAt: execution.result.updatedAt }, { status: 201 }),\n execution.logEntry,\n { resourceKind: DOCUMENTS_ENTITY_IDS.documentComment, resourceId: execution.result.id },\n )\n } catch (error) {\n return handleDocumentsRouteError(error, 'documents.comments.create')\n }\n}\n\nexport async function PATCH(request: Request, context: RouteContext): Promise<Response> {\n try {\n const documentId = await resolveId(context)\n const ctx = await resolveDocumentsContext(request, ['documents.view'])\n const tier = await assertTier(ctx.em, documentId, ctx.auth, 'viewer')\n await assertDocumentNotArchived(ctx, documentId)\n const input = commentResolveSchema.parse(await readBody(request))\n const comment = await loadScopedComment(documentId, input.id, ctx)\n const userId = resolveActorUserId(ctx.auth)\n if (!hasTier(tier, 'commenter') && comment.authorUserId !== userId) {\n throw new CrudHttpError(403, { error: 'Forbidden' })\n }\n await enforceCommandOptimisticLockWithGuards(ctx.container, {\n resourceKind: DOCUMENTS_ENTITY_IDS.documentComment,\n resourceId: comment.id,\n current: comment.updatedAt,\n request,\n })\n\n const guardResult = await validateMutationGuard(ctx, {\n resourceKind: DOCUMENTS_ENTITY_IDS.documentComment,\n resourceId: comment.id,\n operation: 'update',\n mutationPayload: input,\n mutationPayloadSchema: commentResolveSchema,\n })\n\n const commandInput: CommentResolveCommandInput = {\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId,\n documentId,\n actorUserId: userId,\n expectedUpdatedAt: comment.updatedAt.toISOString(),\n comment: input,\n }\n const execution = await resolveDocumentsCommandBus(ctx).execute<CommentResolveCommandInput, CommentResolveCommandResult>(\n 'documents.comment.resolve',\n { input: commandInput, ctx: buildDocumentsCommandRuntimeContext(ctx) },\n )\n await runMutationGuardAfterSuccess(ctx, guardResult, {\n resourceKind: DOCUMENTS_ENTITY_IDS.documentComment,\n resourceId: comment.id,\n operation: 'update',\n })\n\n return attachDocumentsOperationMetadata(\n NextResponse.json({\n id: execution.result.id,\n resolvedAt: execution.result.resolvedAt,\n resolvedByUserId: execution.result.resolvedByUserId,\n updatedAt: execution.result.updatedAt,\n }),\n execution.logEntry,\n { resourceKind: DOCUMENTS_ENTITY_IDS.documentComment, resourceId: execution.result.id },\n )\n } catch (error) {\n return handleDocumentsRouteError(error, 'documents.comments.resolve')\n }\n}\n\nexport const openApi: OpenApiRouteDoc = withDocumentsContextErrors({\n tag: 'Documents',\n summary: 'Document comments',\n pathParams: z.object({ id: z.string().uuid() }),\n methods: {\n GET: {\n summary: 'List document comments',\n query: commentListQuerySchema,\n responses: [{ status: 200, description: 'Threaded document comments', schema: commentListResponseSchema }],\n errors: [\n { status: 401, description: 'Unauthorized', schema: routeErrorSchema },\n { status: 403, description: 'Forbidden', schema: routeErrorSchema },\n ],\n },\n POST: {\n summary: 'Create document comment',\n description: 'Mentions may be sent out-of-band as mentions: [{ userId }]. Legacy bracketed @[user-uuid] tokens in the comment body are still honored.',\n requestBody: { contentType: 'application/json', schema: documentCommentCreateSchema },\n responses: [{ status: 201, description: 'Comment created', schema: commentCreateResponseSchema }],\n errors: [\n { status: 400, description: 'Validation failed', schema: routeErrorSchema },\n { status: 401, description: 'Unauthorized', schema: routeErrorSchema },\n { status: 403, description: 'Forbidden', schema: routeErrorSchema },\n { status: 413, description: 'Request body or document comment limit exceeds the safe resource bound', schema: routeErrorSchema },\n ],\n },\n PATCH: {\n summary: 'Resolve or unresolve document comment',\n requestBody: { contentType: 'application/json', schema: commentResolveSchema },\n responses: [{ status: 200, description: 'Comment resolution updated', schema: commentResolveResponseSchema }],\n errors: [\n { status: 400, description: 'Validation failed', schema: routeErrorSchema },\n { status: 401, description: 'Unauthorized', schema: routeErrorSchema },\n { status: 403, description: 'Forbidden', schema: routeErrorSchema },\n { status: 404, description: 'Comment not found', schema: routeErrorSchema },\n { status: 409, description: 'Optimistic lock conflict', schema: routeErrorSchema },\n { status: 413, description: 'Request body exceeds the safe resource bound', schema: routeErrorSchema },\n ],\n },\n },\n})\n\nexport default { GET, POST, PATCH }\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -8,6 +8,7 @@ import { loadDocumentContent } from "../../../lib/contentService.js";
8
8
  import { assertDocumentContentResourceLimits } from "../../../lib/resourceLimits.js";
9
9
  import { DOCUMENTS_JSON_BODY_LIMITS } from "../../../lib/requestBody.js";
10
10
  import {
11
+ assertDocumentId,
11
12
  assertDocumentNotArchived,
12
13
  handleDocumentsRouteError,
13
14
  readBody,
@@ -44,6 +45,7 @@ async function GET(request, context) {
44
45
  try {
45
46
  const id = await resolveId(context);
46
47
  const ctx = await resolveDocumentsContext(request, ["documents.view"]);
48
+ assertDocumentId(id);
47
49
  await assertTier(ctx.em, id, ctx.auth, "viewer");
48
50
  const content = await loadDocumentContent(ctx.em, id, {
49
51
  tenantId: ctx.tenantId,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../src/modules/documents/api/%5Bid%5D/content/route.ts"],
4
- "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { randomUUID } from 'node:crypto'\nimport { z } from 'zod'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { documentContentPutSchema } from '../../../data/validators'\nimport { DOCUMENTS_ENTITY_IDS } from '../../../lib/constants'\nimport { assertTier } from '../../../lib/permissions'\nimport { loadDocumentContent } from '../../../lib/contentService'\nimport { assertDocumentContentResourceLimits } from '../../../lib/resourceLimits'\nimport { DOCUMENTS_JSON_BODY_LIMITS } from '../../../lib/requestBody'\nimport type {\n ReplaceDocumentContentCommandInput,\n ReplaceDocumentContentCommandResult,\n} from '../../../commands/content'\nimport {\n assertDocumentNotArchived,\n handleDocumentsRouteError,\n readBody,\n resolveDocumentsContext,\n routeErrorSchema,\n runMutationGuardAfterSuccess,\n serializeContent,\n validateMutationGuard,\n withDocumentsContextErrors,\n} from '../../_shared'\nimport {\n attachDocumentsOperationMetadata,\n buildDocumentsCommandRuntimeContext,\n resolveDocumentsCommandBus,\n} from '../../_commands'\n\ntype RouteContext = {\n params: Promise<{ id: string }> | { id: string }\n}\n\nconst contentResponseSchema = z.object({\n contentHtml: z.string(),\n contentText: z.string(),\n updatedAt: z.string().nullable(),\n})\n\nconst contentPutResponseSchema = z.object({\n ok: z.boolean(),\n updatedAt: z.string().nullable(),\n})\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['documents.view'] },\n PUT: { requireAuth: true, requireFeatures: ['documents.edit'] },\n}\n\nasync function resolveId(context: RouteContext): Promise<string> {\n const params = await context.params\n return params.id\n}\n\nexport async function GET(request: Request, context: RouteContext): Promise<Response> {\n try {\n const id = await resolveId(context)\n const ctx = await resolveDocumentsContext(request, ['documents.view'])\n await assertTier(ctx.em, id, ctx.auth, 'viewer')\n const content = await loadDocumentContent(ctx.em, id, {\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId,\n })\n assertDocumentContentResourceLimits({\n yjsState: content?.yjsState,\n contentHtml: content?.contentHtml,\n contentText: content?.contentText,\n })\n return NextResponse.json(serializeContent(content))\n } catch (error) {\n return handleDocumentsRouteError(error, 'documents.content.get')\n }\n}\n\nexport async function PUT(request: Request, context: RouteContext): Promise<Response> {\n try {\n const id = await resolveId(context)\n const ctx = await resolveDocumentsContext(request, ['documents.edit'])\n await assertTier(ctx.em, id, ctx.auth, 'editor')\n await assertDocumentNotArchived(ctx, id)\n const input = documentContentPutSchema.parse(await readBody(\n request,\n DOCUMENTS_JSON_BODY_LIMITS.content,\n ))\n const guardResult = await validateMutationGuard(ctx, {\n resourceKind: DOCUMENTS_ENTITY_IDS.documentContent,\n resourceId: id,\n operation: 'update',\n mutationPayload: input,\n mutationPayloadSchema: documentContentPutSchema,\n })\n const scope = { tenantId: ctx.tenantId, organizationId: ctx.organizationId }\n const currentContent = await loadDocumentContent(ctx.em, id, scope)\n const commandInput: ReplaceDocumentContentCommandInput = {\n ...input,\n documentId: id,\n contentId: currentContent?.id ?? randomUUID(),\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId,\n }\n const { result, logEntry } = await resolveDocumentsCommandBus(ctx).execute<\n ReplaceDocumentContentCommandInput,\n ReplaceDocumentContentCommandResult\n >('documents.content.replace', {\n input: commandInput,\n ctx: buildDocumentsCommandRuntimeContext(ctx),\n })\n await runMutationGuardAfterSuccess(ctx, guardResult, {\n resourceKind: DOCUMENTS_ENTITY_IDS.documentContent,\n resourceId: id,\n operation: 'update',\n })\n\n const response = NextResponse.json({ ok: true, updatedAt: result.updatedAt })\n return attachDocumentsOperationMetadata(response, logEntry, {\n resourceKind: DOCUMENTS_ENTITY_IDS.documentContent,\n resourceId: result.id,\n })\n } catch (error) {\n return handleDocumentsRouteError(error, 'documents.content.put')\n }\n}\n\nexport const openApi: OpenApiRouteDoc = withDocumentsContextErrors({\n tag: 'Documents',\n summary: 'Document content',\n pathParams: z.object({ id: z.string().uuid() }),\n methods: {\n GET: {\n summary: 'Get document content',\n responses: [{ status: 200, description: 'Document content', schema: contentResponseSchema }],\n errors: [\n { status: 401, description: 'Unauthorized', schema: routeErrorSchema },\n { status: 403, description: 'Forbidden', schema: routeErrorSchema },\n { status: 404, description: 'Not found', schema: routeErrorSchema },\n { status: 413, description: 'Stored document content exceeds the safe resource bound', schema: routeErrorSchema },\n ],\n },\n PUT: {\n summary: 'Update document content',\n requestBody: { contentType: 'application/json', schema: documentContentPutSchema },\n responses: [{ status: 200, description: 'Content persisted', schema: contentPutResponseSchema }],\n errors: [\n { status: 400, description: 'Validation failed', schema: routeErrorSchema },\n { status: 401, description: 'Unauthorized', schema: routeErrorSchema },\n { status: 403, description: 'Forbidden', schema: routeErrorSchema },\n { status: 409, description: 'Content changed since it was loaded', schema: routeErrorSchema },\n { status: 413, description: 'Document content exceeds the safe resource bound', schema: routeErrorSchema },\n ],\n },\n },\n})\n\nexport default { GET, PUT }\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { randomUUID } from 'node:crypto'\nimport { z } from 'zod'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { documentContentPutSchema } from '../../../data/validators'\nimport { DOCUMENTS_ENTITY_IDS } from '../../../lib/constants'\nimport { assertTier } from '../../../lib/permissions'\nimport { loadDocumentContent } from '../../../lib/contentService'\nimport { assertDocumentContentResourceLimits } from '../../../lib/resourceLimits'\nimport { DOCUMENTS_JSON_BODY_LIMITS } from '../../../lib/requestBody'\nimport type {\n ReplaceDocumentContentCommandInput,\n ReplaceDocumentContentCommandResult,\n} from '../../../commands/content'\nimport {\n assertDocumentId,\n assertDocumentNotArchived,\n handleDocumentsRouteError,\n readBody,\n resolveDocumentsContext,\n routeErrorSchema,\n runMutationGuardAfterSuccess,\n serializeContent,\n validateMutationGuard,\n withDocumentsContextErrors,\n} from '../../_shared'\nimport {\n attachDocumentsOperationMetadata,\n buildDocumentsCommandRuntimeContext,\n resolveDocumentsCommandBus,\n} from '../../_commands'\n\ntype RouteContext = {\n params: Promise<{ id: string }> | { id: string }\n}\n\nconst contentResponseSchema = z.object({\n contentHtml: z.string(),\n contentText: z.string(),\n updatedAt: z.string().nullable(),\n})\n\nconst contentPutResponseSchema = z.object({\n ok: z.boolean(),\n updatedAt: z.string().nullable(),\n})\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['documents.view'] },\n PUT: { requireAuth: true, requireFeatures: ['documents.edit'] },\n}\n\nasync function resolveId(context: RouteContext): Promise<string> {\n const params = await context.params\n return params.id\n}\n\nexport async function GET(request: Request, context: RouteContext): Promise<Response> {\n try {\n const id = await resolveId(context)\n const ctx = await resolveDocumentsContext(request, ['documents.view'])\n assertDocumentId(id)\n await assertTier(ctx.em, id, ctx.auth, 'viewer')\n const content = await loadDocumentContent(ctx.em, id, {\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId,\n })\n assertDocumentContentResourceLimits({\n yjsState: content?.yjsState,\n contentHtml: content?.contentHtml,\n contentText: content?.contentText,\n })\n return NextResponse.json(serializeContent(content))\n } catch (error) {\n return handleDocumentsRouteError(error, 'documents.content.get')\n }\n}\n\nexport async function PUT(request: Request, context: RouteContext): Promise<Response> {\n try {\n const id = await resolveId(context)\n const ctx = await resolveDocumentsContext(request, ['documents.edit'])\n await assertTier(ctx.em, id, ctx.auth, 'editor')\n await assertDocumentNotArchived(ctx, id)\n const input = documentContentPutSchema.parse(await readBody(\n request,\n DOCUMENTS_JSON_BODY_LIMITS.content,\n ))\n const guardResult = await validateMutationGuard(ctx, {\n resourceKind: DOCUMENTS_ENTITY_IDS.documentContent,\n resourceId: id,\n operation: 'update',\n mutationPayload: input,\n mutationPayloadSchema: documentContentPutSchema,\n })\n const scope = { tenantId: ctx.tenantId, organizationId: ctx.organizationId }\n const currentContent = await loadDocumentContent(ctx.em, id, scope)\n const commandInput: ReplaceDocumentContentCommandInput = {\n ...input,\n documentId: id,\n contentId: currentContent?.id ?? randomUUID(),\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId,\n }\n const { result, logEntry } = await resolveDocumentsCommandBus(ctx).execute<\n ReplaceDocumentContentCommandInput,\n ReplaceDocumentContentCommandResult\n >('documents.content.replace', {\n input: commandInput,\n ctx: buildDocumentsCommandRuntimeContext(ctx),\n })\n await runMutationGuardAfterSuccess(ctx, guardResult, {\n resourceKind: DOCUMENTS_ENTITY_IDS.documentContent,\n resourceId: id,\n operation: 'update',\n })\n\n const response = NextResponse.json({ ok: true, updatedAt: result.updatedAt })\n return attachDocumentsOperationMetadata(response, logEntry, {\n resourceKind: DOCUMENTS_ENTITY_IDS.documentContent,\n resourceId: result.id,\n })\n } catch (error) {\n return handleDocumentsRouteError(error, 'documents.content.put')\n }\n}\n\nexport const openApi: OpenApiRouteDoc = withDocumentsContextErrors({\n tag: 'Documents',\n summary: 'Document content',\n pathParams: z.object({ id: z.string().uuid() }),\n methods: {\n GET: {\n summary: 'Get document content',\n responses: [{ status: 200, description: 'Document content', schema: contentResponseSchema }],\n errors: [\n { status: 401, description: 'Unauthorized', schema: routeErrorSchema },\n { status: 403, description: 'Forbidden', schema: routeErrorSchema },\n { status: 404, description: 'Not found', schema: routeErrorSchema },\n { status: 413, description: 'Stored document content exceeds the safe resource bound', schema: routeErrorSchema },\n ],\n },\n PUT: {\n summary: 'Update document content',\n requestBody: { contentType: 'application/json', schema: documentContentPutSchema },\n responses: [{ status: 200, description: 'Content persisted', schema: contentPutResponseSchema }],\n errors: [\n { status: 400, description: 'Validation failed', schema: routeErrorSchema },\n { status: 401, description: 'Unauthorized', schema: routeErrorSchema },\n { status: 403, description: 'Forbidden', schema: routeErrorSchema },\n { status: 409, description: 'Content changed since it was loaded', schema: routeErrorSchema },\n { status: 413, description: 'Document content exceeds the safe resource bound', schema: routeErrorSchema },\n ],\n },\n },\n})\n\nexport default { GET, PUT }\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -76,6 +76,9 @@ async function GET(request, context) {
76
76
  try {
77
77
  const id = await resolveId(context);
78
78
  const ctx = await resolveDocumentsContext(request, ["documents.view"]);
79
+ if (!z.string().uuid().safeParse(id).success) {
80
+ throw new CrudHttpError(404, { error: "documents.documents.notFound" });
81
+ }
79
82
  const projection = await resolveDocumentCapabilityProjection(ctx, id);
80
83
  if (!projection.relationshipTier && await isRemovedDocumentVisibleToActor(ctx, id)) {
81
84
  throw new CrudHttpError(404, { error: "documents.documents.notFound" });
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/modules/documents/api/%5Bid%5D/route.ts"],
4
- "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { documentUpdateSchema } from '../../data/validators'\nimport { DOCUMENTS_ENTITY_IDS } from '../../lib/constants'\nimport { deriveDocumentCapabilities } from '../../lib/capabilities'\nimport { isDocumentFavorite } from '../../lib/favorites'\nimport { isDocumentWatched } from '../../lib/watchers'\nimport {\n assertDocumentNotArchived,\n handleDocumentsRouteError,\n hasDocumentsFeature,\n isRemovedDocumentVisibleToActor,\n loadScopedDocument,\n readBody,\n resolveActorUserId,\n resolveDocumentCapabilityProjection,\n resolveDocumentsContext,\n routeErrorSchema,\n runMutationGuardAfterSuccess,\n serializeDocument,\n validateMutationGuard,\n withDocumentsContextErrors,\n} from '../_shared'\nimport {\n attachDocumentsOperationMetadata,\n buildDocumentsCommandRuntimeContext,\n resolveDocumentsCommandBus,\n} from '../_commands'\nimport type {\n DocumentDeleteCommandInput,\n DocumentUpdateCommandInput,\n} from '../../commands/document-crud'\n\ntype RouteContext = {\n params: Promise<{ id: string }> | { id: string }\n}\n\nconst capabilitiesSchema = z.object({\n canView: z.boolean(),\n canComment: z.boolean(),\n canEdit: z.boolean(),\n canShare: z.boolean(),\n canDelete: z.boolean(),\n canCreate: z.boolean(),\n canManageTemplates: z.boolean(),\n canArchive: z.boolean(),\n canDuplicate: z.boolean(),\n})\n\nconst detailResponseSchema = z.object({\n id: z.string().uuid(),\n title: z.string(),\n folderId: z.string().uuid().nullable(),\n ownerUserId: z.string().uuid(),\n createdByUserId: z.string().uuid(),\n isActive: z.boolean(),\n archivedAt: z.string().nullable(),\n isFavorite: z.boolean(),\n isWatching: z.boolean(),\n createdAt: z.string(),\n updatedAt: z.string(),\n tier: z.enum(['owner', 'editor', 'commenter', 'viewer']),\n canShare: z.boolean(),\n capabilities: capabilitiesSchema,\n})\n\nconst mutationResponseSchema = z.object({\n id: z.string().uuid(),\n updatedAt: z.string(),\n})\n\nconst deleteResponseSchema = z.object({\n ok: z.boolean(),\n id: z.string().uuid(),\n updatedAt: z.string(),\n})\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['documents.view'] },\n PUT: { requireAuth: true, requireFeatures: ['documents.edit'] },\n DELETE: { requireAuth: true, requireFeatures: ['documents.delete'] },\n}\n\nasync function resolveId(context: RouteContext): Promise<string> {\n const params = await context.params\n return params.id\n}\n\nexport async function GET(request: Request, context: RouteContext): Promise<Response> {\n try {\n const id = await resolveId(context)\n const ctx = await resolveDocumentsContext(request, ['documents.view'])\n const projection = await resolveDocumentCapabilityProjection(ctx, id)\n if (!projection.relationshipTier && await isRemovedDocumentVisibleToActor(ctx, id)) {\n throw new CrudHttpError(404, { error: 'documents.documents.notFound' })\n }\n if (!projection.relationshipTier || !projection.capabilities.canView) {\n throw new CrudHttpError(403, { error: 'Forbidden' })\n }\n const document = await loadScopedDocument(ctx, id)\n const archivedAt = document.archivedAt?.toISOString() ?? null\n const capabilities = deriveDocumentCapabilities({\n relationshipTier: projection.relationshipTier,\n managerOverride: hasDocumentsFeature(ctx.auth, 'documents.manage'),\n archived: archivedAt !== null,\n userFeatures: ctx.auth.features,\n })\n const isFavorite = await isDocumentFavorite(\n ctx.em,\n {\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId,\n userId: resolveActorUserId(ctx.auth),\n },\n id,\n )\n const isWatching = await isDocumentWatched(\n ctx.em,\n {\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId,\n userId: resolveActorUserId(ctx.auth),\n },\n id,\n )\n return NextResponse.json({\n ...serializeDocument(document),\n archivedAt,\n isFavorite,\n isWatching,\n tier: projection.relationshipTier,\n canShare: capabilities.canShare,\n capabilities,\n })\n } catch (error) {\n return handleDocumentsRouteError(error, 'documents.detail')\n }\n}\n\nexport async function PUT(request: Request, context: RouteContext): Promise<Response> {\n try {\n const id = await resolveId(context)\n const ctx = await resolveDocumentsContext(request, ['documents.edit'])\n const projection = await resolveDocumentCapabilityProjection(ctx, id)\n if (!projection.capabilities.canEdit) {\n throw new CrudHttpError(403, { error: 'Forbidden' })\n }\n await assertDocumentNotArchived(ctx, id)\n const body = await readBody(request)\n const input = documentUpdateSchema.parse({ ...body, id })\n const guardResult = await validateMutationGuard(ctx, {\n resourceKind: DOCUMENTS_ENTITY_IDS.document,\n resourceId: id,\n operation: 'update',\n mutationPayload: input,\n mutationPayloadSchema: documentUpdateSchema,\n })\n\n const commandInput: DocumentUpdateCommandInput = {\n ...input,\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId,\n }\n const { result, logEntry } = await resolveDocumentsCommandBus(ctx).execute<\n DocumentUpdateCommandInput,\n { id: string; updatedAt: string }\n >('documents.document.update', {\n input: commandInput,\n ctx: buildDocumentsCommandRuntimeContext(ctx),\n })\n await runMutationGuardAfterSuccess(ctx, guardResult, {\n resourceKind: DOCUMENTS_ENTITY_IDS.document,\n resourceId: id,\n operation: 'update',\n })\n\n const response = NextResponse.json({ id: result.id, updatedAt: result.updatedAt })\n return attachDocumentsOperationMetadata(response, logEntry, {\n resourceKind: DOCUMENTS_ENTITY_IDS.document,\n resourceId: result.id,\n })\n } catch (error) {\n return handleDocumentsRouteError(error, 'documents.update')\n }\n}\n\nexport async function DELETE(request: Request, context: RouteContext): Promise<Response> {\n try {\n const id = await resolveId(context)\n const ctx = await resolveDocumentsContext(request, ['documents.delete'])\n const projection = await resolveDocumentCapabilityProjection(ctx, id)\n if (!projection.capabilities.canDelete) {\n throw new CrudHttpError(403, { error: 'Forbidden' })\n }\n const guardResult = await validateMutationGuard(ctx, {\n resourceKind: DOCUMENTS_ENTITY_IDS.document,\n resourceId: id,\n operation: 'delete',\n })\n\n const commandInput: DocumentDeleteCommandInput = {\n id,\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId,\n }\n const { result, logEntry } = await resolveDocumentsCommandBus(ctx).execute<\n DocumentDeleteCommandInput,\n { id: string; updatedAt: string }\n >('documents.document.delete', {\n input: commandInput,\n ctx: buildDocumentsCommandRuntimeContext(ctx),\n })\n await runMutationGuardAfterSuccess(ctx, guardResult, {\n resourceKind: DOCUMENTS_ENTITY_IDS.document,\n resourceId: id,\n operation: 'delete',\n })\n\n const response = NextResponse.json({ ok: true, id: result.id, updatedAt: result.updatedAt })\n return attachDocumentsOperationMetadata(response, logEntry, {\n resourceKind: DOCUMENTS_ENTITY_IDS.document,\n resourceId: result.id,\n })\n } catch (error) {\n return handleDocumentsRouteError(error, 'documents.delete')\n }\n}\n\nexport const openApi: OpenApiRouteDoc = withDocumentsContextErrors({\n tag: 'Documents',\n summary: 'Document detail',\n pathParams: z.object({ id: z.string().uuid() }),\n methods: {\n GET: {\n summary: 'Get document metadata',\n responses: [{ status: 200, description: 'Document metadata', schema: detailResponseSchema }],\n errors: [\n { status: 401, description: 'Unauthorized', schema: routeErrorSchema },\n { status: 403, description: 'Forbidden', schema: routeErrorSchema },\n { status: 404, description: 'Not found', schema: routeErrorSchema },\n ],\n },\n PUT: {\n summary: 'Update document metadata',\n requestBody: { contentType: 'application/json', schema: documentUpdateSchema },\n responses: [{ status: 200, description: 'Document updated', schema: mutationResponseSchema }],\n errors: [\n { status: 400, description: 'Validation failed', schema: routeErrorSchema },\n { status: 401, description: 'Unauthorized', schema: routeErrorSchema },\n { status: 403, description: 'Forbidden', schema: routeErrorSchema },\n { status: 409, description: 'Optimistic lock conflict', schema: routeErrorSchema },\n { status: 413, description: 'Request body exceeds the safe resource bound', schema: routeErrorSchema },\n ],\n },\n DELETE: {\n summary: 'Delete document',\n description: 'Transactionally releases every document-owned attachment row and its quota usage, soft-deletes the document record, then runs reference-checked provider cleanup after commit. Because attachment bytes cannot be restored, this operation is intentionally not undoable.',\n responses: [{ status: 200, description: 'Document deleted', schema: deleteResponseSchema }],\n errors: [\n { status: 401, description: 'Unauthorized', schema: routeErrorSchema },\n { status: 403, description: 'Forbidden', schema: routeErrorSchema },\n { status: 404, description: 'Not found', schema: routeErrorSchema },\n { status: 409, description: 'Optimistic lock conflict', schema: routeErrorSchema },\n ],\n },\n },\n})\n\nexport default { GET, PUT, DELETE }\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { documentUpdateSchema } from '../../data/validators'\nimport { DOCUMENTS_ENTITY_IDS } from '../../lib/constants'\nimport { deriveDocumentCapabilities } from '../../lib/capabilities'\nimport { isDocumentFavorite } from '../../lib/favorites'\nimport { isDocumentWatched } from '../../lib/watchers'\nimport {\n assertDocumentNotArchived,\n handleDocumentsRouteError,\n hasDocumentsFeature,\n isRemovedDocumentVisibleToActor,\n loadScopedDocument,\n readBody,\n resolveActorUserId,\n resolveDocumentCapabilityProjection,\n resolveDocumentsContext,\n routeErrorSchema,\n runMutationGuardAfterSuccess,\n serializeDocument,\n validateMutationGuard,\n withDocumentsContextErrors,\n} from '../_shared'\nimport {\n attachDocumentsOperationMetadata,\n buildDocumentsCommandRuntimeContext,\n resolveDocumentsCommandBus,\n} from '../_commands'\nimport type {\n DocumentDeleteCommandInput,\n DocumentUpdateCommandInput,\n} from '../../commands/document-crud'\n\ntype RouteContext = {\n params: Promise<{ id: string }> | { id: string }\n}\n\nconst capabilitiesSchema = z.object({\n canView: z.boolean(),\n canComment: z.boolean(),\n canEdit: z.boolean(),\n canShare: z.boolean(),\n canDelete: z.boolean(),\n canCreate: z.boolean(),\n canManageTemplates: z.boolean(),\n canArchive: z.boolean(),\n canDuplicate: z.boolean(),\n})\n\nconst detailResponseSchema = z.object({\n id: z.string().uuid(),\n title: z.string(),\n folderId: z.string().uuid().nullable(),\n ownerUserId: z.string().uuid(),\n createdByUserId: z.string().uuid(),\n isActive: z.boolean(),\n archivedAt: z.string().nullable(),\n isFavorite: z.boolean(),\n isWatching: z.boolean(),\n createdAt: z.string(),\n updatedAt: z.string(),\n tier: z.enum(['owner', 'editor', 'commenter', 'viewer']),\n canShare: z.boolean(),\n capabilities: capabilitiesSchema,\n})\n\nconst mutationResponseSchema = z.object({\n id: z.string().uuid(),\n updatedAt: z.string(),\n})\n\nconst deleteResponseSchema = z.object({\n ok: z.boolean(),\n id: z.string().uuid(),\n updatedAt: z.string(),\n})\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['documents.view'] },\n PUT: { requireAuth: true, requireFeatures: ['documents.edit'] },\n DELETE: { requireAuth: true, requireFeatures: ['documents.delete'] },\n}\n\nasync function resolveId(context: RouteContext): Promise<string> {\n const params = await context.params\n return params.id\n}\n\nexport async function GET(request: Request, context: RouteContext): Promise<Response> {\n try {\n const id = await resolveId(context)\n const ctx = await resolveDocumentsContext(request, ['documents.view'])\n if (!z.string().uuid().safeParse(id).success) {\n throw new CrudHttpError(404, { error: 'documents.documents.notFound' })\n }\n const projection = await resolveDocumentCapabilityProjection(ctx, id)\n if (!projection.relationshipTier && await isRemovedDocumentVisibleToActor(ctx, id)) {\n throw new CrudHttpError(404, { error: 'documents.documents.notFound' })\n }\n if (!projection.relationshipTier || !projection.capabilities.canView) {\n throw new CrudHttpError(403, { error: 'Forbidden' })\n }\n const document = await loadScopedDocument(ctx, id)\n const archivedAt = document.archivedAt?.toISOString() ?? null\n const capabilities = deriveDocumentCapabilities({\n relationshipTier: projection.relationshipTier,\n managerOverride: hasDocumentsFeature(ctx.auth, 'documents.manage'),\n archived: archivedAt !== null,\n userFeatures: ctx.auth.features,\n })\n const isFavorite = await isDocumentFavorite(\n ctx.em,\n {\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId,\n userId: resolveActorUserId(ctx.auth),\n },\n id,\n )\n const isWatching = await isDocumentWatched(\n ctx.em,\n {\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId,\n userId: resolveActorUserId(ctx.auth),\n },\n id,\n )\n return NextResponse.json({\n ...serializeDocument(document),\n archivedAt,\n isFavorite,\n isWatching,\n tier: projection.relationshipTier,\n canShare: capabilities.canShare,\n capabilities,\n })\n } catch (error) {\n return handleDocumentsRouteError(error, 'documents.detail')\n }\n}\n\nexport async function PUT(request: Request, context: RouteContext): Promise<Response> {\n try {\n const id = await resolveId(context)\n const ctx = await resolveDocumentsContext(request, ['documents.edit'])\n const projection = await resolveDocumentCapabilityProjection(ctx, id)\n if (!projection.capabilities.canEdit) {\n throw new CrudHttpError(403, { error: 'Forbidden' })\n }\n await assertDocumentNotArchived(ctx, id)\n const body = await readBody(request)\n const input = documentUpdateSchema.parse({ ...body, id })\n const guardResult = await validateMutationGuard(ctx, {\n resourceKind: DOCUMENTS_ENTITY_IDS.document,\n resourceId: id,\n operation: 'update',\n mutationPayload: input,\n mutationPayloadSchema: documentUpdateSchema,\n })\n\n const commandInput: DocumentUpdateCommandInput = {\n ...input,\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId,\n }\n const { result, logEntry } = await resolveDocumentsCommandBus(ctx).execute<\n DocumentUpdateCommandInput,\n { id: string; updatedAt: string }\n >('documents.document.update', {\n input: commandInput,\n ctx: buildDocumentsCommandRuntimeContext(ctx),\n })\n await runMutationGuardAfterSuccess(ctx, guardResult, {\n resourceKind: DOCUMENTS_ENTITY_IDS.document,\n resourceId: id,\n operation: 'update',\n })\n\n const response = NextResponse.json({ id: result.id, updatedAt: result.updatedAt })\n return attachDocumentsOperationMetadata(response, logEntry, {\n resourceKind: DOCUMENTS_ENTITY_IDS.document,\n resourceId: result.id,\n })\n } catch (error) {\n return handleDocumentsRouteError(error, 'documents.update')\n }\n}\n\nexport async function DELETE(request: Request, context: RouteContext): Promise<Response> {\n try {\n const id = await resolveId(context)\n const ctx = await resolveDocumentsContext(request, ['documents.delete'])\n const projection = await resolveDocumentCapabilityProjection(ctx, id)\n if (!projection.capabilities.canDelete) {\n throw new CrudHttpError(403, { error: 'Forbidden' })\n }\n const guardResult = await validateMutationGuard(ctx, {\n resourceKind: DOCUMENTS_ENTITY_IDS.document,\n resourceId: id,\n operation: 'delete',\n })\n\n const commandInput: DocumentDeleteCommandInput = {\n id,\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId,\n }\n const { result, logEntry } = await resolveDocumentsCommandBus(ctx).execute<\n DocumentDeleteCommandInput,\n { id: string; updatedAt: string }\n >('documents.document.delete', {\n input: commandInput,\n ctx: buildDocumentsCommandRuntimeContext(ctx),\n })\n await runMutationGuardAfterSuccess(ctx, guardResult, {\n resourceKind: DOCUMENTS_ENTITY_IDS.document,\n resourceId: id,\n operation: 'delete',\n })\n\n const response = NextResponse.json({ ok: true, id: result.id, updatedAt: result.updatedAt })\n return attachDocumentsOperationMetadata(response, logEntry, {\n resourceKind: DOCUMENTS_ENTITY_IDS.document,\n resourceId: result.id,\n })\n } catch (error) {\n return handleDocumentsRouteError(error, 'documents.delete')\n }\n}\n\nexport const openApi: OpenApiRouteDoc = withDocumentsContextErrors({\n tag: 'Documents',\n summary: 'Document detail',\n pathParams: z.object({ id: z.string().uuid() }),\n methods: {\n GET: {\n summary: 'Get document metadata',\n responses: [{ status: 200, description: 'Document metadata', schema: detailResponseSchema }],\n errors: [\n { status: 401, description: 'Unauthorized', schema: routeErrorSchema },\n { status: 403, description: 'Forbidden', schema: routeErrorSchema },\n { status: 404, description: 'Not found', schema: routeErrorSchema },\n ],\n },\n PUT: {\n summary: 'Update document metadata',\n requestBody: { contentType: 'application/json', schema: documentUpdateSchema },\n responses: [{ status: 200, description: 'Document updated', schema: mutationResponseSchema }],\n errors: [\n { status: 400, description: 'Validation failed', schema: routeErrorSchema },\n { status: 401, description: 'Unauthorized', schema: routeErrorSchema },\n { status: 403, description: 'Forbidden', schema: routeErrorSchema },\n { status: 409, description: 'Optimistic lock conflict', schema: routeErrorSchema },\n { status: 413, description: 'Request body exceeds the safe resource bound', schema: routeErrorSchema },\n ],\n },\n DELETE: {\n summary: 'Delete document',\n description: 'Transactionally releases every document-owned attachment row and its quota usage, soft-deletes the document record, then runs reference-checked provider cleanup after commit. Because attachment bytes cannot be restored, this operation is intentionally not undoable.',\n responses: [{ status: 200, description: 'Document deleted', schema: deleteResponseSchema }],\n errors: [\n { status: 401, description: 'Unauthorized', schema: routeErrorSchema },\n { status: 403, description: 'Forbidden', schema: routeErrorSchema },\n { status: 404, description: 'Not found', schema: routeErrorSchema },\n { status: 409, description: 'Optimistic lock conflict', schema: routeErrorSchema },\n ],\n },\n },\n})\n\nexport default { GET, PUT, DELETE }\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -16,6 +16,7 @@ import {
16
16
  resolveDocumentsCommandBus
17
17
  } from "../../_commands.js";
18
18
  import {
19
+ assertDocumentId,
19
20
  assertDocumentNotArchived,
20
21
  handleDocumentsRouteError,
21
22
  loadScopedShare,
@@ -106,6 +107,7 @@ async function GET(request, context) {
106
107
  try {
107
108
  const documentId = await resolveId(context);
108
109
  const ctx = await resolveDocumentsContext(request, ["documents.share"]);
110
+ assertDocumentId(documentId);
109
111
  await assertCanShare(ctx, documentId);
110
112
  const loadedShares = await findWithDecryption(
111
113
  ctx.em,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../src/modules/documents/api/%5Bid%5D/shares/route.ts"],
4
- "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { randomUUID } from 'node:crypto'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { enforceCommandOptimisticLockWithGuards } from '@open-mercato/shared/lib/crud/optimistic-lock-command'\nimport { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { findOneWithDecryption, findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { DocumentShare } from '../../../data/entities'\nimport { documentShareCreateSchema, documentShareUpdateSchema } from '../../../data/validators'\nimport { DOCUMENTS_ENTITY_IDS, DOCUMENTS_MAX_LISTED_SHARES } from '../../../lib/constants'\nimport { sanitizeDocumentsDisplayLabel } from '../../../lib/displayLabels'\nimport { resolveUserLabels } from '../../../lib/userLabels'\nimport { resolveAuthPrincipalService } from '../../../lib/platformServices'\nimport type {\n ShareCreateCommandInput,\n ShareDeleteCommandInput,\n ShareUpdateCommandInput,\n ShareCommandResult,\n} from '../../../commands/shares'\nimport {\n attachDocumentsOperationMetadata,\n buildDocumentsCommandRuntimeContext,\n resolveDocumentsCommandBus,\n} from '../../_commands'\nimport {\n assertDocumentNotArchived,\n handleDocumentsRouteError,\n loadScopedShare,\n readBody,\n resolveActorUserId,\n resolveDocumentCapabilityProjection,\n resolveDocumentsContext,\n routeErrorSchema,\n runMutationGuardAfterSuccess,\n serializeShare,\n validateMutationGuard,\n withDocumentsContextErrors,\n} from '../../_shared'\n\ntype RouteContext = {\n params: Promise<{ id: string }> | { id: string }\n}\n\nconst shareDeleteSchema = z.object({\n id: z.string().uuid(),\n})\n\nconst shareListResponseSchema = z.object({\n items: z.array(z.object({\n id: z.string().uuid(),\n documentId: z.string().uuid(),\n principalType: z.enum(['user', 'role']),\n principalId: z.string().uuid(),\n permission: z.enum(['viewer', 'commenter', 'editor']),\n createdByUserId: z.string().uuid(),\n createdAt: z.string(),\n updatedAt: z.string(),\n principalLabel: z.string().nullable(),\n principalSecondary: z.string().nullable(),\n })),\n truncated: z.boolean(),\n})\n\nconst shareMutationResponseSchema = z.object({\n id: z.string().uuid(),\n updatedAt: z.string(),\n})\n\nconst shareDeleteResponseSchema = z.object({\n ok: z.boolean(),\n id: z.string().uuid(),\n updatedAt: z.string(),\n})\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['documents.share'] },\n POST: { requireAuth: true, requireFeatures: ['documents.share'] },\n PUT: { requireAuth: true, requireFeatures: ['documents.share'] },\n DELETE: { requireAuth: true, requireFeatures: ['documents.share'] },\n}\n\nasync function resolveId(context: RouteContext): Promise<string> {\n const params = await context.params\n return params.id\n}\n\nasync function assertCanShare(\n ctx: Awaited<ReturnType<typeof resolveDocumentsContext>>,\n documentId: string,\n): Promise<void> {\n const projection = await resolveDocumentCapabilityProjection(ctx, documentId)\n if (!projection.capabilities.canShare) {\n throw new CrudHttpError(403, { error: 'Forbidden' })\n }\n}\n\nasync function assertCanMutateShares(\n ctx: Awaited<ReturnType<typeof resolveDocumentsContext>>,\n documentId: string,\n): Promise<void> {\n await assertCanShare(ctx, documentId)\n await assertDocumentNotArchived(ctx, documentId)\n}\n\ntype PrincipalLabel = { label: string; secondary?: string | null }\n\nfunction cleanString(value: unknown): string | null {\n return sanitizeDocumentsDisplayLabel(value)\n}\n\nasync function resolvePrincipalLabels(\n container: Awaited<ReturnType<typeof resolveDocumentsContext>>['container'],\n scope: { tenantId: string; organizationId: string },\n shares: DocumentShare[],\n): Promise<Map<string, PrincipalLabel>> {\n const labels = new Map<string, PrincipalLabel>()\n const userIds = [...new Set(shares.filter((s) => s.principalType === 'user').map((s) => s.principalId))]\n const roleIds = [...new Set(shares.filter((s) => s.principalType === 'role').map((s) => s.principalId))]\n\n if (userIds.length > 0) {\n const userLabels = await resolveUserLabels(container, scope, userIds)\n for (const [userId, label] of userLabels.entries()) {\n labels.set(userId, label)\n }\n }\n\n if (roleIds.length > 0) {\n const roles = await resolveAuthPrincipalService(container)?.resolveLabels({\n type: 'role', ids: roleIds, scope,\n }) ?? []\n for (const role of roles) {\n const name = cleanString(role.label)\n if (name) labels.set(role.id, { label: name, secondary: null })\n }\n }\n\n return labels\n}\n\nexport async function GET(request: Request, context: RouteContext): Promise<Response> {\n try {\n const documentId = await resolveId(context)\n const ctx = await resolveDocumentsContext(request, ['documents.share'])\n await assertCanShare(ctx, documentId)\n const loadedShares = await findWithDecryption(\n ctx.em,\n DocumentShare,\n {\n documentId,\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId,\n deletedAt: null,\n },\n { orderBy: { createdAt: 'ASC', id: 'ASC' }, limit: DOCUMENTS_MAX_LISTED_SHARES + 1 },\n { tenantId: ctx.tenantId, organizationId: ctx.organizationId },\n )\n const truncated = loadedShares.length > DOCUMENTS_MAX_LISTED_SHARES\n const shares = loadedShares.slice(0, DOCUMENTS_MAX_LISTED_SHARES)\n const labels = await resolvePrincipalLabels(\n ctx.container,\n { tenantId: ctx.tenantId, organizationId: ctx.organizationId },\n shares,\n )\n const items = shares.map((share) => {\n const resolved = labels.get(share.principalId) ?? null\n return {\n ...serializeShare(share),\n principalLabel: resolved?.label ?? null,\n principalSecondary: resolved?.secondary ?? null,\n }\n })\n return NextResponse.json({ items, truncated })\n } catch (error) {\n return handleDocumentsRouteError(error, 'documents.shares.list')\n }\n}\n\nexport async function POST(request: Request, context: RouteContext): Promise<Response> {\n try {\n const documentId = await resolveId(context)\n const ctx = await resolveDocumentsContext(request, ['documents.share'])\n await assertCanMutateShares(ctx, documentId)\n const input = documentShareCreateSchema.parse(await readBody(request))\n const guardResourceId = `${documentId}:${input.principalType}:${input.principalId}`\n const guardResult = await validateMutationGuard(ctx, {\n resourceKind: DOCUMENTS_ENTITY_IDS.documentShare,\n resourceId: guardResourceId,\n operation: 'create',\n mutationPayload: input,\n mutationPayloadSchema: documentShareCreateSchema,\n })\n\n const existing = await findOneWithDecryption(\n ctx.em,\n DocumentShare,\n {\n documentId,\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId,\n principalType: input.principalType,\n principalId: input.principalId,\n },\n { filters: false, orderBy: { updatedAt: 'DESC' } },\n { tenantId: ctx.tenantId, organizationId: ctx.organizationId },\n )\n const commandInput: ShareCreateCommandInput = {\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId,\n documentId,\n shareId: existing?.id ?? randomUUID(),\n actorUserId: resolveActorUserId(ctx.auth),\n expectedUpdatedAt: existing?.updatedAt.toISOString() ?? null,\n share: input,\n }\n const execution = await resolveDocumentsCommandBus(ctx).execute<ShareCreateCommandInput, ShareCommandResult>(\n 'documents.share.create',\n { input: commandInput, ctx: buildDocumentsCommandRuntimeContext(ctx) },\n )\n await runMutationGuardAfterSuccess(ctx, guardResult, {\n resourceKind: DOCUMENTS_ENTITY_IDS.documentShare,\n resourceId: guardResourceId,\n operation: 'create',\n })\n\n return attachDocumentsOperationMetadata(\n NextResponse.json({ id: execution.result.id, updatedAt: execution.result.updatedAt }, { status: 201 }),\n execution.logEntry,\n { resourceKind: DOCUMENTS_ENTITY_IDS.documentShare, resourceId: execution.result.id },\n )\n } catch (error) {\n return handleDocumentsRouteError(error, 'documents.shares.create')\n }\n}\n\nexport async function PUT(request: Request, context: RouteContext): Promise<Response> {\n try {\n const documentId = await resolveId(context)\n const ctx = await resolveDocumentsContext(request, ['documents.share'])\n await assertCanMutateShares(ctx, documentId)\n const input = documentShareUpdateSchema.parse(await readBody(request))\n const share = await loadScopedShare(ctx, documentId, input.id)\n await enforceCommandOptimisticLockWithGuards(ctx.container, {\n resourceKind: DOCUMENTS_ENTITY_IDS.documentShare,\n resourceId: share.id,\n current: share.updatedAt,\n request,\n })\n const guardResult = await validateMutationGuard(ctx, {\n resourceKind: DOCUMENTS_ENTITY_IDS.documentShare,\n resourceId: share.id,\n operation: 'update',\n mutationPayload: input,\n mutationPayloadSchema: documentShareUpdateSchema,\n })\n\n const commandInput: ShareUpdateCommandInput = {\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId,\n documentId,\n actorUserId: resolveActorUserId(ctx.auth),\n expectedUpdatedAt: share.updatedAt.toISOString(),\n share: input,\n }\n const execution = await resolveDocumentsCommandBus(ctx).execute<ShareUpdateCommandInput, ShareCommandResult>(\n 'documents.share.update',\n { input: commandInput, ctx: buildDocumentsCommandRuntimeContext(ctx) },\n )\n await runMutationGuardAfterSuccess(ctx, guardResult, {\n resourceKind: DOCUMENTS_ENTITY_IDS.documentShare,\n resourceId: share.id,\n operation: 'update',\n })\n\n return attachDocumentsOperationMetadata(\n NextResponse.json({ id: execution.result.id, updatedAt: execution.result.updatedAt }),\n execution.logEntry,\n { resourceKind: DOCUMENTS_ENTITY_IDS.documentShare, resourceId: execution.result.id },\n )\n } catch (error) {\n return handleDocumentsRouteError(error, 'documents.shares.update')\n }\n}\n\nexport async function DELETE(request: Request, context: RouteContext): Promise<Response> {\n try {\n const documentId = await resolveId(context)\n const ctx = await resolveDocumentsContext(request, ['documents.share'])\n await assertCanMutateShares(ctx, documentId)\n const input = shareDeleteSchema.parse(await readBody(request))\n const share = await loadScopedShare(ctx, documentId, input.id)\n await enforceCommandOptimisticLockWithGuards(ctx.container, {\n resourceKind: DOCUMENTS_ENTITY_IDS.documentShare,\n resourceId: share.id,\n current: share.updatedAt,\n request,\n })\n const guardResult = await validateMutationGuard(ctx, {\n resourceKind: DOCUMENTS_ENTITY_IDS.documentShare,\n resourceId: share.id,\n operation: 'delete',\n })\n\n const commandInput: ShareDeleteCommandInput = {\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId,\n documentId,\n shareId: share.id,\n actorUserId: resolveActorUserId(ctx.auth),\n expectedUpdatedAt: share.updatedAt.toISOString(),\n }\n const execution = await resolveDocumentsCommandBus(ctx).execute<ShareDeleteCommandInput, ShareCommandResult>(\n 'documents.share.delete',\n { input: commandInput, ctx: buildDocumentsCommandRuntimeContext(ctx) },\n )\n await runMutationGuardAfterSuccess(ctx, guardResult, {\n resourceKind: DOCUMENTS_ENTITY_IDS.documentShare,\n resourceId: share.id,\n operation: 'delete',\n })\n\n return attachDocumentsOperationMetadata(\n NextResponse.json({ ok: true, id: execution.result.id, updatedAt: execution.result.updatedAt }),\n execution.logEntry,\n { resourceKind: DOCUMENTS_ENTITY_IDS.documentShare, resourceId: execution.result.id },\n )\n } catch (error) {\n return handleDocumentsRouteError(error, 'documents.shares.delete')\n }\n}\n\nexport const openApi: OpenApiRouteDoc = withDocumentsContextErrors({\n tag: 'Documents',\n summary: 'Document shares',\n pathParams: z.object({ id: z.string().uuid() }),\n methods: {\n GET: {\n summary: 'List document shares',\n responses: [{ status: 200, description: 'Document shares', schema: shareListResponseSchema }],\n errors: [{ status: 403, description: 'Forbidden', schema: routeErrorSchema }],\n },\n POST: {\n summary: 'Share document',\n requestBody: { contentType: 'application/json', schema: documentShareCreateSchema },\n responses: [{ status: 201, description: 'Document shared', schema: shareMutationResponseSchema }],\n errors: [\n { status: 400, description: 'Validation failed', schema: routeErrorSchema },\n { status: 413, description: 'Request body exceeds the safe resource bound', schema: routeErrorSchema },\n ],\n },\n PUT: {\n summary: 'Update document share',\n requestBody: { contentType: 'application/json', schema: documentShareUpdateSchema },\n responses: [{ status: 200, description: 'Share updated', schema: shareMutationResponseSchema }],\n errors: [\n { status: 409, description: 'Optimistic lock conflict', schema: routeErrorSchema },\n { status: 413, description: 'Request body exceeds the safe resource bound', schema: routeErrorSchema },\n ],\n },\n DELETE: {\n summary: 'Remove document share',\n requestBody: { contentType: 'application/json', schema: shareDeleteSchema },\n responses: [{ status: 200, description: 'Share removed', schema: shareDeleteResponseSchema }],\n errors: [\n { status: 409, description: 'Optimistic lock conflict', schema: routeErrorSchema },\n { status: 413, description: 'Request body exceeds the safe resource bound', schema: routeErrorSchema },\n ],\n },\n },\n})\n\nexport default { GET, POST, PUT, DELETE }\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { randomUUID } from 'node:crypto'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { enforceCommandOptimisticLockWithGuards } from '@open-mercato/shared/lib/crud/optimistic-lock-command'\nimport { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { findOneWithDecryption, findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { DocumentShare } from '../../../data/entities'\nimport { documentShareCreateSchema, documentShareUpdateSchema } from '../../../data/validators'\nimport { DOCUMENTS_ENTITY_IDS, DOCUMENTS_MAX_LISTED_SHARES } from '../../../lib/constants'\nimport { sanitizeDocumentsDisplayLabel } from '../../../lib/displayLabels'\nimport { resolveUserLabels } from '../../../lib/userLabels'\nimport { resolveAuthPrincipalService } from '../../../lib/platformServices'\nimport type {\n ShareCreateCommandInput,\n ShareDeleteCommandInput,\n ShareUpdateCommandInput,\n ShareCommandResult,\n} from '../../../commands/shares'\nimport {\n attachDocumentsOperationMetadata,\n buildDocumentsCommandRuntimeContext,\n resolveDocumentsCommandBus,\n} from '../../_commands'\nimport {\n assertDocumentId,\n assertDocumentNotArchived,\n handleDocumentsRouteError,\n loadScopedShare,\n readBody,\n resolveActorUserId,\n resolveDocumentCapabilityProjection,\n resolveDocumentsContext,\n routeErrorSchema,\n runMutationGuardAfterSuccess,\n serializeShare,\n validateMutationGuard,\n withDocumentsContextErrors,\n} from '../../_shared'\n\ntype RouteContext = {\n params: Promise<{ id: string }> | { id: string }\n}\n\nconst shareDeleteSchema = z.object({\n id: z.string().uuid(),\n})\n\nconst shareListResponseSchema = z.object({\n items: z.array(z.object({\n id: z.string().uuid(),\n documentId: z.string().uuid(),\n principalType: z.enum(['user', 'role']),\n principalId: z.string().uuid(),\n permission: z.enum(['viewer', 'commenter', 'editor']),\n createdByUserId: z.string().uuid(),\n createdAt: z.string(),\n updatedAt: z.string(),\n principalLabel: z.string().nullable(),\n principalSecondary: z.string().nullable(),\n })),\n truncated: z.boolean(),\n})\n\nconst shareMutationResponseSchema = z.object({\n id: z.string().uuid(),\n updatedAt: z.string(),\n})\n\nconst shareDeleteResponseSchema = z.object({\n ok: z.boolean(),\n id: z.string().uuid(),\n updatedAt: z.string(),\n})\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['documents.share'] },\n POST: { requireAuth: true, requireFeatures: ['documents.share'] },\n PUT: { requireAuth: true, requireFeatures: ['documents.share'] },\n DELETE: { requireAuth: true, requireFeatures: ['documents.share'] },\n}\n\nasync function resolveId(context: RouteContext): Promise<string> {\n const params = await context.params\n return params.id\n}\n\nasync function assertCanShare(\n ctx: Awaited<ReturnType<typeof resolveDocumentsContext>>,\n documentId: string,\n): Promise<void> {\n const projection = await resolveDocumentCapabilityProjection(ctx, documentId)\n if (!projection.capabilities.canShare) {\n throw new CrudHttpError(403, { error: 'Forbidden' })\n }\n}\n\nasync function assertCanMutateShares(\n ctx: Awaited<ReturnType<typeof resolveDocumentsContext>>,\n documentId: string,\n): Promise<void> {\n await assertCanShare(ctx, documentId)\n await assertDocumentNotArchived(ctx, documentId)\n}\n\ntype PrincipalLabel = { label: string; secondary?: string | null }\n\nfunction cleanString(value: unknown): string | null {\n return sanitizeDocumentsDisplayLabel(value)\n}\n\nasync function resolvePrincipalLabels(\n container: Awaited<ReturnType<typeof resolveDocumentsContext>>['container'],\n scope: { tenantId: string; organizationId: string },\n shares: DocumentShare[],\n): Promise<Map<string, PrincipalLabel>> {\n const labels = new Map<string, PrincipalLabel>()\n const userIds = [...new Set(shares.filter((s) => s.principalType === 'user').map((s) => s.principalId))]\n const roleIds = [...new Set(shares.filter((s) => s.principalType === 'role').map((s) => s.principalId))]\n\n if (userIds.length > 0) {\n const userLabels = await resolveUserLabels(container, scope, userIds)\n for (const [userId, label] of userLabels.entries()) {\n labels.set(userId, label)\n }\n }\n\n if (roleIds.length > 0) {\n const roles = await resolveAuthPrincipalService(container)?.resolveLabels({\n type: 'role', ids: roleIds, scope,\n }) ?? []\n for (const role of roles) {\n const name = cleanString(role.label)\n if (name) labels.set(role.id, { label: name, secondary: null })\n }\n }\n\n return labels\n}\n\nexport async function GET(request: Request, context: RouteContext): Promise<Response> {\n try {\n const documentId = await resolveId(context)\n const ctx = await resolveDocumentsContext(request, ['documents.share'])\n assertDocumentId(documentId)\n await assertCanShare(ctx, documentId)\n const loadedShares = await findWithDecryption(\n ctx.em,\n DocumentShare,\n {\n documentId,\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId,\n deletedAt: null,\n },\n { orderBy: { createdAt: 'ASC', id: 'ASC' }, limit: DOCUMENTS_MAX_LISTED_SHARES + 1 },\n { tenantId: ctx.tenantId, organizationId: ctx.organizationId },\n )\n const truncated = loadedShares.length > DOCUMENTS_MAX_LISTED_SHARES\n const shares = loadedShares.slice(0, DOCUMENTS_MAX_LISTED_SHARES)\n const labels = await resolvePrincipalLabels(\n ctx.container,\n { tenantId: ctx.tenantId, organizationId: ctx.organizationId },\n shares,\n )\n const items = shares.map((share) => {\n const resolved = labels.get(share.principalId) ?? null\n return {\n ...serializeShare(share),\n principalLabel: resolved?.label ?? null,\n principalSecondary: resolved?.secondary ?? null,\n }\n })\n return NextResponse.json({ items, truncated })\n } catch (error) {\n return handleDocumentsRouteError(error, 'documents.shares.list')\n }\n}\n\nexport async function POST(request: Request, context: RouteContext): Promise<Response> {\n try {\n const documentId = await resolveId(context)\n const ctx = await resolveDocumentsContext(request, ['documents.share'])\n await assertCanMutateShares(ctx, documentId)\n const input = documentShareCreateSchema.parse(await readBody(request))\n const guardResourceId = `${documentId}:${input.principalType}:${input.principalId}`\n const guardResult = await validateMutationGuard(ctx, {\n resourceKind: DOCUMENTS_ENTITY_IDS.documentShare,\n resourceId: guardResourceId,\n operation: 'create',\n mutationPayload: input,\n mutationPayloadSchema: documentShareCreateSchema,\n })\n\n const existing = await findOneWithDecryption(\n ctx.em,\n DocumentShare,\n {\n documentId,\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId,\n principalType: input.principalType,\n principalId: input.principalId,\n },\n { filters: false, orderBy: { updatedAt: 'DESC' } },\n { tenantId: ctx.tenantId, organizationId: ctx.organizationId },\n )\n const commandInput: ShareCreateCommandInput = {\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId,\n documentId,\n shareId: existing?.id ?? randomUUID(),\n actorUserId: resolveActorUserId(ctx.auth),\n expectedUpdatedAt: existing?.updatedAt.toISOString() ?? null,\n share: input,\n }\n const execution = await resolveDocumentsCommandBus(ctx).execute<ShareCreateCommandInput, ShareCommandResult>(\n 'documents.share.create',\n { input: commandInput, ctx: buildDocumentsCommandRuntimeContext(ctx) },\n )\n await runMutationGuardAfterSuccess(ctx, guardResult, {\n resourceKind: DOCUMENTS_ENTITY_IDS.documentShare,\n resourceId: guardResourceId,\n operation: 'create',\n })\n\n return attachDocumentsOperationMetadata(\n NextResponse.json({ id: execution.result.id, updatedAt: execution.result.updatedAt }, { status: 201 }),\n execution.logEntry,\n { resourceKind: DOCUMENTS_ENTITY_IDS.documentShare, resourceId: execution.result.id },\n )\n } catch (error) {\n return handleDocumentsRouteError(error, 'documents.shares.create')\n }\n}\n\nexport async function PUT(request: Request, context: RouteContext): Promise<Response> {\n try {\n const documentId = await resolveId(context)\n const ctx = await resolveDocumentsContext(request, ['documents.share'])\n await assertCanMutateShares(ctx, documentId)\n const input = documentShareUpdateSchema.parse(await readBody(request))\n const share = await loadScopedShare(ctx, documentId, input.id)\n await enforceCommandOptimisticLockWithGuards(ctx.container, {\n resourceKind: DOCUMENTS_ENTITY_IDS.documentShare,\n resourceId: share.id,\n current: share.updatedAt,\n request,\n })\n const guardResult = await validateMutationGuard(ctx, {\n resourceKind: DOCUMENTS_ENTITY_IDS.documentShare,\n resourceId: share.id,\n operation: 'update',\n mutationPayload: input,\n mutationPayloadSchema: documentShareUpdateSchema,\n })\n\n const commandInput: ShareUpdateCommandInput = {\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId,\n documentId,\n actorUserId: resolveActorUserId(ctx.auth),\n expectedUpdatedAt: share.updatedAt.toISOString(),\n share: input,\n }\n const execution = await resolveDocumentsCommandBus(ctx).execute<ShareUpdateCommandInput, ShareCommandResult>(\n 'documents.share.update',\n { input: commandInput, ctx: buildDocumentsCommandRuntimeContext(ctx) },\n )\n await runMutationGuardAfterSuccess(ctx, guardResult, {\n resourceKind: DOCUMENTS_ENTITY_IDS.documentShare,\n resourceId: share.id,\n operation: 'update',\n })\n\n return attachDocumentsOperationMetadata(\n NextResponse.json({ id: execution.result.id, updatedAt: execution.result.updatedAt }),\n execution.logEntry,\n { resourceKind: DOCUMENTS_ENTITY_IDS.documentShare, resourceId: execution.result.id },\n )\n } catch (error) {\n return handleDocumentsRouteError(error, 'documents.shares.update')\n }\n}\n\nexport async function DELETE(request: Request, context: RouteContext): Promise<Response> {\n try {\n const documentId = await resolveId(context)\n const ctx = await resolveDocumentsContext(request, ['documents.share'])\n await assertCanMutateShares(ctx, documentId)\n const input = shareDeleteSchema.parse(await readBody(request))\n const share = await loadScopedShare(ctx, documentId, input.id)\n await enforceCommandOptimisticLockWithGuards(ctx.container, {\n resourceKind: DOCUMENTS_ENTITY_IDS.documentShare,\n resourceId: share.id,\n current: share.updatedAt,\n request,\n })\n const guardResult = await validateMutationGuard(ctx, {\n resourceKind: DOCUMENTS_ENTITY_IDS.documentShare,\n resourceId: share.id,\n operation: 'delete',\n })\n\n const commandInput: ShareDeleteCommandInput = {\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId,\n documentId,\n shareId: share.id,\n actorUserId: resolveActorUserId(ctx.auth),\n expectedUpdatedAt: share.updatedAt.toISOString(),\n }\n const execution = await resolveDocumentsCommandBus(ctx).execute<ShareDeleteCommandInput, ShareCommandResult>(\n 'documents.share.delete',\n { input: commandInput, ctx: buildDocumentsCommandRuntimeContext(ctx) },\n )\n await runMutationGuardAfterSuccess(ctx, guardResult, {\n resourceKind: DOCUMENTS_ENTITY_IDS.documentShare,\n resourceId: share.id,\n operation: 'delete',\n })\n\n return attachDocumentsOperationMetadata(\n NextResponse.json({ ok: true, id: execution.result.id, updatedAt: execution.result.updatedAt }),\n execution.logEntry,\n { resourceKind: DOCUMENTS_ENTITY_IDS.documentShare, resourceId: execution.result.id },\n )\n } catch (error) {\n return handleDocumentsRouteError(error, 'documents.shares.delete')\n }\n}\n\nexport const openApi: OpenApiRouteDoc = withDocumentsContextErrors({\n tag: 'Documents',\n summary: 'Document shares',\n pathParams: z.object({ id: z.string().uuid() }),\n methods: {\n GET: {\n summary: 'List document shares',\n responses: [{ status: 200, description: 'Document shares', schema: shareListResponseSchema }],\n errors: [{ status: 403, description: 'Forbidden', schema: routeErrorSchema }],\n },\n POST: {\n summary: 'Share document',\n requestBody: { contentType: 'application/json', schema: documentShareCreateSchema },\n responses: [{ status: 201, description: 'Document shared', schema: shareMutationResponseSchema }],\n errors: [\n { status: 400, description: 'Validation failed', schema: routeErrorSchema },\n { status: 413, description: 'Request body exceeds the safe resource bound', schema: routeErrorSchema },\n ],\n },\n PUT: {\n summary: 'Update document share',\n requestBody: { contentType: 'application/json', schema: documentShareUpdateSchema },\n responses: [{ status: 200, description: 'Share updated', schema: shareMutationResponseSchema }],\n errors: [\n { status: 409, description: 'Optimistic lock conflict', schema: routeErrorSchema },\n { status: 413, description: 'Request body exceeds the safe resource bound', schema: routeErrorSchema },\n ],\n },\n DELETE: {\n summary: 'Remove document share',\n requestBody: { contentType: 'application/json', schema: shareDeleteSchema },\n responses: [{ status: 200, description: 'Share removed', schema: shareDeleteResponseSchema }],\n errors: [\n { status: 409, description: 'Optimistic lock conflict', schema: routeErrorSchema },\n { status: 413, description: 'Request body exceeds the safe resource bound', schema: routeErrorSchema },\n ],\n },\n },\n})\n\nexport default { GET, POST, PUT, DELETE }\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -8,6 +8,7 @@ import { DOCUMENTS_VERSION_LIST_PAGE_SIZE } from "../../../lib/historyLimits.js"
8
8
  import { assertTier } from "../../../lib/permissions.js";
9
9
  import { resolveUserLabels } from "../../../lib/userLabels.js";
10
10
  import {
11
+ assertDocumentId,
11
12
  assertDocumentNotArchived,
12
13
  handleDocumentsRouteError,
13
14
  readBody,
@@ -71,6 +72,7 @@ async function GET(request, context) {
71
72
  try {
72
73
  const documentId = await resolveId(context);
73
74
  const ctx = await resolveDocumentsContext(request, ["documents.view"]);
75
+ assertDocumentId(documentId);
74
76
  await assertTier(ctx.em, documentId, ctx.auth, "viewer");
75
77
  const url = new URL(request.url);
76
78
  const query = versionListQuerySchema.parse(Object.fromEntries(url.searchParams.entries()));
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../src/modules/documents/api/%5Bid%5D/versions/route.ts"],
4
- "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { randomUUID } from 'node:crypto'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { findAndCountWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { DocumentVersion } from '../../../data/entities'\nimport { DOCUMENTS_ENTITY_IDS } from '../../../lib/constants'\nimport { DOCUMENTS_VERSION_LIST_PAGE_SIZE } from '../../../lib/historyLimits'\nimport { assertTier } from '../../../lib/permissions'\nimport { resolveUserLabels } from '../../../lib/userLabels'\nimport {\n assertDocumentNotArchived,\n handleDocumentsRouteError,\n readBody,\n resolveDocumentsContext,\n routeErrorSchema,\n runMutationGuardAfterSuccess,\n validateMutationGuard,\n withDocumentsContextErrors,\n} from '../../_shared'\nimport {\n attachDocumentsOperationMetadata,\n buildDocumentsCommandRuntimeContext,\n resolveDocumentsCommandBus,\n} from '../../_commands'\nimport type { CreateVersionCommandInput } from '../../../commands/versions'\nimport { documentVersionLabelSchema } from '../../../data/validators'\nimport { sanitizeDocumentVersionLabel } from '../../../lib/versionLabels'\n\ntype RouteContext = {\n params: Promise<{ id: string }> | { id: string }\n}\n\nexport const versionCreateSchema = z.object({\n label: documentVersionLabelSchema,\n})\n\nexport const versionListQuerySchema = z.object({\n page: z.coerce.number().int().min(1).max(10_000).default(1),\n pageSize: z.coerce.number().int().min(1).max(DOCUMENTS_VERSION_LIST_PAGE_SIZE)\n .default(DOCUMENTS_VERSION_LIST_PAGE_SIZE),\n})\n\nconst versionListItemSchema = z.object({\n id: z.string().uuid(),\n label: z.string().nullable(),\n createdByUserId: z.string().uuid(),\n createdByLabel: z.string().nullable(),\n createdAt: z.string(),\n})\n\nconst versionListResponseSchema = z.object({\n items: z.array(versionListItemSchema),\n page: z.number().int().positive(),\n pageSize: z.number().int().positive(),\n total: z.number().int().nonnegative(),\n totalPages: z.number().int().positive(),\n})\n\nconst versionCreateResponseSchema = z.object({\n id: z.string().uuid(),\n label: z.string().nullable(),\n createdByUserId: z.string().uuid(),\n createdAt: z.string(),\n})\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['documents.view'] },\n POST: { requireAuth: true, requireFeatures: ['documents.edit'] },\n}\n\nasync function resolveId(context: RouteContext): Promise<string> {\n const params = await context.params\n return params.id\n}\n\nfunction serializeVersion(version: DocumentVersion): z.infer<typeof versionCreateResponseSchema> {\n return {\n id: version.id,\n label: sanitizeDocumentVersionLabel(version.label),\n createdByUserId: version.createdByUserId,\n createdAt: version.createdAt.toISOString(),\n }\n}\n\nexport async function GET(request: Request, context: RouteContext): Promise<Response> {\n try {\n const documentId = await resolveId(context)\n const ctx = await resolveDocumentsContext(request, ['documents.view'])\n await assertTier(ctx.em, documentId, ctx.auth, 'viewer')\n const url = new URL(request.url)\n const query = versionListQuerySchema.parse(Object.fromEntries(url.searchParams.entries()))\n const [versions, total] = await findAndCountWithDecryption(\n ctx.em,\n DocumentVersion,\n {\n documentId,\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId,\n },\n {\n fields: ['id', 'label', 'createdByUserId', 'createdAt'],\n orderBy: { createdAt: 'DESC', id: 'ASC' },\n limit: query.pageSize,\n offset: (query.page - 1) * query.pageSize,\n },\n { tenantId: ctx.tenantId, organizationId: ctx.organizationId },\n )\n const labels = await resolveUserLabels(\n ctx.container,\n { tenantId: ctx.tenantId, organizationId: ctx.organizationId },\n versions.map((version) => version.createdByUserId),\n )\n\n return NextResponse.json({\n items: versions.map((version) => ({\n ...serializeVersion(version),\n createdByLabel: labels.get(version.createdByUserId)?.label ?? null,\n })),\n page: query.page,\n pageSize: query.pageSize,\n total,\n totalPages: Math.max(1, Math.ceil(total / query.pageSize)),\n })\n } catch (error) {\n return handleDocumentsRouteError(error, 'documents.versions.list')\n }\n}\n\nexport async function POST(request: Request, context: RouteContext): Promise<Response> {\n try {\n const documentId = await resolveId(context)\n const ctx = await resolveDocumentsContext(request, ['documents.edit'])\n await assertTier(ctx.em, documentId, ctx.auth, 'editor')\n await assertDocumentNotArchived(ctx, documentId)\n const input = versionCreateSchema.parse(await readBody(request))\n const guardResult = await validateMutationGuard(ctx, {\n resourceKind: DOCUMENTS_ENTITY_IDS.documentVersion,\n resourceId: documentId,\n operation: 'create',\n mutationPayload: input,\n mutationPayloadSchema: versionCreateSchema,\n })\n const commandInput: CreateVersionCommandInput = {\n ...input,\n documentId,\n versionId: randomUUID(),\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId,\n }\n const { result, logEntry } = await resolveDocumentsCommandBus(ctx).execute<\n CreateVersionCommandInput,\n z.infer<typeof versionCreateResponseSchema>\n >('documents.version.create', {\n input: commandInput,\n ctx: buildDocumentsCommandRuntimeContext(ctx),\n })\n await runMutationGuardAfterSuccess(ctx, guardResult, {\n resourceKind: DOCUMENTS_ENTITY_IDS.documentVersion,\n resourceId: documentId,\n operation: 'create',\n })\n\n const response = NextResponse.json({\n id: result.id,\n label: sanitizeDocumentVersionLabel(result.label),\n createdByUserId: result.createdByUserId,\n createdAt: result.createdAt,\n }, { status: 201 })\n return attachDocumentsOperationMetadata(response, logEntry, {\n resourceKind: DOCUMENTS_ENTITY_IDS.documentVersion,\n resourceId: result.id,\n })\n } catch (error) {\n return handleDocumentsRouteError(error, 'documents.versions.create')\n }\n}\n\nexport const openApi: OpenApiRouteDoc = withDocumentsContextErrors({\n tag: 'Documents',\n summary: 'Document versions',\n pathParams: z.object({ id: z.string().uuid() }),\n methods: {\n GET: {\n summary: 'List document versions',\n query: versionListQuerySchema,\n responses: [{ status: 200, description: 'Document version metadata', schema: versionListResponseSchema }],\n errors: [\n { status: 401, description: 'Unauthorized', schema: routeErrorSchema },\n { status: 403, description: 'Forbidden', schema: routeErrorSchema },\n ],\n },\n POST: {\n summary: 'Create document version snapshot',\n requestBody: { contentType: 'application/json', schema: versionCreateSchema },\n responses: [{ status: 201, description: 'Version snapshot created', schema: versionCreateResponseSchema }],\n errors: [\n { status: 400, description: 'Validation failed', schema: routeErrorSchema },\n { status: 401, description: 'Unauthorized', schema: routeErrorSchema },\n { status: 403, description: 'Forbidden', schema: routeErrorSchema },\n { status: 413, description: 'Request body, document content, or version history exceeds its safe storage bound', schema: routeErrorSchema },\n ],\n },\n },\n})\n\nexport default { GET, POST }\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { randomUUID } from 'node:crypto'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { findAndCountWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { DocumentVersion } from '../../../data/entities'\nimport { DOCUMENTS_ENTITY_IDS } from '../../../lib/constants'\nimport { DOCUMENTS_VERSION_LIST_PAGE_SIZE } from '../../../lib/historyLimits'\nimport { assertTier } from '../../../lib/permissions'\nimport { resolveUserLabels } from '../../../lib/userLabels'\nimport {\n assertDocumentId,\n assertDocumentNotArchived,\n handleDocumentsRouteError,\n readBody,\n resolveDocumentsContext,\n routeErrorSchema,\n runMutationGuardAfterSuccess,\n validateMutationGuard,\n withDocumentsContextErrors,\n} from '../../_shared'\nimport {\n attachDocumentsOperationMetadata,\n buildDocumentsCommandRuntimeContext,\n resolveDocumentsCommandBus,\n} from '../../_commands'\nimport type { CreateVersionCommandInput } from '../../../commands/versions'\nimport { documentVersionLabelSchema } from '../../../data/validators'\nimport { sanitizeDocumentVersionLabel } from '../../../lib/versionLabels'\n\ntype RouteContext = {\n params: Promise<{ id: string }> | { id: string }\n}\n\nexport const versionCreateSchema = z.object({\n label: documentVersionLabelSchema,\n})\n\nexport const versionListQuerySchema = z.object({\n page: z.coerce.number().int().min(1).max(10_000).default(1),\n pageSize: z.coerce.number().int().min(1).max(DOCUMENTS_VERSION_LIST_PAGE_SIZE)\n .default(DOCUMENTS_VERSION_LIST_PAGE_SIZE),\n})\n\nconst versionListItemSchema = z.object({\n id: z.string().uuid(),\n label: z.string().nullable(),\n createdByUserId: z.string().uuid(),\n createdByLabel: z.string().nullable(),\n createdAt: z.string(),\n})\n\nconst versionListResponseSchema = z.object({\n items: z.array(versionListItemSchema),\n page: z.number().int().positive(),\n pageSize: z.number().int().positive(),\n total: z.number().int().nonnegative(),\n totalPages: z.number().int().positive(),\n})\n\nconst versionCreateResponseSchema = z.object({\n id: z.string().uuid(),\n label: z.string().nullable(),\n createdByUserId: z.string().uuid(),\n createdAt: z.string(),\n})\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['documents.view'] },\n POST: { requireAuth: true, requireFeatures: ['documents.edit'] },\n}\n\nasync function resolveId(context: RouteContext): Promise<string> {\n const params = await context.params\n return params.id\n}\n\nfunction serializeVersion(version: DocumentVersion): z.infer<typeof versionCreateResponseSchema> {\n return {\n id: version.id,\n label: sanitizeDocumentVersionLabel(version.label),\n createdByUserId: version.createdByUserId,\n createdAt: version.createdAt.toISOString(),\n }\n}\n\nexport async function GET(request: Request, context: RouteContext): Promise<Response> {\n try {\n const documentId = await resolveId(context)\n const ctx = await resolveDocumentsContext(request, ['documents.view'])\n assertDocumentId(documentId)\n await assertTier(ctx.em, documentId, ctx.auth, 'viewer')\n const url = new URL(request.url)\n const query = versionListQuerySchema.parse(Object.fromEntries(url.searchParams.entries()))\n const [versions, total] = await findAndCountWithDecryption(\n ctx.em,\n DocumentVersion,\n {\n documentId,\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId,\n },\n {\n fields: ['id', 'label', 'createdByUserId', 'createdAt'],\n orderBy: { createdAt: 'DESC', id: 'ASC' },\n limit: query.pageSize,\n offset: (query.page - 1) * query.pageSize,\n },\n { tenantId: ctx.tenantId, organizationId: ctx.organizationId },\n )\n const labels = await resolveUserLabels(\n ctx.container,\n { tenantId: ctx.tenantId, organizationId: ctx.organizationId },\n versions.map((version) => version.createdByUserId),\n )\n\n return NextResponse.json({\n items: versions.map((version) => ({\n ...serializeVersion(version),\n createdByLabel: labels.get(version.createdByUserId)?.label ?? null,\n })),\n page: query.page,\n pageSize: query.pageSize,\n total,\n totalPages: Math.max(1, Math.ceil(total / query.pageSize)),\n })\n } catch (error) {\n return handleDocumentsRouteError(error, 'documents.versions.list')\n }\n}\n\nexport async function POST(request: Request, context: RouteContext): Promise<Response> {\n try {\n const documentId = await resolveId(context)\n const ctx = await resolveDocumentsContext(request, ['documents.edit'])\n await assertTier(ctx.em, documentId, ctx.auth, 'editor')\n await assertDocumentNotArchived(ctx, documentId)\n const input = versionCreateSchema.parse(await readBody(request))\n const guardResult = await validateMutationGuard(ctx, {\n resourceKind: DOCUMENTS_ENTITY_IDS.documentVersion,\n resourceId: documentId,\n operation: 'create',\n mutationPayload: input,\n mutationPayloadSchema: versionCreateSchema,\n })\n const commandInput: CreateVersionCommandInput = {\n ...input,\n documentId,\n versionId: randomUUID(),\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId,\n }\n const { result, logEntry } = await resolveDocumentsCommandBus(ctx).execute<\n CreateVersionCommandInput,\n z.infer<typeof versionCreateResponseSchema>\n >('documents.version.create', {\n input: commandInput,\n ctx: buildDocumentsCommandRuntimeContext(ctx),\n })\n await runMutationGuardAfterSuccess(ctx, guardResult, {\n resourceKind: DOCUMENTS_ENTITY_IDS.documentVersion,\n resourceId: documentId,\n operation: 'create',\n })\n\n const response = NextResponse.json({\n id: result.id,\n label: sanitizeDocumentVersionLabel(result.label),\n createdByUserId: result.createdByUserId,\n createdAt: result.createdAt,\n }, { status: 201 })\n return attachDocumentsOperationMetadata(response, logEntry, {\n resourceKind: DOCUMENTS_ENTITY_IDS.documentVersion,\n resourceId: result.id,\n })\n } catch (error) {\n return handleDocumentsRouteError(error, 'documents.versions.create')\n }\n}\n\nexport const openApi: OpenApiRouteDoc = withDocumentsContextErrors({\n tag: 'Documents',\n summary: 'Document versions',\n pathParams: z.object({ id: z.string().uuid() }),\n methods: {\n GET: {\n summary: 'List document versions',\n query: versionListQuerySchema,\n responses: [{ status: 200, description: 'Document version metadata', schema: versionListResponseSchema }],\n errors: [\n { status: 401, description: 'Unauthorized', schema: routeErrorSchema },\n { status: 403, description: 'Forbidden', schema: routeErrorSchema },\n ],\n },\n POST: {\n summary: 'Create document version snapshot',\n requestBody: { contentType: 'application/json', schema: versionCreateSchema },\n responses: [{ status: 201, description: 'Version snapshot created', schema: versionCreateResponseSchema }],\n errors: [\n { status: 400, description: 'Validation failed', schema: routeErrorSchema },\n { status: 401, description: 'Unauthorized', schema: routeErrorSchema },\n { status: 403, description: 'Forbidden', schema: routeErrorSchema },\n { status: 413, description: 'Request body, document content, or version history exceeds its safe storage bound', schema: routeErrorSchema },\n ],\n },\n },\n})\n\nexport default { GET, POST }\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -350,6 +350,12 @@ async function handleDocumentsRouteError(error, label) {
350
350
  { status: 500 }
351
351
  );
352
352
  }
353
+ const documentIdSchema = z.string().uuid();
354
+ function assertDocumentId(id) {
355
+ if (!documentIdSchema.safeParse(id).success) {
356
+ throw new CrudHttpError(404, { error: "documents.documents.notFound" });
357
+ }
358
+ }
353
359
  async function loadScopedDocument(ctx, id) {
354
360
  const document = await findOneWithDecryption(ctx.em, Document, {
355
361
  id,
@@ -458,6 +464,7 @@ export {
458
464
  ORGANIZATION_SELECTION_INVALID_DESCRIPTION,
459
465
  ORGANIZATION_SELECTION_INVALID_ERROR_CODE,
460
466
  ROUTE_ERROR_TRANSLATIONS,
467
+ assertDocumentId,
461
468
  assertDocumentNotArchived,
462
469
  deriveCapabilitiesForContext,
463
470
  handleDocumentsRouteError,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/documents/api/_shared.ts"],
4
- "sourcesContent": ["import { resolveActorUserId } from '../lib/actor'\nexport { resolveActorUserId } from '../lib/actor'\nimport { NextResponse } from 'next/server'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport type { AwilixContainer } from 'awilix'\nimport { z } from 'zod'\nimport { getAuthFromRequest, type AuthContext } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { hasAllFeatures } from '@open-mercato/shared/lib/auth/featureMatch'\nimport {\n ORGANIZATION_SCOPE_REQUIRED_ERROR_CODE,\n resolveActiveOrganizationId,\n} from '@open-mercato/shared/lib/auth/organizationScope'\nimport { CrudHttpError, isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { getCommandInterceptorHttpRejection } from '@open-mercato/shared/lib/commands/errors'\nimport {\n runRouteMutationGuards,\n type RouteMutationGuardPassed,\n} from '@open-mercato/shared/lib/crud/route-mutation-guard'\nimport { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\nimport type { OpenApiHttpMethod, OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport {\n Document,\n DocumentContent,\n DocumentFolder,\n DocumentShare,\n} from '../data/entities'\nimport {\n deriveDocumentCapabilities,\n type DocumentCapabilities,\n} from '../lib/capabilities'\nimport {\n resolveActiveSubjectRoleIds,\n resolvePermission,\n type DocumentTier,\n} from '../lib/permissions'\nimport { hasResolvedDocumentsOrganizationAccess } from '../lib/organizationAccess'\nimport { containsCanonicalUuid } from '../lib/displayLabels'\nimport {\n DOCUMENTS_JSON_BODY_LIMITS,\n readBoundedJsonBody,\n} from '../lib/requestBody'\nimport { resolveOrganizationScopeService } from '../lib/platformServices'\n\nconst logger = createLogger('documents').child({ component: 'api' })\n\nexport type DocumentsAuthContext = NonNullable<AuthContext> & {\n features: string[]\n roleIds: string[]\n resolvedRoleIds: string[]\n organizationId: string\n}\n\nexport type DocumentsRouteContext = {\n container: AwilixContainer\n em: EntityManager\n auth: DocumentsAuthContext\n tenantId: string\n organizationId: string\n request: Request\n}\n\ntype RbacServiceLike = {\n loadAcl: (\n userId: string,\n scope: { tenantId: string | null; organizationId: string | null },\n ) => Promise<{\n isSuperAdmin: boolean\n features: string[]\n organizations: string[] | null\n }>\n}\n\nexport type MutationGuardResult = RouteMutationGuardPassed\n\nexport type DocumentCapabilityProjection = {\n relationshipTier: DocumentTier | null\n capabilities: DocumentCapabilities\n}\n\n/**\n * Every documents error body carries a translated `error` message. Some also\n * carry a machine-readable `code` discriminator: without it a client cannot\n * tell an unresolved organization scope (recoverable \u2014 pick an organization)\n * apart from an ordinary validation rejection, since both are a 400.\n */\nexport const routeErrorSchema = z.object({ error: z.string(), code: z.string().optional() })\n\nexport const ORGANIZATION_SCOPE_REQUIRED_DESCRIPTION =\n `Organization scope could not be resolved (\\`code: ${ORGANIZATION_SCOPE_REQUIRED_ERROR_CODE}\\`)`\n\nexport const ORGANIZATION_SELECTION_INVALID_ERROR_CODE = 'organization_selection_invalid'\n\nexport const ORGANIZATION_SELECTION_INVALID_DESCRIPTION =\n `The selected organization no longer resolves (\\`code: ${ORGANIZATION_SELECTION_INVALID_ERROR_CODE}\\`)`\n\n/**\n * `resolveDocumentsContext` answers 400 with the\n * `organization_scope_required` discriminator whenever an unrestricted caller\n * (a super-admin viewing a foreign tenant with \"all organizations\" selected)\n * has no resolvable organization. Every route that resolves a documents\n * context can therefore return it, including methods whose own contract\n * declares no 400 at all \u2014 so the contract has to say so, or generated clients\n * and the API reference cannot model the recovery path.\n *\n * Responses are keyed by status when the document is built, so a route that\n * already documents a 400 must merge rather than append a second entry.\n */\nexport function withDocumentsContextErrors(doc: OpenApiRouteDoc): OpenApiRouteDoc {\n const methods: OpenApiRouteDoc['methods'] = {}\n for (const method of Object.keys(doc.methods) as OpenApiHttpMethod[]) {\n const methodDoc = doc.methods[method]\n if (!methodDoc) continue\n const merge = (\n errors: NonNullable<typeof methodDoc.errors>,\n status: number,\n description: string,\n ): NonNullable<typeof methodDoc.errors> => (\n errors.some((error) => error.status === status)\n ? errors.map((error) => (error.status === status\n ? {\n ...error,\n description: error.description\n ? `${error.description}, or ${description}`\n : description,\n schema: error.schema ?? routeErrorSchema,\n }\n : error))\n : [...errors, { status, description, schema: routeErrorSchema }]\n )\n // `resolveDocumentsContext` answers 400 `organization_scope_required` and\n // 422 `organization_selection_invalid` from the same call, so both ride on\n // every route that resolves a documents context.\n let errors = merge(methodDoc.errors ?? [], 400, ORGANIZATION_SCOPE_REQUIRED_DESCRIPTION)\n errors = merge(errors, 422, ORGANIZATION_SELECTION_INVALID_DESCRIPTION)\n methods[method] = { ...methodDoc, errors }\n }\n return { ...doc, methods }\n}\n\nexport const ROUTE_ERROR_TRANSLATIONS: Record<string, { key: string; fallback: string }> = {\n Unauthorized: { key: 'api.errors.unauthorized', fallback: 'Unauthorized' },\n Forbidden: { key: 'api.errors.forbidden', fallback: 'Forbidden' },\n 'Organization context is required': {\n key: 'documents.errors.organizationRequired',\n fallback: 'Organization context is required',\n },\n 'Validation failed': { key: 'api.errors.invalidPayload', fallback: 'Invalid payload.' },\n 'Internal server error': { key: 'api.errors.internal', fallback: 'Internal server error' },\n 'Record changed by another user': {\n key: 'documents.errors.recordChanged',\n fallback: 'Record changed by another user',\n },\n 'Document not found': { key: 'documents.documents.notFound', fallback: 'Document not found.' },\n 'documents.content.notFound': {\n key: 'documents.content.notFound',\n fallback: 'Document content not found.',\n },\n 'documents.versions.notFound': {\n key: 'documents.versions.notFound',\n fallback: 'Version not found.',\n },\n 'documents.folders.error.invalidPlacement': {\n key: 'documents.folders.error.invalidPlacement',\n fallback: 'This folder cannot be placed there.',\n },\n 'Folder not found': { key: 'documents.folders.notFound', fallback: 'Folder not found.' },\n 'Share not found': { key: 'documents.share.notFound', fallback: 'Share not found.' },\n 'Comment not found': { key: 'documents.comments.notFound', fallback: 'Comment not found.' },\n 'Document comment limit reached': {\n key: 'documents.comments.limitExceeded',\n fallback: 'This document has reached its comment limit.',\n },\n 'Document version history storage limit exceeded': {\n key: 'documents.versions.quotaExceeded',\n fallback: 'Version history could not retain this snapshot within the document storage limit.',\n },\n 'documents.errors.requestBodyTooLarge': {\n key: 'documents.errors.requestBodyTooLarge',\n fallback: 'Request body is too large.',\n },\n 'Share principal not found in this organization': {\n key: 'documents.share.principalNotFound',\n fallback: 'Share principal not found in this organization',\n },\n 'Private attachment partition is not configured': {\n key: 'documents.attachments.partitionUnavailable',\n fallback: 'Private attachment partition is not configured',\n },\n 'Expected multipart/form-data': {\n key: 'documents.attachments.multipartRequired',\n fallback: 'Expected multipart/form-data',\n },\n 'File is required': { key: 'documents.attachments.fileRequired', fallback: 'File is required' },\n 'Attachment exceeds the maximum upload size.': {\n key: 'documents.attachments.tooLarge',\n fallback: 'Attachment exceeds the maximum upload size.',\n },\n 'Executable file types are not allowed as attachments.': {\n key: 'documents.attachments.executableBlocked',\n fallback: 'Executable file types are not allowed as attachments.',\n },\n 'Active content uploads are not allowed.': {\n key: 'documents.attachments.activeContentBlocked',\n fallback: 'Active content uploads are not allowed.',\n },\n 'Attachment storage quota exceeded for this tenant.': {\n key: 'documents.attachments.quotaExceeded',\n fallback: 'Attachment storage quota exceeded for this tenant.',\n },\n 'Attachment not found': { key: 'documents.attachments.notFound', fallback: 'Attachment not found' },\n 'Partition misconfigured': {\n key: 'documents.attachments.partitionMisconfigured',\n fallback: 'Attachment partition is misconfigured',\n },\n 'File not available': {\n key: 'documents.attachments.fileUnavailable',\n fallback: 'File not available',\n },\n 'Unsupported format': { key: 'documents.export.unsupportedFormat', fallback: 'Unsupported format' },\n 'documents.export.runtimeUnavailable': {\n key: 'documents.export.runtimeUnavailable',\n fallback: 'PDF export is temporarily unavailable.',\n },\n 'documents.errors.organizationSelectionInvalid': {\n key: 'documents.errors.organizationSelectionInvalid',\n fallback: 'Your selected organization is no longer available. Please re-select an organization and try again.',\n },\n}\n\nconst ROUTE_ERROR_KEY_FALLBACKS = Object.fromEntries(\n Object.values(ROUTE_ERROR_TRANSLATIONS).map(({ key, fallback }) => [key, fallback]),\n) as Record<string, string>\n\nfunction normalizeStrings(values: unknown): string[] {\n if (!Array.isArray(values)) return []\n return Array.from(\n new Set(\n values\n .map((value) => (typeof value === 'string' ? value.trim() : ''))\n .filter((value) => value.length > 0),\n ),\n )\n}\n\nfunction resolveSelectedOrganization(\n auth: NonNullable<AuthContext>,\n scope: { selectedId?: string | null; filterIds?: string[] | null; allowedIds?: string[] | null; tenantId?: string | null } | null,\n): string | null {\n // Every document row carries one organization, so there is no \"all\n // organizations\" view of this module. When the super-admin switcher clears\n // `auth.orgId`, keep the operator on their own organization (the shared\n // helper refuses the fallback once the tenant override moved them elsewhere)\n // instead of failing every request.\n const activeOrganizationId = resolveActiveOrganizationId(auth)\n const allowed = scope?.filterIds ?? scope?.allowedIds ?? (activeOrganizationId ? [activeOrganizationId] : null)\n const candidates = [\n scope?.selectedId ?? null,\n activeOrganizationId,\n Array.isArray(allowed) && allowed.length > 0 ? allowed[0] : null,\n ]\n for (const candidate of candidates) {\n if (!candidate) continue\n if (!Array.isArray(allowed) || allowed.includes(candidate)) return candidate\n }\n return null\n}\n\nfunction readAuthScopeId(auth: NonNullable<AuthContext>, key: 'actorTenantId' | 'actorOrgId'): string | null {\n const value = auth[key]\n if (typeof value === 'string' && value.trim().length > 0) return value.trim()\n return null\n}\n\nexport function hasDocumentsFeature(auth: DocumentsAuthContext, feature: string): boolean {\n if (auth.isSuperAdmin === true) return true\n return hasAllFeatures([feature], auth.features)\n}\n\nexport function hasAnyDocumentsFeature(auth: DocumentsAuthContext, features: string[]): boolean {\n if (auth.isSuperAdmin === true) return true\n return features.some((feature) => hasAllFeatures([feature], auth.features))\n}\n\nexport function deriveCapabilitiesForContext(\n ctx: DocumentsRouteContext,\n relationshipTier: DocumentTier | null,\n options: { archived?: boolean } = {},\n): DocumentCapabilities {\n return deriveDocumentCapabilities({\n relationshipTier,\n managerOverride: hasDocumentsFeature(ctx.auth, 'documents.manage'),\n archived: options.archived === true,\n userFeatures: ctx.auth.features,\n })\n}\n\nexport async function resolveDocumentCapabilityProjection(\n ctx: DocumentsRouteContext,\n documentId: string,\n options: { archived?: boolean } = {},\n): Promise<DocumentCapabilityProjection> {\n const relationshipTier = await resolvePermission(ctx.em, documentId, ctx.auth)\n return {\n relationshipTier,\n capabilities: deriveCapabilitiesForContext(ctx, relationshipTier, options),\n }\n}\n\nexport async function loadDocumentArchivedState(\n ctx: DocumentsRouteContext,\n documentId: string,\n): Promise<{ archivedAt: Date | null }> {\n const document = await findOneWithDecryption(\n ctx.em,\n Document,\n {\n id: documentId,\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId,\n deletedAt: null,\n },\n { fields: ['id', 'archivedAt'] },\n { tenantId: ctx.tenantId, organizationId: ctx.organizationId },\n )\n if (!document) throw new CrudHttpError(404, { error: 'documents.documents.notFound' })\n return { archivedAt: document.archivedAt ?? null }\n}\n\nexport async function assertDocumentNotArchived(\n ctx: DocumentsRouteContext,\n documentId: string,\n): Promise<void> {\n const state = await loadDocumentArchivedState(ctx, documentId)\n if (state.archivedAt !== null) {\n throw new CrudHttpError(403, { error: 'documents.errors.documentArchived' })\n }\n}\n\nexport async function resolveDocumentsContext(\n request: Request,\n requiredFeatures: string[],\n): Promise<DocumentsRouteContext> {\n const container = await createRequestContainer()\n const em = container.resolve('em') as EntityManager\n const auth = await getAuthFromRequest(request)\n if (!auth || !auth.tenantId) {\n throw new CrudHttpError(401, { error: 'api.errors.unauthorized' })\n }\n\n const authenticatedActorUserId = resolveActorUserId(auth)\n const actorTenantId = readAuthScopeId(auth, 'actorTenantId') ?? auth.tenantId\n const actorOrganizationId = readAuthScopeId(auth, 'actorOrgId') ?? auth.orgId\n const rbacService = container.resolve('rbacService') as RbacServiceLike\n const actorAcl = await rbacService.loadAcl(auth.sub, {\n tenantId: actorTenantId,\n organizationId: actorOrganizationId,\n })\n // Organization scope resolution historically accepts the authenticated\n // superadmin bit to support tenant/org switching. Replace that potentially\n // stale token bit with the live RBAC decision before asking the shared scope\n // resolver to expand parent grants into descendant organizations.\n const scopeAuth = {\n ...auth,\n // API-key authentication validates both keyId and any backing user. Keep\n // the prefixed subject for ACL while projecting its UUID domain actor for\n // ownership/share/audit helpers. An unbound key uses its own key UUID.\n userId: authenticatedActorUserId,\n isSuperAdmin: actorAcl.isSuperAdmin === true,\n }\n const organizationScopeService = resolveOrganizationScopeService(container)\n if (!organizationScopeService) {\n throw new CrudHttpError(403, { error: 'api.errors.forbidden' })\n }\n const scope = await organizationScopeService.resolveForRequest({ auth: scopeAuth, request })\n // The caller explicitly selected an organization that no longer resolves to a\n // real, accessible org (stale selected-org cookie after a delete or an access\n // loss). The scope resolver falls back to another allowed org, so continuing\n // would silently read and write against an organization the caller never\n // chose. Fail loud on every documents operation instead \u2014 the same contract\n // `makeCrudRoute` enforces via `rejectInvalidOrgSelection` (#3936).\n if (scope?.selectionRejected) {\n throw new CrudHttpError(422, {\n error: 'documents.errors.organizationSelectionInvalid',\n code: ORGANIZATION_SELECTION_INVALID_ERROR_CODE,\n })\n }\n const tenantId = scope?.tenantId ?? auth.tenantId\n const organizationId = resolveSelectedOrganization(scopeAuth, scope)\n if (!tenantId) {\n throw new CrudHttpError(400, { error: 'documents.errors.organizationRequired' })\n }\n if (!organizationId) {\n // An unrestricted caller with no resolvable organization (a super-admin\n // viewing a foreign tenant with \"all organizations\" selected) is a 400, not\n // a 401 (which `apiFetch` would turn into a refresh loop) and not a 403 (the\n // caller is allowed in; they only need to pick an organization). A caller\n // whose grants expand to no organization at all stays a 403.\n const unrestricted = (scope?.filterIds ?? scope?.allowedIds ?? null) === null\n if (unrestricted) {\n throw new CrudHttpError(400, {\n error: 'documents.errors.organizationRequired',\n code: ORGANIZATION_SCOPE_REQUIRED_ERROR_CODE,\n })\n }\n throw new CrudHttpError(403, { error: 'api.errors.forbidden' })\n }\n\n const acl = await rbacService.loadAcl(auth.sub, { tenantId, organizationId })\n if (!hasResolvedDocumentsOrganizationAccess(acl, organizationId, scope)) {\n throw new CrudHttpError(403, { error: 'api.errors.forbidden' })\n }\n const roleIds = await resolveActiveSubjectRoleIds(\n container,\n { tenantId, organizationId },\n auth.sub,\n )\n const documentsAuth: DocumentsAuthContext = {\n ...scopeAuth,\n tenantId,\n orgId: organizationId,\n organizationId,\n roleIds,\n resolvedRoleIds: roleIds,\n // JWT/trusted-request claims are only authentication hints. The current\n // RBAC projection is authoritative for features and superadmin status so\n // a revoked grant cannot survive in a long-lived token.\n features: acl.isSuperAdmin === true ? ['*'] : normalizeStrings(acl.features),\n isSuperAdmin: acl.isSuperAdmin === true,\n }\n\n if (!hasAnyDocumentsFeature(documentsAuth, requiredFeatures)) {\n throw new CrudHttpError(403, { error: 'api.errors.forbidden' })\n }\n\n return {\n container,\n em,\n auth: documentsAuth,\n tenantId,\n organizationId,\n request,\n }\n}\n\nexport async function readBody(\n request: Request,\n maxBytes = DOCUMENTS_JSON_BODY_LIMITS.standard,\n): Promise<Record<string, unknown>> {\n return (await readBoundedJsonBody<Record<string, unknown>>(request, maxBytes, {})) ?? {}\n}\n\nasync function localizeRouteErrorBody(body: Record<string, unknown>): Promise<Record<string, unknown>> {\n const rawError = typeof body.error === 'string' ? body.error : null\n if (!rawError) return body\n\n if (containsCanonicalUuid(rawError)) {\n const { key, fallback } = ROUTE_ERROR_TRANSLATIONS['Internal server error']!\n try {\n const { translate } = await resolveTranslations()\n return { ...body, error: translate(key, fallback) }\n } catch {\n return { ...body, error: fallback }\n }\n }\n\n const literal = ROUTE_ERROR_TRANSLATIONS[rawError]\n const key = literal?.key ?? rawError\n const fallback = literal?.fallback ?? ROUTE_ERROR_KEY_FALLBACKS[key]\n // Attachment failures reach these routes as the attachments module's own\n // catalogued keys. The locale dictionary is merged across modules, so they\n // resolve here; without this prefix the raw key would reach the client.\n if (\n !key.startsWith('documents.')\n && !key.startsWith('api.')\n && !key.startsWith('attachments.')\n ) return body\n\n try {\n const { translate } = await resolveTranslations()\n return { ...body, error: translate(key, fallback) }\n } catch {\n return { ...body, error: fallback ?? key }\n }\n}\n\nexport async function handleDocumentsRouteError(error: unknown, label: string): Promise<Response> {\n if (isCrudHttpError(error)) {\n const body = error.body && typeof error.body === 'object' && !Array.isArray(error.body)\n ? error.body as Record<string, unknown>\n : { error: 'api.errors.internal' }\n return NextResponse.json(await localizeRouteErrorBody(body), { status: error.status })\n }\n const interceptorRejection = getCommandInterceptorHttpRejection(error)\n if (interceptorRejection) {\n return NextResponse.json(interceptorRejection.body, { status: interceptorRejection.status })\n }\n if (error instanceof z.ZodError) {\n return NextResponse.json(\n await localizeRouteErrorBody({ error: 'api.errors.invalidPayload', details: error.issues }),\n { status: 400 },\n )\n }\n logger.error(`${label} failed`, { err: error })\n return NextResponse.json(\n await localizeRouteErrorBody({ error: 'api.errors.internal' }),\n { status: 500 },\n )\n}\n\nexport async function loadScopedDocument(ctx: DocumentsRouteContext, id: string): Promise<Document> {\n const document = await findOneWithDecryption(ctx.em, Document, {\n id,\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId,\n deletedAt: null,\n }, undefined, { tenantId: ctx.tenantId, organizationId: ctx.organizationId })\n if (!document) throw new CrudHttpError(404, { error: 'documents.documents.notFound' })\n return document\n}\n\n/**\n * Only the owner (or a documents manager) may learn that a document was\n * removed, e.g. after undoing its creation; everyone else keeps the\n * existence-hiding 403 so deleted ids stay indistinguishable from foreign ones.\n */\nexport async function isRemovedDocumentVisibleToActor(ctx: DocumentsRouteContext, id: string): Promise<boolean> {\n const document = await findOneWithDecryption(ctx.em, Document, {\n id,\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId,\n deletedAt: { $ne: null },\n }, { fields: ['id', 'ownerUserId'] }, { tenantId: ctx.tenantId, organizationId: ctx.organizationId })\n if (!document) return false\n if (hasDocumentsFeature(ctx.auth, 'documents.manage')) return true\n return document.ownerUserId === resolveActorUserId(ctx.auth)\n}\n\nexport async function loadScopedShare(\n ctx: DocumentsRouteContext,\n documentId: string,\n shareId: string,\n): Promise<DocumentShare> {\n const share = await findOneWithDecryption(ctx.em, DocumentShare, {\n id: shareId,\n documentId,\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId,\n deletedAt: null,\n }, undefined, { tenantId: ctx.tenantId, organizationId: ctx.organizationId })\n if (!share) throw new CrudHttpError(404, { error: 'documents.share.notFound' })\n return share\n}\n\nexport function serializeDocument(document: Document): Record<string, unknown> {\n return {\n id: document.id,\n title: document.title,\n folderId: document.folderId ?? null,\n ownerUserId: document.ownerUserId,\n createdByUserId: document.createdByUserId,\n isActive: document.isActive,\n createdAt: document.createdAt.toISOString(),\n updatedAt: document.updatedAt.toISOString(),\n }\n}\n\nexport function serializeContent(content: DocumentContent | null): Record<string, unknown> {\n return {\n contentHtml: content?.contentHtml ?? '',\n contentText: content?.contentText ?? '',\n updatedAt: content?.updatedAt ? content.updatedAt.toISOString() : null,\n }\n}\n\nexport function serializeFolder(folder: DocumentFolder): Record<string, unknown> {\n return {\n id: folder.id,\n name: folder.name,\n parentFolderId: folder.parentFolderId ?? null,\n ownerUserId: folder.ownerUserId,\n createdAt: folder.createdAt.toISOString(),\n updatedAt: folder.updatedAt.toISOString(),\n }\n}\n\nexport function serializeShare(share: DocumentShare): Record<string, unknown> {\n return {\n id: share.id,\n documentId: share.documentId,\n principalType: share.principalType,\n principalId: share.principalId,\n permission: share.permission,\n createdByUserId: share.createdByUserId,\n createdAt: share.createdAt.toISOString(),\n updatedAt: share.updatedAt.toISOString(),\n }\n}\n\nexport async function validateMutationGuard(\n ctx: DocumentsRouteContext,\n input: {\n resourceKind: string\n resourceId: string\n operation: 'create' | 'update' | 'delete' | 'custom'\n mutationPayload?: Record<string, unknown> | null\n /**\n * Re-parses a guard's `modifiedPayload` before it reaches the command. A\n * registry guard may rewrite the payload, and the route's own schema is the\n * only thing that still guarantees the command receives a shape it can trust.\n */\n mutationPayloadSchema?: { parse: (value: unknown) => Record<string, unknown> }\n },\n): Promise<MutationGuardResult> {\n // `runRouteMutationGuards` runs EVERY registered guard plus the bridged legacy\n // DI service. The deprecated `validateCrudMutationGuard` /\n // `runCrudMutationGuardAfterSuccess` pair used here before resolved only the\n // single DI-registered guard service, so every guard a module contributed\n // through the registry \u2014 along with its payload transformation and its\n // after-success callback \u2014 was silently skipped on Documents writes.\n const guardResult = await runRouteMutationGuards({\n container: ctx.container,\n req: ctx.request,\n auth: {\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId,\n userId: resolveActorUserId(ctx.auth),\n userFeatures: ctx.auth.features,\n },\n input: {\n resourceKind: input.resourceKind,\n resourceId: input.resourceId,\n operation: input.operation,\n mutationPayload: input.mutationPayload ?? null,\n },\n })\n if (!guardResult.ok) {\n throw new CrudHttpError(guardResult.errorStatus, guardResult.errorBody)\n }\n if (guardResult.modifiedPayload && input.mutationPayload) {\n const transformed = input.mutationPayloadSchema\n ? input.mutationPayloadSchema.parse(guardResult.modifiedPayload)\n : guardResult.modifiedPayload\n // Routes read the object they handed in, so the transformation has to land\n // there rather than only on the guard result.\n for (const key of Object.keys(input.mutationPayload)) delete input.mutationPayload[key]\n Object.assign(input.mutationPayload, transformed)\n }\n return guardResult\n}\n\nexport async function runMutationGuardAfterSuccess(\n _ctx: DocumentsRouteContext,\n guardResult: MutationGuardResult,\n _input: {\n resourceKind: string\n resourceId: string\n operation: 'create' | 'update' | 'delete' | 'custom'\n },\n): Promise<void> {\n await guardResult.runAfterSuccess()\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { resolveActorUserId } from '../lib/actor'\nexport { resolveActorUserId } from '../lib/actor'\nimport { NextResponse } from 'next/server'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport type { AwilixContainer } from 'awilix'\nimport { z } from 'zod'\nimport { getAuthFromRequest, type AuthContext } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { hasAllFeatures } from '@open-mercato/shared/lib/auth/featureMatch'\nimport {\n ORGANIZATION_SCOPE_REQUIRED_ERROR_CODE,\n resolveActiveOrganizationId,\n} from '@open-mercato/shared/lib/auth/organizationScope'\nimport { CrudHttpError, isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { getCommandInterceptorHttpRejection } from '@open-mercato/shared/lib/commands/errors'\nimport {\n runRouteMutationGuards,\n type RouteMutationGuardPassed,\n} from '@open-mercato/shared/lib/crud/route-mutation-guard'\nimport { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\nimport type { OpenApiHttpMethod, OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport {\n Document,\n DocumentContent,\n DocumentFolder,\n DocumentShare,\n} from '../data/entities'\nimport {\n deriveDocumentCapabilities,\n type DocumentCapabilities,\n} from '../lib/capabilities'\nimport {\n resolveActiveSubjectRoleIds,\n resolvePermission,\n type DocumentTier,\n} from '../lib/permissions'\nimport { hasResolvedDocumentsOrganizationAccess } from '../lib/organizationAccess'\nimport { containsCanonicalUuid } from '../lib/displayLabels'\nimport {\n DOCUMENTS_JSON_BODY_LIMITS,\n readBoundedJsonBody,\n} from '../lib/requestBody'\nimport { resolveOrganizationScopeService } from '../lib/platformServices'\n\nconst logger = createLogger('documents').child({ component: 'api' })\n\nexport type DocumentsAuthContext = NonNullable<AuthContext> & {\n features: string[]\n roleIds: string[]\n resolvedRoleIds: string[]\n organizationId: string\n}\n\nexport type DocumentsRouteContext = {\n container: AwilixContainer\n em: EntityManager\n auth: DocumentsAuthContext\n tenantId: string\n organizationId: string\n request: Request\n}\n\ntype RbacServiceLike = {\n loadAcl: (\n userId: string,\n scope: { tenantId: string | null; organizationId: string | null },\n ) => Promise<{\n isSuperAdmin: boolean\n features: string[]\n organizations: string[] | null\n }>\n}\n\nexport type MutationGuardResult = RouteMutationGuardPassed\n\nexport type DocumentCapabilityProjection = {\n relationshipTier: DocumentTier | null\n capabilities: DocumentCapabilities\n}\n\n/**\n * Every documents error body carries a translated `error` message. Some also\n * carry a machine-readable `code` discriminator: without it a client cannot\n * tell an unresolved organization scope (recoverable \u2014 pick an organization)\n * apart from an ordinary validation rejection, since both are a 400.\n */\nexport const routeErrorSchema = z.object({ error: z.string(), code: z.string().optional() })\n\nexport const ORGANIZATION_SCOPE_REQUIRED_DESCRIPTION =\n `Organization scope could not be resolved (\\`code: ${ORGANIZATION_SCOPE_REQUIRED_ERROR_CODE}\\`)`\n\nexport const ORGANIZATION_SELECTION_INVALID_ERROR_CODE = 'organization_selection_invalid'\n\nexport const ORGANIZATION_SELECTION_INVALID_DESCRIPTION =\n `The selected organization no longer resolves (\\`code: ${ORGANIZATION_SELECTION_INVALID_ERROR_CODE}\\`)`\n\n/**\n * `resolveDocumentsContext` answers 400 with the\n * `organization_scope_required` discriminator whenever an unrestricted caller\n * (a super-admin viewing a foreign tenant with \"all organizations\" selected)\n * has no resolvable organization. Every route that resolves a documents\n * context can therefore return it, including methods whose own contract\n * declares no 400 at all \u2014 so the contract has to say so, or generated clients\n * and the API reference cannot model the recovery path.\n *\n * Responses are keyed by status when the document is built, so a route that\n * already documents a 400 must merge rather than append a second entry.\n */\nexport function withDocumentsContextErrors(doc: OpenApiRouteDoc): OpenApiRouteDoc {\n const methods: OpenApiRouteDoc['methods'] = {}\n for (const method of Object.keys(doc.methods) as OpenApiHttpMethod[]) {\n const methodDoc = doc.methods[method]\n if (!methodDoc) continue\n const merge = (\n errors: NonNullable<typeof methodDoc.errors>,\n status: number,\n description: string,\n ): NonNullable<typeof methodDoc.errors> => (\n errors.some((error) => error.status === status)\n ? errors.map((error) => (error.status === status\n ? {\n ...error,\n description: error.description\n ? `${error.description}, or ${description}`\n : description,\n schema: error.schema ?? routeErrorSchema,\n }\n : error))\n : [...errors, { status, description, schema: routeErrorSchema }]\n )\n // `resolveDocumentsContext` answers 400 `organization_scope_required` and\n // 422 `organization_selection_invalid` from the same call, so both ride on\n // every route that resolves a documents context.\n let errors = merge(methodDoc.errors ?? [], 400, ORGANIZATION_SCOPE_REQUIRED_DESCRIPTION)\n errors = merge(errors, 422, ORGANIZATION_SELECTION_INVALID_DESCRIPTION)\n methods[method] = { ...methodDoc, errors }\n }\n return { ...doc, methods }\n}\n\nexport const ROUTE_ERROR_TRANSLATIONS: Record<string, { key: string; fallback: string }> = {\n Unauthorized: { key: 'api.errors.unauthorized', fallback: 'Unauthorized' },\n Forbidden: { key: 'api.errors.forbidden', fallback: 'Forbidden' },\n 'Organization context is required': {\n key: 'documents.errors.organizationRequired',\n fallback: 'Organization context is required',\n },\n 'Validation failed': { key: 'api.errors.invalidPayload', fallback: 'Invalid payload.' },\n 'Internal server error': { key: 'api.errors.internal', fallback: 'Internal server error' },\n 'Record changed by another user': {\n key: 'documents.errors.recordChanged',\n fallback: 'Record changed by another user',\n },\n 'Document not found': { key: 'documents.documents.notFound', fallback: 'Document not found.' },\n 'documents.content.notFound': {\n key: 'documents.content.notFound',\n fallback: 'Document content not found.',\n },\n 'documents.versions.notFound': {\n key: 'documents.versions.notFound',\n fallback: 'Version not found.',\n },\n 'documents.folders.error.invalidPlacement': {\n key: 'documents.folders.error.invalidPlacement',\n fallback: 'This folder cannot be placed there.',\n },\n 'Folder not found': { key: 'documents.folders.notFound', fallback: 'Folder not found.' },\n 'Share not found': { key: 'documents.share.notFound', fallback: 'Share not found.' },\n 'Comment not found': { key: 'documents.comments.notFound', fallback: 'Comment not found.' },\n 'Document comment limit reached': {\n key: 'documents.comments.limitExceeded',\n fallback: 'This document has reached its comment limit.',\n },\n 'Document version history storage limit exceeded': {\n key: 'documents.versions.quotaExceeded',\n fallback: 'Version history could not retain this snapshot within the document storage limit.',\n },\n 'documents.errors.requestBodyTooLarge': {\n key: 'documents.errors.requestBodyTooLarge',\n fallback: 'Request body is too large.',\n },\n 'Share principal not found in this organization': {\n key: 'documents.share.principalNotFound',\n fallback: 'Share principal not found in this organization',\n },\n 'Private attachment partition is not configured': {\n key: 'documents.attachments.partitionUnavailable',\n fallback: 'Private attachment partition is not configured',\n },\n 'Expected multipart/form-data': {\n key: 'documents.attachments.multipartRequired',\n fallback: 'Expected multipart/form-data',\n },\n 'File is required': { key: 'documents.attachments.fileRequired', fallback: 'File is required' },\n 'Attachment exceeds the maximum upload size.': {\n key: 'documents.attachments.tooLarge',\n fallback: 'Attachment exceeds the maximum upload size.',\n },\n 'Executable file types are not allowed as attachments.': {\n key: 'documents.attachments.executableBlocked',\n fallback: 'Executable file types are not allowed as attachments.',\n },\n 'Active content uploads are not allowed.': {\n key: 'documents.attachments.activeContentBlocked',\n fallback: 'Active content uploads are not allowed.',\n },\n 'Attachment storage quota exceeded for this tenant.': {\n key: 'documents.attachments.quotaExceeded',\n fallback: 'Attachment storage quota exceeded for this tenant.',\n },\n 'Attachment not found': { key: 'documents.attachments.notFound', fallback: 'Attachment not found' },\n 'Partition misconfigured': {\n key: 'documents.attachments.partitionMisconfigured',\n fallback: 'Attachment partition is misconfigured',\n },\n 'File not available': {\n key: 'documents.attachments.fileUnavailable',\n fallback: 'File not available',\n },\n 'Unsupported format': { key: 'documents.export.unsupportedFormat', fallback: 'Unsupported format' },\n 'documents.export.runtimeUnavailable': {\n key: 'documents.export.runtimeUnavailable',\n fallback: 'PDF export is temporarily unavailable.',\n },\n 'documents.errors.organizationSelectionInvalid': {\n key: 'documents.errors.organizationSelectionInvalid',\n fallback: 'Your selected organization is no longer available. Please re-select an organization and try again.',\n },\n}\n\nconst ROUTE_ERROR_KEY_FALLBACKS = Object.fromEntries(\n Object.values(ROUTE_ERROR_TRANSLATIONS).map(({ key, fallback }) => [key, fallback]),\n) as Record<string, string>\n\nfunction normalizeStrings(values: unknown): string[] {\n if (!Array.isArray(values)) return []\n return Array.from(\n new Set(\n values\n .map((value) => (typeof value === 'string' ? value.trim() : ''))\n .filter((value) => value.length > 0),\n ),\n )\n}\n\nfunction resolveSelectedOrganization(\n auth: NonNullable<AuthContext>,\n scope: { selectedId?: string | null; filterIds?: string[] | null; allowedIds?: string[] | null; tenantId?: string | null } | null,\n): string | null {\n // Every document row carries one organization, so there is no \"all\n // organizations\" view of this module. When the super-admin switcher clears\n // `auth.orgId`, keep the operator on their own organization (the shared\n // helper refuses the fallback once the tenant override moved them elsewhere)\n // instead of failing every request.\n const activeOrganizationId = resolveActiveOrganizationId(auth)\n const allowed = scope?.filterIds ?? scope?.allowedIds ?? (activeOrganizationId ? [activeOrganizationId] : null)\n const candidates = [\n scope?.selectedId ?? null,\n activeOrganizationId,\n Array.isArray(allowed) && allowed.length > 0 ? allowed[0] : null,\n ]\n for (const candidate of candidates) {\n if (!candidate) continue\n if (!Array.isArray(allowed) || allowed.includes(candidate)) return candidate\n }\n return null\n}\n\nfunction readAuthScopeId(auth: NonNullable<AuthContext>, key: 'actorTenantId' | 'actorOrgId'): string | null {\n const value = auth[key]\n if (typeof value === 'string' && value.trim().length > 0) return value.trim()\n return null\n}\n\nexport function hasDocumentsFeature(auth: DocumentsAuthContext, feature: string): boolean {\n if (auth.isSuperAdmin === true) return true\n return hasAllFeatures([feature], auth.features)\n}\n\nexport function hasAnyDocumentsFeature(auth: DocumentsAuthContext, features: string[]): boolean {\n if (auth.isSuperAdmin === true) return true\n return features.some((feature) => hasAllFeatures([feature], auth.features))\n}\n\nexport function deriveCapabilitiesForContext(\n ctx: DocumentsRouteContext,\n relationshipTier: DocumentTier | null,\n options: { archived?: boolean } = {},\n): DocumentCapabilities {\n return deriveDocumentCapabilities({\n relationshipTier,\n managerOverride: hasDocumentsFeature(ctx.auth, 'documents.manage'),\n archived: options.archived === true,\n userFeatures: ctx.auth.features,\n })\n}\n\nexport async function resolveDocumentCapabilityProjection(\n ctx: DocumentsRouteContext,\n documentId: string,\n options: { archived?: boolean } = {},\n): Promise<DocumentCapabilityProjection> {\n const relationshipTier = await resolvePermission(ctx.em, documentId, ctx.auth)\n return {\n relationshipTier,\n capabilities: deriveCapabilitiesForContext(ctx, relationshipTier, options),\n }\n}\n\nexport async function loadDocumentArchivedState(\n ctx: DocumentsRouteContext,\n documentId: string,\n): Promise<{ archivedAt: Date | null }> {\n const document = await findOneWithDecryption(\n ctx.em,\n Document,\n {\n id: documentId,\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId,\n deletedAt: null,\n },\n { fields: ['id', 'archivedAt'] },\n { tenantId: ctx.tenantId, organizationId: ctx.organizationId },\n )\n if (!document) throw new CrudHttpError(404, { error: 'documents.documents.notFound' })\n return { archivedAt: document.archivedAt ?? null }\n}\n\nexport async function assertDocumentNotArchived(\n ctx: DocumentsRouteContext,\n documentId: string,\n): Promise<void> {\n const state = await loadDocumentArchivedState(ctx, documentId)\n if (state.archivedAt !== null) {\n throw new CrudHttpError(403, { error: 'documents.errors.documentArchived' })\n }\n}\n\nexport async function resolveDocumentsContext(\n request: Request,\n requiredFeatures: string[],\n): Promise<DocumentsRouteContext> {\n const container = await createRequestContainer()\n const em = container.resolve('em') as EntityManager\n const auth = await getAuthFromRequest(request)\n if (!auth || !auth.tenantId) {\n throw new CrudHttpError(401, { error: 'api.errors.unauthorized' })\n }\n\n const authenticatedActorUserId = resolveActorUserId(auth)\n const actorTenantId = readAuthScopeId(auth, 'actorTenantId') ?? auth.tenantId\n const actorOrganizationId = readAuthScopeId(auth, 'actorOrgId') ?? auth.orgId\n const rbacService = container.resolve('rbacService') as RbacServiceLike\n const actorAcl = await rbacService.loadAcl(auth.sub, {\n tenantId: actorTenantId,\n organizationId: actorOrganizationId,\n })\n // Organization scope resolution historically accepts the authenticated\n // superadmin bit to support tenant/org switching. Replace that potentially\n // stale token bit with the live RBAC decision before asking the shared scope\n // resolver to expand parent grants into descendant organizations.\n const scopeAuth = {\n ...auth,\n // API-key authentication validates both keyId and any backing user. Keep\n // the prefixed subject for ACL while projecting its UUID domain actor for\n // ownership/share/audit helpers. An unbound key uses its own key UUID.\n userId: authenticatedActorUserId,\n isSuperAdmin: actorAcl.isSuperAdmin === true,\n }\n const organizationScopeService = resolveOrganizationScopeService(container)\n if (!organizationScopeService) {\n throw new CrudHttpError(403, { error: 'api.errors.forbidden' })\n }\n const scope = await organizationScopeService.resolveForRequest({ auth: scopeAuth, request })\n // The caller explicitly selected an organization that no longer resolves to a\n // real, accessible org (stale selected-org cookie after a delete or an access\n // loss). The scope resolver falls back to another allowed org, so continuing\n // would silently read and write against an organization the caller never\n // chose. Fail loud on every documents operation instead \u2014 the same contract\n // `makeCrudRoute` enforces via `rejectInvalidOrgSelection` (#3936).\n if (scope?.selectionRejected) {\n throw new CrudHttpError(422, {\n error: 'documents.errors.organizationSelectionInvalid',\n code: ORGANIZATION_SELECTION_INVALID_ERROR_CODE,\n })\n }\n const tenantId = scope?.tenantId ?? auth.tenantId\n const organizationId = resolveSelectedOrganization(scopeAuth, scope)\n if (!tenantId) {\n throw new CrudHttpError(400, { error: 'documents.errors.organizationRequired' })\n }\n if (!organizationId) {\n // An unrestricted caller with no resolvable organization (a super-admin\n // viewing a foreign tenant with \"all organizations\" selected) is a 400, not\n // a 401 (which `apiFetch` would turn into a refresh loop) and not a 403 (the\n // caller is allowed in; they only need to pick an organization). A caller\n // whose grants expand to no organization at all stays a 403.\n const unrestricted = (scope?.filterIds ?? scope?.allowedIds ?? null) === null\n if (unrestricted) {\n throw new CrudHttpError(400, {\n error: 'documents.errors.organizationRequired',\n code: ORGANIZATION_SCOPE_REQUIRED_ERROR_CODE,\n })\n }\n throw new CrudHttpError(403, { error: 'api.errors.forbidden' })\n }\n\n const acl = await rbacService.loadAcl(auth.sub, { tenantId, organizationId })\n if (!hasResolvedDocumentsOrganizationAccess(acl, organizationId, scope)) {\n throw new CrudHttpError(403, { error: 'api.errors.forbidden' })\n }\n const roleIds = await resolveActiveSubjectRoleIds(\n container,\n { tenantId, organizationId },\n auth.sub,\n )\n const documentsAuth: DocumentsAuthContext = {\n ...scopeAuth,\n tenantId,\n orgId: organizationId,\n organizationId,\n roleIds,\n resolvedRoleIds: roleIds,\n // JWT/trusted-request claims are only authentication hints. The current\n // RBAC projection is authoritative for features and superadmin status so\n // a revoked grant cannot survive in a long-lived token.\n features: acl.isSuperAdmin === true ? ['*'] : normalizeStrings(acl.features),\n isSuperAdmin: acl.isSuperAdmin === true,\n }\n\n if (!hasAnyDocumentsFeature(documentsAuth, requiredFeatures)) {\n throw new CrudHttpError(403, { error: 'api.errors.forbidden' })\n }\n\n return {\n container,\n em,\n auth: documentsAuth,\n tenantId,\n organizationId,\n request,\n }\n}\n\nexport async function readBody(\n request: Request,\n maxBytes = DOCUMENTS_JSON_BODY_LIMITS.standard,\n): Promise<Record<string, unknown>> {\n return (await readBoundedJsonBody<Record<string, unknown>>(request, maxBytes, {})) ?? {}\n}\n\nasync function localizeRouteErrorBody(body: Record<string, unknown>): Promise<Record<string, unknown>> {\n const rawError = typeof body.error === 'string' ? body.error : null\n if (!rawError) return body\n\n if (containsCanonicalUuid(rawError)) {\n const { key, fallback } = ROUTE_ERROR_TRANSLATIONS['Internal server error']!\n try {\n const { translate } = await resolveTranslations()\n return { ...body, error: translate(key, fallback) }\n } catch {\n return { ...body, error: fallback }\n }\n }\n\n const literal = ROUTE_ERROR_TRANSLATIONS[rawError]\n const key = literal?.key ?? rawError\n const fallback = literal?.fallback ?? ROUTE_ERROR_KEY_FALLBACKS[key]\n // Attachment failures reach these routes as the attachments module's own\n // catalogued keys. The locale dictionary is merged across modules, so they\n // resolve here; without this prefix the raw key would reach the client.\n if (\n !key.startsWith('documents.')\n && !key.startsWith('api.')\n && !key.startsWith('attachments.')\n ) return body\n\n try {\n const { translate } = await resolveTranslations()\n return { ...body, error: translate(key, fallback) }\n } catch {\n return { ...body, error: fallback ?? key }\n }\n}\n\nexport async function handleDocumentsRouteError(error: unknown, label: string): Promise<Response> {\n if (isCrudHttpError(error)) {\n const body = error.body && typeof error.body === 'object' && !Array.isArray(error.body)\n ? error.body as Record<string, unknown>\n : { error: 'api.errors.internal' }\n return NextResponse.json(await localizeRouteErrorBody(body), { status: error.status })\n }\n const interceptorRejection = getCommandInterceptorHttpRejection(error)\n if (interceptorRejection) {\n return NextResponse.json(interceptorRejection.body, { status: interceptorRejection.status })\n }\n if (error instanceof z.ZodError) {\n return NextResponse.json(\n await localizeRouteErrorBody({ error: 'api.errors.invalidPayload', details: error.issues }),\n { status: 400 },\n )\n }\n logger.error(`${label} failed`, { err: error })\n return NextResponse.json(\n await localizeRouteErrorBody({ error: 'api.errors.internal' }),\n { status: 500 },\n )\n}\n\nconst documentIdSchema = z.string().uuid()\n\nexport function assertDocumentId(id: string): void {\n if (!documentIdSchema.safeParse(id).success) {\n throw new CrudHttpError(404, { error: 'documents.documents.notFound' })\n }\n}\n\nexport async function loadScopedDocument(ctx: DocumentsRouteContext, id: string): Promise<Document> {\n const document = await findOneWithDecryption(ctx.em, Document, {\n id,\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId,\n deletedAt: null,\n }, undefined, { tenantId: ctx.tenantId, organizationId: ctx.organizationId })\n if (!document) throw new CrudHttpError(404, { error: 'documents.documents.notFound' })\n return document\n}\n\n/**\n * Only the owner (or a documents manager) may learn that a document was\n * removed, e.g. after undoing its creation; everyone else keeps the\n * existence-hiding 403 so deleted ids stay indistinguishable from foreign ones.\n */\nexport async function isRemovedDocumentVisibleToActor(ctx: DocumentsRouteContext, id: string): Promise<boolean> {\n const document = await findOneWithDecryption(ctx.em, Document, {\n id,\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId,\n deletedAt: { $ne: null },\n }, { fields: ['id', 'ownerUserId'] }, { tenantId: ctx.tenantId, organizationId: ctx.organizationId })\n if (!document) return false\n if (hasDocumentsFeature(ctx.auth, 'documents.manage')) return true\n return document.ownerUserId === resolveActorUserId(ctx.auth)\n}\n\nexport async function loadScopedShare(\n ctx: DocumentsRouteContext,\n documentId: string,\n shareId: string,\n): Promise<DocumentShare> {\n const share = await findOneWithDecryption(ctx.em, DocumentShare, {\n id: shareId,\n documentId,\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId,\n deletedAt: null,\n }, undefined, { tenantId: ctx.tenantId, organizationId: ctx.organizationId })\n if (!share) throw new CrudHttpError(404, { error: 'documents.share.notFound' })\n return share\n}\n\nexport function serializeDocument(document: Document): Record<string, unknown> {\n return {\n id: document.id,\n title: document.title,\n folderId: document.folderId ?? null,\n ownerUserId: document.ownerUserId,\n createdByUserId: document.createdByUserId,\n isActive: document.isActive,\n createdAt: document.createdAt.toISOString(),\n updatedAt: document.updatedAt.toISOString(),\n }\n}\n\nexport function serializeContent(content: DocumentContent | null): Record<string, unknown> {\n return {\n contentHtml: content?.contentHtml ?? '',\n contentText: content?.contentText ?? '',\n updatedAt: content?.updatedAt ? content.updatedAt.toISOString() : null,\n }\n}\n\nexport function serializeFolder(folder: DocumentFolder): Record<string, unknown> {\n return {\n id: folder.id,\n name: folder.name,\n parentFolderId: folder.parentFolderId ?? null,\n ownerUserId: folder.ownerUserId,\n createdAt: folder.createdAt.toISOString(),\n updatedAt: folder.updatedAt.toISOString(),\n }\n}\n\nexport function serializeShare(share: DocumentShare): Record<string, unknown> {\n return {\n id: share.id,\n documentId: share.documentId,\n principalType: share.principalType,\n principalId: share.principalId,\n permission: share.permission,\n createdByUserId: share.createdByUserId,\n createdAt: share.createdAt.toISOString(),\n updatedAt: share.updatedAt.toISOString(),\n }\n}\n\nexport async function validateMutationGuard(\n ctx: DocumentsRouteContext,\n input: {\n resourceKind: string\n resourceId: string\n operation: 'create' | 'update' | 'delete' | 'custom'\n mutationPayload?: Record<string, unknown> | null\n /**\n * Re-parses a guard's `modifiedPayload` before it reaches the command. A\n * registry guard may rewrite the payload, and the route's own schema is the\n * only thing that still guarantees the command receives a shape it can trust.\n */\n mutationPayloadSchema?: { parse: (value: unknown) => Record<string, unknown> }\n },\n): Promise<MutationGuardResult> {\n // `runRouteMutationGuards` runs EVERY registered guard plus the bridged legacy\n // DI service. The deprecated `validateCrudMutationGuard` /\n // `runCrudMutationGuardAfterSuccess` pair used here before resolved only the\n // single DI-registered guard service, so every guard a module contributed\n // through the registry \u2014 along with its payload transformation and its\n // after-success callback \u2014 was silently skipped on Documents writes.\n const guardResult = await runRouteMutationGuards({\n container: ctx.container,\n req: ctx.request,\n auth: {\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId,\n userId: resolveActorUserId(ctx.auth),\n userFeatures: ctx.auth.features,\n },\n input: {\n resourceKind: input.resourceKind,\n resourceId: input.resourceId,\n operation: input.operation,\n mutationPayload: input.mutationPayload ?? null,\n },\n })\n if (!guardResult.ok) {\n throw new CrudHttpError(guardResult.errorStatus, guardResult.errorBody)\n }\n if (guardResult.modifiedPayload && input.mutationPayload) {\n const transformed = input.mutationPayloadSchema\n ? input.mutationPayloadSchema.parse(guardResult.modifiedPayload)\n : guardResult.modifiedPayload\n // Routes read the object they handed in, so the transformation has to land\n // there rather than only on the guard result.\n for (const key of Object.keys(input.mutationPayload)) delete input.mutationPayload[key]\n Object.assign(input.mutationPayload, transformed)\n }\n return guardResult\n}\n\nexport async function runMutationGuardAfterSuccess(\n _ctx: DocumentsRouteContext,\n guardResult: MutationGuardResult,\n _input: {\n resourceKind: string\n resourceId: string\n operation: 'create' | 'update' | 'delete' | 'custom'\n },\n): Promise<void> {\n await guardResult.runAfterSuccess()\n}\n"],
5
+ "mappings": "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",
6
6
  "names": ["resolveActorUserId", "errors", "key", "fallback"]
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-mercato/documents",
3
- "version": "0.10.1-develop.8180.1.c6789f43f3",
3
+ "version": "0.10.1-develop.8182.1.0acd6a564c",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -75,8 +75,8 @@
75
75
  "@hocuspocus/server": "^4.4.0",
76
76
  "@hocuspocus/transformer": "^4.3.0",
77
77
  "@mikro-orm/core": "^7.1.14",
78
- "@open-mercato/events": "0.10.1-develop.8180.1.c6789f43f3",
79
- "@open-mercato/ui": "0.10.1-develop.8180.1.c6789f43f3",
78
+ "@open-mercato/events": "0.10.1-develop.8182.1.0acd6a564c",
79
+ "@open-mercato/ui": "0.10.1-develop.8182.1.0acd6a564c",
80
80
  "@tiptap/core": "3.32.0",
81
81
  "@tiptap/extension-bubble-menu": "3.32.0",
82
82
  "@tiptap/extension-collaboration": "3.32.0",
@@ -112,14 +112,14 @@
112
112
  },
113
113
  "peerDependencies": {
114
114
  "@mikro-orm/postgresql": "^7.0.14",
115
- "@open-mercato/shared": "0.10.1-develop.8180.1.c6789f43f3",
115
+ "@open-mercato/shared": "0.10.1-develop.8182.1.0acd6a564c",
116
116
  "react": "^19.0.0",
117
117
  "react-dom": "^19.0.0",
118
118
  "react-is": ">=18.0.0"
119
119
  },
120
120
  "devDependencies": {
121
- "@open-mercato/core": "0.10.1-develop.8180.1.c6789f43f3",
122
- "@open-mercato/shared": "0.10.1-develop.8180.1.c6789f43f3",
121
+ "@open-mercato/core": "0.10.1-develop.8182.1.0acd6a564c",
122
+ "@open-mercato/shared": "0.10.1-develop.8182.1.0acd6a564c",
123
123
  "@types/jest": "^30.0.0",
124
124
  "@types/pg": "^8.23.1",
125
125
  "@types/react": "^19.2.18",
@@ -26,6 +26,7 @@ import {
26
26
  resolveDocumentsCommandBus,
27
27
  } from '../../_commands'
28
28
  import {
29
+ assertDocumentId,
29
30
  assertDocumentNotArchived,
30
31
  handleDocumentsRouteError,
31
32
  readBody,
@@ -243,6 +244,7 @@ export async function GET(request: Request, context: RouteContext): Promise<Resp
243
244
  try {
244
245
  const documentId = await resolveId(context)
245
246
  const ctx = await resolveDocumentsContext(request, ['documents.view'])
247
+ assertDocumentId(documentId)
246
248
  const tier = await assertTier(ctx.em, documentId, ctx.auth, 'viewer')
247
249
  const userId = resolveActorUserId(ctx.auth)
248
250
  const url = new URL(request.url)
@@ -13,6 +13,7 @@ import type {
13
13
  ReplaceDocumentContentCommandResult,
14
14
  } from '../../../commands/content'
15
15
  import {
16
+ assertDocumentId,
16
17
  assertDocumentNotArchived,
17
18
  handleDocumentsRouteError,
18
19
  readBody,
@@ -58,6 +59,7 @@ export async function GET(request: Request, context: RouteContext): Promise<Resp
58
59
  try {
59
60
  const id = await resolveId(context)
60
61
  const ctx = await resolveDocumentsContext(request, ['documents.view'])
62
+ assertDocumentId(id)
61
63
  await assertTier(ctx.em, id, ctx.auth, 'viewer')
62
64
  const content = await loadDocumentContent(ctx.em, id, {
63
65
  tenantId: ctx.tenantId,
@@ -92,6 +92,9 @@ export async function GET(request: Request, context: RouteContext): Promise<Resp
92
92
  try {
93
93
  const id = await resolveId(context)
94
94
  const ctx = await resolveDocumentsContext(request, ['documents.view'])
95
+ if (!z.string().uuid().safeParse(id).success) {
96
+ throw new CrudHttpError(404, { error: 'documents.documents.notFound' })
97
+ }
95
98
  const projection = await resolveDocumentCapabilityProjection(ctx, id)
96
99
  if (!projection.relationshipTier && await isRemovedDocumentVisibleToActor(ctx, id)) {
97
100
  throw new CrudHttpError(404, { error: 'documents.documents.notFound' })
@@ -24,6 +24,7 @@ import {
24
24
  resolveDocumentsCommandBus,
25
25
  } from '../../_commands'
26
26
  import {
27
+ assertDocumentId,
27
28
  assertDocumentNotArchived,
28
29
  handleDocumentsRouteError,
29
30
  loadScopedShare,
@@ -142,6 +143,7 @@ export async function GET(request: Request, context: RouteContext): Promise<Resp
142
143
  try {
143
144
  const documentId = await resolveId(context)
144
145
  const ctx = await resolveDocumentsContext(request, ['documents.share'])
146
+ assertDocumentId(documentId)
145
147
  await assertCanShare(ctx, documentId)
146
148
  const loadedShares = await findWithDecryption(
147
149
  ctx.em,
@@ -9,6 +9,7 @@ import { DOCUMENTS_VERSION_LIST_PAGE_SIZE } from '../../../lib/historyLimits'
9
9
  import { assertTier } from '../../../lib/permissions'
10
10
  import { resolveUserLabels } from '../../../lib/userLabels'
11
11
  import {
12
+ assertDocumentId,
12
13
  assertDocumentNotArchived,
13
14
  handleDocumentsRouteError,
14
15
  readBody,
@@ -87,6 +88,7 @@ export async function GET(request: Request, context: RouteContext): Promise<Resp
87
88
  try {
88
89
  const documentId = await resolveId(context)
89
90
  const ctx = await resolveDocumentsContext(request, ['documents.view'])
91
+ assertDocumentId(documentId)
90
92
  await assertTier(ctx.em, documentId, ctx.auth, 'viewer')
91
93
  const url = new URL(request.url)
92
94
  const query = versionListQuerySchema.parse(Object.fromEntries(url.searchParams.entries()))
@@ -510,6 +510,14 @@ export async function handleDocumentsRouteError(error: unknown, label: string):
510
510
  )
511
511
  }
512
512
 
513
+ const documentIdSchema = z.string().uuid()
514
+
515
+ export function assertDocumentId(id: string): void {
516
+ if (!documentIdSchema.safeParse(id).success) {
517
+ throw new CrudHttpError(404, { error: 'documents.documents.notFound' })
518
+ }
519
+ }
520
+
513
521
  export async function loadScopedDocument(ctx: DocumentsRouteContext, id: string): Promise<Document> {
514
522
  const document = await findOneWithDecryption(ctx.em, Document, {
515
523
  id,