Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

v2 of API #40

Draft
wants to merge 45 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
a0e6004
v2 api
MrRefactoring Mar 9, 2023
f79e11b
v2 api build fixes
MrRefactoring Mar 9, 2023
ffc1685
Version2 API updated
MrRefactoring May 20, 2023
bdc7002
prettier update
MrRefactoring May 20, 2023
4a849f4
Version 2 Client new portion of API
MrRefactoring May 20, 2023
b696854
Models updated
MrRefactoring May 20, 2023
14056f4
Models updated
MrRefactoring May 21, 2023
4bd887b
Parameters updated
MrRefactoring May 21, 2023
6877cf8
unit tests fixes
MrRefactoring May 21, 2023
ff52483
#35 `apiPrefix` as configuration option (#48)
MrRefactoring May 21, 2023
b8efec9
#45 `results` and `_links` wrapped to Pagination generic (#49)
MrRefactoring May 21, 2023
61d4d6b
#47 'serialize-ids-as-strings' set to true by default (#50)
MrRefactoring May 21, 2023
ea0a700
Merge remote-tracking branch 'origin/master' into feature/v2-api
MrRefactoring Jun 7, 2023
8cde479
dependencies upgraded
MrRefactoring Jun 7, 2023
263cef9
sorts replaced to right versions (#56)
MrRefactoring Jun 23, 2023
8765fe8
Refactor getBlogPostById interface and implementation params (#85)
augustus-thomas Jun 26, 2023
ece3787
converted parameters in getTasks to CamelCase (#89)
haiderzaidi07 Jun 26, 2023
0a72177
dependencies updated
MrRefactoring Jun 26, 2023
54897f5
fix(getInlineCommentVersions): dashed to camelCase convention (#88)
mukultotla Jun 27, 2023
abe5001
Update task.ts camelCase 84 1 (#86)
frodri13 Jun 27, 2023
cc8f4b9
camelCase getCustomContentByTypeInBlogPost (#104)
frodri13 Jun 30, 2023
2e8d3d6
camelCase getCustomContentByType (#103)
frodri13 Jun 30, 2023
be3730e
camelCase getCustomContentById (#102)
frodri13 Jun 30, 2023
2e1e7fb
camelCase getCustomContentByTypeInPage (#101)
frodri13 Jun 30, 2023
2f9cbba
camelCase getCustomContentByTypeInSpace (#100)
frodri13 Jun 30, 2023
6e23b31
camelCase getLabelPages (#99)
frodri13 Jun 30, 2023
d8dfab1
camelCase getPages (#98)
frodri13 Jun 30, 2023
3d6417c
camelCase for getPageById (#97)
frodri13 Jun 30, 2023
977bdd1
camelCase getPagesInSpace #75 (#96)
frodri13 Jun 30, 2023
85f014f
camelCase for getSpaces #76 (#95)
frodri13 Jun 30, 2023
bf34f19
camelCase descriptionFormat for getSpaceByID (#94)
frodri13 Jun 30, 2023
c1340fe
camelCase for getBlogPostVersions #78 (#93)
frodri13 Jun 30, 2023
f3f89be
camelCase for getPageVersions #79 (#92)
frodri13 Jun 30, 2023
6be4500
CamelCase for getCustomContentVersions #80 (#91)
frodri13 Jun 30, 2023
a7ecc9c
camelCase getFooterCommentVersions #81 (#90)
frodri13 Jun 30, 2023
512f0a1
updated dash-cased to camelCase (#107)
Jul 1, 2023
41c0692
CamelCase in getInlineCommentById (#109)
frodri13 Jul 1, 2023
210bd40
CamelCase in getInlineCommentChildren (#108)
frodri13 Jul 1, 2023
2d64254
CamelCase in getFooterCommentChildren (#112)
frodri13 Jul 2, 2023
413b97b
CamelCase in getFooterCommentById (#111)
frodri13 Jul 2, 2023
2777ef6
CamelCase in getBlogPostInlineComments (#113)
frodri13 Jul 2, 2023
5f19980
CamelCase in getBlogPostFooterComments (#114)
frodri13 Jul 2, 2023
80dc433
CamelCase in getPageInlineComments (#115)
frodri13 Jul 2, 2023
3c73a29
#110 next function added for version2 endpoints (#116)
MrRefactoring Jul 2, 2023
78169c0
getAll... methods added (#118)
MrRefactoring Oct 16, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ jobs:
echo API_TOKEN=${{ secrets.API_TOKEN }} >> .env
- run: npm run build
- run: npm run test:unit
- run: npm run test:e2e
- run: npm run test:integration
- run: npm run lint
env:
CI: true
592 changes: 218 additions & 374 deletions package-lock.json

Large diffs are not rendered by default.

22 changes: 11 additions & 11 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,13 @@
"scripts": {
"build": "tsc",
"prepublishOnly": "npm run build && npm run test && npm run lint",
"test": "npm run test:unit && npm run test:e2e",
"prettier": "prettier --write src",
"doc": "typedoc --name \"Confluence.js - Cloud and Server API library\" --out docs ./src/index.ts --plugin typedoc-plugin-extras --footerDate --footerTime --footerTypedocVersion --favicon https://svgshare.com/i/bVi.svg",
"lint": "eslint src tests --ext .ts",
"lint:fix": "npm run lint -- --fix",
"test": "npm run test:unit && npm run test:integration",
"test:unit": "ava tests/unit",
"test:e2e": "ava --timeout=2m --fail-fast --no-worker-threads -c 1 -s tests/e2e/**/*.test.ts"
"test:integration": "ava --timeout=2m --fail-fast --no-worker-threads -c 1 -s tests/integration/**.test.ts"
},
"ava": {
"extensions": [
Expand All @@ -42,32 +42,32 @@
"atlassian"
],
"devDependencies": {
"@swc-node/register": "^1.6.5",
"@swc-node/register": "^1.6.6",
"@swc/helpers": "^0.5.1",
"@types/express": "^4.17.17",
"@types/oauth": "^0.9.1",
"@types/sinon": "^10.0.15",
"@typescript-eslint/eslint-plugin": "^5.59.9",
"@typescript-eslint/parser": "^5.59.9",
"ava": "^5.3.0",
"dotenv": "^16.1.4",
"eslint": "^8.42.0",
"@typescript-eslint/eslint-plugin": "^5.60.1",
"@typescript-eslint/parser": "^5.60.1",
"ava": "^5.3.1",
"dotenv": "^16.3.1",
"eslint": "^8.44.0",
"eslint-config-airbnb": "^19.0.4",
"eslint-config-airbnb-typescript": "^17.0.0",
"eslint-import-resolver-typescript": "^3.5.5",
"eslint-plugin-import": "^2.27.5",
"prettier": "^2.8.8",
"prettier-plugin-jsdoc": "^0.4.2",
"sinon": "^15.1.0",
"sinon": "^15.2.0",
"typedoc": "^0.24.8",
"typedoc-plugin-extras": "^2.3.3",
"typescript": "^5.1.3"
"typescript": "^5.1.6"
},
"dependencies": {
"atlassian-jwt": "^2.0.2",
"axios": "^1.4.0",
"form-data": "^4.0.0",
"oauth": "^0.10.0",
"tslib": "^2.5.3"
"tslib": "^2.6.0"
}
}
30 changes: 19 additions & 11 deletions src/clients/baseClient.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { AuthenticationService } from '../services/authenticationService';
import { AuthenticationService } from '../services';
import type { Callback } from '../callback';
import type { Client } from './client';
import type { Config } from '../config';
Expand Down Expand Up @@ -84,6 +84,22 @@ export class BaseClient implements Client {
return this.#instance;
}

getResponseHandler<T>(callback?: Callback<T>) {
const callbackResponseHandler = callback && ((data: T): void => callback(null, data));
const defaultResponseHandler = (data: T): T => data;

return callbackResponseHandler ?? defaultResponseHandler;
}

getErrorHandler(callback?: Callback<never>) {
const callbackErrorHandler = callback && ((error: Config.Error) => callback(error));
const defaultErrorHandler = (error: Error) => {
throw error;
};

return callbackErrorHandler ?? defaultErrorHandler;
}

async sendRequest<T>(requestConfig: RequestConfig, callback: never, telemetryData?: any): Promise<T>;
async sendRequest<T>(requestConfig: RequestConfig, callback: Callback<T>, telemetryData?: any): Promise<void>;
async sendRequest<T>(requestConfig: RequestConfig, callback: Callback<T> | never): Promise<void | T> {
Expand All @@ -102,23 +118,15 @@ export class BaseClient implements Client {

const response = await this.instance.request<T>(modifiedRequestConfig);

const callbackResponseHandler = callback && ((data: T): void => callback(null, data));
const defaultResponseHandler = (data: T): T => data;

const responseHandler = callbackResponseHandler ?? defaultResponseHandler;
const responseHandler = this.getResponseHandler(callback);

this.config.middlewares?.onResponse?.(response.data);

return responseHandler(response.data);
} catch (e: any) {
const err = this.config.newErrorHandling && e.isAxiosError ? e.response.data : e;

const callbackErrorHandler = callback && ((error: Config.Error) => callback(error));
const defaultErrorHandler = (error: Error) => {
throw error;
};

const errorHandler = callbackErrorHandler ?? defaultErrorHandler;
const errorHandler = this.getErrorHandler(callback);

this.config.middlewares?.onError?.(err);

Expand Down
5 changes: 5 additions & 0 deletions src/clients/client.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
import type { Callback } from '../callback';
import type { Config } from '../config';
import type { RequestConfig } from '../requestConfig';

export interface Client {
getResponseHandler<T>(callback?: Callback<T>): (data: T) => void;

getErrorHandler(callback?: Callback<never>): (error: Config.Error) => void;

sendRequest<T>(requestConfig: RequestConfig, callback?: never, telemetryData?: any): Promise<T>;
sendRequest<T>(requestConfig: RequestConfig, callback?: Callback<T>, telemetryData?: any): Promise<void>;
}
1 change: 1 addition & 0 deletions src/clients/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ export * from './baseClient';
export * from './client';
export * from './confluenceClient';
export * from './serverClient';
export * from './version2Client';
43 changes: 43 additions & 0 deletions src/clients/version2Client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { BaseClient } from './baseClient';
import { Config } from '../config';
import {
Attachment,
BlogPost,
Children,
Comment,
Content,
ContentProperties,
CustomContent,
Label,
Page,
Space,
SpaceProperties,
Task,
User,
Version,
} from '../version2';

export class Version2Client extends BaseClient {
constructor(config: Config) {
super({
...config,
newErrorHandling: true,
apiPrefix: config.apiPrefix ?? '/wiki/api/v2',
});
}

attachment = new Attachment(this);
blogPost = new BlogPost(this);
children = new Children(this);
comment = new Comment(this);
content = new Content(this);
contentProperties = new ContentProperties(this);
customContent = new CustomContent(this);
label = new Label(this);
page = new Page(this);
space = new Space(this);
spaceProperties = new SpaceProperties(this);
task = new Task(this);
user = new User(this);
version = new Version(this);
}
13 changes: 11 additions & 2 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,18 @@ export interface Config {
telemetry?: Config.Telemetry;
/** Adds `'X-Atlassian-Token': 'no-check'` to each request header */
noCheckAtlassianToken?: boolean;
/** Enable new API error handling. `false` by default. */
/**
* Enable new API error handling. For "ConfluenceClient" `false` by default.
*
* For "Version2Client" enabled by default.
*/
newErrorHandling?: boolean;
/** Prefix for all API routes. */
/**
* Prefix for all API routes.
*
* - For "ConfluenceCloud" by default it is "/wiki/rest"
* - For "Version2Client" by default it is "/wiki/api/v2"
*/
apiPrefix?: string;
}

Expand Down
4 changes: 4 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,7 @@ export * as Models from './api/models';
export * as Parameters from './api/parameters';
export * as ServerModels from './server/models';
export * as ServerParameters from './server/parameters';

export * as Version2 from './version2';
export * as Version2Models from './version2/models';
export * as Version2Parameters from './version2/parameters';
2 changes: 2 additions & 0 deletions src/services/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './authenticationService';
export * from './paginationService';
90 changes: 90 additions & 0 deletions src/services/paginationService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import type { Pagination } from '../version2/models';

/** This service is responsible for handling pagination. */
export class PaginationService {
/**
* This method builds a paginated result based on the given pagination object and a function to call for the next
* page.
*/
buildPaginatedResult<T>(
pagination: Pagination<T>,
callFn: (params: any) => Promise<Pagination<T>>,
): Pagination<T> {
if (!pagination._links.next) {
return this.buildEmptyPagination(pagination);
}

const parameters = this.parseParameters(pagination._links.next);

return {
results: pagination.results,
_links: pagination._links,
parameters,
hasNext: true,
next: () => callFn(parameters),
getAll: () => this.getAll<T>(pagination.results, parameters, callFn),
};
}

private parseParameters(urlString: string) {
const url = new URL(urlString, 'http://temp.temp');
const searchParams = new URLSearchParams(url.search);
const paramsObject: Record<string, string> = {};

searchParams.forEach((value, key) => {
paramsObject[this.toCamelCase(key)] = value;
});

return paramsObject;
}

/** This method retrieves all pages of results. */
private async getAll<T>(
actualResult: T[],
params: Record<string, string>,
callFn: (params: Record<string, string>) => Promise<Pagination<T>>,
): Promise<T[]> {
let hasNext = true;

while (hasNext) {
const paginationResult: Pagination<T> = await callFn(params);

actualResult.push(...paginationResult.results);

if (paginationResult._links.next) {
params = this.parseParameters(paginationResult._links.next);
} else {
hasNext = false;
}
}

return actualResult;
}

private toCamelCase(str: string): string {
return str.replace(/([-_][a-z])/gi, piece => {
return piece.toUpperCase().replace('-', '').replace('_', '');
});
}

private buildEmptyPagination<T>(pagination: Pagination<T>): Pagination<T> {
const emptyPagination: Pagination<T> = {
results: pagination.results,
parameters: pagination.parameters,
_links: {},
hasNext: false,
next: () =>
Promise.resolve({
results: [],
hasNext: false,
parameters: pagination.parameters,
_links: pagination._links,
next: emptyPagination.next,
getAll: () => Promise.resolve([]),
}),
getAll: () => Promise.resolve(pagination.results),
};

return emptyPagination;
}
}
Loading
Loading