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

feat(users): Add users collection to SDK #52

Merged
merged 35 commits into from
Jan 23, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
3f3c501
Add docs related to adding the users namespace to the API
kwelch Dec 28, 2018
89530c7
updates to users doc based on review
kwelch Dec 31, 2018
174d8b8
more code review updates
kwelch Jan 2, 2019
3f2d2d7
update doc to match emailLookup name
kwelch Jan 2, 2019
6026411
update getById example to use a string for id
kwelch Jan 2, 2019
698b878
Add implementation for users.me
kwelch Jan 2, 2019
ab23e91
add keys to UserObject interface to fix lint issue
kwelch Jan 2, 2019
a3fd6e1
remove unnecessary typing
kwelch Jan 2, 2019
3c16eaa
update users test to use request instead of full sdk
kwelch Jan 2, 2019
5288f3f
add index test for users collection
kwelch Jan 2, 2019
a1f11d4
updated to use instance of instead of any
kwelch Jan 2, 2019
57a0e5c
update doc on each method to remove extra words
kwelch Jan 3, 2019
22b978b
Update type name from collection to methods
kwelch Jan 3, 2019
90b597a
Update type from RequestHelper to JSONRequest
kwelch Jan 3, 2019
c232d53
Add Email type and rename UserObject to User
kwelch Jan 3, 2019
24aae2b
Add transformation of snakeToCamel for objects keys
kwelch Jan 3, 2019
926c305
fixed tests for transformed data
kwelch Jan 3, 2019
ac627ec
fixed test for node@6
kwelch Jan 3, 2019
a71616a
replace Object.entries with keys for node@6 support
kwelch Jan 3, 2019
ab65a28
added rejection case to users.me
kwelch Jan 4, 2019
f1e5574
Added more test and methods to userMethods
kwelch Jan 4, 2019
fcdb247
swap functions for classes
jcreamer898 Jan 7, 2019
28437e5
clean up a bit
jcreamer898 Jan 7, 2019
e05a3b2
a bit more cleanup
jcreamer898 Jan 7, 2019
dfca786
rename interfaces while I make up my mind
jcreamer898 Jan 7, 2019
392ac6d
Merge pull request #1 from jcreamer898/jc-add-users
kwelch Jan 8, 2019
9b6dabb
minor updates to make jsonRequest generic
kwelch Jan 8, 2019
95c2b15
fix(types): update generics in request
jcreamer898 Jan 8, 2019
54dd2b3
add default api response
jcreamer898 Jan 8, 2019
2cb171e
Merge pull request #2 from jcreamer898/jc-add-users
kwelch Jan 9, 2019
817fa36
remove support for emailLookup since it is deprecated
kwelch Jan 10, 2019
7afe721
update test to deconstruct to specific methods
kwelch Jan 10, 2019
2a8c562
avoid async/await for compile purposes
kwelch Jan 10, 2019
1170cc6
fix binding issue with users api functions
kwelch Jan 10, 2019
4c18869
revert arrow functions and fix tests
kwelch Jan 10, 2019
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 README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ The official JavaScript SDK for the [Eventbrite v3 API](https://www.eventbrite.c

> NOTE: This library is still in **beta** as we flesh out the API of the SDK.

## ToC
## Table of Contents

* [Installation](#installation)
* [Quick Usage](#quick-usage)
Expand Down
3 changes: 2 additions & 1 deletion docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@

This SDK interface closely mirors the [Eventbrite v3 REST API](https://www.eventbrite.com/developer/v3/) endpoints that it wraps. The SDK provides many conveniences for making requests and processing responses to make it easier to use in the JavaScript environment.

## ToC
## Table of Contents

- [Including the package](#including-the-package)
- [Configuring a SDK object](#configuring-a-sdk-object)
- [`request()`](./request.md)
- [Users](./users.md)

## Including the package

Expand Down
66 changes: 66 additions & 0 deletions docs/users.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# Users
kwelch-eb marked this conversation as resolved.
Show resolved Hide resolved

This is a collection of method that are intended to be helpful wrappers around the [Users API endpoints](user-api-docs).

View the [User response object](user-object-reference) for details on the properties you'll get back with each response.

## Table on Contents

- [`sdk.users.me()`](#me)
- [`sdk.users.get(id)`](#getById)

<a id="me"></a>

## `sdk.users.me()`
Gets details about the current logged in user.

**Read [`/users/me` documentation](user-get-me) for more details.**

### API
```js
sdk.users.me(): Promise<User>
```

### Example

```js
const eventbrite = require('eventbrite');

// Create configured Eventbrite SDK
const sdk = eventbrite({token: 'OATH_TOKEN_HERE'});

sdk.users.me().then((user) => {
console.log(`Hi ${user.name}!`);
});
```

<a id="getById"></a>

## `sdk.users.get(id)`
Gets the details for a specific user by their user id.

**Read [`/users/:id` documentation](user-get-me) for more details.**

### API
```js
sdk.users.get(id: string): Promise<User>
```

### Example

```js
const eventbrite = require('eventbrite');

// Create configured Eventbrite SDK
const sdk = eventbrite({token: 'OATH_TOKEN_HERE'});

sdk.users.get('1234567890').then((user) => {
console.log(`Hi ${user.name}!`);
});
```

<!-- link reference section -->
[user-api-docs]: https://www.eventbrite.com/platform/api#/reference/user
[user-object-reference]: https://www.eventbrite.com/platform/api#/reference/user/retrieve-a-user
[user-by-id]: https://www.eventbrite.com/platform/api#/reference/user/retrieve-a-user
[user-get-me]: https://www.eventbrite.com/platform/api#/reference/user/retrieve/retrieve-your-user
16 changes: 16 additions & 0 deletions src/__tests__/__fixtures__/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,22 @@ export const MOCK_USERS_ME_RESPONSE_DATA = {
image_id: null as string,
};

export const MOCK_TRANSFORMED_USERS_ME_RESPONSE_DATA = {
emails: [
{
email: '[email protected]',
verified: true,
primary: true,
},
],
id: '142429416488',
name: 'Eventbrite Engineer',
firstName: 'Eventbrite',
lastName: 'Engineer',
isPublic: false,
imageId: null as string,
};

export const MOCK_INTERNAL_ERROR_RESPONSE_DATA = {
status_code: 500,
error: 'INTERNAL_ERROR',
Expand Down
42 changes: 41 additions & 1 deletion src/__tests__/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@ import {
restoreMockFetch,
getMockResponse,
} from './utils';
import {MOCK_USERS_ME_RESPONSE_DATA} from './__fixtures__';
import {
MOCK_USERS_ME_RESPONSE_DATA,
MOCK_TRANSFORMED_USERS_ME_RESPONSE_DATA,
} from './__fixtures__';

describe('configurations', () => {
it('does not error when creating sdk object w/o configuration', () => {
Expand Down Expand Up @@ -90,4 +93,41 @@ describe('request', () => {
})
);
});

describe('users collection', () => {
it('should return an object of functions', () => {
const {users} = eventbrite({
token: MOCK_TOKEN,
baseUrl: MOCK_BASE_URL,
});

expect(users).toBeDefined();
Object.keys(users).forEach((key) => {
const value = (users as any)[key];

expect(value).toBeInstanceOf(Function);
});
});

it('makes request to API base url override w/ specified token', async() => {
const {users} = eventbrite({
token: MOCK_TOKEN,
baseUrl: MOCK_BASE_URL,
});

await expect(users.me()).resolves.toEqual(
MOCK_TRANSFORMED_USERS_ME_RESPONSE_DATA
);

expect(getMockFetch()).toHaveBeenCalledTimes(1);
expect(getMockFetch()).toHaveBeenCalledWith(
`${MOCK_BASE_URL}/users/me/`,
expect.objectContaining({
headers: expect.objectContaining({
Authorization: `Bearer ${MOCK_TOKEN}`,
}),
})
);
});
});
});
112 changes: 112 additions & 0 deletions src/__tests__/users.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import {
mockFetch,
getMockFetch,
getMockResponse,
restoreMockFetch,
} from './utils';
import {
MOCK_USERS_ME_RESPONSE_DATA,
MOCK_TRANSFORMED_USERS_ME_RESPONSE_DATA,
} from './__fixtures__';

import request from '../request';
import {UserApi} from '../users';

describe('me()', () => {
it('calls fetch and calls fetch with appropriate defaults', async() => {
const users = new UserApi(request);

mockFetch(getMockResponse(MOCK_USERS_ME_RESPONSE_DATA));

await expect(users.me()).resolves.toEqual(
MOCK_TRANSFORMED_USERS_ME_RESPONSE_DATA
);

expect(getMockFetch()).toHaveBeenCalledTimes(1);
expect(getMockFetch()).toHaveBeenCalledWith(
'/users/me/',
expect.objectContaining({})
);

restoreMockFetch();
});

it('handle token missing requests', async() => {
const users = new UserApi(request);

mockFetch(
getMockResponse(
{
status_code: 401,
error_description:
'An OAuth token is required for all requests',
error: 'NO_AUTH',
},
{status: 401}
)
);

await expect(users.me()).rejects.toMatchObject({
response: expect.objectContaining({
status: 401,
statusText: 'Unauthorized',
ok: false,
}),
parsedError: {
description: 'An OAuth token is required for all requests',
error: 'NO_AUTH',
},
});

restoreMockFetch();
});
});

describe('get(id)', () => {
it('calls fetch and calls fetch with appropriate defaults', async() => {
const users = new UserApi(request);

mockFetch(getMockResponse(MOCK_USERS_ME_RESPONSE_DATA));

await expect(users.get('142429416488')).resolves.toEqual(
MOCK_TRANSFORMED_USERS_ME_RESPONSE_DATA
);

expect(getMockFetch()).toHaveBeenCalledTimes(1);
expect(getMockFetch()).toHaveBeenCalledWith(
'/users/142429416488/',
expect.objectContaining({})
);

restoreMockFetch();
});

it('should handle not found users', async() => {
const users = new UserApi(request);

mockFetch(
getMockResponse(
{
status_code: 404,
error_description: 'The user you requested does not exist.',
error: 'NOT_FOUND',
},
{status: 404}
)
);

await expect(users.get('123')).rejects.toMatchObject({
response: expect.objectContaining({
status: 404,
statusText: 'Not Found',
ok: false,
}),
parsedError: {
description: 'The user you requested does not exist.',
error: 'NOT_FOUND',
},
});

restoreMockFetch();
});
});
54 changes: 54 additions & 0 deletions src/baseApi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import {JSONRequest} from './types';

const SNAKE_CASE_MATCH = /_\w/g;
const snakeToCamel = (str: string) =>
str.replace(SNAKE_CASE_MATCH, (chars: string) => chars[1].toUpperCase());

const transformKeysSnakeToCamel = <T extends { [key: string]: any } = {}>(
obj: T
) =>
Object.keys(obj).reduce((memo, key) => {
let newValue = obj[key];
const camelKey = snakeToCamel(key);

if (
newValue &&
typeof newValue === 'object' &&
!Array.isArray(newValue)
) {
newValue = transformKeysSnakeToCamel(newValue);
}

return {
...memo,
[camelKey]: newValue,
};
}, {}) as T;

/**
* Returns a function that sends a request, and transforms its results
*/
const makeJsonRequest = <T>(
request: JSONRequest,
transformers: Array<(obj: T) => T>
) => (url: string, options?: RequestInit) =>
request(url, options).then((response: T) =>
transformers.reduce<T>((acc, transformer) => {
let memo = acc;

memo = transformer(response);
return memo;
}, response)
);

/**
* Base API class for creating new API Classes.
* Also encapsulates default transformers such as snake to camel.
*/
export abstract class BaseApi<T> {
request: JSONRequest<T>;

constructor(req: JSONRequest<T>) {
this.request = makeJsonRequest(req, [transformKeysSnakeToCamel]);
}
}
51 changes: 32 additions & 19 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,30 +1,43 @@
import {Sdk, SdkConfig} from './types';
import {Sdk, SdkConfig, JSONRequest} from './types';
import request from './request';
import {UserApi} from './users';

export * from './constants';

const DEFAULT_API_URL = 'https://www.eventbriteapi.com/v3';

type MakeRequestFunction = (baseUrl: string, token: string) => JSONRequest;

const makeRequest: MakeRequestFunction = (baseUrl: string, token: string) => (
endpoint,
options = {}
) => {
const url = `${baseUrl}${endpoint}`;
let requestOptions = options;

if (token) {
requestOptions = {
...requestOptions,
headers: {
...(requestOptions.headers || {}),
Authorization: `Bearer ${token}`,
},
};
}

return request(url, requestOptions);
};

const eventbrite = ({
baseUrl = DEFAULT_API_URL,
token,
}: SdkConfig = {}): Sdk => ({
request: (endpoint, options = {}) => {
const url = `${baseUrl}${endpoint}`;
let requestOptions = options;

if (token) {
requestOptions = {
...requestOptions,
headers: {
...(requestOptions.headers || {}),
Authorization: `Bearer ${token}`,
},
};
}

return request(url, requestOptions);
},
});
}: SdkConfig = {}): Sdk => {
const jsonRequest = makeRequest(baseUrl, token);

return {
request: jsonRequest,
users: new UserApi(jsonRequest),
};
};

export default eventbrite;
Loading