Skip to content

Commit

Permalink
feat: create workspace invitation
Browse files Browse the repository at this point in the history
  • Loading branch information
apsantiso committed Mar 20, 2024
1 parent 5b3e593 commit 292b754
Show file tree
Hide file tree
Showing 10 changed files with 344 additions and 1 deletion.
53 changes: 53 additions & 0 deletions migrations/20240319130531-create-workspace-invites-table.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
'use strict';

const tableName = 'workspaces_invites';

module.exports = {
up: async (queryInterface, Sequelize) => {
await queryInterface.createTable(tableName, {
id: {
type: Sequelize.UUID,
defaultValue: Sequelize.UUIDV4,
allowNull: false,
primaryKey: true,
},
workspace_id: {
type: Sequelize.UUID,
allowNull: false,
references: {
model: 'workspaces',
key: 'id',
},
},
invited_user: {
type: Sequelize.UUID,
allowNull: false,
},
encryption_algorithm: {
type: Sequelize.STRING,
allowNull: false,
},
encryption_key: {
type: Sequelize.STRING(800),
allowNull: false,
},
space_limit: {
type: Sequelize.BIGINT.UNSIGNED,
allowNull: false,
},
created_at: {
type: Sequelize.DATE,
allowNull: false,
defaultValue: Sequelize.NOW,
},
updated_at: {
type: Sequelize.DATE,
allowNull: false,
defaultValue: Sequelize.NOW,
},
});
},
down: async (queryInterface) => {
await queryInterface.dropTable(tableName);
},
};
1 change: 1 addition & 0 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ async function bootstrap() {
'internxt-mnemonic',
'x-share-password',
'X-Internxt-Captcha',
'*',
],
exposedHeaders: ['sessionId'],
origin: '*',
Expand Down
10 changes: 10 additions & 0 deletions src/modules/workspaces/attributes/workspace-invite.attribute.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export interface WorkspaceInviteAttributes {
id: string;
workspaceId: string;
invitedUser: string;
encryptionAlgorithm: string;
encryptionKey: string;
spaceLimit: bigint;
createdAt: Date;
updatedAt: Date;
}
49 changes: 49 additions & 0 deletions src/modules/workspaces/domains/workspace-invite.domain.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { WorkspaceInviteAttributes } from '../attributes/workspace-invite.attribute';

export class WorkspaceInvite implements WorkspaceInviteAttributes {
id: string;
workspaceId: string;
invitedUser: string;
encryptionAlgorithm: string;
encryptionKey: string;
spaceLimit: bigint;
createdAt: Date;
updatedAt: Date;

constructor({
id,
workspaceId,
invitedUser,
encryptionAlgorithm,
encryptionKey,
spaceLimit,
createdAt,
updatedAt,
}: WorkspaceInviteAttributes) {
this.id = id;
this.workspaceId = workspaceId;
this.invitedUser = invitedUser;
this.encryptionAlgorithm = encryptionAlgorithm;
this.encryptionKey = encryptionKey;
this.spaceLimit = spaceLimit;
this.createdAt = createdAt;
this.updatedAt = updatedAt;
}

static build(attributes: WorkspaceInviteAttributes): WorkspaceInvite {
return new WorkspaceInvite(attributes);
}

toJSON() {
return {
id: this.id,
workspaceId: this.workspaceId,
invitedUser: this.invitedUser,
encryptionAlgorithm: this.encryptionAlgorithm,
encryptionKey: this.encryptionKey,
spaceLimit: this.spaceLimit.toString(),
createdAt: this.createdAt,
updatedAt: this.updatedAt,
};
}
}
36 changes: 36 additions & 0 deletions src/modules/workspaces/dto/create-workspace-invite.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsNotEmpty, IsPositive } from 'class-validator';
import { User } from '../../user/user.domain';
import { WorkspaceInvite } from '../domains/workspace-invite.domain';

export class CreateWorkspaceInviteDto {
@ApiProperty({
example: '[email protected]',
description: 'The email of the user you want to invite',
})
@IsNotEmpty()
invitedUser: User['email'];

@ApiProperty({
example: '1073741824',
description: 'Space assigned to user in bytes',
})
@IsNotEmpty()
@IsPositive()
spaceLimit: WorkspaceInvite['spaceLimit'];

@ApiProperty({
example: 'encrypted encryption key',
description:
"Owner's encryption key encrypted with the invited user's public key.",
})
@IsNotEmpty()
encryptionKey: WorkspaceInvite['encryptionKey'];

@ApiProperty({
example: 'aes-256-gcm',
description: 'Encryption algorithm used to encrypt the encryption key.',
})
@IsNotEmpty()
encryptionAlgorithm: WorkspaceInvite['encryptionAlgorithm'];
}
54 changes: 54 additions & 0 deletions src/modules/workspaces/models/workspace-invite.model.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import {
Model,
Table,
Column,
DataType,
PrimaryKey,
ForeignKey,
BelongsTo,
} from 'sequelize-typescript';
import { WorkspaceModel } from './workspace.model';
import { WorkspaceInviteAttributes } from '../attributes/workspace-invite.attribute';

