Skip to content

Commit

Permalink
Merge pull request #25 from microsoft/feature/tag-categories
Browse files Browse the repository at this point in the history
Feature/tag categories
  • Loading branch information
batch08 authored Aug 26, 2021
2 parents 0bafd5f + 0736827 commit 1006061
Show file tree
Hide file tree
Showing 33 changed files with 421 additions and 223 deletions.
3 changes: 2 additions & 1 deletion packages/api/config/default.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@
"organizationsCollection": "organizations",
"contactsCollection": "contacts",
"userTokensCollection": "user_tokens",
"engagementsCollection": "engagements"
"engagementsCollection": "engagements",
"tagsCollection": "tags"
},
"constants": {
"defaultPageOffset": 0,
Expand Down
8 changes: 3 additions & 5 deletions packages/api/migrations/20210818223159-tag_collection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
*/

import { Db } from 'mongodb'
import { DbTag, DbOrganization } from '../src/db/types'
import { DbOrganization } from '../src/db/types'

// DO NOT CHANGE THE NEXT LINE module.exports is needed for migrate-mongo to funciton properly
module.exports = {
Expand All @@ -24,7 +24,7 @@ module.exports = {
.find()
.forEach(async (org: DbOrganization) => {
// For each tag in each org
const newOrgTags = org.tags.map((tag: DbTag) => {
const newOrgTags = org.tags.map((tag: any) => {
// Add a group and org_id to the tag
const nextTag = { ...tag, org_id: org.id }

Expand Down Expand Up @@ -60,7 +60,7 @@ module.exports = {
.forEach(async (org) => {
// For each tag in each org
const newOrgTags = await Promise.all(
org.tags.map(async (tag: string) => {
org.tags.map(async (tag: any) => {
// Get the tag from the tags collection
const _tag = await db.collection('tags').findOne({ id: tag })

Expand All @@ -73,8 +73,6 @@ module.exports = {
})
)

console.log('newOrgTags', newOrgTags)

// Update the current organization with a list of tags
db.collection('organizations').updateOne(
{ id: org.id },
Expand Down
2 changes: 2 additions & 0 deletions packages/api/src/components/AppBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,8 @@ export class AppBuilder {
}
},
formatError: (err) => {
console.log('err in formatError', err)

// Don't give the specific errors to the client.
const message = err.message?.toLocaleLowerCase?.() || ''
if (message.includes('invalid token') || message.includes('not authenticated')) {
Expand Down
7 changes: 5 additions & 2 deletions packages/api/src/components/AppContextProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ import {
OrganizationCollection,
UserCollection,
UserTokenCollection,
EngagementCollection
EngagementCollection,
TagCollection
} from '~db'
import { PubSub } from 'apollo-server'
import { AsyncProvider, BuiltAppContext } from '~types'
Expand All @@ -33,6 +34,7 @@ export class AppContextProvider implements AsyncProvider<BuiltAppContext> {
const userCollection = new UserCollection(conn.usersCollection)
const userTokenCollection = new UserTokenCollection(conn.userTokensCollection)
const orgCollection = new OrganizationCollection(conn.orgsCollection)
const tagCollection = new TagCollection(conn.tagsCollection)
const localization = new Localization()
const notify = new Notifications(config)
const mailer = nodemailer.createTransport(
Expand Down Expand Up @@ -60,7 +62,8 @@ export class AppContextProvider implements AsyncProvider<BuiltAppContext> {
orgs: orgCollection,
contacts: contactCollection,
userTokens: userTokenCollection,
engagements: engagementCollection
engagements: engagementCollection,
tags: tagCollection
},
components: {
mailer,
Expand Down
3 changes: 3 additions & 0 deletions packages/api/src/components/Authenticator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,10 +75,12 @@ export class Authenticator {

// Verify the bearerToken is a valid JWT
const verifyJwt = jwt.verify(bearerToken, this.#jwtSecret)

if (!verifyJwt) return null

// Get the dbToken
const token = await this.#userTokenCollection.item({ user: userId })

if (!token.item) return null

// Check the bearerToken matches the encrypted db token
Expand Down Expand Up @@ -113,6 +115,7 @@ export class Authenticator {

// Create a token for the user and save it to the token collection
const token = jwt.sign({}, this.#jwtSecret)

await this.#userTokenCollection.save(user, await bcrypt.hash(token, 10))

// Return the user and the created token
Expand Down
4 changes: 4 additions & 0 deletions packages/api/src/components/Configuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,10 @@ export class Configuration {
return this.c.get<string>('db.engagementsCollection')
}

public get dbTagsCollection(): string {
return this.c.get<string>('db.tagsCollection')
}

public get dbContactsCollection(): string {
return this.c.get<string>('db.contactsCollection')
}
Expand Down
3 changes: 3 additions & 0 deletions packages/api/src/components/DatabaseConnector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,4 +58,7 @@ export class DatabaseConnector {
public get engagementsCollection(): Collection {
return this.db.collection(this.#config.dbEngagementsCollection)
}
public get tagsCollection(): Collection {
return this.db.collection(this.#config.dbTagsCollection)
}
}
8 changes: 8 additions & 0 deletions packages/api/src/db/TagCollection.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/*!
* Copyright (c) Microsoft. All rights reserved.
* Licensed under the MIT license. See LICENSE file in the project.
*/
import { CollectionBase } from './CollectionBase'
import type { DbTag } from './types'

export class TagCollection extends CollectionBase<DbTag> {}
1 change: 1 addition & 0 deletions packages/api/src/db/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ export * from './OrganizationCollection'
export * from './UserCollection'
export * from './UserTokenCollection'
export * from './EngagementCollection'
export * from './TagCollection'
4 changes: 3 additions & 1 deletion packages/api/src/db/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,14 +123,16 @@ export interface DbOrganization {
name: string
users: string[]
contacts: string[]
tags: DbTag[]
tags: string[]
attributes?: DbAttribute[]
}

export interface DbTag {
id: string
label: string
description?: string
org_id: string
category?: string
}

export interface DbAttribute {
Expand Down
8 changes: 5 additions & 3 deletions packages/api/src/dto/createDBTag.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,16 @@
* Copyright (c) Microsoft. All rights reserved.
* Licensed under the MIT license. See LICENSE file in the project.
*/
import { TagInput } from '@cbosuite/schema/lib/provider-types'
import { TagInput, TagCategory } from '@cbosuite/schema/lib/provider-types'
import { v4 as createId } from 'uuid'
import { DbTag } from '~db'

export function createDBTag(tag: TagInput): DbTag {
export function createDBTag(tag: TagInput, orgId: string): DbTag {
return {
id: createId(),
label: tag.label || '',
description: tag.description || undefined
description: tag.description || undefined,
org_id: orgId,
category: (tag.category as TagCategory) || undefined
}
}
2 changes: 1 addition & 1 deletion packages/api/src/dto/createGQLOrganization.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export function createGQLOrganization(org: DbOrganization): Organization {
description: org.description,
id: org.id,
name: org.name,
tags: org.tags,
tags: org.tags as any,
// These are just IDs, resolve into user objects in the resolve stack
users: org.users as any,
contacts: org.contacts as any,
Expand Down
19 changes: 19 additions & 0 deletions packages/api/src/dto/createGQLTag.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/*!
* Copyright (c) Microsoft. All rights reserved.
* Licensed under the MIT license. See LICENSE file in the project.
*/

import type { Tag, TagUsageCount } from '@cbosuite/schema/lib/provider-types'
import type { DbTag } from '~db'

export function createGQLTag(tag: DbTag, usageCount?: TagUsageCount): Tag {
return {
__typename: 'Tag',
id: tag.id,
orgId: tag.org_id,
label: tag.label,
description: tag.description,
category: tag.category,
usageCount: usageCount
}
}
4 changes: 3 additions & 1 deletion packages/api/src/dto/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@ export * from './createGQLRole'
export * from './createGQLContact'
export * from './createGQLEngagement'
export * from './createGQLAction'
export * from './createGQLMention'
export * from './createGQLTag'
export * from './createDBEngagement'
export * from './createDBUser'
export * from './createDBTag'
export * from './createDBAction'
export * from './createDBMention'
export * from './createGQLMention'
57 changes: 33 additions & 24 deletions packages/api/src/resolvers/Mutation/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ import {
createDBEngagement,
createDBUser,
createDBAction,
createDBMention
createDBMention,
createGQLTag
} from '~dto'
import {
getAccountCreatedHTMLTemplate,
Expand Down Expand Up @@ -990,16 +991,26 @@ export const Mutation: MutationResolvers<AppContext> = {
},
createNewTag: async (_, { body }, context) => {
const { orgId, tag } = body
const newTag = createDBTag(tag)
if (!orgId) {
return {
tag: null,
message: context.components.localization.t('mutation.createNewTag.orgIdRequired'),
status: 'FAILED'
}
}
const newTag = createDBTag(tag, orgId)

await context.collections.orgs.updateItem({ id: orgId }, { $push: { tags: newTag } })
try {
await context.collections.tags.insertItem(newTag)
} catch (err) {
throw err
}

try {
await context.collections.orgs.updateItem({ id: orgId }, { $push: { tags: newTag.id } })
} catch (err) {
throw err
}

return {
tag: newTag,
Expand All @@ -1008,38 +1019,36 @@ export const Mutation: MutationResolvers<AppContext> = {
}
},
updateTag: async (_, { body }, context) => {
const { orgId, tag } = body
const { tag } = body
if (!tag.id) {
return {
tag: null,
message: context.components.localization.t('mutation.updateTag.tagIdRequired'),
status: 'FAILED'
}
}
if (!orgId) {
return {
tag: null,
message: context.components.localization.t('mutation.updateTag.orgIdRequired'),
status: 'FAILED'
}
}

await context.collections.orgs.updateItem(
{ id: orgId, 'tags.id': tag.id },
{
$set: {
'tags.$.label': tag.label,
'tags.$.description': tag.description
// Update the tag
try {
await context.collections.tags.updateItem(
{ id: tag.id },
{
$set: {
label: tag.label,
description: tag.description,
category: tag.category
}
}
}
)
)
} catch (error) {
console.log('Failed to update tag', error)
}

// Get the updated tag from the database
const { item: updatedTag } = await context.collections.tags.itemById(tag.id)

return {
tag: {
id: tag.id || '',
label: tag.label || '',
description: tag.description || ''
},
tag: createGQLTag(updatedTag),
message: context.components.localization.t('mutation.updateTag.success'),
status: 'SUCCESS'
}
Expand Down
24 changes: 6 additions & 18 deletions packages/api/src/resolvers/types/Action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* Licensed under the MIT license. See LICENSE file in the project.
*/
import { AppContext } from '~types'
import { Action as ActionType, Tag, ActionResolvers } from '@cbosuite/schema/lib/provider-types'
import { Action as ActionType, ActionResolvers } from '@cbosuite/schema/lib/provider-types'
import { createGQLUser } from '~dto'

export const Action: ActionResolvers<AppContext> = {
Expand All @@ -30,23 +30,11 @@ export const Action: ActionResolvers<AppContext> = {
tags: async (_: ActionType, args, context) => {
if (!_.tags) return null

const returnTags: Tag[] = []
// Get orgId from action
const orgId = _.orgId as any as string
const actionTags = _.tags as any as string[]
const returnTags = await context.collections.tags.items(
{},
{ id: { $in: _.tags as any as string[] } }
)

// Load org from db
const org = await context.collections.orgs.itemById(orgId)

// Assign org tags to action
if (org.item && org.item.tags) {
for (const tagKey of actionTags) {
const tag = org.item.tags.find((orgTag) => orgTag.id === tagKey)
if (tag) {
returnTags.push(tag)
}
}
}
return returnTags
return returnTags?.items ?? null
}
}
17 changes: 3 additions & 14 deletions packages/api/src/resolvers/types/Engagement.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
*/
import { AppContext } from '~types'
import {
Tag,
Engagement as EngagementType,
EngagementResolvers
} from '@cbosuite/schema/lib/provider-types'
Expand Down Expand Up @@ -46,21 +45,11 @@ export const Engagement: EngagementResolvers<AppContext> = {
return contacts
},
tags: async (_: EngagementType, args, context) => {
const returnTags: Tag[] = []

const orgId = _.orgId as string
const engagementTags = (_.tags || []) as any as string[]

const org = await context.collections.orgs.itemById(orgId)
if (org.item && org.item.tags) {
for (const tagKey of engagementTags) {
const tag = org.item.tags.find((orgTag) => orgTag.id === tagKey)
if (tag) {
returnTags.push(tag)
}
}
}
return returnTags
const returnTags = await context.collections.tags.items({}, { id: { $in: engagementTags } })

return returnTags.items ?? []
},
actions: async (_: EngagementType, args, context) => {
return _.actions.sort(sortByDate)
Expand Down
Loading

0 comments on commit 1006061

Please sign in to comment.