mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-08-02 01:49:51 +08:00
feat(server): improve team invite (#9092)
This commit is contained in:
@@ -1,3 +1,5 @@
|
||||
import { pick } from 'lodash-es';
|
||||
|
||||
import { PrismaTransaction } from '../../fundamentals';
|
||||
import { formatDate, formatSize, Quota, QuotaSchema } from './types';
|
||||
|
||||
@@ -5,7 +7,7 @@ const QuotaCache = new Map<number, QuotaConfig>();
|
||||
|
||||
export class QuotaConfig {
|
||||
readonly config: Quota;
|
||||
readonly override?: Quota['configs'];
|
||||
readonly override?: Partial<Quota['configs']>;
|
||||
|
||||
static async get(tx: PrismaTransaction, featureId: number) {
|
||||
const cachedQuota = QuotaCache.get(featureId);
|
||||
@@ -49,7 +51,10 @@ export class QuotaConfig {
|
||||
configs: Object.assign({}, config.data.configs, override),
|
||||
});
|
||||
if (overrideConfig.success) {
|
||||
this.override = overrideConfig.data.configs;
|
||||
this.override = pick(
|
||||
overrideConfig.data.configs,
|
||||
Object.keys(override)
|
||||
);
|
||||
} else {
|
||||
throw new Error(
|
||||
`Invalid quota override config: ${override.error.message}, ${JSON.stringify(
|
||||
|
||||
@@ -280,7 +280,7 @@ export class QuotaService {
|
||||
.findFirst({
|
||||
where: {
|
||||
workspaceId,
|
||||
feature: { feature: type, type: FeatureKind.Feature },
|
||||
feature: { feature: type, type: FeatureKind.Quota },
|
||||
activated: true,
|
||||
},
|
||||
select: { configs: true },
|
||||
|
||||
@@ -191,7 +191,7 @@ export class QuotaManagementService {
|
||||
FeatureType.UnlimitedWorkspace
|
||||
);
|
||||
|
||||
const quota = {
|
||||
const quota: QuotaBusinessType = {
|
||||
name,
|
||||
blobLimit,
|
||||
businessBlobLimit,
|
||||
|
||||
@@ -16,12 +16,14 @@ import {
|
||||
NotInSpace,
|
||||
RequestMutex,
|
||||
TooManyRequest,
|
||||
URLHelper,
|
||||
} from '../../../fundamentals';
|
||||
import { CurrentUser } from '../../auth';
|
||||
import { Permission, PermissionService } from '../../permission';
|
||||
import { QuotaManagementService } from '../../quota';
|
||||
import { UserService } from '../../user';
|
||||
import {
|
||||
InviteLink,
|
||||
InviteResult,
|
||||
WorkspaceInviteLinkExpireTime,
|
||||
WorkspaceType,
|
||||
@@ -41,6 +43,7 @@ export class TeamWorkspaceResolver {
|
||||
private readonly cache: Cache,
|
||||
private readonly event: EventEmitter,
|
||||
private readonly mailer: MailService,
|
||||
private readonly url: URLHelper,
|
||||
private readonly prisma: PrismaClient,
|
||||
private readonly permissions: PermissionService,
|
||||
private readonly users: UserService,
|
||||
@@ -71,6 +74,10 @@ export class TeamWorkspaceResolver {
|
||||
Permission.Admin
|
||||
);
|
||||
|
||||
if (emails.length > 512) {
|
||||
return new TooManyRequest();
|
||||
}
|
||||
|
||||
// lock to prevent concurrent invite
|
||||
const lockFlag = `invite:${workspaceId}`;
|
||||
await using lock = await this.mutex.lock(lockFlag);
|
||||
@@ -150,8 +157,27 @@ export class TeamWorkspaceResolver {
|
||||
return results;
|
||||
}
|
||||
|
||||
@ResolveField(() => InviteLink, {
|
||||
description: 'invite link for workspace',
|
||||
nullable: true,
|
||||
})
|
||||
async inviteLink(@Parent() workspace: WorkspaceType) {
|
||||
const cacheId = `workspace:inviteLink:${workspace.id}`;
|
||||
const id = await this.cache.get<{ inviteId: string }>(cacheId);
|
||||
if (id) {
|
||||
const expireTime = await this.cache.ttl(cacheId);
|
||||
if (Number.isSafeInteger(expireTime)) {
|
||||
return {
|
||||
link: this.url.link(`/invite/${id.inviteId}`),
|
||||
expireTime: new Date(Date.now() + expireTime),
|
||||
};
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Mutation(() => String)
|
||||
async inviteLink(
|
||||
async createInviteLink(
|
||||
@CurrentUser() user: CurrentUser,
|
||||
@Args('workspaceId') workspaceId: string,
|
||||
@Args('expireTime', { type: () => WorkspaceInviteLinkExpireTime })
|
||||
@@ -171,7 +197,11 @@ export class TeamWorkspaceResolver {
|
||||
const inviteId = nanoid();
|
||||
const cacheInviteId = `workspace:inviteLinkId:${inviteId}`;
|
||||
await this.cache.set(cacheWorkspaceId, { inviteId }, { ttl: expireTime });
|
||||
await this.cache.set(cacheInviteId, { workspaceId }, { ttl: expireTime });
|
||||
await this.cache.set(
|
||||
cacheInviteId,
|
||||
{ workspaceId, inviteeUserId: user.id },
|
||||
{ ttl: expireTime }
|
||||
);
|
||||
return inviteId;
|
||||
}
|
||||
|
||||
|
||||
@@ -485,12 +485,15 @@ export class WorkspaceResolver {
|
||||
})
|
||||
async getInviteInfo(@Args('inviteId') inviteId: string) {
|
||||
let workspaceId = null;
|
||||
let invitee = null;
|
||||
// invite link
|
||||
const invite = await this.cache.get<{ workspaceId: string }>(
|
||||
`workspace:inviteLinkId:${inviteId}`
|
||||
);
|
||||
const invite = await this.cache.get<{
|
||||
workspaceId: string;
|
||||
inviteeUserId: string;
|
||||
}>(`workspace:inviteLinkId:${inviteId}`);
|
||||
if (typeof invite?.workspaceId === 'string') {
|
||||
workspaceId = invite.workspaceId;
|
||||
invitee = { user: await this.users.findUserById(invite.inviteeUserId) };
|
||||
}
|
||||
if (!workspaceId) {
|
||||
workspaceId = await this.prisma.workspaceUserPermission
|
||||
@@ -508,10 +511,13 @@ export class WorkspaceResolver {
|
||||
const workspaceContent = await this.doc.getWorkspaceContent(workspaceId);
|
||||
|
||||
const owner = await this.permissions.getWorkspaceOwner(workspaceId);
|
||||
const invitee = await this.permissions.getWorkspaceInvitation(
|
||||
inviteId,
|
||||
workspaceId
|
||||
);
|
||||
|
||||
if (!invitee) {
|
||||
invitee = await this.permissions.getWorkspaceInvitation(
|
||||
inviteId,
|
||||
workspaceId
|
||||
);
|
||||
}
|
||||
|
||||
let avatar = '';
|
||||
if (workspaceContent?.avatarKey) {
|
||||
|
||||
@@ -115,6 +115,15 @@ export class UpdateWorkspaceInput extends PickType(
|
||||
id!: string;
|
||||
}
|
||||
|
||||
@ObjectType()
|
||||
export class InviteLink {
|
||||
@Field(() => String, { description: 'Invite link' })
|
||||
link!: string;
|
||||
|
||||
@Field(() => Date, { description: 'Invite link expire time' })
|
||||
expireTime!: Date;
|
||||
}
|
||||
|
||||
@ObjectType()
|
||||
export class InviteResult {
|
||||
@Field(() => String)
|
||||
|
||||
Reference in New Issue
Block a user