@Table({
underscored: true,
timestamps: true,
tableName: 'workspaces_invites',
})
export class WorkspaceInviteModel
extends Model
implements WorkspaceInviteAttributes
{
@PrimaryKey
@Column({ type: DataType.UUID, defaultValue: DataType.UUIDV4 })
id: string;

@ForeignKey(() => WorkspaceModel)
@Column({ type: DataType.UUID, allowNull: false })
workspaceId: string;

@BelongsTo(() => WorkspaceModel, {
foreignKey: 'workspaceId',
targetKey: 'id',
as: 'workspace',
})
workspace: WorkspaceModel;

@Column({ type: DataType.UUID, allowNull: false })
invitedUser: string;

@Column({ type: DataType.STRING, allowNull: false })
encryptionAlgorithm: string;

@Column({ type: DataType.STRING, allowNull: false })
encryptionKey: string;

@Column({ type: DataType.BIGINT.UNSIGNED, allowNull: false })
spaceLimit: bigint;

@Column({ allowNull: false, defaultValue: DataType.NOW })
createdAt: Date;

@Column({ allowNull: false, defaultValue: DataType.NOW })
updatedAt: Date;
}
43 changes: 43 additions & 0 deletions src/modules/workspaces/repositories/workspaces.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ import { Workspace } from '../domains/workspaces.domain';
import { WorkspaceModel } from '../models/workspace.model';
import { WorkspaceUserModel } from '../models/workspace-users.model';
import { WorkspaceUser } from '../domains/workspace-user.domain';
import { WorkspaceInviteModel } from '../models/workspace-invite.model';
import { WorkspaceInvite } from '../domains/workspace-invite.domain';
import { WorkspaceInviteAttributes } from '../attributes/workspace-invite.attribute';

export interface WorkspaceRepository {
findById(id: WorkspaceAttributes['id']): Promise<Workspace | null>;
Expand All @@ -20,6 +23,9 @@ export interface WorkspaceRepository {
offset?: number,
): Promise<Workspace[]>;
findOne(where: Partial<WorkspaceAttributes>): Promise<Workspace | null>;
findInvite(
where: Partial<WorkspaceInviteAttributes>,
): Promise<WorkspaceInvite | null>;
updateById(
id: WorkspaceAttributes['id'],
update: Partial<WorkspaceAttributes>,
Expand All @@ -39,6 +45,8 @@ export class SequelizeWorkspaceRepository implements WorkspaceRepository {
private modelWorkspace: typeof WorkspaceModel,
@InjectModel(WorkspaceUserModel)
private modelWorkspaceUser: typeof WorkspaceUserModel,
@InjectModel(WorkspaceInviteModel)
private modelWorkspaceInvite: typeof WorkspaceInviteModel,
) {}
async findById(id: WorkspaceAttributes['id']): Promise<Workspace | null> {
const workspace = await this.modelWorkspace.findByPk(id);
Expand Down Expand Up @@ -69,6 +77,41 @@ export class SequelizeWorkspaceRepository implements WorkspaceRepository {
return workspace ? this.toDomain(workspace) : null;
}

async findInvite(
where: Partial<WorkspaceInviteAttributes>,
): Promise<WorkspaceInvite | null> {
const invite = await this.modelWorkspaceInvite.findOne({ where });

return invite ? WorkspaceInvite.build(invite) : null;
}

async getSpaceLimitInInvitations(
workspaceId: WorkspaceAttributes['id'],
): Promise<bigint> {
const totalSpaceLimit = await this.modelWorkspaceInvite.sum('spaceLimit', {
where: { workspaceId: workspaceId },
});

return BigInt(totalSpaceLimit || 0);
}

async getTotalSpaceLimitInWorkspaceUsers(
workspaceId: WorkspaceAttributes['id'],
): Promise<bigint> {
const total = await this.modelWorkspaceUser.sum('spaceLimit', {
where: { workspaceId },
});
return BigInt(total);
}

async createInvite(
invite: Omit<WorkspaceInvite, 'id'>,
): Promise<WorkspaceInvite | null> {
const raw = await this.modelWorkspaceInvite.create(invite);

return raw ? WorkspaceInvite.build(raw) : null;
}

async create(workspace: any): Promise<Workspace> {
const dbWorkspace = await this.modelWorkspace.create(workspace);
return this.toDomain(dbWorkspace);
Expand Down
14 changes: 14 additions & 0 deletions src/modules/workspaces/workspaces.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
WorkspaceRequiredAccess,
WorkspaceRole,
} from './guards/workspace-required-access.decorator';
import { CreateWorkspaceInviteDto } from './dto/create-workspace-invite.dto';

@ApiTags('Workspaces')
@Controller('workspaces')
Expand All @@ -37,6 +38,19 @@ export class WorkspacesController {
throw new NotImplementedException();
}

@Post('/:workspaceId/members/invite')
@UseGuards(WorkspaceGuard)
@WorkspaceRequiredAccess(AccessContext.WORKSPACE, WorkspaceRole.OWNER)
async inviteUsersToWorkspace(
@Param('workspaceId') workspaceId: WorkspaceAttributes['id'],
@Body() createInviteDto: CreateWorkspaceInviteDto,
) {
return this.workspaceUseCases.inviteUserToWorkspace(
workspaceId,
createInviteDto,
);
}

@Post('/:workspaceId/teams')
@ApiOperation({
summary: 'Creates a team in a workspace',
Expand Down
2 changes: 2 additions & 0 deletions src/modules/workspaces/workspaces.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { BridgeModule } from '../../externals/bridge/bridge.module';
import { WorkspaceTeamModel } from './models/workspace-team.model';
import { WorkspaceTeamUserModel } from './models/workspace-team-users.model';
import { WorkspaceGuard } from './guards/workspaces.guard';
import { WorkspaceInviteModel } from './models/workspace-invite.model';

@Module({
imports: [
Expand All @@ -21,6 +22,7 @@ import { WorkspaceGuard } from './guards/workspaces.guard';
WorkspaceTeamModel,
WorkspaceTeamUserModel,
WorkspaceUserModel,
WorkspaceInviteModel,
]),
UserModule,
BridgeModule,
Expand Down
Loading

0 comments on commit 292b754

Please sign in to comment.