feat: workspace level feature schema (#5466)

This commit is contained in:
DarkSky
2024-01-05 04:13:44 +00:00
parent 7b6430ebc3
commit 97f8927c21
3 changed files with 214 additions and 5 deletions

View File

@@ -61,7 +61,7 @@ export class FeatureManagementService implements OnModuleInit {
});
if (user) {
const canEarlyAccess = await this.feature
.hasFeature(user.id, FeatureType.EarlyAccess)
.hasUserFeature(user.id, FeatureType.EarlyAccess)
.catch(() => false);
if (canEarlyAccess) {
return true;
@@ -86,4 +86,36 @@ export class FeatureManagementService implements OnModuleInit {
return true;
}
}
// ======== Workspace Feature ========
async addWorkspaceFeatures(
workspaceId: string,
feature: FeatureType,
version?: number,
reason?: string
) {
const latestVersions = await this.feature.getFeaturesVersion();
// use latest version if not specified
const latestVersion = version || latestVersions[feature];
if (!Number.isInteger(latestVersion)) {
throw new Error(`Version of feature ${feature} not found`);
}
return this.feature.addWorkspaceFeature(
workspaceId,
feature,
latestVersion,
reason || 'add feature by api'
);
}
async getWorkspaceFeatures(workspaceId: string) {
const features = await this.feature.getWorkspaceFeatures(workspaceId);
return features.map(feature => feature.feature.name);
}
async removeWorkspaceFeature(workspaceId: string, feature: FeatureType) {
return this.feature
.removeWorkspaceFeature(workspaceId, feature)
.then(c => c > 0);
}
}

View File

@@ -2,6 +2,7 @@ import { Injectable } from '@nestjs/common';
import { PrismaService } from '../../prisma';
import { UserType } from '../users/types';
import { WorkspaceType } from '../workspaces/types';
import { FeatureConfigType, getFeature } from './feature';
import { FeatureKind, FeatureType } from './types';
@@ -21,7 +22,14 @@ export class FeatureService {
});
return features.reduce(
(acc, feature) => {
acc[feature.feature] = feature.version;
// only keep the latest version
if (acc[feature.feature]) {
if (acc[feature.feature] < feature.version) {
acc[feature.feature] = feature.version;
}
} else {
acc[feature.feature] = feature.version;
}
return acc;
},
{} as Record<string, number>
@@ -47,6 +55,8 @@ export class FeatureService {
return undefined;
}
// ======== User Features ========
async addUserFeature(
userId: string,
feature: FeatureType,
@@ -169,7 +179,7 @@ export class FeatureService {
.then(users => users.map(user => user.user));
}
async hasFeature(userId: string, feature: FeatureType) {
async hasUserFeature(userId: string, feature: FeatureType) {
return this.prisma.userFeatures
.count({
where: {
@@ -183,4 +193,142 @@ export class FeatureService {
})
.then(count => count > 0);
}
// ======== Workspace Features ========
async addWorkspaceFeature(
workspaceId: string,
feature: FeatureType,
version: number,
reason: string,
expiredAt?: Date | string
) {
return this.prisma.$transaction(async tx => {
const latestFlag = await tx.workspaceFeatures.findFirst({
where: {
workspaceId,
feature: {
feature,
type: FeatureKind.Feature,
},
activated: true,
},
orderBy: {
createdAt: 'desc',
},
});
if (latestFlag) {
return latestFlag.id;
} else {
return tx.workspaceFeatures
.create({
data: {
reason,
expiredAt,
activated: true,
workspace: {
connect: {
id: workspaceId,
},
},
feature: {
connect: {
feature_version: {
feature,
version,
},
type: FeatureKind.Feature,
},
},
},
})
.then(r => r.id);
}
});
}
async removeWorkspaceFeature(workspaceId: string, feature: FeatureType) {
return this.prisma.workspaceFeatures
.updateMany({
where: {
workspaceId,
feature: {
feature,
type: FeatureKind.Feature,
},
activated: true,
},
data: {
activated: false,
},
})
.then(r => r.count);
}
async getWorkspaceFeatures(workspaceId: string) {
const features = await this.prisma.workspaceFeatures.findMany({
where: {
workspace: { id: workspaceId },
feature: {
type: FeatureKind.Feature,
},
},
select: {
activated: true,
reason: true,
createdAt: true,
expiredAt: true,
featureId: true,
},
});
const configs = await Promise.all(
features.map(async feature => ({
...feature,
feature: await getFeature(this.prisma, feature.featureId),
}))
);
return configs.filter(feature => !!feature.feature);
}
async listFeatureWorkspaces(
feature: FeatureType
): Promise<Omit<WorkspaceType, 'members'>[]> {
return this.prisma.workspaceFeatures
.findMany({
where: {
activated: true,
feature: {
feature: feature,
type: FeatureKind.Feature,
},
},
select: {
workspace: {
select: {
id: true,
public: true,
createdAt: true,
},
},
},
})
.then(wss => wss.map(ws => ws.workspace));
}
async hasWorkspaceFeature(workspaceId: string, feature: FeatureType) {
return this.prisma.workspaceFeatures
.count({
where: {
workspaceId,
activated: true,
feature: {
feature,
type: FeatureKind.Feature,
},
},
})
.then(count => count > 0);
}
}