mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-15 05:37:32 +00:00
feat: add copilot feature type (#5465)
This commit is contained in:
@@ -19,7 +19,20 @@ class FeatureConfig {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class CopilotFeatureConfig extends FeatureConfig {
|
||||||
|
override config!: Feature & { feature: FeatureType.Copilot };
|
||||||
|
constructor(data: any) {
|
||||||
|
super(data);
|
||||||
|
|
||||||
|
if (this.config.feature !== FeatureType.Copilot) {
|
||||||
|
throw new Error('Invalid feature config: type is not Copilot');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export class EarlyAccessFeatureConfig extends FeatureConfig {
|
export class EarlyAccessFeatureConfig extends FeatureConfig {
|
||||||
|
override config!: Feature & { feature: FeatureType.EarlyAccess };
|
||||||
|
|
||||||
constructor(data: any) {
|
constructor(data: any) {
|
||||||
super(data);
|
super(data);
|
||||||
|
|
||||||
@@ -39,13 +52,15 @@ export class EarlyAccessFeatureConfig extends FeatureConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const FeatureConfigMap = {
|
const FeatureConfigMap = {
|
||||||
|
[FeatureType.Copilot]: CopilotFeatureConfig,
|
||||||
[FeatureType.EarlyAccess]: EarlyAccessFeatureConfig,
|
[FeatureType.EarlyAccess]: EarlyAccessFeatureConfig,
|
||||||
};
|
};
|
||||||
|
|
||||||
const FeatureCache = new Map<
|
export type FeatureConfigType<F extends FeatureType> = InstanceType<
|
||||||
number,
|
(typeof FeatureConfigMap)[F]
|
||||||
InstanceType<(typeof FeatureConfigMap)[FeatureType]>
|
>;
|
||||||
>();
|
|
||||||
|
const FeatureCache = new Map<number, FeatureConfigType<FeatureType>>();
|
||||||
|
|
||||||
export async function getFeature(prisma: PrismaService, featureId: number) {
|
export async function getFeature(prisma: PrismaService, featureId: number) {
|
||||||
const cachedQuota = FeatureCache.get(featureId);
|
const cachedQuota = FeatureCache.get(featureId);
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { Injectable } from '@nestjs/common';
|
|||||||
|
|
||||||
import { PrismaService } from '../../prisma';
|
import { PrismaService } from '../../prisma';
|
||||||
import { UserType } from '../users/types';
|
import { UserType } from '../users/types';
|
||||||
import { getFeature } from './feature';
|
import { FeatureConfigType, getFeature } from './feature';
|
||||||
import { FeatureKind, FeatureType } from './types';
|
import { FeatureKind, FeatureType } from './types';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
@@ -28,7 +28,9 @@ export class FeatureService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getFeature(feature: FeatureType) {
|
async getFeature<F extends FeatureType>(
|
||||||
|
feature: F
|
||||||
|
): Promise<FeatureConfigType<F> | undefined> {
|
||||||
const data = await this.prisma.features.findFirst({
|
const data = await this.prisma.features.findFirst({
|
||||||
where: {
|
where: {
|
||||||
feature,
|
feature,
|
||||||
@@ -40,7 +42,7 @@ export class FeatureService {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
if (data) {
|
if (data) {
|
||||||
return getFeature(this.prisma, data.id);
|
return getFeature(this.prisma, data.id) as FeatureConfigType<F>;
|
||||||
}
|
}
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
export enum FeatureType {
|
||||||
|
Copilot = 'copilot',
|
||||||
|
EarlyAccess = 'early_access',
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
import { FeatureType } from './common';
|
||||||
|
|
||||||
|
export const featureCopilot = z.object({
|
||||||
|
feature: z.literal(FeatureType.Copilot),
|
||||||
|
configs: z.object({}),
|
||||||
|
});
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
import { URL } from 'node:url';
|
||||||
|
|
||||||
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
import { FeatureType } from './common';
|
||||||
|
|
||||||
|
function checkHostname(host: string) {
|
||||||
|
try {
|
||||||
|
return new URL(`https://${host}`).hostname === host;
|
||||||
|
} catch (_) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const featureEarlyAccess = z.object({
|
||||||
|
feature: z.literal(FeatureType.EarlyAccess),
|
||||||
|
configs: z.object({
|
||||||
|
whitelist: z
|
||||||
|
.string()
|
||||||
|
.startsWith('@')
|
||||||
|
.refine(domain => checkHostname(domain.slice(1)))
|
||||||
|
.array(),
|
||||||
|
}),
|
||||||
|
});
|
||||||
@@ -1,7 +1,9 @@
|
|||||||
import { URL } from 'node:url';
|
|
||||||
|
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
import { FeatureType } from './common';
|
||||||
|
import { featureCopilot } from './copilot';
|
||||||
|
import { featureEarlyAccess } from './early-access';
|
||||||
|
|
||||||
/// ======== common schema ========
|
/// ======== common schema ========
|
||||||
|
|
||||||
export enum FeatureKind {
|
export enum FeatureKind {
|
||||||
@@ -20,30 +22,13 @@ export type CommonFeature = z.infer<typeof commonFeatureSchema>;
|
|||||||
|
|
||||||
/// ======== feature define ========
|
/// ======== feature define ========
|
||||||
|
|
||||||
export enum FeatureType {
|
|
||||||
EarlyAccess = 'early_access',
|
|
||||||
}
|
|
||||||
|
|
||||||
function checkHostname(host: string) {
|
|
||||||
try {
|
|
||||||
return new URL(`https://${host}`).hostname === host;
|
|
||||||
} catch (_) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const featureEarlyAccess = z.object({
|
|
||||||
feature: z.literal(FeatureType.EarlyAccess),
|
|
||||||
configs: z.object({
|
|
||||||
whitelist: z
|
|
||||||
.string()
|
|
||||||
.startsWith('@')
|
|
||||||
.refine(domain => checkHostname(domain.slice(1)))
|
|
||||||
.array(),
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
export const Features: Feature[] = [
|
export const Features: Feature[] = [
|
||||||
|
{
|
||||||
|
feature: FeatureType.Copilot,
|
||||||
|
type: FeatureKind.Feature,
|
||||||
|
version: 1,
|
||||||
|
configs: {},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
feature: FeatureType.EarlyAccess,
|
feature: FeatureType.EarlyAccess,
|
||||||
type: FeatureKind.Feature,
|
type: FeatureKind.Feature,
|
||||||
@@ -60,6 +45,8 @@ export const FeatureSchema = commonFeatureSchema
|
|||||||
.extend({
|
.extend({
|
||||||
type: z.literal(FeatureKind.Feature),
|
type: z.literal(FeatureKind.Feature),
|
||||||
})
|
})
|
||||||
.and(z.discriminatedUnion('feature', [featureEarlyAccess]));
|
.and(z.discriminatedUnion('feature', [featureCopilot, featureEarlyAccess]));
|
||||||
|
|
||||||
export type Feature = z.infer<typeof FeatureSchema>;
|
export type Feature = z.infer<typeof FeatureSchema>;
|
||||||
|
|
||||||
|
export { FeatureType };
|
||||||
@@ -27,4 +27,5 @@ import {
|
|||||||
exports: [PermissionService],
|
exports: [PermissionService],
|
||||||
})
|
})
|
||||||
export class WorkspaceModule {}
|
export class WorkspaceModule {}
|
||||||
export { InvitationType, WorkspaceType } from './resolvers';
|
|
||||||
|
export type { InvitationType, WorkspaceType } from './types';
|
||||||
|
|||||||
@@ -34,6 +34,9 @@ export class PermissionService {
|
|||||||
accepted: true,
|
accepted: true,
|
||||||
type: Permission.Owner,
|
type: Permission.Owner,
|
||||||
},
|
},
|
||||||
|
select: {
|
||||||
|
workspaceId: true,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
.then(data => data.map(({ workspaceId }) => workspaceId));
|
.then(data => data.map(({ workspaceId }) => workspaceId));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,8 +19,7 @@ import { QuotaManagementService } from '../../quota';
|
|||||||
import { WorkspaceBlobStorage } from '../../storage';
|
import { WorkspaceBlobStorage } from '../../storage';
|
||||||
import { UserType } from '../../users';
|
import { UserType } from '../../users';
|
||||||
import { PermissionService } from '../permission';
|
import { PermissionService } from '../permission';
|
||||||
import { Permission } from '../types';
|
import { Permission, WorkspaceBlobSizes, WorkspaceType } from '../types';
|
||||||
import { WorkspaceBlobSizes, WorkspaceType } from './workspace';
|
|
||||||
|
|
||||||
@UseGuards(CloudThrottlerGuard)
|
@UseGuards(CloudThrottlerGuard)
|
||||||
@Auth()
|
@Auth()
|
||||||
|
|||||||
@@ -18,8 +18,7 @@ import { Auth, CurrentUser } from '../../auth';
|
|||||||
import { DocHistoryManager } from '../../doc/history';
|
import { DocHistoryManager } from '../../doc/history';
|
||||||
import { UserType } from '../../users';
|
import { UserType } from '../../users';
|
||||||
import { PermissionService } from '../permission';
|
import { PermissionService } from '../permission';
|
||||||
import { Permission } from '../types';
|
import { Permission, WorkspaceType } from '../types';
|
||||||
import { WorkspaceType } from './workspace';
|
|
||||||
|
|
||||||
@ObjectType()
|
@ObjectType()
|
||||||
class DocHistoryType implements Partial<SnapshotHistory> {
|
class DocHistoryType implements Partial<SnapshotHistory> {
|
||||||
|
|||||||
@@ -17,8 +17,7 @@ import { DocID } from '../../../utils/doc';
|
|||||||
import { Auth, CurrentUser } from '../../auth';
|
import { Auth, CurrentUser } from '../../auth';
|
||||||
import { UserType } from '../../users';
|
import { UserType } from '../../users';
|
||||||
import { PermissionService, PublicPageMode } from '../permission';
|
import { PermissionService, PublicPageMode } from '../permission';
|
||||||
import { Permission } from '../types';
|
import { Permission, WorkspaceType } from '../types';
|
||||||
import { WorkspaceType } from './workspace';
|
|
||||||
|
|
||||||
registerEnumType(PublicPageMode, {
|
registerEnumType(PublicPageMode, {
|
||||||
name: 'PublicPageMode',
|
name: 'PublicPageMode',
|
||||||
|
|||||||
@@ -7,23 +7,14 @@ import {
|
|||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
import {
|
import {
|
||||||
Args,
|
Args,
|
||||||
Field,
|
|
||||||
Float,
|
|
||||||
ID,
|
|
||||||
InputType,
|
|
||||||
Int,
|
Int,
|
||||||
Mutation,
|
Mutation,
|
||||||
ObjectType,
|
|
||||||
OmitType,
|
|
||||||
Parent,
|
Parent,
|
||||||
PartialType,
|
|
||||||
PickType,
|
|
||||||
Query,
|
Query,
|
||||||
registerEnumType,
|
|
||||||
ResolveField,
|
ResolveField,
|
||||||
Resolver,
|
Resolver,
|
||||||
} from '@nestjs/graphql';
|
} from '@nestjs/graphql';
|
||||||
import type { User, Workspace } from '@prisma/client';
|
import type { User } from '@prisma/client';
|
||||||
import { getStreamAsBuffer } from 'get-stream';
|
import { getStreamAsBuffer } from 'get-stream';
|
||||||
import GraphQLUpload from 'graphql-upload/GraphQLUpload.mjs';
|
import GraphQLUpload from 'graphql-upload/GraphQLUpload.mjs';
|
||||||
import { applyUpdate, Doc } from 'yjs';
|
import { applyUpdate, Doc } from 'yjs';
|
||||||
@@ -38,91 +29,15 @@ import { AuthService } from '../../auth/service';
|
|||||||
import { WorkspaceBlobStorage } from '../../storage';
|
import { WorkspaceBlobStorage } from '../../storage';
|
||||||
import { UsersService, UserType } from '../../users';
|
import { UsersService, UserType } from '../../users';
|
||||||
import { PermissionService } from '../permission';
|
import { PermissionService } from '../permission';
|
||||||
import { Permission } from '../types';
|
import {
|
||||||
|
InvitationType,
|
||||||
|
InviteUserType,
|
||||||
|
Permission,
|
||||||
|
UpdateWorkspaceInput,
|
||||||
|
WorkspaceType,
|
||||||
|
} from '../types';
|
||||||
import { defaultWorkspaceAvatar } from '../utils';
|
import { defaultWorkspaceAvatar } from '../utils';
|
||||||
|
|
||||||
registerEnumType(Permission, {
|
|
||||||
name: 'Permission',
|
|
||||||
description: 'User permission in workspace',
|
|
||||||
});
|
|
||||||
|
|
||||||
@ObjectType()
|
|
||||||
export class InviteUserType extends OmitType(
|
|
||||||
PartialType(UserType),
|
|
||||||
['id'],
|
|
||||||
ObjectType
|
|
||||||
) {
|
|
||||||
@Field(() => ID)
|
|
||||||
id!: string;
|
|
||||||
|
|
||||||
@Field(() => Permission, { description: 'User permission in workspace' })
|
|
||||||
permission!: Permission;
|
|
||||||
|
|
||||||
@Field({ description: 'Invite id' })
|
|
||||||
inviteId!: string;
|
|
||||||
|
|
||||||
@Field({ description: 'User accepted' })
|
|
||||||
accepted!: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
@ObjectType()
|
|
||||||
export class WorkspaceType implements Partial<Workspace> {
|
|
||||||
@Field(() => ID)
|
|
||||||
id!: string;
|
|
||||||
|
|
||||||
@Field({ description: 'is Public workspace' })
|
|
||||||
public!: boolean;
|
|
||||||
|
|
||||||
@Field({ description: 'Workspace created date' })
|
|
||||||
createdAt!: Date;
|
|
||||||
|
|
||||||
@Field(() => [InviteUserType], {
|
|
||||||
description: 'Members of workspace',
|
|
||||||
})
|
|
||||||
members!: InviteUserType[];
|
|
||||||
}
|
|
||||||
|
|
||||||
@ObjectType()
|
|
||||||
export class InvitationWorkspaceType {
|
|
||||||
@Field(() => ID)
|
|
||||||
id!: string;
|
|
||||||
|
|
||||||
@Field({ description: 'Workspace name' })
|
|
||||||
name!: string;
|
|
||||||
|
|
||||||
@Field(() => String, {
|
|
||||||
// nullable: true,
|
|
||||||
description: 'Base64 encoded avatar',
|
|
||||||
})
|
|
||||||
avatar!: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
@ObjectType()
|
|
||||||
export class WorkspaceBlobSizes {
|
|
||||||
@Field(() => Float)
|
|
||||||
size!: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
@ObjectType()
|
|
||||||
export class InvitationType {
|
|
||||||
@Field({ description: 'Workspace information' })
|
|
||||||
workspace!: InvitationWorkspaceType;
|
|
||||||
@Field({ description: 'User information' })
|
|
||||||
user!: UserType;
|
|
||||||
@Field({ description: 'Invitee information' })
|
|
||||||
invitee!: UserType;
|
|
||||||
}
|
|
||||||
|
|
||||||
@InputType()
|
|
||||||
export class UpdateWorkspaceInput extends PickType(
|
|
||||||
PartialType(WorkspaceType),
|
|
||||||
['public'],
|
|
||||||
InputType
|
|
||||||
) {
|
|
||||||
@Field(() => ID)
|
|
||||||
id!: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Workspace resolver
|
* Workspace resolver
|
||||||
* Public apis rate limit: 10 req/m
|
* Public apis rate limit: 10 req/m
|
||||||
|
|||||||
@@ -1,6 +1,103 @@
|
|||||||
|
import {
|
||||||
|
Field,
|
||||||
|
Float,
|
||||||
|
ID,
|
||||||
|
InputType,
|
||||||
|
ObjectType,
|
||||||
|
OmitType,
|
||||||
|
PartialType,
|
||||||
|
PickType,
|
||||||
|
registerEnumType,
|
||||||
|
} from '@nestjs/graphql';
|
||||||
|
import type { Workspace } from '@prisma/client';
|
||||||
|
|
||||||
|
import { UserType } from '../users/types';
|
||||||
|
|
||||||
export enum Permission {
|
export enum Permission {
|
||||||
Read = 0,
|
Read = 0,
|
||||||
Write = 1,
|
Write = 1,
|
||||||
Admin = 10,
|
Admin = 10,
|
||||||
Owner = 99,
|
Owner = 99,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
registerEnumType(Permission, {
|
||||||
|
name: 'Permission',
|
||||||
|
description: 'User permission in workspace',
|
||||||
|
});
|
||||||
|
|
||||||
|
@ObjectType()
|
||||||
|
export class InviteUserType extends OmitType(
|
||||||
|
PartialType(UserType),
|
||||||
|
['id'],
|
||||||
|
ObjectType
|
||||||
|
) {
|
||||||
|
@Field(() => ID)
|
||||||
|
id!: string;
|
||||||
|
|
||||||
|
@Field(() => Permission, { description: 'User permission in workspace' })
|
||||||
|
permission!: Permission;
|
||||||
|
|
||||||
|
@Field({ description: 'Invite id' })
|
||||||
|
inviteId!: string;
|
||||||
|
|
||||||
|
@Field({ description: 'User accepted' })
|
||||||
|
accepted!: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
@ObjectType()
|
||||||
|
export class WorkspaceType implements Partial<Workspace> {
|
||||||
|
@Field(() => ID)
|
||||||
|
id!: string;
|
||||||
|
|
||||||
|
@Field({ description: 'is Public workspace' })
|
||||||
|
public!: boolean;
|
||||||
|
|
||||||
|
@Field({ description: 'Workspace created date' })
|
||||||
|
createdAt!: Date;
|
||||||
|
|
||||||
|
@Field(() => [InviteUserType], {
|
||||||
|
description: 'Members of workspace',
|
||||||
|
})
|
||||||
|
members!: InviteUserType[];
|
||||||
|
}
|
||||||
|
|
||||||
|
@ObjectType()
|
||||||
|
export class InvitationWorkspaceType {
|
||||||
|
@Field(() => ID)
|
||||||
|
id!: string;
|
||||||
|
|
||||||
|
@Field({ description: 'Workspace name' })
|
||||||
|
name!: string;
|
||||||
|
|
||||||
|
@Field(() => String, {
|
||||||
|
// nullable: true,
|
||||||
|
description: 'Base64 encoded avatar',
|
||||||
|
})
|
||||||
|
avatar!: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
@ObjectType()
|
||||||
|
export class WorkspaceBlobSizes {
|
||||||
|
@Field(() => Float)
|
||||||
|
size!: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
@ObjectType()
|
||||||
|
export class InvitationType {
|
||||||
|
@Field({ description: 'Workspace information' })
|
||||||
|
workspace!: InvitationWorkspaceType;
|
||||||
|
@Field({ description: 'User information' })
|
||||||
|
user!: UserType;
|
||||||
|
@Field({ description: 'Invitee information' })
|
||||||
|
invitee!: UserType;
|
||||||
|
}
|
||||||
|
|
||||||
|
@InputType()
|
||||||
|
export class UpdateWorkspaceInput extends PickType(
|
||||||
|
PartialType(WorkspaceType),
|
||||||
|
['public'],
|
||||||
|
InputType
|
||||||
|
) {
|
||||||
|
@Field(() => ID)
|
||||||
|
id!: string;
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user