-
Notifications
You must be signed in to change notification settings - Fork 397
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
API functions for adding an add-on to a collection #4119
Changes from 4 commits
b484ac3
43daf30
78fd5b4
de041d8
332e358
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,10 @@ | ||
/* @flow */ | ||
import { callApi } from 'core/api'; | ||
import { callApi, allPages } from 'core/api'; | ||
import type { | ||
ExternalCollectionAddon, ExternalCollectionDetail, | ||
} from 'amo/reducers/collections'; | ||
import type { ApiStateType } from 'core/reducers/api'; | ||
import type { PaginatedApiResponse } from 'core/types/api'; | ||
|
||
|
||
type GetCollectionParams = {| | ||
|
@@ -11,7 +15,7 @@ type GetCollectionParams = {| | |
|
||
export const getCollectionDetail = ( | ||
{ api, slug, user }: GetCollectionParams | ||
) => { | ||
): Promise<ExternalCollectionDetail> => { | ||
if (!slug) { | ||
throw new Error('slug is required'); | ||
} | ||
|
@@ -28,42 +32,118 @@ export const getCollectionDetail = ( | |
|
||
type GetCollectionAddonsParams = {| | ||
...GetCollectionParams, | ||
nextURL?: string, | ||
page?: number, | ||
|}; | ||
|
||
export const getCollectionAddons = ( | ||
{ api, page, slug, user }: GetCollectionAddonsParams | ||
) => { | ||
{ api, nextURL, page, slug, user }: GetCollectionAddonsParams | ||
): Promise<PaginatedApiResponse<ExternalCollectionAddon>> => { | ||
if (!slug) { | ||
throw new Error('slug is required'); | ||
} | ||
if (!user) { | ||
throw new Error('user is required'); | ||
} | ||
|
||
return callApi({ | ||
const request = { | ||
auth: true, | ||
endpoint: `accounts/account/${user}/collections/${slug}/addons`, | ||
params: { page }, | ||
endpoint: | ||
nextURL || `accounts/account/${user}/collections/${slug}/addons`, | ||
params: undefined, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Huh, I guess this is fine but it seems weird to pass There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's necessary so that Flow knows that There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would it be okay to use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It has to be In fact, Flow will help you not make this mistake:
|
||
state: api, | ||
}); | ||
}; | ||
if (page) { | ||
request.params = { page }; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would there ever be a case where other params exist? Maybe we should be defining There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
No. Here's an explanation that will hopefully clarify things. This function does not accept
Also, due to the way spreads work, const query = {
// This is how callApi() will extract ?page= from the `next` URL
...{ page: 2 },
// This would be the custom `request.params` value passed to `callApi()`
...{ page: undefined }
}; In that code the value of callApi({ endpoint: nextUrlWithPageParam, params: undefined, ... }) Let me know if you have more questions about it. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'll add some comments to the code |
||
} | ||
|
||
return callApi(request); | ||
}; | ||
|
||
type GetAllCollectionAddonsParams = {| | ||
...GetCollectionParams, | ||
_allPages?: typeof allPages, | ||
_getCollectionAddons?: typeof getCollectionAddons, | ||
|}; | ||
|
||
export const getAllCollectionAddons = async ( | ||
{ | ||
api, | ||
slug, | ||
user, | ||
_allPages = allPages, | ||
_getCollectionAddons = getCollectionAddons, | ||
}: GetAllCollectionAddonsParams | ||
): Promise<Array<ExternalCollectionAddon>> => { | ||
const { results } = await _allPages( | ||
(nextURL) => _getCollectionAddons({ api, nextURL, slug, user }) | ||
); | ||
return results; | ||
}; | ||
|
||
type ListCollectionsParams = {| | ||
api: ApiStateType, | ||
nextURL?: string, | ||
user: string | number, | ||
|}; | ||
|
||
export const listCollections = ( | ||
{ api, user }: ListCollectionsParams | ||
) => { | ||
{ api, nextURL, user }: ListCollectionsParams | ||
): Promise<PaginatedApiResponse<ExternalCollectionDetail>> => { | ||
if (!user) { | ||
throw new Error('The user parameter is required'); | ||
} | ||
const endpoint = nextURL || `accounts/account/${user}/collections`; | ||
|
||
return callApi({ auth: true, endpoint, state: api }); | ||
}; | ||
|
||
type GetAllUserCollectionsParams = {| | ||
...ListCollectionsParams, | ||
_allPages?: typeof allPages, | ||
_listCollections?: typeof listCollections, | ||
|}; | ||
|
||
export const getAllUserCollections = async ( | ||
{ | ||
api, | ||
user, | ||
_allPages = allPages, | ||
_listCollections = listCollections, | ||
}: GetAllUserCollectionsParams | ||
): Promise<Array<ExternalCollectionDetail>> => { | ||
const { results } = await _allPages( | ||
(nextURL) => _listCollections({ api, nextURL, user }) | ||
); | ||
return results; | ||
}; | ||
|
||
type AddAddonToCollectionParams = {| | ||
addon: string | number, | ||
api: ApiStateType, | ||
collection: string | number, | ||
notes?: string, | ||
user: string | number, | ||
|}; | ||
|
||
export const addAddonToCollection = ( | ||
{ addon, api, collection, notes, user }: AddAddonToCollectionParams | ||
): Promise<void> => { | ||
if (!addon) { | ||
throw new Error('The addon parameter is required'); | ||
} | ||
if (!collection) { | ||
throw new Error('The collection parameter is required'); | ||
} | ||
if (!user) { | ||
throw new Error('The user parameter is required'); | ||
} | ||
|
||
return callApi({ | ||
auth: true, | ||
endpoint: `accounts/account/${user}/collections`, | ||
body: { addon, notes }, | ||
endpoint: `accounts/account/${user}/collections/${collection}/addons`, | ||
method: 'POST', | ||
state: api, | ||
}); | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -16,6 +16,7 @@ import { | |
} from 'core/searchUtils'; | ||
import type { ErrorHandlerType } from 'core/errorHandler'; | ||
import type { ApiStateType } from 'core/reducers/api'; | ||
import type { PaginatedApiResponse } from 'core/types/api'; | ||
import type { ReactRouterLocation } from 'core/types/router'; | ||
|
||
|
||
|
@@ -265,3 +266,42 @@ export function autocomplete({ api, filters }: AutocompleteParams) { | |
state: api, | ||
}); | ||
} | ||
|
||
type GetNextResponseType = | ||
(nextURL?: string) => Promise<PaginatedApiResponse<any>>; | ||
|
||
type AllPagesOptions = {| pageLimit: number |}; | ||
|
||
export const allPages = async ( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is neat and probably fine for now but as mentioned it might be nice to request an API for this. I'd think a single request/endpoint would be better than what we're doing here 😄 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I removed the code that makes a lot of API requests but we still need to fetch all of the user's collections to fill up the select box. |
||
getNextResponse: GetNextResponseType, | ||
{ pageLimit = 100 }: AllPagesOptions = {}, | ||
): Promise<PaginatedApiResponse<any>> => { | ||
let results = []; | ||
let nextURL; | ||
let count = 0; | ||
let pageSize = 0; | ||
|
||
for (let page = 1; page <= pageLimit; page++) { | ||
// eslint-disable-next-line no-await-in-loop | ||
const response = await getNextResponse(nextURL); | ||
if (!count) { | ||
// Every response page returns a count for all results. | ||
count = response.count; | ||
} | ||
if (!pageSize) { | ||
pageSize = response.page_size; | ||
} | ||
results = results.concat(response.results); | ||
|
||
if (response.next) { | ||
nextURL = response.next; | ||
log.debug(oneLine`Fetching next page "${nextURL}" of | ||
${getNextResponse}`); | ||
} else { | ||
return { count, page_size: pageSize, results }; | ||
} | ||
} | ||
|
||
// If we get this far the callback may not be advancing pages correctly. | ||
throw new Error(`Fetched too many pages (the limit is ${pageLimit})`); | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
import 'babel-polyfill'; | ||
import 'raf/polyfill'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is just an aside but I think once we can't fit the entire import statement on one line we might as well do each one on a line; I think it's easier to scan the names if they're on newlines and it makes it easier to add/remove imports. 🤷🏻♂️