From ef976f76045e9d5fabf1261770592b4594e59353 Mon Sep 17 00:00:00 2001 From: mit-27 Date: Tue, 23 Jul 2024 00:29:59 -0400 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20Add=20github=20objects=20{User,Tag,?= =?UTF-8?q?Team,Collection}?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../collection/services/github/index.ts | 61 ++++ .../collection/services/github/mappers.ts | 69 +++++ .../collection/services/github/types.ts | 127 ++++++++ .../ticketing/tag/services/github/index.ts | 76 +++++ .../ticketing/tag/services/github/mappers.ts | 58 ++++ .../ticketing/tag/services/github/types.ts | 16 + .../ticketing/team/services/github/index.ts | 59 ++++ .../ticketing/team/services/github/mappers.ts | 58 ++++ .../ticketing/team/services/github/types.ts | 22 ++ .../ticketing/ticket/services/github/index.ts | 0 .../ticket/services/github/mappers.ts | 0 .../ticketing/ticket/services/github/types.ts | 287 ++++++++++++++++++ .../ticketing/user/services/github/index.ts | 59 ++++ .../ticketing/user/services/github/mappers.ts | 70 +++++ .../ticketing/user/services/github/types.ts | 23 ++ 15 files changed, 985 insertions(+) create mode 100644 packages/api/src/ticketing/collection/services/github/index.ts create mode 100644 packages/api/src/ticketing/collection/services/github/mappers.ts create mode 100644 packages/api/src/ticketing/collection/services/github/types.ts create mode 100644 packages/api/src/ticketing/tag/services/github/index.ts create mode 100644 packages/api/src/ticketing/tag/services/github/mappers.ts create mode 100644 packages/api/src/ticketing/tag/services/github/types.ts create mode 100644 packages/api/src/ticketing/team/services/github/index.ts create mode 100644 packages/api/src/ticketing/team/services/github/mappers.ts create mode 100644 packages/api/src/ticketing/team/services/github/types.ts create mode 100644 packages/api/src/ticketing/ticket/services/github/index.ts create mode 100644 packages/api/src/ticketing/ticket/services/github/mappers.ts create mode 100644 packages/api/src/ticketing/ticket/services/github/types.ts create mode 100644 packages/api/src/ticketing/user/services/github/index.ts create mode 100644 packages/api/src/ticketing/user/services/github/mappers.ts create mode 100644 packages/api/src/ticketing/user/services/github/types.ts diff --git a/packages/api/src/ticketing/collection/services/github/index.ts b/packages/api/src/ticketing/collection/services/github/index.ts new file mode 100644 index 000000000..3197829aa --- /dev/null +++ b/packages/api/src/ticketing/collection/services/github/index.ts @@ -0,0 +1,61 @@ +import { Injectable } from '@nestjs/common'; +import { LoggerService } from '@@core/@core-services/logger/logger.service'; +import { PrismaService } from '@@core/@core-services/prisma/prisma.service'; +import { EncryptionService } from '@@core/@core-services/encryption/encryption.service'; +import { TicketingObject } from '@ticketing/@lib/@types'; +import { ApiResponse } from '@@core/utils/types'; +import axios from 'axios'; +import { ActionType, handle3rdPartyServiceError } from '@@core/utils/errors'; +import { ServiceRegistry } from '../registry.service'; +import { ICollectionService } from '@ticketing/collection/types'; +import { GithubCollectionInput, GithubCollectionOutput } from './types'; +import { SyncParam } from '@@core/utils/types/interface'; + +@Injectable() +export class GithubService implements ICollectionService { + constructor( + private prisma: PrismaService, + private logger: LoggerService, + private cryptoService: EncryptionService, + private registry: ServiceRegistry, + ) { + this.logger.setContext( + TicketingObject.collection.toUpperCase() + ':' + GithubService.name, + ); + this.registry.registerService('github', this); + } + + async sync(data: SyncParam): Promise> { + try { + const { linkedUserId } = data; + + const connection = await this.prisma.connections.findFirst({ + where: { + id_linked_user: linkedUserId, + provider_slug: 'github', + vertical: 'ticketing', + }, + }); + const resp = await axios.get( + `${connection.account_url}/user/repos`, + { + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${this.cryptoService.decrypt( + connection.access_token, + )}`, + }, + }, + ); + this.logger.log(`Synced github collections !`); + + return { + data: resp.data, + message: 'Github collections retrieved', + statusCode: 200, + }; + } catch (error) { + throw error; + } + } +} diff --git a/packages/api/src/ticketing/collection/services/github/mappers.ts b/packages/api/src/ticketing/collection/services/github/mappers.ts new file mode 100644 index 000000000..268f31ae5 --- /dev/null +++ b/packages/api/src/ticketing/collection/services/github/mappers.ts @@ -0,0 +1,69 @@ +import { ICollectionMapper } from '@ticketing/collection/types'; +import { GithubCollectionInput, GithubCollectionOutput } from './types'; +import { + UnifiedCollectionInput, + UnifiedCollectionOutput, +} from '@ticketing/collection/types/model.unified'; +import { MappersRegistry } from '@@core/@core-services/registries/mappers.registry'; +import { Injectable } from '@nestjs/common'; +import { Utils } from '@ticketing/@lib/@utils'; + +@Injectable() +export class GithubCollectionMapper implements ICollectionMapper { + constructor(private mappersRegistry: MappersRegistry, private utils: Utils) { + this.mappersRegistry.registerService( + 'ticketing', + 'collection', + 'github', + this, + ); + } + desunify( + source: UnifiedCollectionInput, + customFieldMappings?: { + slug: string; + remote_id: string; + }[], + ): GithubCollectionInput { + return; + } + + unify( + source: GithubCollectionOutput | GithubCollectionOutput[], + connectionId: string, + customFieldMappings?: { + slug: string; + remote_id: string; + }[], + ): UnifiedCollectionOutput | UnifiedCollectionOutput[] { + // If the source is not an array, convert it to an array for mapping + const sourcesArray = Array.isArray(source) ? source : [source]; + + return sourcesArray.map((collection) => + this.mapSingleCollectionToUnified( + collection, + connectionId, + customFieldMappings, + ), + ); + } + + private mapSingleCollectionToUnified( + collection: GithubCollectionOutput, + connectionId: string, + customFieldMappings?: { + slug: string; + remote_id: string; + }[], + ): UnifiedCollectionOutput { + const unifiedCollection: UnifiedCollectionOutput = { + remote_id: String(collection.id), + remote_data: collection, + name: collection.name, + description: collection.description, + collection_type: 'PROJECT', + }; + + return unifiedCollection; + } +} diff --git a/packages/api/src/ticketing/collection/services/github/types.ts b/packages/api/src/ticketing/collection/services/github/types.ts new file mode 100644 index 000000000..acf317e91 --- /dev/null +++ b/packages/api/src/ticketing/collection/services/github/types.ts @@ -0,0 +1,127 @@ +interface GithubCollection { + id: number + node_id: string + name: string + full_name: string + owner: Owner + private: boolean + html_url: string + description: string + fork: boolean + url: string + archive_url: string + assignees_url: string + blobs_url: string + branches_url: string + collaborators_url: string + comments_url: string + commits_url: string + compare_url: string + contents_url: string + contributors_url: string + deployments_url: string + downloads_url: string + events_url: string + forks_url: string + git_commits_url: string + git_refs_url: string + git_tags_url: string + git_url: string + issue_comment_url: string + issue_events_url: string + issues_url: string + keys_url: string + labels_url: string + languages_url: string + merges_url: string + milestones_url: string + notifications_url: string + pulls_url: string + releases_url: string + ssh_url: string + stargazers_url: string + statuses_url: string + subscribers_url: string + subscription_url: string + tags_url: string + teams_url: string + trees_url: string + clone_url: string + mirror_url: string + hooks_url: string + svn_url: string + homepage: string + language: any + forks_count: number + stargazers_count: number + watchers_count: number + size: number + default_branch: string + open_issues_count: number + is_template: boolean + topics: string[] + has_issues: boolean + has_projects: boolean + has_wiki: boolean + has_pages: boolean + has_downloads: boolean + archived: boolean + disabled: boolean + visibility: string + pushed_at: string + created_at: string + updated_at: string + permissions: Permissions + allow_rebase_merge: boolean + template_repository: any + temp_clone_token: string + allow_squash_merge: boolean + allow_auto_merge: boolean + delete_branch_on_merge: boolean + allow_merge_commit: boolean + subscribers_count: number + network_count: number + license: License + forks: number + open_issues: number + watchers: number +} + +interface Owner { + login: string + id: number + node_id: string + avatar_url: string + gravatar_id: string + url: string + html_url: string + followers_url: string + following_url: string + gists_url: string + starred_url: string + subscriptions_url: string + organizations_url: string + repos_url: string + events_url: string + received_events_url: string + type: string + site_admin: boolean +} + +interface Permissions { + admin: boolean + push: boolean + pull: boolean +} + +interface License { + key: string + name: string + url: string + spdx_id: string + node_id: string + html_url: string +} + +export type GithubCollectionInput = Partial; +export type GithubCollectionOutput = GithubCollectionInput; diff --git a/packages/api/src/ticketing/tag/services/github/index.ts b/packages/api/src/ticketing/tag/services/github/index.ts new file mode 100644 index 000000000..f0e889a88 --- /dev/null +++ b/packages/api/src/ticketing/tag/services/github/index.ts @@ -0,0 +1,76 @@ +import { Injectable } from '@nestjs/common'; +import { LoggerService } from '@@core/@core-services/logger/logger.service'; +import { PrismaService } from '@@core/@core-services/prisma/prisma.service'; +import { EncryptionService } from '@@core/@core-services/encryption/encryption.service'; +import { TicketingObject } from '@ticketing/@lib/@types'; +import { ApiResponse } from '@@core/utils/types'; +import axios from 'axios'; +import { ActionType, handle3rdPartyServiceError } from '@@core/utils/errors'; +import { ServiceRegistry } from '../registry.service'; +import { ITagService } from '@ticketing/tag/types'; +import { GithubTagOutput } from './types'; +import { SyncParam } from '@@core/utils/types/interface'; + +@Injectable() +export class GithubService implements ITagService { + constructor( + private prisma: PrismaService, + private logger: LoggerService, + private cryptoService: EncryptionService, + private registry: ServiceRegistry, + ) { + this.logger.setContext( + TicketingObject.tag.toUpperCase() + ':' + GithubService.name, + ); + this.registry.registerService('github', this); + } + + async sync(data: SyncParam): Promise> { + try { + const { linkedUserId } = data; + + const connection = await this.prisma.connections.findFirst({ + where: { + id_linked_user: linkedUserId, + provider_slug: 'github', + vertical: 'ticketing', + }, + }); + + const repos = await axios.get(`${connection.account_url}/user/repos`, { + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${this.cryptoService.decrypt( + connection.access_token, + )}`, + }, + }); + let resp: any = []; + for (const repo of repos.data) { + if (repo.id) { + const tags = await axios.get( + `${connection.account_url}/repos/${repo.owner.login}/${repo.name}/labels`, + { + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${this.cryptoService.decrypt( + connection.access_token, + )}`, + }, + }, + ); + resp = [...resp, tags.data]; + } + } + this.logger.log(`Synced github tags !`); + + return { + data: resp.flat(), + message: 'Github tags retrieved', + statusCode: 200, + }; + } catch (error) { + throw error; + } + } +} diff --git a/packages/api/src/ticketing/tag/services/github/mappers.ts b/packages/api/src/ticketing/tag/services/github/mappers.ts new file mode 100644 index 000000000..06f745ce6 --- /dev/null +++ b/packages/api/src/ticketing/tag/services/github/mappers.ts @@ -0,0 +1,58 @@ +import { ITagMapper } from '@ticketing/tag/types'; +import { GithubTagInput, GithubTagOutput } from './types'; +import { + UnifiedTagInput, + UnifiedTagOutput, +} from '@ticketing/tag/types/model.unified'; +import { MappersRegistry } from '@@core/@core-services/registries/mappers.registry'; +import { Injectable } from '@nestjs/common'; +import { Utils } from '@ticketing/@lib/@utils'; + +@Injectable() +export class GithubTagMapper implements ITagMapper { + constructor(private mappersRegistry: MappersRegistry, private utils: Utils) { + this.mappersRegistry.registerService('ticketing', 'tag', 'github', this); + } + desunify( + source: UnifiedTagInput, + customFieldMappings?: { + slug: string; + remote_id: string; + }[], + ): GithubTagInput { + return; + } + + unify( + source: GithubTagOutput | GithubTagOutput[], + connectionId: string, + customFieldMappings?: { + slug: string; + remote_id: string; + }[], + ): UnifiedTagOutput | UnifiedTagOutput[] { + // If the source is not an array, convert it to an array for mapping + const sourcesArray = Array.isArray(source) ? source : [source]; + + return sourcesArray.map((tag) => + this.mapSingleTagToUnified(tag, connectionId, customFieldMappings), + ); + } + + private mapSingleTagToUnified( + tag: GithubTagOutput, + connectionId: string, + customFieldMappings?: { + slug: string; + remote_id: string; + }[], + ): UnifiedTagOutput { + const unifiedTag: UnifiedTagOutput = { + remote_id: tag.id ? String(tag.id) : null, + remote_data: tag, + name: tag.name, + }; + + return unifiedTag; + } +} diff --git a/packages/api/src/ticketing/tag/services/github/types.ts b/packages/api/src/ticketing/tag/services/github/types.ts new file mode 100644 index 000000000..e6ae1abd6 --- /dev/null +++ b/packages/api/src/ticketing/tag/services/github/types.ts @@ -0,0 +1,16 @@ +interface GithubTag { + id: number + node_id: string + url: string + name: string + description: string + color: string + default: boolean +} + +export type GithubTagInput = { + id: string +} + + +export type GithubTagOutput = Partial; \ No newline at end of file diff --git a/packages/api/src/ticketing/team/services/github/index.ts b/packages/api/src/ticketing/team/services/github/index.ts new file mode 100644 index 000000000..8bee342ce --- /dev/null +++ b/packages/api/src/ticketing/team/services/github/index.ts @@ -0,0 +1,59 @@ +import { Injectable } from '@nestjs/common'; +import { LoggerService } from '@@core/@core-services/logger/logger.service'; +import { PrismaService } from '@@core/@core-services/prisma/prisma.service'; +import { EncryptionService } from '@@core/@core-services/encryption/encryption.service'; +import { TicketingObject } from '@ticketing/@lib/@types'; +import { ApiResponse } from '@@core/utils/types'; +import axios from 'axios'; +import { ActionType, handle3rdPartyServiceError } from '@@core/utils/errors'; +import { ServiceRegistry } from '../registry.service'; +import { ITeamService } from '@ticketing/team/types'; +import { GithubTeamOutput } from './types'; +import { SyncParam } from '@@core/utils/types/interface'; + +@Injectable() +export class GithubService implements ITeamService { + constructor( + private prisma: PrismaService, + private logger: LoggerService, + private cryptoService: EncryptionService, + private registry: ServiceRegistry, + ) { + this.logger.setContext( + TicketingObject.team.toUpperCase() + ':' + GithubService.name, + ); + this.registry.registerService('github', this); + } + + async sync(data: SyncParam): Promise> { + try { + const { linkedUserId } = data; + + const connection = await this.prisma.connections.findFirst({ + where: { + id_linked_user: linkedUserId, + provider_slug: 'github', + vertical: 'ticketing', + }, + }); + + const resp = await axios.get(`${connection.account_url}/user/teams`, { + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${this.cryptoService.decrypt( + connection.access_token, + )}`, + }, + }); + this.logger.log(`Synced github teams !`); + + return { + data: resp.data, + message: 'Github teams retrieved', + statusCode: 200, + }; + } catch (error) { + throw error; + } + } +} diff --git a/packages/api/src/ticketing/team/services/github/mappers.ts b/packages/api/src/ticketing/team/services/github/mappers.ts new file mode 100644 index 000000000..23f75599f --- /dev/null +++ b/packages/api/src/ticketing/team/services/github/mappers.ts @@ -0,0 +1,58 @@ +import { ITeamMapper } from '@ticketing/team/types'; +import { GithubTeamInput, GithubTeamOutput } from './types'; +import { + UnifiedTeamInput, + UnifiedTeamOutput, +} from '@ticketing/team/types/model.unified'; +import { MappersRegistry } from '@@core/@core-services/registries/mappers.registry'; +import { Injectable } from '@nestjs/common'; +import { Utils } from '@ticketing/@lib/@utils'; + +@Injectable() +export class GithubTeamMapper implements ITeamMapper { + constructor(private mappersRegistry: MappersRegistry, private utils: Utils) { + this.mappersRegistry.registerService('ticketing', 'team', 'github', this); + } + desunify( + source: UnifiedTeamInput, + customFieldMappings?: { + slug: string; + remote_id: string; + }[], + ): GithubTeamInput { + return; + } + + unify( + source: GithubTeamOutput | GithubTeamOutput[], + connectionId: string, + customFieldMappings?: { + slug: string; + remote_id: string; + }[], + ): UnifiedTeamOutput | UnifiedTeamOutput[] { + // If the source is not an array, convert it to an array for mapping + const sourcesArray = Array.isArray(source) ? source : [source]; + + return sourcesArray.map((team) => + this.mapSingleTeamToUnified(team, connectionId, customFieldMappings), + ); + } + + private mapSingleTeamToUnified( + team: GithubTeamOutput, + connectionId: string, + customFieldMappings?: { + slug: string; + remote_id: string; + }[], + ): UnifiedTeamOutput { + const unifiedTeam: UnifiedTeamOutput = { + remote_id: String(team.id), + remote_data: team, + name: team.name, + }; + + return unifiedTeam; + } +} diff --git a/packages/api/src/ticketing/team/services/github/types.ts b/packages/api/src/ticketing/team/services/github/types.ts new file mode 100644 index 000000000..1a202c23b --- /dev/null +++ b/packages/api/src/ticketing/team/services/github/types.ts @@ -0,0 +1,22 @@ +interface GithubTeam { + id: number + node_id: string + url: string + html_url: string + name: string + slug: string + description: string + privacy: string + notification_setting: string + permission: string + members_url: string + repositories_url: string + parent: any +} + +export type GithubTeamInput = { + id: string +} + + +export type GithubTeamOutput = Partial; \ No newline at end of file diff --git a/packages/api/src/ticketing/ticket/services/github/index.ts b/packages/api/src/ticketing/ticket/services/github/index.ts new file mode 100644 index 000000000..e69de29bb diff --git a/packages/api/src/ticketing/ticket/services/github/mappers.ts b/packages/api/src/ticketing/ticket/services/github/mappers.ts new file mode 100644 index 000000000..e69de29bb diff --git a/packages/api/src/ticketing/ticket/services/github/types.ts b/packages/api/src/ticketing/ticket/services/github/types.ts new file mode 100644 index 000000000..6cb68b9ee --- /dev/null +++ b/packages/api/src/ticketing/ticket/services/github/types.ts @@ -0,0 +1,287 @@ +interface GithubTicketOutputType { + id: number + node_id: string + url: string + repository_url: string + labels_url: string + comments_url: string + events_url: string + html_url: string + number: number + state: string + title: string + body: string + user: User + labels: Label[] + assignee: Assignee + assignees: Assignee2[] + milestone: Milestone + locked: boolean + active_lock_reason: string + comments: number + pull_request: PullRequest + closed_at: any + created_at: string + updated_at: string + repository: Repository + author_association: string +} + +interface User { + login: string + id: number + node_id: string + avatar_url: string + gravatar_id: string + url: string + html_url: string + followers_url: string + following_url: string + gists_url: string + starred_url: string + subscriptions_url: string + organizations_url: string + repos_url: string + events_url: string + received_events_url: string + type: string + site_admin: boolean +} + +interface Label { + id: number + node_id: string + url: string + name: string + description: string + color: string + default: boolean +} + +interface Assignee { + login: string + id: number + node_id: string + avatar_url: string + gravatar_id: string + url: string + html_url: string + followers_url: string + following_url: string + gists_url: string + starred_url: string + subscriptions_url: string + organizations_url: string + repos_url: string + events_url: string + received_events_url: string + type: string + site_admin: boolean +} + +interface Assignee2 { + login: string + id: number + node_id: string + avatar_url: string + gravatar_id: string + url: string + html_url: string + followers_url: string + following_url: string + gists_url: string + starred_url: string + subscriptions_url: string + organizations_url: string + repos_url: string + events_url: string + received_events_url: string + type: string + site_admin: boolean +} + +interface Milestone { + url: string + html_url: string + labels_url: string + id: number + node_id: string + number: number + state: string + title: string + description: string + creator: Creator + open_issues: number + closed_issues: number + created_at: string + updated_at: string + closed_at: string + due_on: string +} + +interface Creator { + login: string + id: number + node_id: string + avatar_url: string + gravatar_id: string + url: string + html_url: string + followers_url: string + following_url: string + gists_url: string + starred_url: string + subscriptions_url: string + organizations_url: string + repos_url: string + events_url: string + received_events_url: string + type: string + site_admin: boolean +} + +interface PullRequest { + url: string + html_url: string + diff_url: string + patch_url: string +} + +interface Repository { + id: number + node_id: string + name: string + full_name: string + owner: Owner + private: boolean + html_url: string + description: string + fork: boolean + url: string + archive_url: string + assignees_url: string + blobs_url: string + branches_url: string + collaborators_url: string + comments_url: string + commits_url: string + compare_url: string + contents_url: string + contributors_url: string + deployments_url: string + downloads_url: string + events_url: string + forks_url: string + git_commits_url: string + git_refs_url: string + git_tags_url: string + git_url: string + issue_comment_url: string + issue_events_url: string + issues_url: string + keys_url: string + labels_url: string + languages_url: string + merges_url: string + milestones_url: string + notifications_url: string + pulls_url: string + releases_url: string + ssh_url: string + stargazers_url: string + statuses_url: string + subscribers_url: string + subscription_url: string + tags_url: string + teams_url: string + trees_url: string + clone_url: string + mirror_url: string + hooks_url: string + svn_url: string + homepage: string + language: any + forks_count: number + stargazers_count: number + watchers_count: number + size: number + default_branch: string + open_issues_count: number + is_template: boolean + topics: string[] + has_issues: boolean + has_projects: boolean + has_wiki: boolean + has_pages: boolean + has_downloads: boolean + archived: boolean + disabled: boolean + visibility: string + pushed_at: string + created_at: string + updated_at: string + permissions: Permissions + allow_rebase_merge: boolean + template_repository: any + temp_clone_token: string + allow_squash_merge: boolean + allow_auto_merge: boolean + delete_branch_on_merge: boolean + allow_merge_commit: boolean + subscribers_count: number + network_count: number + license: License + forks: number + open_issues: number + watchers: number +} + +interface Owner { + login: string + id: number + node_id: string + avatar_url: string + gravatar_id: string + url: string + html_url: string + followers_url: string + following_url: string + gists_url: string + starred_url: string + subscriptions_url: string + organizations_url: string + repos_url: string + events_url: string + received_events_url: string + type: string + site_admin: boolean +} + +interface Permissions { + admin: boolean + push: boolean + pull: boolean +} + +interface License { + key: string + name: string + url: string + spdx_id: string + node_id: string + html_url: string +} + + +export type GithubTicketInput = { + title: string | number, + body?: string, + assignee?: string, + milestone?: string | number, + labels?: string[], + assignees?: string[] + +} + + +export type GithubTicketOutput = Partial; diff --git a/packages/api/src/ticketing/user/services/github/index.ts b/packages/api/src/ticketing/user/services/github/index.ts new file mode 100644 index 000000000..e4a3d75f5 --- /dev/null +++ b/packages/api/src/ticketing/user/services/github/index.ts @@ -0,0 +1,59 @@ +import { Injectable } from '@nestjs/common'; +import { LoggerService } from '@@core/@core-services/logger/logger.service'; +import { PrismaService } from '@@core/@core-services/prisma/prisma.service'; +import { EncryptionService } from '@@core/@core-services/encryption/encryption.service'; +import { TicketingObject } from '@ticketing/@lib/@types'; +import { ApiResponse } from '@@core/utils/types'; +import axios from 'axios'; +import { ActionType, handle3rdPartyServiceError } from '@@core/utils/errors'; +import { ServiceRegistry } from '../registry.service'; +import { IUserService } from '@ticketing/user/types'; +import { GithubUserOutput } from './types'; +import { SyncParam } from '@@core/utils/types/interface'; + +@Injectable() +export class GithubService implements IUserService { + constructor( + private prisma: PrismaService, + private logger: LoggerService, + private cryptoService: EncryptionService, + private registry: ServiceRegistry, + ) { + this.logger.setContext( + TicketingObject.user.toUpperCase() + ':' + GithubService.name, + ); + this.registry.registerService('github', this); + } + + async sync(data: SyncParam): Promise> { + const { linkedUserId } = data; + + try { + const connection = await this.prisma.connections.findFirst({ + where: { + id_linked_user: linkedUserId, + provider_slug: 'github', + vertical: 'ticketing', + }, + }); + + const resp = await axios.get(`${connection.account_url}/users`, { + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${this.cryptoService.decrypt( + connection.access_token, + )}`, + }, + }); + this.logger.log(`Synced github users !`); + + return { + data: resp.data, + message: 'github users retrieved', + statusCode: 200, + }; + } catch (error) { + throw error; + } + } +} diff --git a/packages/api/src/ticketing/user/services/github/mappers.ts b/packages/api/src/ticketing/user/services/github/mappers.ts new file mode 100644 index 000000000..c9d49862c --- /dev/null +++ b/packages/api/src/ticketing/user/services/github/mappers.ts @@ -0,0 +1,70 @@ +import { IUserMapper } from '@ticketing/user/types'; +import { + UnifiedUserInput, + UnifiedUserOutput, +} from '@ticketing/user/types/model.unified'; +import { GithubUserInput, GithubUserOutput } from './types'; +import { MappersRegistry } from '@@core/@core-services/registries/mappers.registry'; +import { Injectable } from '@nestjs/common'; +import { Utils } from '@ticketing/@lib/@utils'; + +@Injectable() +export class GithubUserMapper implements IUserMapper { + constructor(private mappersRegistry: MappersRegistry, private utils: Utils) { + this.mappersRegistry.registerService('ticketing', 'user', 'github', this); + } + desunify( + source: UnifiedUserInput, + customFieldMappings?: { + slug: string; + remote_id: string; + }[], + ): GithubUserInput { + return; + } + + async unify( + source: GithubUserOutput | GithubUserOutput[], + connectionId: string, + customFieldMappings?: { + slug: string; + remote_id: string; + }[], + ): Promise { + const sourcesArray = Array.isArray(source) ? source : [source]; + return sourcesArray.map((user) => + this.mapSingleUserToUnified(user, connectionId, customFieldMappings), + ); + } + + private mapSingleUserToUnified( + user: GithubUserOutput, + connectionId: string, + customFieldMappings?: { + slug: string; + remote_id: string; + }[], + ): UnifiedUserOutput { + // Initialize field_mappings array from customFields, if provided + const field_mappings = customFieldMappings + ? customFieldMappings + .map((mapping) => ({ + key: mapping.slug, + value: user ? user[mapping.remote_id] : undefined, + })) + .filter((mapping) => mapping.value !== undefined) + : []; + + const unifiedUser: UnifiedUserOutput = { + remote_id: String(user.id), + remote_data: user, + // does not have name, only github username is available + name: null, + // Email address is not available + email_address: null, + field_mappings, + }; + + return unifiedUser; + } +} diff --git a/packages/api/src/ticketing/user/services/github/types.ts b/packages/api/src/ticketing/user/services/github/types.ts new file mode 100644 index 000000000..f4134f4bc --- /dev/null +++ b/packages/api/src/ticketing/user/services/github/types.ts @@ -0,0 +1,23 @@ +interface GithubUser { + login: string + id: number + node_id: string + avatar_url: string + gravatar_id: string + url: string + html_url: string + followers_url: string + following_url: string + gists_url: string + starred_url: string + subscriptions_url: string + organizations_url: string + repos_url: string + events_url: string + received_events_url: string + type: string + site_admin: boolean +} + +export type GithubUserInput = Partial; +export type GithubUserOutput = GithubUserInput; \ No newline at end of file