From 40fd973c033403cd8111e0d74cf165ff7157c784 Mon Sep 17 00:00:00 2001 From: Behzad Rabiei <53224485+Behzad-rabiei@users.noreply.github.com> Date: Thu, 12 Sep 2024 16:29:33 +0400 Subject: [PATCH 1/6] feat: add category property api --- src/controllers/platform.controller.ts | 5 +- src/docs/platform.doc.yml | 230 ++++++++++----------- src/services/discourse/category.service.ts | 55 +++++ src/services/discourse/core.service.ts | 24 +++ src/services/discourse/heatmap.service.ts | 2 +- src/services/discourse/index.ts | 5 +- src/validations/platform.validation.ts | 26 +++ 7 files changed, 229 insertions(+), 118 deletions(-) create mode 100644 src/services/discourse/category.service.ts create mode 100644 src/services/discourse/core.service.ts diff --git a/src/controllers/platform.controller.ts b/src/controllers/platform.controller.ts index c189f6a..80220ec 100644 --- a/src/controllers/platform.controller.ts +++ b/src/controllers/platform.controller.ts @@ -8,6 +8,7 @@ import { tokenService, githubService, notionService, + discourseService, } from '../services'; import { IAuthRequest } from '../interfaces/Request.interface'; import { catchAsync, pick, ApiError } from '../utils'; @@ -17,7 +18,7 @@ import config from '../config'; import httpStatus from 'http-status'; import querystring from 'querystring'; import parentLogger from '../config/logger'; -import { IPlatform, PlatformNames } from '@togethercrew.dev/db'; +import { PlatformNames } from '@togethercrew.dev/db'; import { DatabaseManager } from '@togethercrew.dev/db'; const logger = parentLogger.child({ module: 'PlatformController' }); @@ -362,6 +363,8 @@ const getProperties = catchAsync(async function (req: IAuthAndPlatform, res: Res let result; if (platform?.name === PlatformNames.Discord) { result = await discordServices.coreService.getPropertyHandler(req); + } else if (platform?.name === PlatformNames.Discourse) { + result = await discourseService.coreService.getPropertyHandler(req); } res.status(httpStatus.OK).send(result); }); diff --git a/src/docs/platform.doc.yml b/src/docs/platform.doc.yml index f6696b9..82c4291 100644 --- a/src/docs/platform.doc.yml +++ b/src/docs/platform.doc.yml @@ -15,7 +15,7 @@ paths: enum: - discord - google - - twitter + - twitter - github - notion - name: userId @@ -35,9 +35,9 @@ paths: enum: ['googleDrive'] responses: - "302": + '302': description: Found - Redirects to the OAuth2 authorization page appropriate to the selected platform. - $ref: "#/components/responses/Found" + $ref: '#/components/responses/Found' /api/v1/platforms/: post: @@ -124,7 +124,7 @@ paths: id: type: string avatarUrl: - type: string + type: string description: Metadata for Github. - type: object required: [userId, workspace_id, workspace_name, bot_id, request_id, owner] @@ -159,31 +159,31 @@ paths: name: type: string avatar_url: - type: string + type: string description: Metadata for Notion. - type: object - required: [baseURL , path] + required: [baseURL, path] properties: baseURL: - type: string - path: type: string - default: /w/api.php + path: + type: string + default: /w/api.php description: Metadata for MediaWiki. responses: - "201": + '201': description: Platform created successfully. content: application/json: schema: - $ref: "#/components/schemas/Platform" - "400": + $ref: '#/components/schemas/Platform' + '400': description: Bad Request - $ref: "#/components/responses/BadRequest" - "401": + $ref: '#/components/responses/BadRequest' + '401': description: Unauthorized - $ref: "#/components/responses/Unauthorized" - + $ref: '#/components/responses/Unauthorized' + get: tags: - Platform @@ -207,7 +207,7 @@ paths: schema: type: string format: objectId - description: + description: required: true - in: query name: sortBy @@ -229,7 +229,7 @@ paths: default: 1 description: Page number responses: - "200": + '200': description: OK content: application/json: @@ -239,7 +239,7 @@ paths: results: type: array items: - $ref: "#/components/schemas/Platform" + $ref: '#/components/schemas/Platform' page: type: integer example: 1 @@ -252,9 +252,9 @@ paths: totalResults: type: integer example: 1 - "401": + '401': description: Unauthorized - $ref: "#/components/responses/Unauthorized" + $ref: '#/components/responses/Unauthorized' /api/v1/platforms/{platformId}: get: @@ -271,18 +271,18 @@ paths: schema: type: string responses: - "200": + '200': description: Platform details retrieved successfully. content: application/json: schema: - $ref: "#/components/schemas/Platform" - "401": + $ref: '#/components/schemas/Platform' + '401': description: Unauthorized - $ref: "#/components/responses/Unauthorized" - "404": + $ref: '#/components/responses/Unauthorized' + '404': description: NotFound - $ref: "#/components/responses/NotFound" + $ref: '#/components/responses/NotFound' patch: tags: @@ -310,18 +310,18 @@ paths: additionalProperties: true minProperties: 1 responses: - "200": + '200': description: Platform updated successfully. content: application/json: schema: - $ref: "#/components/schemas/Platform" - "401": + $ref: '#/components/schemas/Platform' + '401': description: Unauthorized - $ref: "#/components/responses/Unauthorized" - "404": + $ref: '#/components/responses/Unauthorized' + '404': description: NotFound - $ref: "#/components/responses/NotFound" + $ref: '#/components/responses/NotFound' delete: tags: @@ -345,18 +345,18 @@ paths: properties: deleteType: type: string - required: true - enum: ['soft','hard'] - example: soft + required: true + enum: ['soft', 'hard'] + example: soft responses: - "204": + '204': description: Platform deleted successfully. - "401": + '401': description: Unauthorized - $ref: "#/components/responses/Unauthorized" - "404": + $ref: '#/components/responses/Unauthorized' + '404': description: NotFound - $ref: "#/components/responses/NotFound" + $ref: '#/components/responses/NotFound' /api/v1/platforms/{platformId}/properties: post: @@ -376,14 +376,14 @@ paths: - name: property in: query required: true - description: The type of property to retrieve (e.g., 'channel' or 'role' for Discord platform). + description: The type of property to retrieve (e.g., 'channel' or 'role' for Discord platform - 'category' for discoruse platform). schema: type: string - enum: ['channel','role','guildMember'] + enum: ['channel', 'role', 'guildMember', 'category'] - name: name in: query required: false - description: Name of the role (supported for 'discord-role' property). + description: Name of the role (supported for 'discord-role' property) - Name of the category (supported for 'discourse-category' property). schema: type: string - name: ngu @@ -395,7 +395,7 @@ paths: - name: sortBy in: query required: false - description: The parameter to sort by (supported for 'discord-role' and 'discord-guildMember' properties). + description: The parameter to sort by (supported for 'discord-role', 'discord-guildMember', and 'discourse-category' properties). for the 'discourse-category' the format is like 'dc.name' schema: type: string - name: limit @@ -422,85 +422,85 @@ paths: type: string description: Array of channel IDs (required if 'property' is 'channel'). responses: - "200": + '200': description: Platformx properties retrieved successfully. content: application/json: schema: oneOf: - - schema: - type: object - properties: - results: - type: array - items: - $ref: "#/components/schemas/Role" - page: - type: integer - example: 1 - limit: - type: integer - example: 10 - totalPages: - type: integer - example: 1 - totalResults: - type: integer - example: 1 - - schema: - type: array - items: + - schema: type: object - properties: - channelId: - type: string - format: Snowflake - title: - type: string - subChannels: + properties: + results: type: array items: - type: object - properties: - channelId: - type: Snowflake - name: - type: string - parentId: - type: Snowflake - canReadMessageHistoryAndViewChannel: - type: boolean - announcementAccess: - type: boolean - type: - type: integer - - schema: - type: array - items: - type: object - properties: - discordId: - type: string - format: Snowflake - nickname: - type: string - globalName: - type: string - username: - type: string - ngu: - type: string - discriminator: - type: integer - "400": + $ref: '#/components/schemas/Role' + page: + type: integer + example: 1 + limit: + type: integer + example: 10 + totalPages: + type: integer + example: 1 + totalResults: + type: integer + example: 1 + - schema: + type: array + items: + type: object + properties: + channelId: + type: string + format: Snowflake + title: + type: string + subChannels: + type: array + items: + type: object + properties: + channelId: + type: Snowflake + name: + type: string + parentId: + type: Snowflake + canReadMessageHistoryAndViewChannel: + type: boolean + announcementAccess: + type: boolean + type: + type: integer + - schema: + type: array + items: + type: object + properties: + discordId: + type: string + format: Snowflake + nickname: + type: string + globalName: + type: string + username: + type: string + ngu: + type: string + discriminator: + type: integer + '400': description: Bad Request - $ref: "#/components/responses/BadRequest" - "401": + $ref: '#/components/responses/BadRequest' + '401': description: Unauthorized - $ref: "#/components/responses/Unauthorized" - "404": + $ref: '#/components/responses/Unauthorized' + '404': description: Not Found - $ref: "#/components/responses/NotFound" + $ref: '#/components/responses/NotFound' /api/v1/platforms/request-access/{platform}/{module}/{id}: get: tags: @@ -531,6 +531,6 @@ paths: schema: type: string responses: - "302": + '302': description: Found - Redirect to Discord OAuth2 page if conditions are met. - $ref: "#/components/responses/Found" + $ref: '#/components/responses/Found' diff --git a/src/services/discourse/category.service.ts b/src/services/discourse/category.service.ts new file mode 100644 index 0000000..1afb34c --- /dev/null +++ b/src/services/discourse/category.service.ts @@ -0,0 +1,55 @@ +import fetch from 'node-fetch'; +import { Client, GatewayIntentBits, Snowflake, Guild } from 'discord.js'; +import config from '../../config'; +import { DatabaseManager } from '@togethercrew.dev/db'; +import { ApiError, pick, sort } from '../../utils'; +import parentLogger from '../../config/logger'; +import { IAuthAndPlatform, IDiscordOAuth2EchangeCode, IDiscordUser } from '../../interfaces'; +import httpStatus from 'http-status'; +import * as Neo4j from '../../neo4j'; + +const logger = parentLogger.child({ module: 'DiscourseCategoryService' }); + +/** + * get list of categories + * @param {string} refreshToken + * @returns {Promise} + */ +async function getCategoriesByEndPoint(filters: any, options: any): Promise { + const { endPoint, name } = filters; + let { limit, page, skip, sortBy } = options; + limit = limit && parseInt(limit, 10) > 0 ? parseInt(limit, 10) : 10; + page = page && parseInt(page, 10) > 0 ? parseInt(page, 10) : 1; + skip = (page - 1) * limit; + console.log(endPoint, name, limit, page, skip, sortBy); + const query = ` + MATCH (forum:DiscourseForum {endpoint: "${endPoint}"}) + MATCH (dc:DiscourseCategory {forumUuid: forum.uuid}) + ${name ? `WHERE dc.name =~ '(?i).*${name}.*'` : ''} + RETURN dc.id as categoryId, dc.name as name, dc.color as color, dc.descriptionText as description + ORDER BY ${sortBy ? sortBy : 'dc.id'} + SKIP ${skip} + LIMIT ${limit} +`; + try { + const neo4jCategories = await Neo4j.read(query); + const { records: categories } = neo4jCategories; + const CategoryList = categories.map((category) => { + // @ts-ignore + const { _fieldLookup, _fields } = category; + const categoryId = _fields[_fieldLookup['categoryId']] as string; + const name = _fields[_fieldLookup['name']] as string; + const color = _fields[_fieldLookup['color']] as string; + const description = _fields[_fieldLookup['description']] as string; + return { categoryId, name, color, description }; + }); + return CategoryList; + } catch (error) { + logger.error(error, 'Failed to get category list by endpoint'); + throw new ApiError(httpStatus.INTERNAL_SERVER_ERROR, 'Failed to get category list by endpoint'); + } +} + +export default { + getCategoriesByEndPoint, +}; diff --git a/src/services/discourse/core.service.ts b/src/services/discourse/core.service.ts new file mode 100644 index 0000000..756f1f1 --- /dev/null +++ b/src/services/discourse/core.service.ts @@ -0,0 +1,24 @@ +import parentLogger from '../../config/logger'; +import { IAuthAndPlatform } from '../../interfaces'; +import categoryService from './category.service'; +import { ApiError, pick, sort } from '../../utils'; + +const logger = parentLogger.child({ module: 'DiscordCoreService' }); + +/** + * exchange discord code with access token + * @param {string} code + @param {string} redirect_uri + * @returns {Promise} + */ +async function getPropertyHandler(req: IAuthAndPlatform) { + const filter = pick(req.query, ['name']); + const options = pick(req.query, ['sortBy', 'limit', 'page']); + if (req.query.property === 'category') { + return await categoryService.getCategoriesByEndPoint({ endPoint: req.platform.metadata?.id, ...filter }, options); + } +} + +export default { + getPropertyHandler, +}; diff --git a/src/services/discourse/heatmap.service.ts b/src/services/discourse/heatmap.service.ts index ae71047..6ca1a31 100644 --- a/src/services/discourse/heatmap.service.ts +++ b/src/services/discourse/heatmap.service.ts @@ -3,7 +3,7 @@ import { IHeatmapChartRequestBody } from '../../interfaces/Request.interface'; import { date, math } from '../../utils'; import parentLogger from '../../config/logger'; -const logger = parentLogger.child({ module: 'HeatmapService' }); +const logger = parentLogger.child({ module: 'DiscourseHeatmapService' }); /** * get heatmap chart diff --git a/src/services/discourse/index.ts b/src/services/discourse/index.ts index 250c309..a9403a7 100644 --- a/src/services/discourse/index.ts +++ b/src/services/discourse/index.ts @@ -1,5 +1,8 @@ import heatmapService from './heatmap.service'; - +import categoryService from './category.service'; +import coreService from './core.service'; export default { heatmapService, + coreService, + categoryService, }; diff --git a/src/validations/platform.validation.ts b/src/validations/platform.validation.ts index ba54e99..e248321 100644 --- a/src/validations/platform.validation.ts +++ b/src/validations/platform.validation.ts @@ -232,6 +232,29 @@ const discordProperties = (req: Request, property: string) => { } }; +const discourseProperties = (req: Request, property: string) => { + switch (property) { + case 'category': { + return { + params: Joi.object().keys({ + platformId: Joi.required().custom(objectId), + }), + query: Joi.object() + .required() + .keys({ + property: Joi.string().valid('category'), + name: Joi.string().default(null), + sortBy: Joi.string(), + limit: Joi.number().integer(), + page: Joi.number().integer(), + }), + }; + } + default: + req.allowInput = false; + return {}; + } +}; const dynamicPlatformProperty = (req: Request) => { const platformName = req.platform?.name; const property = req.query.property as string; @@ -239,6 +262,9 @@ const dynamicPlatformProperty = (req: Request) => { case PlatformNames.Discord: { return discordProperties(req, property); } + case PlatformNames.Discourse: { + return discourseProperties(req, property); + } default: req.allowInput = false; return {}; From 95ceb3723f05d65c3b68d8fa2be9747ffa71dee8 Mon Sep 17 00:00:00 2001 From: Behzad Rabiei <53224485+Behzad-rabiei@users.noreply.github.com> Date: Thu, 12 Sep 2024 16:32:28 +0400 Subject: [PATCH 2/6] feat: add category property api --- src/services/discord/core.service.ts | 6 ------ src/services/discourse/category.service.ts | 5 +++-- src/services/discourse/core.service.ts | 6 ------ 3 files changed, 3 insertions(+), 14 deletions(-) diff --git a/src/services/discord/core.service.ts b/src/services/discord/core.service.ts index 84236e5..e7af03a 100644 --- a/src/services/discord/core.service.ts +++ b/src/services/discord/core.service.ts @@ -13,12 +13,6 @@ import httpStatus from 'http-status'; const logger = parentLogger.child({ module: 'DiscordCoreService' }); -/** - * exchange discord code with access token - * @param {string} code - @param {string} redirect_uri - * @returns {Promise} - */ async function getPropertyHandler(req: IAuthAndPlatform) { const guildConnection = await DatabaseManager.getInstance().getGuildDb(req.platform?.metadata?.id); diff --git a/src/services/discourse/category.service.ts b/src/services/discourse/category.service.ts index 1afb34c..5fe762c 100644 --- a/src/services/discourse/category.service.ts +++ b/src/services/discourse/category.service.ts @@ -12,8 +12,9 @@ const logger = parentLogger.child({ module: 'DiscourseCategoryService' }); /** * get list of categories - * @param {string} refreshToken - * @returns {Promise} + * @param {string} filters + * @param {options} filters + * @returns {Promise} */ async function getCategoriesByEndPoint(filters: any, options: any): Promise { const { endPoint, name } = filters; diff --git a/src/services/discourse/core.service.ts b/src/services/discourse/core.service.ts index 756f1f1..ebd2cc0 100644 --- a/src/services/discourse/core.service.ts +++ b/src/services/discourse/core.service.ts @@ -5,12 +5,6 @@ import { ApiError, pick, sort } from '../../utils'; const logger = parentLogger.child({ module: 'DiscordCoreService' }); -/** - * exchange discord code with access token - * @param {string} code - @param {string} redirect_uri - * @returns {Promise} - */ async function getPropertyHandler(req: IAuthAndPlatform) { const filter = pick(req.query, ['name']); const options = pick(req.query, ['sortBy', 'limit', 'page']); From 7066d3f3ac60bc6473daa9c87b85761578d6401e Mon Sep 17 00:00:00 2001 From: Behzad Rabiei <53224485+Behzad-rabiei@users.noreply.github.com> Date: Thu, 12 Sep 2024 16:34:35 +0400 Subject: [PATCH 3/6] feat: add category property api --- src/services/discourse/category.service.ts | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/services/discourse/category.service.ts b/src/services/discourse/category.service.ts index 5fe762c..08a7981 100644 --- a/src/services/discourse/category.service.ts +++ b/src/services/discourse/category.service.ts @@ -1,10 +1,5 @@ -import fetch from 'node-fetch'; -import { Client, GatewayIntentBits, Snowflake, Guild } from 'discord.js'; -import config from '../../config'; -import { DatabaseManager } from '@togethercrew.dev/db'; -import { ApiError, pick, sort } from '../../utils'; +import { ApiError } from '../../utils'; import parentLogger from '../../config/logger'; -import { IAuthAndPlatform, IDiscordOAuth2EchangeCode, IDiscordUser } from '../../interfaces'; import httpStatus from 'http-status'; import * as Neo4j from '../../neo4j'; @@ -22,7 +17,6 @@ async function getCategoriesByEndPoint(filters: any, options: any): Promise limit = limit && parseInt(limit, 10) > 0 ? parseInt(limit, 10) : 10; page = page && parseInt(page, 10) > 0 ? parseInt(page, 10) : 1; skip = (page - 1) * limit; - console.log(endPoint, name, limit, page, skip, sortBy); const query = ` MATCH (forum:DiscourseForum {endpoint: "${endPoint}"}) MATCH (dc:DiscourseCategory {forumUuid: forum.uuid}) From 14c6e5bcd328d1c27a98ab44677016f9978d21d3 Mon Sep 17 00:00:00 2001 From: Behzad Rabiei <53224485+Behzad-rabiei@users.noreply.github.com> Date: Thu, 12 Sep 2024 17:15:51 +0400 Subject: [PATCH 4/6] feat: add pagination for category property api --- src/services/discourse/category.service.ts | 45 +++++++++++++++++----- 1 file changed, 36 insertions(+), 9 deletions(-) diff --git a/src/services/discourse/category.service.ts b/src/services/discourse/category.service.ts index 08a7981..01a4e7a 100644 --- a/src/services/discourse/category.service.ts +++ b/src/services/discourse/category.service.ts @@ -17,18 +17,34 @@ async function getCategoriesByEndPoint(filters: any, options: any): Promise limit = limit && parseInt(limit, 10) > 0 ? parseInt(limit, 10) : 10; page = page && parseInt(page, 10) > 0 ? parseInt(page, 10) : 1; skip = (page - 1) * limit; + const totalQuery = ` + MATCH (forum:DiscourseForum {endpoint: "${endPoint}"}) + MATCH (dc:DiscourseCategory {forumUuid: forum.uuid}) + ${name ? `WHERE dc.name =~ '(?i).*${name}.*'` : ''} + RETURN COUNT(dc) as totalResults + `; const query = ` - MATCH (forum:DiscourseForum {endpoint: "${endPoint}"}) - MATCH (dc:DiscourseCategory {forumUuid: forum.uuid}) - ${name ? `WHERE dc.name =~ '(?i).*${name}.*'` : ''} - RETURN dc.id as categoryId, dc.name as name, dc.color as color, dc.descriptionText as description - ORDER BY ${sortBy ? sortBy : 'dc.id'} - SKIP ${skip} - LIMIT ${limit} -`; + MATCH (forum:DiscourseForum {endpoint: "${endPoint}"}) + MATCH (dc:DiscourseCategory {forumUuid: forum.uuid}) + ${name ? `WHERE dc.name =~ '(?i).*${name}.*'` : ''} + RETURN dc.id as categoryId, dc.name as name, dc.color as color, dc.descriptionText as description + ORDER BY ${sortBy ? sortBy : 'dc.id'} + SKIP ${skip} + LIMIT ${limit} + `; + try { + const totalResultDataNeo4j = await Neo4j.read(totalQuery); + const { records: totalResultData } = totalResultDataNeo4j; + // @ts-ignore + const { _fieldLookup, _fields } = totalResultData[0]; + const totalResults = _fields[_fieldLookup['totalResults']] as number; + + // Fetch paginated category results const neo4jCategories = await Neo4j.read(query); const { records: categories } = neo4jCategories; + + // Map the results to an object array const CategoryList = categories.map((category) => { // @ts-ignore const { _fieldLookup, _fields } = category; @@ -38,7 +54,18 @@ async function getCategoriesByEndPoint(filters: any, options: any): Promise const description = _fields[_fieldLookup['description']] as string; return { categoryId, name, color, description }; }); - return CategoryList; + + // Calculate total pages + const totalPages = Math.ceil(totalResults / limit); + + // Return structured response + return { + results: CategoryList, + page, + limit, + totalPages, + totalResults, + }; } catch (error) { logger.error(error, 'Failed to get category list by endpoint'); throw new ApiError(httpStatus.INTERNAL_SERVER_ERROR, 'Failed to get category list by endpoint'); From 69a8bf184f53fb51a17b3b651c2296d05e1d8346 Mon Sep 17 00:00:00 2001 From: Behzad Rabiei <53224485+Behzad-rabiei@users.noreply.github.com> Date: Thu, 12 Sep 2024 18:38:38 +0400 Subject: [PATCH 5/6] feat: add category filter to discurse heatmap chart --- src/docs/discourse.doc.yml | 15 ++++++++++++++ src/services/discourse/heatmap.service.ts | 25 ++++++++++++++++------- src/validations/discourse.validation.ts | 15 +++++++++----- 3 files changed, 43 insertions(+), 12 deletions(-) diff --git a/src/docs/discourse.doc.yml b/src/docs/discourse.doc.yml index 7a9449d..3f13b68 100644 --- a/src/docs/discourse.doc.yml +++ b/src/docs/discourse.doc.yml @@ -28,10 +28,25 @@ paths: format: date-time timeZone: type: string + allCategories: + type: boolean + default: true + include: + type: array + items: + type: string + description: category id + exclude: + type: array + items: + type: string + description: category id example: startDate: '2023-01-17T13:02:10.911+00:00' endDate: '2023-01-29T10:50:01.513Z' timeZone: 'America/Fortaleza' + allCategories: false + include: [123456, 654321] responses: '200': description: OK diff --git a/src/services/discourse/heatmap.service.ts b/src/services/discourse/heatmap.service.ts index 6ca1a31..b6d61df 100644 --- a/src/services/discourse/heatmap.service.ts +++ b/src/services/discourse/heatmap.service.ts @@ -11,25 +11,36 @@ const logger = parentLogger.child({ module: 'DiscourseHeatmapService' }); * @param {IHeatmapChartRequestBody} body * @returns {Array>} */ -async function getHeatmapChart(platformConnection: Connection, body: IHeatmapChartRequestBody) { - const { startDate, endDate } = body; +async function getHeatmapChart(platformConnection: Connection, body: any) { + const { startDate, endDate, allCategories, include, exclude } = body; try { + let matchStage: any = { + $and: [{ date: { $gte: new Date(startDate) } }, { date: { $lte: new Date(endDate) } }], + }; + + if (allCategories === false) { + if (include && include.length > 0) { + matchStage.category_id = { $in: include.map(Number) }; + } else if (exclude && exclude.length > 0) { + matchStage.category_id = { $nin: exclude.map(Number) }; + } + } + const heatmaps = await platformConnection.models.HeatMap.aggregate([ // Stage1 : convert date from string to date type and extract needed data { $project: { _id: 0, date: { $convert: { input: '$date', to: 'date' } }, + category_id: 1, category_messages: 1, replier: 1, }, }, - // Stage2: find heatmaps between startDate and endDate + // Stage2: find heatmaps between startDate and endDate and apply category filter { - $match: { - $and: [{ date: { $gte: new Date(startDate) } }, { date: { $lte: new Date(endDate) } }], - }, + $match: matchStage, }, // Stage3 : provide one document for each element of interactions array @@ -51,7 +62,7 @@ async function getHeatmapChart(platformConnection: Connection, body: IHeatmapCha }, }, - // Stage5 : group documents base on day and hour + // Stage5 : group documents based on day and hour { $group: { _id: { dayOfWeek: '$dayOfWeek', hour: '$hour' }, diff --git a/src/validations/discourse.validation.ts b/src/validations/discourse.validation.ts index 03bfbfe..05f24e8 100644 --- a/src/validations/discourse.validation.ts +++ b/src/validations/discourse.validation.ts @@ -7,11 +7,16 @@ const heatmapChart = { .keys({ platformId: Joi.string().custom(objectId).required(), }), - body: Joi.object().required().keys({ - startDate: Joi.date().required(), - endDate: Joi.date().required(), - timeZone: Joi.string().required(), - }), + body: Joi.object() + .required() + .keys({ + startDate: Joi.date().required(), + endDate: Joi.date().required(), + timeZone: Joi.string().required(), + allCategories: Joi.boolean().default(true), + include: Joi.array().items(Joi.number()).when('allCategories', { is: true, then: Joi.forbidden() }), + exclude: Joi.array().items(Joi.number()).when('allCategories', { is: true, then: Joi.forbidden() }), + }), }; const lineGraph = { From e32649eeebf2dd82d655a9a8427f57a8046b8437 Mon Sep 17 00:00:00 2001 From: Behzad Rabiei <53224485+Behzad-rabiei@users.noreply.github.com> Date: Thu, 12 Sep 2024 18:42:33 +0400 Subject: [PATCH 6/6] docs: update doc --- src/docs/platform.doc.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/docs/platform.doc.yml b/src/docs/platform.doc.yml index 82c4291..012e064 100644 --- a/src/docs/platform.doc.yml +++ b/src/docs/platform.doc.yml @@ -376,14 +376,14 @@ paths: - name: property in: query required: true - description: The type of property to retrieve (e.g., 'channel' or 'role' for Discord platform - 'category' for discoruse platform). + description: The type of property to retrieve (e.g., 'channel' or 'role' for Discord platform and'category' for discoruse platform). schema: type: string enum: ['channel', 'role', 'guildMember', 'category'] - name: name in: query required: false - description: Name of the role (supported for 'discord-role' property) - Name of the category (supported for 'discourse-category' property). + description: Name of the role (supported for 'discord-role' and 'discourse-category' properties). schema: type: string - name: ngu @@ -395,7 +395,7 @@ paths: - name: sortBy in: query required: false - description: The parameter to sort by (supported for 'discord-role', 'discord-guildMember', and 'discourse-category' properties). for the 'discourse-category' the format is like 'dc.name' + description: The parameter to sort by (supported for 'discord-role', 'discord-guildMember', and 'discourse-category' (example dc.name) properties). schema: type: string - name: limit