Skip to content

Commit

Permalink
Merge pull request #292 from internxt/feat/invite-users-to-org
Browse files Browse the repository at this point in the history
[PB-1551]: Feat/invite users to org
  • Loading branch information
apsantiso authored Apr 9, 2024
2 parents 5b3e593 + 02c8c2f commit 0304f22
Show file tree
Hide file tree
Showing 16 changed files with 1,141 additions and 3 deletions.
5 changes: 4 additions & 1 deletion .env.template
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,7 @@ STRIPE_SK_TEST=sk_test_y
GATEWAY_USER=user
GATEWAY_PASS=gatewaypass

PAYMENTS_API_URL=http://host.docker.internal:8003
PAYMENTS_API_URL=http://host.docker.internal:8003

WORKSPACES_USER_INVITATION_EMAIL_ID=d-de1ed6df4a9947129c0bf592c808b58d
WORKSPACES_GUEST_USER_INVITATION_EMAIL_ID=d-41b4608fc94a41bca65aab7ed6ccad15
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);
},
};
4 changes: 4 additions & 0 deletions src/config/configuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,10 @@ export default () => ({
process.env.SENDGRID_TEMPLATE_DRIVE_UPDATE_USER_EMAIL || '',
unblockAccountEmail:
process.env.SENDGRID_TEMPLATE_DRIVE_UNBLOCK_ACCOUNT || '',
invitationToWorkspaceUser:
process.env.WORKSPACES_USER_INVITATION_EMAIL_ID || '',
invitationToWorkspaceGuestUser:
process.env.WORKSPACES_GUEST_USER_INVITATION_EMAIL_ID || '',
},
},
newsletter: {
Expand Down
57 changes: 57 additions & 0 deletions src/externals/mailer/mailer.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import sendgrid from '@sendgrid/mail';
import { User } from '../../modules/user/user.domain';
import { Folder } from '../../modules/folder/folder.domain';
import { File } from '../../modules/file/file.domain';
import { Workspace } from '../../modules/workspaces/domains/workspaces.domain';

type SendInvitationToSharingContext = {
notification_message: string;
Expand Down Expand Up @@ -116,6 +117,62 @@ export class MailerService {
);
}

async sendWorkspaceUserInvitation(
senderName: User['name'],
invitedUserEmail: User['email'],
workspaceName: Workspace['name'],
mailInfo: {
acceptUrl: string;
declineUrl: string;
},
avatar?: {
pictureUrl: string;
initials: string;
},
): Promise<void> {
const context = {
sender_name: senderName,
workspace_name: workspaceName,
avatar: {
picture_url: avatar.pictureUrl,
initials: avatar.initials,
},
signup_url: mailInfo.acceptUrl,
decline_url: mailInfo.declineUrl,
};
await this.send(
invitedUserEmail,
this.configService.get('mailer.templates.invitationToWorkspaceUser'),
context,
);
}

async sendWorkspaceUserExternalInvitation(
senderName: User['name'],
invitedUserEmail: User['email'],
workspaceName: Workspace['name'],
signUpUrl: string,
avatar?: {
pictureUrl: string;
initials: string;
},
): Promise<void> {
const context = {
sender_name: senderName,
workspace_name: workspaceName,
avatar: {
picture_url: avatar.pictureUrl,
initials: avatar.initials,
},
signup_url: signUpUrl,
};
await this.send(
invitedUserEmail,
this.configService.get('mailer.templates.invitationToWorkspaceGuestUser'),
context,
);
}

async sendRemovedFromSharingEmail(
userRemovedFromSharingEmail: User['email'],
itemName: File['plainName'] | Folder['plainName'],
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;
}
Loading

0 comments on commit 0304f22

Please sign in to comment.