-
Notifications
You must be signed in to change notification settings - Fork 14
feat(files): file interactions. find, findone #63
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
Conversation
innerdvations
left a comment
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.
Just took a quick look, overall looks good, will test it out tomorrow!
Convly
left a comment
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.
Overall the main logic looks very good to me, some comments in the tests and error handling.
My main, biggest concern is with the terminology: When I'm using client.files.<>, I'm expecting to interact with actual files, not files' info/metadata.
Do we want to maybe rethink the methods' naming (the namespace iself seems fine if we want to add actual files method later on)? IThe thing is I'm not sure I can think of better replacements...
…ce error handling in tests
…essary error handling tests
|
@Convly I've made the changes - I think the only 2 comments left are #63 (comment) and #63 (comment) |
src/files/manager.ts
Outdated
|
|
||
| return json; | ||
| } catch (error) { | ||
| debug('error finding file with ID %o: %o', fileId, error); |
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.
I kinda liked the idea of handling and remapping some HTTP errors with domain logic (e.g. 404 -> file with id {id} not found) or other errors).
My comment was mainly about how it's checking for the actual errors + which one it re-throws rather than the idea itself.
I don't know if it's clear though, but basically
if (error instanceof HTTPNotFoundError) {
throw new FileNotFoundError(fileId, error) // passing the original error to set an origin/source and be able to access the actual request/response objects
}Or even better, cloning the HTTP client and adding an interceptor (so that it can be re-used by all the methods, it's even possible to add a getter to automatically apply everything and make it available for each method. Very simplified but smth like this
const mapFileErrors = ({ response }) => {
if (error instanceof HTTPNotFoundError) {
throw new FileNotFoundError(fileId, error) // passing the original error to set an origin/source and be able to access the actual request/response objects
}
return { response };
}
const client = this._httpClient.create();
client.interceptors.response.use(mapFileErrors);
const response = await client.get(url);
const json = await response.json();What do you think about those options?
…ve unnecessary error handling tests" This reverts commit 91098f5.
…andling in file operations
…e images in node demos
Convly
left a comment
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.
We're getting there! Final super minor changes and let's merge 🙌
Also, it seems like you forgot to update the generated types for the demo Strapi app.
diff --git a/demo/.strapi-app/types/generated/contentTypes.d.ts b/demo/.strapi-app/types/generated/contentTypes.d.ts
index 4a2f513..aa0a2e9 100644
--- a/demo/.strapi-app/types/generated/contentTypes.d.ts
+++ b/demo/.strapi-app/types/generated/contentTypes.d.ts
@@ -441,6 +441,7 @@ export interface ApiCategoryCategory extends Struct.CollectionTypeSchema {
createdAt: Schema.Attribute.DateTime;
createdBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> & Schema.Attribute.Private;
description: Schema.Attribute.Text;
+ image: Schema.Attribute.Media<'images'>;
locale: Schema.Attribute.String & Schema.Attribute.Private;
localizations: Schema.Attribute.Relation<'oneToMany', 'api::category.category'> &
Schema.Attribute.Private;
src/files/manager.ts
Outdated
| }, | ||
| // Error handler | ||
| (error) => { | ||
| const mapper = FileErrorMapper.createMapper(fileId); |
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.
You can create this outside the interceptor to avoid too many instantiations
src/files/manager.ts
Outdated
| ({ request, response }) => { | ||
| return { request, response }; | ||
| }, |
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.
| ({ request, response }) => { | |
| return { request, response }; | |
| }, | |
| undefined, |
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.
If the interceptor isn't doing anything, you can just pass undefined instead
src/files/errors.ts
Outdated
| return new FileForbiddenError(error, fileId); | ||
| } | ||
|
|
||
| return null; |
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.
| return null; | |
| return error; |
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.
mapping an error to a null value seems counter-intuitive upon usage, maybe we could return the original error untouched if it's not smth the file error mapper is not interested in?
This way we can always treat the result as an Error instance no matter what
What do you think? (I've included the proposed change in the usage too)
src/files/errors.ts
Outdated
| * @returns A function that maps HTTP errors to domain-specific file errors. | ||
| */ | ||
| static createMapper(fileId?: number) { | ||
| return (error: Error): Error | null => { |
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.
| return (error: Error): Error | null => { | |
| return (error: Error): Error => { |
src/files/manager.ts
Outdated
| const mappedError = mapper(error as Error); | ||
| if (mappedError) { | ||
| throw mappedError; | ||
| } | ||
|
|
||
| // For other errors, rethrow the original error | ||
| throw error; |
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.
| const mappedError = mapper(error as Error); | |
| if (mappedError) { | |
| throw mappedError; | |
| } | |
| // For other errors, rethrow the original error | |
| throw error; | |
| if (error instanceof Error) { | |
| throw mapper(error); | |
| } | |
| // For other errors types, re-throw the original value | |
| throw error; |
|
|
||
| // Verify the interceptor was added to the client | ||
| const createdClient = createSpy.mock.results[0]?.value; | ||
| expect(createdClient?.interceptors.response).toBeDefined(); |
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 will return and verify that the interceptors' manager (to add a new one) exists, but I'm not sure what else it's supposed to check? Should we instead make sure it contains the actual interceptor?
Thing is you'll need to access the private _handlers property to do that 🤔
What does it do?
It adds a new
FilesManagerclass that provides methods to retrieve files uploaded to Strapi. The implementation includes:A
FilesManagerclass with two primary methods:find(): Retrieves a list of files with optional filtering and sortingfindOne(): Retrieves a single file by its IDType definitions for file responses and query parameters:
FileResponseinterface for individual file dataFileListResponsetype for handling lists of filesFileQueryParamsinterface for filtering and sorting optionsRobust validation for file queries and file IDs:
validateFileQueryParams(): Ensures proper format for filters and sortingvalidateFileId(): Verifies that file IDs are valid positive integersIntegration with the main Strapi client:
filesgetter property on the Strapi clientWhy is it needed?
Previously, there was no standardized way to interact with files in the Strapi Media Library through the client.
How to test it?
Environment setup:
const client = new Strapi({ baseURL: 'http://localhost:1337/api' })Testing file list retrieval:
Testing single file retrieval:
Verify error handling:
Related issue(s)/PR(s)
DX-1822