feat: feature flag upgrade

This commit is contained in:
Philipinho
2026-03-07 21:57:14 +00:00
parent 66c26af34b
commit 73ed0c54e5
41 changed files with 415 additions and 303 deletions
-9
View File
@@ -91,15 +91,6 @@ export function extractBearerTokenFromHeader(
return type === 'Bearer' ? token : undefined;
}
export function hasLicenseOrEE(opts: {
licenseKey: string;
plan: string;
isCloud: boolean;
}): boolean {
const { licenseKey, plan, isCloud } = opts;
return Boolean(licenseKey) || (isCloud && plan === 'business');
}
/**
* Normalizes a database URL for postgres.js compatibility.
* - Removes `sslmode=no-verify` (not supported by postgres.js), keeps other sslmode values
+10 -13
View File
@@ -28,8 +28,7 @@ import { JwtAuthGuard } from '../../common/guards/jwt-auth.guard';
import { Public } from '../../common/decorators/public.decorator';
import { ShareRepo } from '@docmost/db/repos/share/share.repo';
import { PaginationOptions } from '@docmost/db/pagination/pagination-options';
import { EnvironmentService } from '../../integrations/environment/environment.service';
import { hasLicenseOrEE } from '../../common/helpers';
import { LicenseCheckService } from '../../integrations/environment/license-check.service';
import { AuditEvent, AuditResource } from '../../common/events/audit-events';
import {
AUDIT_SERVICE,
@@ -45,7 +44,7 @@ export class ShareController {
private readonly pageRepo: PageRepo,
private readonly pagePermissionRepo: PagePermissionRepo,
private readonly pageAccessService: PageAccessService,
private readonly environmentService: EnvironmentService,
private readonly licenseCheckService: LicenseCheckService,
@Inject(AUDIT_SERVICE) private readonly auditService: IAuditService,
) {}
@@ -81,11 +80,10 @@ export class ShareController {
return {
...shareData,
hasLicenseKey: hasLicenseOrEE({
licenseKey: workspace.licenseKey,
isCloud: this.environmentService.isCloud(),
plan: workspace.plan,
}),
features: this.licenseCheckService.resolveFeatures(
workspace.licenseKey,
workspace.plan,
),
};
}
@@ -259,11 +257,10 @@ export class ShareController {
return {
...treeData,
hasLicenseKey: hasLicenseOrEE({
licenseKey: workspace.licenseKey,
isCloud: this.environmentService.isCloud(),
plan: workspace.plan,
}),
features: this.licenseCheckService.resolveFeatures(
workspace.licenseKey,
workspace.plan,
),
};
}
}
@@ -139,10 +139,10 @@ export class SpaceService {
});
if (
!this.licenseCheckService.isValidEELicense(workspace.licenseKey)
!this.licenseCheckService.hasFeature(workspace.licenseKey, 'security:settings')
) {
throw new ForbiddenException(
'This feature requires a valid enterprise license',
'This feature requires a valid license',
);
}
}
@@ -13,6 +13,7 @@ import { JwtAuthGuard } from '../../common/guards/jwt-auth.guard';
import { AuthWorkspace } from '../../common/decorators/auth-workspace.decorator';
import { User, Workspace } from '@docmost/db/types/entity.types';
import { WorkspaceRepo } from '@docmost/db/repos/workspace/workspace.repo';
import { LicenseCheckService } from '../../integrations/environment/license-check.service';
@UseGuards(JwtAuthGuard)
@Controller('users')
@@ -20,6 +21,7 @@ export class UserController {
constructor(
private readonly userService: UserService,
private readonly workspaceRepo: WorkspaceRepo,
private readonly licenseCheckService: LicenseCheckService,
) {}
@HttpCode(HttpStatus.OK)
@@ -34,10 +36,16 @@ export class UserController {
const { licenseKey, ...rest } = workspace;
const features = this.licenseCheckService.resolveFeatures(
licenseKey,
rest.plan,
);
const workspaceInfo = {
...rest,
memberCount,
hasLicenseKey: Boolean(licenseKey),
features,
};
return { user: authUser, workspace: workspaceInfo };
@@ -85,7 +85,7 @@ export class WorkspaceService {
async getWorkspacePublicData(workspaceId: string) {
const workspace = await this.db
.selectFrom('workspaces')
.select(['id', 'name', 'logo', 'hostname', 'enforceSso', 'licenseKey'])
.select(['id', 'name', 'logo', 'hostname', 'enforceSso', 'licenseKey', 'plan'])
.select((eb) =>
jsonArrayFrom(
eb
@@ -111,6 +111,7 @@ export class WorkspaceService {
return {
...rest,
hasLicenseKey: Boolean(licenseKey),
features: this.licenseCheckService.resolveFeatures(licenseKey, rest.plan),
};
}
@@ -336,9 +337,9 @@ export class WorkspaceService {
.where('id', '=', workspaceId)
.executeTakeFirst();
if (!this.licenseCheckService.isValidEELicense(ws.licenseKey)) {
if (!this.licenseCheckService.hasFeature(ws.licenseKey, 'security:settings')) {
throw new ForbiddenException(
'This feature requires a valid enterprise license',
'This feature requires a valid license',
);
}
@@ -506,6 +507,7 @@ export class WorkspaceService {
return {
...rest,
hasLicenseKey: Boolean(licenseKey),
features: this.licenseCheckService.resolveFeatures(licenseKey, rest.plan),
};
}
@@ -25,4 +25,48 @@ export class LicenseCheckService {
return false;
}
}
hasFeature(licenseKey: string, feature: string): boolean {
if (this.environmentService.isCloud()) {
return true;
}
try {
// eslint-disable-next-line @typescript-eslint/no-require-imports
const LicenseModule = require('../../ee/licence/license.service');
const licenseService = this.moduleRef.get(LicenseModule.LicenseService, {
strict: false,
});
return licenseService.hasFeature(licenseKey, feature);
} catch {
return false;
}
}
getFeatures(licenseKey: string): string[] {
try {
// eslint-disable-next-line @typescript-eslint/no-require-imports
const LicenseModule = require('../../ee/licence/license.service');
const licenseService = this.moduleRef.get(LicenseModule.LicenseService, {
strict: false,
});
return licenseService.getFeatures(licenseKey);
} catch {
return [];
}
}
resolveFeatures(licenseKey: string, plan: string): string[] {
if (this.environmentService.isCloud()) {
try {
// eslint-disable-next-line @typescript-eslint/no-require-imports
const { getFeaturesForCloudPlan } = require('../../ee/licence/feature-registry');
return [...getFeaturesForCloudPlan(plan)];
} catch {
return [];
}
}
return this.getFeatures(licenseKey);
}
}