feat(server): entitlement based model (#14996)

#### PR Dependency Tree


* **PR #14996** 👈

This tree was auto-generated by
[Charcoal](https://github.com/danerwilliams/charcoal)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
  * Admin mutations to grant/revoke commercial entitlements.
  * New Doc comment-update permission.
  * Realtime user/workspace quota-state endpoints and live-update rooms.

* **Bug Fixes**
  * More accurate readable-doc filtering and permission evaluation.

* **Refactor**
* Workspace feature management moved to entitlement-based model;
permission and quota pipelines redesigned.
  * Admin workspace UI now edits flags only (feature toggles removed).

* **Tests**
* Extensive new and updated tests for permissions, entitlements, quota,
projection, and backfills.

<!-- review_stack_entry_start -->

[![Review Change
Stack](https://storage.googleapis.com/coderabbit_public_assets/review-stack-in-coderabbit-ui.svg)](https://app.coderabbit.ai/change-stack/toeverything/AFFiNE/pull/14996?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack)

<!-- review_stack_entry_end -->
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
DarkSky
2026-05-19 22:48:05 +08:00
committed by GitHub
parent 103ad2a810
commit c53457691d
150 changed files with 13463 additions and 2458 deletions
+41 -19
View File
@@ -351,27 +351,44 @@ export class DocModel extends BaseModel {
/**
* Create or update the doc meta.
*/
@Transactional()
async upsertMeta(
workspaceId: string,
docId: string,
data?: DocMetaUpsertInput
) {
const doc = await this.db.workspaceDoc.upsert({
where: {
workspaceId_docId: {
if (
data &&
('public' in data || 'defaultRole' in data || 'publishedAt' in data)
) {
await this.models.docAccessPolicy.upsert(workspaceId, docId, {
public: data.public,
defaultRole: data.defaultRole,
publishedAt:
typeof data.publishedAt === 'string'
? new Date(data.publishedAt)
: data.publishedAt,
});
}
const doc = await this.withPermissionProjectionMetric(
this.db.workspaceDoc.upsert({
where: {
workspaceId_docId: {
workspaceId,
docId,
},
},
update: {
...data,
},
create: {
...data,
workspaceId,
docId,
},
},
update: {
...data,
},
create: {
...data,
workspaceId,
docId,
},
});
})
);
this.event.emit('doc.updated', {
workspaceId,
docId,
@@ -643,13 +660,17 @@ export class DocModel extends BaseModel {
async paginateDocInfoByUpdatedAt(
workspaceId: string,
pagination: PaginationInput
pagination: PaginationInput,
readablePredicate: Prisma.Sql = Prisma.sql`TRUE`
) {
const count = await this.db.workspaceDoc.count({
where: {
workspaceId,
},
});
const [countRow] = await this.db.$queryRaw<{ count: bigint | number }[]>`
SELECT COUNT(*) AS count
FROM "workspace_pages"
WHERE
"workspace_pages"."workspace_id" = ${workspaceId}
AND ${readablePredicate}
`;
const count = Number(countRow?.count ?? 0);
const after = pagination.after
? Prisma.sql`AND "snapshots"."updated_at" < ${new Date(pagination.after)}`
@@ -686,6 +707,7 @@ export class DocModel extends BaseModel {
AND "workspace_pages"."page_id" = "snapshots"."guid"
WHERE
"workspace_pages"."workspace_id" = ${workspaceId}
AND ${readablePredicate}
${after}
ORDER BY
"snapshots"."updated_at" DESC