Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature to delete workspace #378

Open
wants to merge 46 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
fadd6e2
resolver added
robonetphy Oct 26, 2021
685d067
mutation added in defination
robonetphy Oct 26, 2021
4930cb9
api added
robonetphy Oct 26, 2021
e748e47
Merge branch 'master' into feature/delete/workspace
robonetphy Oct 26, 2021
b4ea989
Bump version up to 1.0.6
github-actions[bot] Oct 26, 2021
391dc51
Merge branch 'master' into feature/delete/workspace
robonetphy Oct 31, 2021
c724783
Bump version up to 1.0.7
github-actions[bot] Oct 31, 2021
a62bab1
delete workspace added
robonetphy Oct 31, 2021
62bce74
Merge branch 'feature/delete/workspace' of https://github.com/codex-t…
robonetphy Oct 31, 2021
3df315c
the is removed added
robonetphy Nov 3, 2021
305189b
lint removed
robonetphy Nov 3, 2021
c35832c
workspace and project removal undo
robonetphy Nov 4, 2021
d28e394
lint removed
robonetphy Nov 4, 2021
25cdd04
the logic modified
robonetphy Nov 4, 2021
30557c6
the schema modified
robonetphy Nov 4, 2021
d610da5
linting added
robonetphy Nov 4, 2021
c5e77f8
delete project collection in added
robonetphy Nov 4, 2021
f4b9d17
lint removed
robonetphy Nov 4, 2021
560fd03
remove workspace using isRemoved flag
robonetphy Nov 14, 2021
8361c89
remove project using isRemoved flag
robonetphy Nov 14, 2021
65722da
remove deletion of projectToWorkspace Relation
robonetphy Nov 14, 2021
5115652
remove workspace from user using isRemoved flag
robonetphy Nov 14, 2021
1b9b7c3
integration with the delete workspace
robonetphy Nov 14, 2021
e9b802c
lint removed
robonetphy Nov 14, 2021
6af6203
rename the functions
robonetphy Nov 19, 2021
e2f0af3
lint removed
robonetphy Nov 19, 2021
ac47016
$ne=>$exists
robonetphy Dec 7, 2021
f7f0c50
remove teamcollection dropping
robonetphy Dec 7, 2021
4e4fa39
not delete the event factories
robonetphy Dec 7, 2021
0a69fa6
removed lint and event factory
robonetphy Dec 7, 2021
df25814
Merge branch 'master' into feature/delete/workspace
robonetphy Dec 20, 2021
628a86e
Bump version up to 1.0.8
github-actions[bot] Dec 20, 2021
f116fc2
forbidden error remove and replace with return flag
robonetphy Jan 19, 2022
430e4f6
Merge branch 'master' of https://github.com/codex-team/hawk.api.nodej…
neSpecc Jan 21, 2022
bc244c2
remove isAdmin from deleteworkspace
robonetphy Jan 28, 2022
f192ccc
update data loader with not to get isRemoved filed
robonetphy Feb 1, 2022
20c75ce
join by link and invitation link function updated
robonetphy Feb 1, 2022
ee773b8
jsdocs warning resolved
robonetphy Feb 1, 2022
56cb42b
Merge branch 'master' into feature/delete/workspace
robonetphy Feb 1, 2022
b37dfc8
Bump version up to 1.0.9
github-actions[bot] Feb 1, 2022
8d645a1
Merge branch 'master' into feature/delete/workspace
robonetphy Feb 15, 2022
52c2658
Bump version up to 1.0.10
github-actions[bot] Feb 15, 2022
2e99fbd
Merge branch 'master' into feature/delete/workspace
robonetphy Feb 16, 2022
bf1c901
lint removed
robonetphy Feb 16, 2022
cfd7499
Merge branch 'master' into feature/delete/workspace
robonetphy Dec 25, 2022
375d4da
Bump version up to 1.0.23
github-actions[bot] Dec 25, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "hawk.api",
"version": "1.0.22",
"version": "1.0.23",
"main": "index.ts",
"license": "UNLICENSED",
"scripts": {
Expand Down
3 changes: 3 additions & 0 deletions src/dataLoaders.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,9 @@ export default class DataLoaders {
const queryResult = await this.dbConnection.collection(collectionName)
.find({
[fieldName]: { $in: values },
isRemoved: {
$ne: true,
},
})
.toArray();

Expand Down
22 changes: 22 additions & 0 deletions src/models/project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,7 @@ export default class ProjectModel extends AbstractModel<ProjectDBScheme> impleme

/**
* Generates integration ID that's used in collector URL for sending events
* @returns integration ID as string.
*/
public static generateIntegrationId(): string {
return uuid();
Expand All @@ -203,6 +204,7 @@ export default class ProjectModel extends AbstractModel<ProjectDBScheme> impleme
* Generates new integration token with integration id field
*
* @param integrationId - integration id for using in collector URL
* @returns generated integration token.
*/
public static generateIntegrationToken(integrationId: string): string {
const secret = uuid();
Expand Down Expand Up @@ -383,4 +385,24 @@ export default class ProjectModel extends AbstractModel<ProjectDBScheme> impleme
console.log(error);
}
}

/**
* Mark project as removed.
*/
public async markProjectAsRemoved(): Promise<void> {
await this.collection.updateOne({ _id: this._id }, {
$set: { isRemoved: true },
});

try {
/**
* Remove users in project collection
*/
await this.dbConnection.collection('users-in-project:' + this._id)
.drop();
} catch (error) {
console.log(`Can't remove collection "users-in-project:${this._id}" because it doesn't exist.`);
console.log(error);
}
}
}
5 changes: 4 additions & 1 deletion src/models/projectToWorkspace.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ const { ObjectID } = require('mongodb');
class ProjectToWorkspace {
/**
* Creates an instance of ProjectToWorkspace
* @param {string|ObjectID} workspaceId
* @param {string|ObjectID} workspaceId workspace.
*/
constructor(workspaceId) {
this.workspaceId = new ObjectID(workspaceId);
Expand Down Expand Up @@ -53,6 +53,9 @@ class ProjectToWorkspace {
async findById(projectWorkspaceId) {
const projectWorkspace = await this.collection.findOne({
_id: new ObjectID(projectWorkspaceId),
isRemoved: {
$ne: true,
},
});

if (!projectWorkspace) {
Expand Down
31 changes: 27 additions & 4 deletions src/models/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ export interface UserNotificationsDBScheme {
/**
* Types of notifications to receive
*/
whatToReceive: {[key in UserNotificationType]: boolean};
whatToReceive: { [key in UserNotificationType]: boolean };
}

/**
Expand Down Expand Up @@ -165,14 +165,15 @@ export default class UserModel extends AbstractModel<UserDBScheme> implements Us

/**
* Generate 16bytes password
* @returns generated password.
*/
public static generatePassword(): Promise<string> {
return new Promise((resolve, reject) => {
crypto.randomBytes(8, (err, buff) => {
if (err) {
return reject(err);
}

console.log(buff.toString('hex'));
resolve(buff.toString('hex'));
});
});
Expand All @@ -182,6 +183,7 @@ export default class UserModel extends AbstractModel<UserDBScheme> implements Us
* Compose default notifications settings for new users.
*
* @param email - user email from the sign-up form will be used as email-channel endpoint
* @returns default notification settings for new users.
*/
public static generateDefaultNotificationsSettings(email: string): UserNotificationsDBScheme {
return {
Expand Down Expand Up @@ -212,7 +214,6 @@ export default class UserModel extends AbstractModel<UserDBScheme> implements Us
* Change user's password
* Hashes new password and updates the document
*
* @param userId - user ID
* @param newPassword - new user password
*/
public async changePassword(newPassword: string): Promise<void> {
Expand Down Expand Up @@ -318,7 +319,7 @@ export default class UserModel extends AbstractModel<UserDBScheme> implements Us
* Remove workspace from membership collection
* @param workspaceId - id of workspace to remove
*/
public async removeWorkspace(workspaceId: string): Promise<{workspaceId: string}> {
public async removeWorkspace(workspaceId: string): Promise<{ workspaceId: string }> {
await this.membershipCollection.deleteOne({
workspaceId: new ObjectId(workspaceId),
});
Expand All @@ -328,6 +329,22 @@ export default class UserModel extends AbstractModel<UserDBScheme> implements Us
};
}

/**
* Mark workspace as removed.
* @param workspaceId - id of workspace to remove
*/
public async markWorkspaceAsRemoved(workspaceId: string): Promise<{ workspaceId: string }> {
await this.membershipCollection.updateOne({
workspaceId: new ObjectId(workspaceId),
}, {
$set: { isRemoved: true },
});

return {
workspaceId,
};
}

/**
* Confirm membership of workspace by id
* @param workspaceId - workspace id to confirm
Expand All @@ -352,10 +369,16 @@ export default class UserModel extends AbstractModel<UserDBScheme> implements Us
workspaceId: {
$in: idsAsObjectId,
},
isRemoved: {
$ne: true,
},
isPending: {
$ne: true,
},
} : {
isRemoved: {
$ne: true,
},
isPending: {
$ne: true,
},
Expand Down
20 changes: 19 additions & 1 deletion src/models/workspace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ export default class WorkspaceModel extends AbstractModel<WorkspaceDBScheme> imp

/**
* Generates SHA-256 hash that used as invite hash
* @returns invitation hash as string.
*/
public static generateInviteHash(): string {
return crypto
Expand All @@ -106,6 +107,7 @@ export default class WorkspaceModel extends AbstractModel<WorkspaceDBScheme> imp
* Checks is provided document represents pending member
*
* @param doc - doc to check
* @returns status of is current member pending.
*/
public static isPendingMember(doc: MemberDBScheme): doc is PendingMemberDBScheme {
return !!(doc as PendingMemberDBScheme).userEmail && !(doc as ConfirmedMemberDBScheme).userId;
Expand All @@ -127,6 +129,20 @@ export default class WorkspaceModel extends AbstractModel<WorkspaceDBScheme> imp
}
}

/**
* Mark workspace as removed.
*/
public async markWorkspaceAsRemoved(): Promise<void> {
/**
* Delete the workspace data.
*/
await this.collection.updateOne({
_id: new ObjectId(this._id),
}, {
$set: { isRemoved: true },
});
}

/**
* Update invite hash of workspace
* @param inviteHash - new invite hash
Expand Down Expand Up @@ -407,6 +423,7 @@ export default class WorkspaceModel extends AbstractModel<WorkspaceDBScheme> imp

/**
* Due date of the current workspace tariff plan
* @returns Date object of due date.
*/
public getTariffPlanDueDate(): Date {
const lastChargeDate = new Date(this.lastChargeDate);
Expand All @@ -415,7 +432,8 @@ export default class WorkspaceModel extends AbstractModel<WorkspaceDBScheme> imp
}

/**
* Is tariff plan expired or not
* Is tariff plan expired or not.
* @returns current status of tariff plan.
*/
public isTariffPlanExpired(): boolean {
const date = new Date();
Expand Down
11 changes: 10 additions & 1 deletion src/models/workspacesFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,16 @@ export default class WorkspacesFactory extends AbstractModelFactory<WorkspaceDBS
* @param inviteHash - workspace invite hash
*/
public async findByInviteHash(inviteHash: string): Promise<WorkspaceModel | null> {
const workspaceData = await this.collection.findOne({ inviteHash });
const workspaceData = await this.collection.findOne({
inviteHash,
isRemoved: {
$ne: true,
},
});

if (!workspaceData) {
return null;
}

return workspaceData && new WorkspaceModel(workspaceData);
}
Expand Down
84 changes: 81 additions & 3 deletions src/resolvers/workspace.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import ProjectToWorkspace from '../models/projectToWorkspace';
import Validator from '../utils/validator';
import { dateFromObjectId } from '../utils/dates';

const { ApolloError, UserInputError, ForbiddenError } = require('apollo-server-express');
const { ApolloError, UserInputError } = require('apollo-server-express');
const crypto = require('crypto');

/**
Expand Down Expand Up @@ -145,6 +145,10 @@ module.exports = {
const currentUser = await factories.usersFactory.findById(user.id);
const workspace = await factories.workspacesFactory.findByInviteHash(inviteHash);

if (!workspace) {
throw new ApolloError('There is no workspace with provided id');
}

if (await workspace.getMemberInfo(user.id)) {
throw new ApolloError('You are already member of this workspace');
}
Expand Down Expand Up @@ -175,6 +179,10 @@ module.exports = {
const currentUser = await factories.usersFactory.findById(user.id);
const workspace = await factories.workspacesFactory.findById(workspaceId);

if (!workspace) {
throw new ApolloError('There is no workspace with provided id');
}

const hash = crypto
.createHash('sha256')
.update(`${workspaceId}:${currentUser.email}:${process.env.INVITE_LINK_HASH_SALT}`)
Expand Down Expand Up @@ -288,7 +296,7 @@ module.exports = {
* @param {string} workspaceId - id of the workspace where the user should be removed
* @param {UserInContext} user - current authorized user {@see ../index.js}
* @param {ContextFactories} factories - factories for working with models
* @return {Promise<boolean>} - true if operation is successful
* @return {Promise<boolean>} - true if operation is successful, false if there is no other admin left.
*/
async leaveWorkspace(_obj, { workspaceId }, { user, factories }) {
const userModel = await factories.usersFactory.findById(user.id);
Expand All @@ -307,14 +315,80 @@ module.exports = {
);

if (!isThereOtherAdmins) {
throw new ForbiddenError('You can\'t leave this workspace because you are the last admin');
return false;
}
}
await workspaceModel.removeMember(userModel);

return true;
},

/**
* Mutation in order to leave workspace
* @param {ResolverObj} _obj - object that contains the result returned from the resolver on the parent field
* @param {string} workspaceId - id of the workspace where the user should be removed
* @param {UserInContext} user - current authorized user {@see ../index.js}
* @param {ContextFactories} factories - factories for working with models
* @return {Promise<boolean>} - true if operation is successful
*/
async deleteWorkspace(_obj, { workspaceId }, { user, factories }) {
const workspaceModel = await factories.workspacesFactory.findById(workspaceId);

if (!workspaceModel) {
throw new UserInputError('There is no workspace with provided id');
}

const membersInfo = (await workspaceModel.getMembers());

for (const member of membersInfo) {
/**
* remove members from workspace.
*/
if (member.userId) {
const userModel = await factories.usersFactory.findById(member.userId.toString());

await userModel.markWorkspaceAsRemoved(workspaceId.toString());
}
/**
* remove the members who's invitation is pending.
*/
if (member.userEmail) {
const invitedUser = await factories.usersFactory.findByEmail(member.userEmail);

/**
* If user is already uses Hawk
*/
if (invitedUser) {
await invitedUser.removeWorkspace(workspaceId);
}

/**
* Remove User's invitation from workspace.
*/
await workspaceModel.removeMemberByEmail(member.userEmail);
}
}

const projectToWorkspace = new ProjectToWorkspace(workspaceId.toString());

const projectsInfo = await projectToWorkspace.getProjects();

if (projectsInfo.length) {
for (const project of projectsInfo) {
/**
* Remove project
*/
const projectModel = await factories.projectsFactory.findById(project.id.toString());

await projectModel.markProjectAsRemoved();
robonetphy marked this conversation as resolved.
Show resolved Hide resolved
}
}

await workspaceModel.markWorkspaceAsRemoved();

return true;
},

/**
* Change workspace plan for default plan mutation implementation
*
Expand Down Expand Up @@ -379,6 +453,7 @@ module.exports = {

/**
* Return empty object to call resolver for specific mutation
* @returns Empty object.
*/
workspace: () => ({}),
},
Expand Down Expand Up @@ -481,6 +556,7 @@ module.exports = {
/**
* Returns type of the team member
* @param {MemberDBScheme} memberData - result from resolver above
* @returns type of member.
*/
__resolveType(memberData) {
return WorkspaceModel.isPendingMember(memberData) ? 'PendingMember' : 'ConfirmedMember';
Expand All @@ -496,6 +572,7 @@ module.exports = {
* @param {ConfirmedMemberDBScheme} memberData - result from resolver above
* @param _args - empty list of args
* @param {ContextFactories} factories - factories for working with models
* @returns user data of the workspace
*/
user(memberData, _args, { factories }) {
return factories.usersFactory.findById(memberData.userId.toString());
Expand All @@ -504,6 +581,7 @@ module.exports = {
/**
* True if user has admin permissions
* @param {ConfirmedMemberDBScheme} memberData - result from resolver above
* @returns status of is user admin
*/
isAdmin(memberData) {
return !WorkspaceModel.isPendingMember(memberData) && (memberData.isAdmin || false);
Expand Down
Loading