This package contains several modules to handle upload files, list it, or delete it for Janis APIs.
npm install @janiscommerce/sls-api-upload
JANIS_SERVICE_NAME
(required): The name of the service that will be use to create the path when saving the file into the S3 through the Storage Microservice.
For Own Bucket usage is required to add the following configuration.
Required
This getter must be used in all APIs to indicate the bucket where files are uploaded, listed, retrieved individually, or deleted.
get bucket() {
return 'bucket-name';
}
In this package, you can found several modules to create APIs to manage files, uploads, delete or get them.
- A Basic Model
- API for Get Credentials to upload a File
- API for Relate File to a entity
- APIs for List and Get Files
- API for Delete Files
- APIs for Upload and Save Files
- SLS-API-Upload (Own bucket exclusive)
- SLS-API-File-Relation
Every Module can be customize.
Some APIs Modules offers some custom validation for specfics features such as file size, or file type, but everyone has a common validation hook.
In order to add some custom validations you can use re-write this method, can be async, if fails the status-code is setted to 400
by default. If it exist, executes after validate()
and before process()
.
class MyApiUpload extends SlsApiUpload {
// ... other code
postValidateHook() {
if(!Controller.isValidData(this.data))
throw new Error('Invalid Data');
}
};
This Module allows you to create a Model for file-data document.
This Class extends from @janiscommerce/model
'use strict';
const { BaseFileModel } = require('@janiscommerce/sls-api-upload');
class FileModel extends BaseFileModel {
static get table() {
return 'your_table_files';
}
static get fields() {
return {
...super.fields,
productId: true
};
}
}
The following getters can be used to customize and validate your BaseFileModel.
Optional
Default: "files"
This is used to indicate the name of the files table/collection
static get table() {
return 'your_table_files';
}
Optional
Default:
{
id: true,
path: true,
size: true,
name: true,
type: true,
dateCreated: true
}
This is used to indicate the fields of the files table/collection
static get fields() {
return {
...super.fields,
productId: true
};
}
This Module allows you to create an API to get the credentials to upload multiples Document.
This Class extends from @janiscommerce/api
// in src/api/{entity}/file-get-credentials/list.js
'use strict';
const { SlsApiFileGetCredentials } = require('@janiscommerce/sls-api-upload');
class MyApiRelation extends SlsApiFileGetCredentials {
get entity() {
return 'entityName';
}
}
Required
This is used to indicate the entity name, it will be use in the file path when it's saved
get entity() {
return 'entityName';
}
Optional
Allows you to set a custom expiration for the file.
Possible values: oneDay
| tendays
| month
| never
get fileExpiration() {
return 'oneDay';
}
This API has the following required request data:
- fileName: (string) The file name to upload to S3. It's required if fileNames its not sended.
- fileNames: (array) List of file names to upload to S3. It's required if fileName its not sended.
- expiration: (string) The name and extension of the file.
{
"fileNames": ["front-image.png"],
"expiration": 120
}
This API response with status-code 201
and id
if success to Save the file data Document.
// status-code 201
{
"fileNames": {
"front-image.png": {
"url": "https://s3.amazonaws.com/janis-storage-service-prod",
"fields": {
"Content-Type": "image/png",
"key": "cdn/files/defaultClient/9ea2lbLalrQrjkoWqyJ5gOsJGBtzbml1.png",
"bucket": "janis-storage-service-beta",
"X-Amz-Algorithm": "AWS4-HMAC-SHA256",
"X-Amz-Credential": "ASIASJHJMNZZ5MVD5YHU/20230112/us-east-1/s3/aws4_request",
"X-Amz-Date": "20230112T114452Z",
"X-Amz-Security-Token": "IQoJb3JpZ2luX2VjEGQaCXVzLWVhc3QtMSJGMEQCIHJFEKy124C1P0svU5z3M/szk8tN92pSnn5uR=",
"Policy": "eyJleHBpcmF0aW9uIjoiMjAyMy0wMS0xMlQxMTo0NTo1MloiLCJjb25kaXRpb124IjpbWyJjb250ZW50LWxlbmd0aC1y",
"X-Amz-Signature": "c9b0e78d8b166847c2583383ac5da48e92e95501ed2991058e5a1244c1514aba"
}
}
}
}
{
"fileName": "front-image.png",
"expiration": 120
}
This API response with status-code 201
and id
if success to Save the file data Document.
// status-code 201
{
"url": "https://s3.amazonaws.com/janis-storage-service-prod",
"fields": {
"Content-Type": "image/png",
"key": "cdn/files/defaultClient/9ea2lbLalrQrjkoWqyJ5gOsJGBtzbml1.png",
"bucket": "janis-storage-service-beta",
"X-Amz-Algorithm": "AWS4-HMAC-SHA256",
"X-Amz-Credential": "ASIASJHJMNZZ5MVD5YHU/20230112/us-east-1/s3/aws4_request",
"X-Amz-Date": "20230112T114452Z",
"X-Amz-Security-Token": "IQoJb3JpZ2luX2VjEGQaCXVzLWVhc3QtMSJGMEQCIHJFEKy124C1P0svU5z3M/szk8tN92pSnn5uR=",
"Policy": "eyJleHBpcmF0aW9uIjoiMjAyMy0wMS0xMlQxMTo0NTo1MloiLCJjb25kaXRpb124IjpbWyJjb250ZW50LWxlbmd0aC1y",
"X-Amz-Signature": "c9b0e78d8b166847c2583383ac5da48e92e95501ed2991058e5a1244c1514aba"
}
}
Optional
This is used to indicate the Model class that should be used to save the file relationship
const FileModel = require('../models/your-file-model');
get model() {
return FileModel;
}
This Module allows you to create an API to create a Document with the file data in the Database Collection.
This Class extends from @janiscommerce/api
// in src/api/{entity}/file/post.js
'use strict';
const { SlsApiFileRelation } = require('@janiscommerce/sls-api-upload');
class MyApiRelation extends SlsApiFileRelation {
get entityIdField() {
return 'productId';
}
}
This API has the following required request data:
- filename: (string) The name and extension of the file.
- filesSource: (string) The full key of the file stored in S3.
- fileExpiration: (string) The expiration of the file stored in S3.
{
"fileName": "front-image.png",
"fileSource": "files/images/1f368ddd-97b6-4076-ba63-9e0a71273aac.png",
"fileExpiration": "month"
}
This API response with status-code 201
and id
if success to Save the file data Document.
// status-code 201
{
"id": "5e866d89fc33220011108188"
}
Optional
This is used to indicate the Model class that should be used to save the file relationship
const FileModel = require('../models/your-file-model');
get model() {
return FileModel;
}
Required
This is used to indicate the field name where the related entity ID should be saved
...
get entityIdField() {
return 'productId';
}
...
Optional
This is used to indicate more fields to be validated from the request and saved with the relationship.
get customFieldsStruct() {
return {
myRelationshipCustomField: 'string',
myOptionalRelationshipCustomField: 'string?'
};
}
Request data example;
{
"fileName": "image.png",
"fileSource": "files/images/1f368ddd-97b6-4076-ba63-9e0a71273aac.png",
"myRelationshipCustomField": "theValue"
}
Optional
Allows you to set a custom expiration for the file.
Possible values: oneDay
| tendays
| month
| never
get fileExpiration() {
return 'oneDay';
}
This module has 2 Hooks:
This hooks is async and execute after save the document. You can used it to emit an Event, invoke a Lambda function, create an extra Log, make a Request or whatever you need to the do after save.
postSaveHook(id, itemFormatted) {
return Invoker.call('ItemNotify', { id, ...itemFormatted});
}
The object is created with the following fields:
name
: the filename, example:front-image.png
path
: the relative path in S3 Bucket, examplefiles/images/1f368ddd-97b6-4076-ba63-9e0a71273aac.png
mimeType
: the file full type, example:ìmage/png
type
: the simplified type, exampleimage
size
: the file size in Bytes, example:1000
But if you have more fields, or you can add any others, you can use a custom Format method
It's async and received the extra file data (if you added customFieldsStruct
).
format({ myRelationshipCustomField, myOptionalRelationshipCustomField }) {
return {
relations: {
default: myRelationshipCustomField,
optional: myOptionalRelationshipCustomField
},
lucky: Math.random() * 1000
};
}
And final document saved in database would be:
{
path: 'files/images/1f368ddd-97b6-4076-ba63-9e0a71273aac.png',
name: 'front-image.png',
mimeType: 'image/png',
type: 'image',
size: 10000,
relations: {
default: 'stuff',
optional: 'accesory'
},
lucky: 667
}
This Module allows you to create an API to List file-data documents.
This API extends from @janiscommerce/api-list
// in src/api/item/file/list.js
'use strict';
const { SlsApiFileDelete } = require('@janiscommerce/sls-api-upload');
class MyApiList extends SlsApiFileList {}
In this example, the List API only can
- sort and filter by
id
: file-data document internal IDname
: filenamedateCreated
: strict mode only search by exact Date
Also, every file-data document will NOT have a URL to use it for show it, download it, etc..
If you need more fields to sort or filter exist 2 optionals getters.
To add more fields to be sortable. Must return an Array of Strings
get customSortableFields() {
return ['type', 'order'];
}
To add more fields to be sortable. Must return an Array of Strings or Object, see more in @janiscommerce/api-list filters.
get customAvailableFilters() {
return [
'type',
{
name: 'order',
valueMapper: Number
}
];
}
You can format each file-data document and/or the file's URL.
To format the file data except file-path
formatFileData({ order, ...fileData }) {
return {
...fileData,
order: `#${order}`
};
}
This module has only one Hook:
This Module allows you to create an API to get a single file-data document.
This API extends from @janiscommerce/api-get
// in src/api/item/file/get.js
'use strict';
const { SlsApiFileGet } = require('@janiscommerce/sls-api-upload');
class MyApiGet extends SlsApiFileGet {
}
This API module always return the file-data document with the url
field.
The File-Document can be formatted in the same way as in the SLS-API-List using
This module has only one Hook:
This Module allows you to create an API to delete a file from S3 Bucket and Database Collection.
This Class extends from @janiscommerce/api
// in src/api/item/file/delete.js
'use strict';
const { SlsApiFileDelete } = require('@janiscommerce/sls-api-upload');
class MyApiDelete extends SlsApiFileDelete {
get entityIdField() {
return 'productId';
}
}
The following getters can be used to customize and validate your SlsApiFileDelete.
Optional
This is used to indicate the Model class that should be used to remove the file relationship
const FileModel = require('../models/your-file-model');
get model() {
return FileModel;
}
Required
This is used to indicate the field name where the related entity ID was saved
get entityIdField() {
return 'productId';
}
This module has two Hooks:
- postValidateHook
- postDeleteHook
This hooks is async and execute after delete the document from S3 Bucket. You can used it to emit an Event, invoke a Lambda function, create an extra Log, make a Request or whatever you need to the do after delete it.
postDeleteHook(itemDeleted) {
return EventEmitter.emit({
entity: 'item',
event: 'deleted',
client: this.session.clientCode,
id: itemDeleted.id
});
}
This is an example to implement in the serverless configuration.
This Configuration file use this packages @sls-helper and @sls-helper-plugin-janis
[
[
"janis.api",
{
"path": "/{entityName}/{id}/file",
"method": "get",
"methodName": "list",
"authorizer": "FullAuthorizer",
"cors": true
}
],
[
"janis.api",
{
"path": "/{entityName}/{id}/file/{fileId}",
"method": "get",
"authorizer": "FullAuthorizer",
"cors": true
}
],
[
"janis.api",
{
"path": "/{entityName}/{id}/file/{fileId}",
"method": "delete",
"authorizer": "FullAuthorizer",
"cors": true
}
],
[
"janis.api",
{
"path": "/{entityName}/{id}/file",
"method": "post",
"authorizer": "FullAuthorizer",
"cors": true,
"package": {
"include": ["src/models/file.js", "src/api/{entityName}/file-related/post.js"]
}
}
],
[
"janis.api",
{
"path": "/{entityName}/{id}/file-get-credentials",
"method": "get",
"methodName": "list",
"authorizer": "FullAuthorizer",
"cors": true,
"package": {
"include": ["src/models/file.js", "src/api/{entityName}/file-related/post.js"]
}
}
]
]
This Module allows you to create an API to get a valid pre-signed URL and headers in order to upload a file to a S3 Bucket (Own Bucket exclusive).
This Class extends from @janiscommerce/api
If you want to see more about it:
// in src/api/item/file-upload/list.js
'use strict';
const { SlsApiUpload } = require('@janiscommerce/sls-api-upload');
module.exports = class MyApiUpload extends SlsApiUpload {
get bucket() {
return 'bucket-name';
}
get path() {
return 'files/';
}
get availableTypes() {
return ['application/pdf']
}
get expiration() {
return 300;
}
get sizeRange() {
return [1, 1024 * 1024 * 5]; // 1byte - 5mb
}
};
{
fileName: 'my-file.jpg'
}
{
url: 'https://s3.amazonaws.com/bucket-name',
fields: {
'Content-Type': 'image/jpg',
key: 'files/06311e0c-6f32-4a13-93e4-c89a7765e571.jpg',
bucket: 'bucket-name',
'X-Amz-Algorithm': 'AWS4-HMAC-SHA256',
'X-Amz-Credential': 'AAAAAAA99BB0BOCCCCCC/10000000/us-east-2/s3/aws4_request',
'X-Amz-Date': '20200406T185857Z',
Policy: 'eyJleHBpcmF0aW9uIjoiMjAyMC0wNC0wNlQxODo1OTo1N1oiLCJjb25kaXRpb25zIjpbWyJjb250ZW5=',
'X-Amz-Signature': '4e99b9e991df4aa4370e88aa3390000d1a543527fcc1cdb6583b193aed00bf00'
}
}
The following getters can be used to customize and validate your SlsApiUpload
.
Required
This is used to indicate the bucket where the file should be saved
get bucket() {
return 'bucket-name';
}
Optional
Default: ""
This is used to indicate the path where the file should be saved
get path() {
return 'files/pdf/';
}
Optional
Default: []
This is used to indicate the accepted file types to be uploaded. If you not define them, all types will be valid. Example:
get availableTypes() {
return ['image/jpg', 'image/jpeg', 'image/png']
}
Optional
Default: 60
This is used to indicate the expiration time in seconds of the generated URL
get expiration() {
return 120;
}
Optional
Default: [1,10485760] // 1B to 10MB
This is used to indicate the valid file size range to be uploaded
get sizeRange() {
return [1, 20 * 1024 * 1024]; // 1byte - 20mb
}
This module has only one Hook: