feat: add workspace experimental features api (#5525)

This commit is contained in:
DarkSky
2024-01-06 11:04:49 +00:00
parent 9650a5a6a1
commit 443908da22
10 changed files with 259 additions and 59 deletions
@@ -19,7 +19,13 @@ import { Auth, CurrentUser, Public, Publicable } from '../auth/guard';
import { FeatureManagementService } from '../features';
import { QuotaService } from '../quota';
import { AvatarStorage } from '../storage';
import { DeleteAccount, RemoveAvatar, UserQuotaType, UserType } from './types';
import {
DeleteAccount,
RemoveAvatar,
UserOrLimitedUser,
UserQuotaType,
UserType,
} from './types';
import { UsersService } from './users';
/**
@@ -77,14 +83,17 @@ export class UserResolver {
ttl: 60,
},
})
@Query(() => UserType, {
@Query(() => UserOrLimitedUser, {
name: 'user',
description: 'Get user by email',
nullable: true,
})
@Public()
async user(@Args('email') email: string) {
if (!(await this.feature.canEarlyAccess(email))) {
async user(
@CurrentUser() currentUser?: UserType,
@Args('email') email?: string
) {
if (!email || !(await this.feature.canEarlyAccess(email))) {
return new GraphQLError(
`You don't have early access permission\nVisit https://community.affine.pro/c/insider-general/ for more information`,
{
@@ -95,13 +104,16 @@ export class UserResolver {
}
);
}
// TODO: need to limit a user can only get another user witch is in the same workspace
const user = await this.users.findUserByEmail(email);
if (user?.password) {
const userResponse: UserType = user;
userResponse.hasPassword = true;
}
return user;
if (currentUser) return user;
// only return limited info when not logged in
return {
email: user?.email,
hasPassword: !!user?.password,
};
}
@Throttle({ default: { limit: 10, ttl: 60 } })
@@ -1,4 +1,4 @@
import { Field, Float, ID, ObjectType } from '@nestjs/graphql';
import { createUnionType, Field, Float, ID, ObjectType } from '@nestjs/graphql';
import type { User } from '@prisma/client';
@ObjectType('UserQuotaHumanReadable')
@@ -67,6 +67,29 @@ export class UserType implements Partial<User> {
hasPassword?: boolean;
}
@ObjectType()
export class LimitedUserType implements Partial<User> {
@Field({ description: 'User email' })
email!: string;
@Field(() => Boolean, {
description: 'User password has been set',
nullable: true,
})
hasPassword?: boolean;
}
export const UserOrLimitedUser = createUnionType({
name: 'UserOrLimitedUser',
types: () => [UserType, LimitedUserType] as const,
resolveType(value) {
if (value.id) {
return UserType;
}
return LimitedUserType;
},
});
@ObjectType()
export class DeleteAccount {
@Field()
@@ -13,13 +13,17 @@ import { CloudThrottlerGuard, Throttle } from '../../throttler';
import { Auth, CurrentUser } from '../auth';
import { FeatureManagementService, FeatureType } from '../features';
import { UserType } from '../users';
import { PermissionService } from './permission';
import { WorkspaceType } from './types';
@UseGuards(CloudThrottlerGuard)
@Auth()
@Resolver(() => WorkspaceType)
export class WorkspaceManagementResolver {
constructor(private readonly feature: FeatureManagementService) {}
constructor(
private readonly feature: FeatureManagementService,
private readonly permission: PermissionService
) {}
@Throttle({
default: {
@@ -77,6 +81,51 @@ export class WorkspaceManagementResolver {
return this.feature.listFeatureWorkspaces(feature);
}
@Mutation(() => Boolean)
async setWorkspaceExperimentalFeature(
@CurrentUser() user: UserType,
@Args('workspaceId') workspaceId: string,
@Args('feature', { type: () => FeatureType }) feature: FeatureType,
@Args('enable') enable: boolean
): Promise<boolean> {
if (!(await this.feature.canEarlyAccess(user.email))) {
throw new ForbiddenException('You are not allowed to do this');
}
const owner = await this.permission.getWorkspaceOwner(workspaceId);
const availableFeatures = await this.availableFeatures(user);
if (owner.user.id !== user.id || !availableFeatures.includes(feature)) {
throw new ForbiddenException('You are not allowed to do this');
}
if (enable) {
return await this.feature
.addWorkspaceFeatures(
workspaceId,
feature,
undefined,
'add by experimental feature api'
)
.then(id => id > 0);
} else {
return await this.feature.removeWorkspaceFeature(workspaceId, feature);
}
}
@ResolveField(() => [FeatureType], {
description: 'Available features of workspace',
complexity: 2,
})
async availableFeatures(
@CurrentUser() user: UserType
): Promise<FeatureType[]> {
if (await this.feature.canEarlyAccess(user.email)) {
return [FeatureType.Copilot];
} else {
return [];
}
}
@ResolveField(() => [FeatureType], {
description: 'Enabled features of workspace',
complexity: 2,