{"record":{"id":"5c813974b051f0f4","repo":"toeverything/AFFiNE","slug":"expect-to-revoke-public-doc","errorCode":"expect_to_revoke_public_doc","errorMessage":"Expect doc not to be workspace","messagePattern":"Expect doc not to be workspace","errorType":"exception","errorClass":"ExpectToRevokePublicDoc","httpStatus":400,"severity":"error","filePath":"packages/backend/server/src/core/workspaces/resolvers/doc.ts","lineNumber":463,"sourceCode":"    this.logger.log(\n      `Publish page ${docId} with mode ${mode} in workspace ${workspaceId}`\n    );\n\n    return doc;\n  }\n\n  @Mutation(() => DocType)\n  async revokePublicDoc(\n    @CurrentUser() user: CurrentUser,\n    @Args('workspaceId') workspaceId: string,\n    @Args('docId') docId: string\n  ) {\n    if (workspaceId === docId) {\n      this.logger.error('Expect to revoke public doc, but it is a workspace', {\n        workspaceId,\n        docId,\n      });\n      throw new ExpectToRevokePublicDoc('Expect doc not to be workspace');\n    }\n\n    await this.ac.user(user.id).doc(workspaceId, docId).assert('Doc.Publish');\n\n    const doc = await this.models.doc.unpublish(workspaceId, docId);\n    this.event.emit('doc.public_state.changed', { workspaceId, docId });\n\n    this.logger.log(`Revoke public doc ${docId} in workspace ${workspaceId}`);\n\n    return doc;\n  }\n\n  private async tryFixDocOwner(workspaceId: string, docId: string) {\n    const allowed = await this.cache.setnx(\n      `fixingOwner:${workspaceId}:${docId}`,\n      1,\n      // TODO(@forehalo): we definitely need a timer helper\n      { ttl: 1000 * 60 * 60 * 24 }","sourceCodeStart":445,"sourceCodeEnd":481,"githubUrl":"https://github.com/toeverything/AFFiNE/blob/b6de0ad51b76f3daac2d3d6325369ea623ed7ed4/packages/backend/server/src/core/workspaces/resolvers/doc.ts#L445-L481","documentation":"revokePublicDoc rejects calls where workspaceId === docId, mirroring publishDoc: the workspace root is a Space, not a published doc, so revoking 'the workspace id itself' is a client mistake and throws ExpectToRevokePublicDoc before permissions are evaluated.","triggerScenarios":"Calling revokePublicDoc(workspaceId, docId) with the workspace id in both arguments — stale id from an older public-doc record, or a client defaulting docId to the workspace root.","commonSituations":"Migration code replaying public-doc lists that stored the workspace id as docId; UI passing the workspace root id for the 'home' page.","solutions":["Guard client-side: only call revokePublicDoc when docId !== workspaceId","Fix the data source that produced a docId equal to the workspace id","If the goal was to unpublish everything, revoke each published page individually"],"exampleFix":"// before\nawait revokePublicDoc(ws.id, docIdFromLegacyRecord /* === ws.id */);\n\n// after\nif (docId === ws.id) {\n  // legacy record pointed at the root; nothing to revoke\n  return;\n}\nawait revokePublicDoc(ws.id, docId);","handlingStrategy":"validation","validationCode":"// Skip revoke calls that target the root\nif (docId !== ws.id) {\n  await revokePublicDoc(ws.id, docId);\n}","typeGuard":"function isExpectToRevokePublicDoc(e: unknown): boolean {\n  return (\n    typeof e === 'object' && e !== null &&\n    (e as { extensions?: { code?: string } }).extensions?.code === 'expect_to_revoke_public_doc'\n  );\n}","tryCatchPattern":"try {\n  await revokePublicDoc(ws.id, docId);\n} catch (e) {\n  if (isExpectToRevokePublicDoc(e)) {\n    // legacy record pointed at the root; nothing to revoke, treat as success\n    return;\n  } else throw e;\n}","preventionTips":["Sanitize legacy public-doc records whose docId equals the workspace id","Apply one shared workspaceId !== docId guard to every doc-level mutation call site","Log client-side when the guard trips to catch bad id sources early"],"tags":["doc","publish","validation","affine"],"backgroundTag":"invalid-identifier","analyzedSha":"b6de0ad51b76f3daac2d3d6325369ea623ed7ed4","analyzedAt":"2026-08-18T21:16:52.546Z","contentChangedAt":"2026-08-18T21:16:52.546Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}