Skip to content

Commit

Permalink
Merge pull request #19 from cabinetoffice/NTRNL-374-update-github-api…
Browse files Browse the repository at this point in the history
…-sdk-methods

Ntrnl 374 update GitHub api sdk methods
  • Loading branch information
Mouhajer-CO authored Mar 1, 2024
2 parents 91c5ee7 + c7a26d3 commit 3e203bb
Show file tree
Hide file tree
Showing 8 changed files with 209 additions and 44 deletions.
42 changes: 25 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@
![Static Badge](https://img.shields.io/badge/test_coverage-%E2%89%A595%25-green)

This Node.js SDK module is a development tool that simplifies and accelerates the integration of external services or APIs into Node.js applications. It aims to make the developer's life easier by providing a well-documented, customizable, and reliable interface to interact with the external service.
Allows developers to extend the SDK's behavior through configuration options and callbacks and implements security best practices that protect against common vulnerabilities and threats, especially if handling sensitive data or credentials.
Allows developers to extend the SDK's behaviour through configuration options and callbacks and implements security best practices that protect against common vulnerabilities and threats, especially if handling sensitive data or credentials.

## Files Structure

| Directory Path | Description |
| -------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `./api-client/` | This directory is used to create api client object based on Authentication headers (OAuth or api Key). |
| `./api-sdk/` | This directory is where the department related SDK modules are keept. |
| `./api-sdk/` | This directory is where the department related SDK modules are kept. |
| `./api-sdk/github` | This folder is the entry point for the GitHub API SDK calls, data types and includes TypeScript interfaces that describe the structure and shape of various objects used in the module and possible mapping. |
| `./api-sdk/identity` | This folder is the entry point for the Identity API SDK calls, data types and mapping. |
| `./http-request/` | This folder contains the core class of this module, the `HttpRequest` class. It serves as the foundation for creating an `Axios` object with a predefined sequence of `AxiosRequestConfig` configurations before initiating the actual HTTP request. This object will then be provided as a parameter to the ApiSDK and passed to all SDKs for executing the corresponding HTTP calls. |
Expand Down Expand Up @@ -156,36 +156,44 @@ The HttpRequest class is responsible for configuring Axios `request` with predef
```js
import { HttpRequest } from "../../http-request";
import { ApiResponse, ApiErrorResponse } from "../response";
import { GitHubRepos, GitHubIssueRequest } from './type';
import { reposMapping } from './mapping';

export class Github {
constructor(private readonly request: HttpRequest) {
/**/
}
public async getRepos(url: string): Promise<ApiResponse<GitHubRepos> | ApiErrorResponse> {
return this.fetchData<GitHubRepos>(url, reposMapping);
}

public async getMembers(url: string): Promise<ApiResponse<GitHubMembers> | ApiErrorResponse> {
return this.fetchData<GitHubMembers>(url, membersMapping);
constructor(private readonly request: HttpRequest) { /**/ }

public async getGitHubInfo(url: string): Promise<ApiResponse<any[]> | ApiErrorResponse> {
const response = await this.request.httpGet(url);
return this.responseHandler(response);
}

public async getTeams(url: string): Promise<ApiResponse<GitHubTeams> | ApiErrorResponse> {
return this.fetchData<GitHubTeams>(url, teamsMapping);
public async postIssue (url: string, body: GitHubIssueRequest): Promise<ApiResponse<any> | ApiErrorResponse> {
const response = await this.request.httpPost(url, body);
return this.responseHandler(response);
}

private async fetchData<T>(
url: string,
mappingFunction: (body: any) => T
): Promise<ApiResponse<T> | ApiErrorResponse> {
public async getRepos(url: string): Promise<ApiResponse<GitHubRepos[]> | ApiErrorResponse> {
const response = await this.request.httpGet(url);
return this.responseHandler<GitHubRepos[]>(response, reposMapping);
}

// ...

private responseHandler<T>(
response: any,
responseMap?: (body: any) => T
): ApiResponse<T> | ApiErrorResponse {
const resource: ApiResponse<T> & ApiErrorResponse = {
httpStatusCode: response.status
};

if (response.error) {
resource.errors = [response.error];
} else if (response.status >= 400) {
resource.errors = [response.body];
} else {
resource.resource = mappingFunction(response.body);
resource.resource = (responseMap) ? responseMap(response.body) : response.body;
}

return resource;
Expand Down
10 changes: 5 additions & 5 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@co-digital/api-sdk",
"version": "1.0.4",
"version": "1.0.5",
"description": "An API SDK for Node.JS applications in CO Digital.",
"homepage": "https://github.com/cabinetoffice/node-api-sdk#README.md",
"main": "./lib/index.js",
Expand Down
66 changes: 52 additions & 14 deletions src/api-sdk/github/github.ts
Original file line number Diff line number Diff line change
@@ -1,37 +1,75 @@
import { GitHubRepos, GitHubMembers, GitHubTeams, GitHubMembersPerTeam, GitHubReposPerTeam } from './type';
import {
GitHubRepos,
GitHubMembers,
GitHubTeams,
GitHubMembersPerTeam,
GitHubReposPerTeam,
GitHubCollaboratorsPerRepo,
GitHubIssueRequest
} from './type';
import {
reposMapping,
membersMapping,
teamsMapping,
membersPerTeamMapping,
reposPerTeamMapping,
collaboratorsPerRepoMapping
} from './mapping';

import { ApiResponse, ApiErrorResponse } from '../response';
import { reposMapping, membersMapping, teamsMapping, membersPerTeamMapping, reposPerTeamMapping } from './mapping';
import { HttpRequest } from '../../http-request';

export class Github {
constructor(private readonly request: HttpRequest) {
/**/

constructor(private readonly request: HttpRequest) { /**/ }

// Get info data. Could be repos/members/collaborator/teams ...
public async getGitHubInfo(url: string): Promise<ApiResponse<any[]> | ApiErrorResponse> {
const response = await this.request.httpGet(url);
return this.responseHandler(response);
}

// Create Issue on defined repo
public async postIssue (url: string, body: GitHubIssueRequest): Promise<ApiResponse<any> | ApiErrorResponse> {
const response = await this.request.httpPost(url, body);
return this.responseHandler(response);
}

// Github data Mapped for IDC project
public async getRepos(url: string): Promise<ApiResponse<GitHubRepos[]> | ApiErrorResponse> {
return await this.fetchData<GitHubRepos[]>(url, reposMapping);
const response = await this.request.httpGet(url);
return this.responseHandler<GitHubRepos[]>(response, reposMapping);
}

public async getMembers(url: string): Promise<ApiResponse<GitHubMembers[]> | ApiErrorResponse> {
return await this.fetchData<GitHubMembers[]>(url, membersMapping);
const response = await this.request.httpGet(url);
return this.responseHandler<GitHubMembers[]>(response, membersMapping);
}

public async getTeams(url: string): Promise<ApiResponse<GitHubTeams[]> | ApiErrorResponse> {
return await this.fetchData<GitHubTeams[]>(url, teamsMapping);
const response = await this.request.httpGet(url);
return this.responseHandler<GitHubTeams[]>(response, teamsMapping);
}

public async getMembersPerTeam(url: string): Promise<ApiResponse<GitHubMembersPerTeam[]> | ApiErrorResponse> {
return await this.fetchData<GitHubMembersPerTeam[]>(url, membersPerTeamMapping);
const response = await this.request.httpGet(url);
return this.responseHandler<GitHubMembersPerTeam[]>(response, membersPerTeamMapping);
}

public async getReposPerTeam(url: string): Promise<ApiResponse<GitHubReposPerTeam[]> | ApiErrorResponse> {
return await this.fetchData<GitHubReposPerTeam[]>(url, reposPerTeamMapping);
const response = await this.request.httpGet(url);
return this.responseHandler<GitHubReposPerTeam[]>(response, reposPerTeamMapping);
}

private async fetchData<T>(
url: string,
mappingFunction: (body: any) => T
): Promise<ApiResponse<T> | ApiErrorResponse> {
public async getCollaboratorsPerRepo(url: string): Promise<ApiResponse<GitHubCollaboratorsPerRepo[]> | ApiErrorResponse> {
const response = await this.request.httpGet(url);
return this.responseHandler<GitHubCollaboratorsPerRepo[]>(response, collaboratorsPerRepoMapping);
}

private responseHandler<T>(
response: any,
responseMap?: (body: any) => T
): ApiResponse<T> | ApiErrorResponse {
const resource: ApiResponse<T> & ApiErrorResponse = {
httpStatusCode: response.status
};
Expand All @@ -41,7 +79,7 @@ export class Github {
} else if (response.status >= 400) {
resource.errors = [response.body];
} else {
resource.resource = mappingFunction(response.body);
resource.resource = (responseMap) ? responseMap(response.body) : response.body;
}

return resource;
Expand Down
16 changes: 15 additions & 1 deletion src/api-sdk/github/mapping.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
import { GitHubMembers, GitHubRepos, GitHubTeams, GitHubMembersPerTeam, GitHubReposPerTeam } from './type';
import {
GitHubMembers,
GitHubRepos,
GitHubTeams,
GitHubMembersPerTeam,
GitHubReposPerTeam,
GitHubCollaboratorsPerRepo
} from './type';

export const reposMapping = (body: any[]): GitHubRepos[] => {
return body.map((obj) => ({
Expand Down Expand Up @@ -41,3 +48,10 @@ export const reposPerTeamMapping = (body: any[]): GitHubReposPerTeam[] => {
name: obj.name,
}));
};

export const collaboratorsPerRepoMapping = (body: any[]): GitHubCollaboratorsPerRepo[] => {
return body.map((obj) => ({
login: obj.login,
permissions: { ...obj.permissions }
}));
};
19 changes: 19 additions & 0 deletions src/api-sdk/github/type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,22 @@ export interface GitHubMembersPerTeam {
export interface GitHubReposPerTeam {
name: string;
}

export interface GitHubCollaboratorsPerRepo {
login: string;
permissions: {
pull: boolean;
triage: boolean;
push: boolean;
maintain: boolean;
admin: boolean;
}
}

export interface GitHubIssueRequest {
title: string,
body: string,
milestone: number,
assignees: string[],
labels: string[]
}
40 changes: 39 additions & 1 deletion test/mock/data.mock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export const MOCK_REPOS = [
}
];

export const MOCK_UNMAPPED_ENTREE_REPO = [
export const MOCK_UNMAPPED_RESPONSE_REPO = [
{
name: "repo1",
archived: false,
Expand All @@ -54,6 +54,8 @@ export const MOCK_UNMAPPED_ENTREE_REPO = [
}
];

export const MOCK_FULL_RESPONSE_REPO = [ ...MOCK_UNMAPPED_RESPONSE_REPO ];

export const MOCK_REPO_FETCH_RESPONSE = {
httpStatusCode: 200,
resource: MOCK_REPOS
Expand All @@ -74,6 +76,8 @@ export const MOCK_MEMBERS = [
}
];

export const MOCK_FULL_RESPONSE_MEMBERS = [ ...MOCK_MEMBERS ];

export const MOCK_MEMBER_FETCH_RESPONSE = {
httpStatusCode: 200,
resource: MOCK_MEMBERS.map(({ repos_url, ...rest }) => rest)
Expand Down Expand Up @@ -131,6 +135,40 @@ export const MOCK_REPOS_PER_TEAM_RESPONSE = {
resource: MOCK_REPOS_PER_TEAM
};

export const MOCK_COLLABORATORS_PER_REPO = [
{
other: 'other',
login: 'name',
permissions: { pull: true, triage: true, push: true, maintain: true, admin: true }
},
{
other: 'other2',
login: 'name2',
permissions: { pull: true, triage: false, push: false, maintain: false, admin: false }
},
];

export const MOCK_COLLABORATORS_PER_REPO_RESPONSE = {
httpStatusCode: 200,
resource: MOCK_COLLABORATORS_PER_REPO.map(({ other, ...rest }) => rest)
};

export const MOCK_ISSUE_BODY = {
title: "Add member to the team",
body: "Add member Bob to the team Alice.",
assignees: ["IDC TEAM"],
milestone: 1,
labels: ["GIT"]
};

export const MOCK_ISSUE_RESPONSE = {
id: 1,
number: 1347,
state: "open",
title: "Add member to the team",
body: "Add member Bob to the team Alice."
};

export const MOCK_ERROR = {
error: 'Error: test error'
};
Expand Down
Loading

0 comments on commit 3e203bb

Please sign in to comment.