diff --git a/src/dataLoaders.ts b/src/dataLoaders.ts index 61b11b51..2a0bee95 100644 --- a/src/dataLoaders.ts +++ b/src/dataLoaders.ts @@ -1,7 +1,6 @@ import DataLoader from 'dataloader'; import { Db, ObjectId } from 'mongodb'; -import { PlanDBScheme, UserDBScheme, WorkspaceDBScheme } from 'hawk.types'; -import { ProjectDBScheme } from './models/project'; +import { PlanDBScheme, UserDBScheme, WorkspaceDBScheme, ProjectDBScheme } from 'hawk.types'; /** * Class for setting up data loaders diff --git a/src/models/project.ts b/src/models/project.ts index d09234f4..28e82724 100644 --- a/src/models/project.ts +++ b/src/models/project.ts @@ -1,51 +1,8 @@ import { Collection, ObjectId } from 'mongodb'; import AbstractModel from './abstractModel'; import { NotificationsChannelsDBScheme } from '../types/notification-channels'; - -/** - * Structure represents a Project in DataBase - */ -export interface ProjectDBScheme { - /** - * Project ID - */ - _id: ObjectId; - - /** - * Project Integration Token - */ - token: string; - - /** - * Project name - */ - name: string; - - /** - * User who created project - */ - uidAdded: ObjectId; - - /** - * Workspace id which project is belong - */ - workspaceId: ObjectId; - - /** - * Project description - */ - description?: string; - - /** - * URL of a project logo - */ - image?: string; - - /** - * Project notifications settings - */ - notifications: ProjectNotificationsRuleDBScheme[]; -} +import { ProjectDBScheme } from 'hawk.types'; +import uuid from 'uuid'; /** * This structure represents a single rule of notifications settings @@ -181,6 +138,11 @@ export default class ProjectModel extends AbstractModel impleme */ public _id!: ObjectId; + /** + * Integration id that's used in collector URL + */ + public integrationId!: string; + /** * Project Integration Token */ @@ -230,6 +192,31 @@ export default class ProjectModel extends AbstractModel impleme this.collection = this.dbConnection.collection('projects'); } + /** + * Generates integration ID that's used in collector URL for sending events + */ + public static generateIntegrationId(): string { + return uuid.v4(); + } + + /** + * Generates new integration token with integration id field + * + * @param integrationId - integration id for using in collector URL + */ + public static generateIntegrationToken(integrationId: string): string { + const secret = uuid.v4(); + + const decodedIntegrationToken = { + integrationId, + secret, + }; + + return Buffer + .from(JSON.stringify(decodedIntegrationToken)) + .toString('base64'); + } + /** * Creates new notification rule and add it to start of the array of notifications rules * @param payload - rule data to save diff --git a/src/models/projectsFactory.ts b/src/models/projectsFactory.ts index 71dcaab0..e099d9d6 100644 --- a/src/models/projectsFactory.ts +++ b/src/models/projectsFactory.ts @@ -1,9 +1,9 @@ import AbstractModelFactory from './abstactModelFactory'; import { Collection, Db } from 'mongodb'; import DataLoaders from '../dataLoaders'; -import ProjectModel, { ProjectDBScheme } from './project'; -import jwt, { Secret } from 'jsonwebtoken'; +import ProjectModel from './project'; import ProjectToWorkspace from './projectToWorkspace'; +import { ProjectDBScheme } from 'hawk.types'; /** * Users factory to work with User Model @@ -59,25 +59,25 @@ export default class ProjectsFactory extends AbstractModelFactory { - const projectId = (await this.collection.insertOne(projectData)).insertedId; + const integrationId = ProjectModel.generateIntegrationId(); + const encodedIntegrationToken = ProjectModel.generateIntegrationToken(integrationId); + const data = { + ...projectData, + integrationId, + token: encodedIntegrationToken, + }; + const projectId = (await this.collection.insertOne(data)).insertedId; - const token = await jwt.sign({ projectId }, process.env.JWT_SECRET_PROJECT_TOKEN as Secret); - - const result = await this.collection.findOneAndUpdate( - { _id: projectId }, - { $set: { token } }, - { returnOriginal: false } - ); - - if (!result.value) { - throw new Error('Can\'t create project due to unknown error'); - } - - // Create Project to Workspace relationship - await new ProjectToWorkspace(projectData.workspaceId).add({ + /** + * Create Project to Workspace relationship + */ + await new ProjectToWorkspace(data.workspaceId).add({ projectId: projectId, }); - return new ProjectModel(result.value); + return new ProjectModel({ + ...data, + _id: projectId, + }); } } diff --git a/src/resolvers/project.js b/src/resolvers/project.js index 29606aef..bb7e7d43 100644 --- a/src/resolvers/project.js +++ b/src/resolvers/project.js @@ -4,6 +4,7 @@ const Validator = require('../utils/validator'); const UserInProject = require('../models/userInProject'); const EventsFactory = require('../models/eventsFactory'); const ProjectToWorkspace = require('../models/projectToWorkspace'); +const ProjectModel = require('../models/project').default; const EVENTS_GROUP_HASH_INDEX_NAME = 'groupHashUnique'; const REPETITIONS_GROUP_HASH_INDEX_NAME = 'groupHash_hashed'; @@ -131,6 +132,42 @@ module.exports = { } }, + /** + * Generates new project integration token by id + * + * @param {ResolverObj} _obj - default resolver object + * @param {string} id - id of the project in which the token field is being regenerated + * @param {UserInContext} user - current authorized user {@see ../index.js} + * @param {ContextFactories} factories - factories for working with models + * + * @returns {Object} + */ + async generateNewIntegrationToken(_obj, { id }, { factories }) { + const project = await factories.projectsFactory.findById(id); + + if (!project) { + throw new ApolloError('There is no project with that id:', id); + } + + const integrationId = project.integrationId || ProjectModel.generateIntegrationId(); + + const encodedIntegrationToken = ProjectModel.generateIntegrationToken(integrationId); + + try { + const updatedProject = await project.updateProject({ + token: encodedIntegrationToken, + integrationId, + }); + + return { + recordId: updatedProject._id, + record: updatedProject, + }; + } catch (err) { + throw new ApolloError('Can\'t update integration token', err); + } + }, + /** * Remove project * diff --git a/src/typeDefs/project.ts b/src/typeDefs/project.ts index ceff9860..8acbf809 100644 --- a/src/typeDefs/project.ts +++ b/src/typeDefs/project.ts @@ -28,6 +28,21 @@ input EventsFiltersInput { ignored: Boolean } +""" +Respose object with updated project and his id +""" +type UpdateProjectResponse { + """ + Project id + """ + recordId: ID! + + """ + Modified project + """ + record: Project! +} + """ Project representation """ @@ -181,6 +196,16 @@ extend type Mutation { image: Upload @uploadImage ): Project! @requireAuth + """ + Generates new project integration token by id + """ + generateNewIntegrationToken( + """ + What project to regenerate integration token + """ + id: ID! + ): UpdateProjectResponse! @requireAdmin + """ Remove project """ diff --git a/yarn.lock b/yarn.lock index a3a403a2..705bda0a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3877,8 +3877,8 @@ has@^1.0.3: function-bind "^1.1.1" hawk.types@codex-team/hawk.types: - version "0.1.4" - resolved "https://codeload.github.com/codex-team/hawk.types/tar.gz/de2e46b1258215f0c979873854dcd7d8e18588fe" + version "0.1.7" + resolved "https://codeload.github.com/codex-team/hawk.types/tar.gz/3dd99216abd49578b794cecf28edc34ee2e4928b" dependencies: "@types/mongodb" "^3.5.34"