├── .editorconfig ├── .gitattributes ├── .gitignore ├── README.md ├── admin ├── build │ ├── main.js │ └── main.js.map └── src │ ├── containers │ ├── App │ │ ├── actions.js │ │ ├── constants.js │ │ ├── index.js │ │ ├── reducer.js │ │ └── selectors.js │ ├── HomePage │ │ ├── actions.js │ │ ├── constants.js │ │ ├── index.js │ │ ├── reducer.js │ │ ├── saga.js │ │ ├── selectors.js │ │ └── styles.scss │ └── NotFoundPage │ │ └── index.js │ └── translations │ ├── ar.json │ ├── de.json │ ├── en.json │ ├── es.json │ ├── fr.json │ ├── it.json │ ├── ko.json │ ├── nl.json │ ├── pl.json │ ├── pt-BR.json │ ├── pt.json │ ├── ru.json │ ├── tr.json │ ├── zh-Hans.json │ └── zh.json ├── config ├── functions │ └── bootstrap.js └── routes.json ├── controllers └── Images.js ├── middlewares └── images │ ├── defaults.json │ └── index.js ├── package.json └── services └── Images.js /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | insert_final_newline = false 6 | indent_style = space 7 | indent_size = 2 8 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # From https://github.com/Danimoth/gitattributes/blob/master/Web.gitattributes 2 | 3 | # Handle line endings automatically for files detected as text 4 | # and leave all files detected as binary untouched. 5 | * text=auto 6 | 7 | # 8 | # The above will handle all files NOT found below 9 | # 10 | 11 | # 12 | ## These files are text and should be normalized (Convert crlf => lf) 13 | # 14 | 15 | # source code 16 | *.php text 17 | *.css text 18 | *.sass text 19 | *.scss text 20 | *.less text 21 | *.styl text 22 | *.js text eol=lf 23 | *.coffee text 24 | *.json text 25 | *.htm text 26 | *.html text 27 | *.xml text 28 | *.svg text 29 | *.txt text 30 | *.ini text 31 | *.inc text 32 | *.pl text 33 | *.rb text 34 | *.py text 35 | *.scm text 36 | *.sql text 37 | *.sh text 38 | *.bat text 39 | 40 | # templates 41 | *.ejs text 42 | *.hbt text 43 | *.jade text 44 | *.haml text 45 | *.hbs text 46 | *.dot text 47 | *.tmpl text 48 | *.phtml text 49 | 50 | # git config 51 | .gitattributes text 52 | .gitignore text 53 | .gitconfig text 54 | 55 | # code analysis config 56 | .jshintrc text 57 | .jscsrc text 58 | .jshintignore text 59 | .csslintrc text 60 | 61 | # misc config 62 | *.yaml text 63 | *.yml text 64 | .editorconfig text 65 | 66 | # build config 67 | *.npmignore text 68 | *.bowerrc text 69 | 70 | # Heroku 71 | Procfile text 72 | .slugignore text 73 | 74 | # Documentation 75 | *.md text 76 | LICENSE text 77 | AUTHORS text 78 | 79 | 80 | # 81 | ## These files are binary and should be left untouched 82 | # 83 | 84 | # (binary is a macro for -text -diff) 85 | *.png binary 86 | *.jpg binary 87 | *.jpeg binary 88 | *.gif binary 89 | *.ico binary 90 | *.mov binary 91 | *.mp4 binary 92 | *.mp3 binary 93 | *.flv binary 94 | *.fla binary 95 | *.swf binary 96 | *.gz binary 97 | *.zip binary 98 | *.7z binary 99 | *.ttf binary 100 | *.eot binary 101 | *.woff binary 102 | *.pyc binary 103 | *.pdf binary 104 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Don't check auto-generated stuff into git 2 | coverage 3 | node_modules 4 | stats.json 5 | package-lock.json 6 | 7 | # Cruft 8 | .DS_Store 9 | npm-debug.log 10 | .idea 11 | 12 | package-lock.json -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Strapi images plugin 2 | 3 | Images plugin was made because I needed to fetch image-uploads in an exact size, determined by the client. 4 | 5 | ## Installation 6 | 7 | Due to a limitation in strapis installation script, you will have to install this plugin manually. 8 | 9 | In the root of your project, run the following: 10 | ``` 11 | npm install strapi-plugin-images --no-save 12 | ``` 13 | *This will install the plugin node `node_modules` folder* 14 | 15 | ``` 16 | mv node_modules/strapi-plugin-images plugins/images 17 | ``` 18 | *In order for strapi to use this plugin, it has to be moved to the folder `plugins`* 19 | 20 | ``` 21 | cp node_modules/strapi-generate-plugin/templates/gitignore plugins/images/.gitignore 22 | ``` 23 | *To keep a clean project, you'll need the `.gitignore` plugin template from strapi* 24 | 25 | ## Configuration 26 | 27 | When plugin has been installed, you need to allow access to the `GET: images` endpoint. 28 | 29 | 1. Navigate to Users & Permissions. 30 | 2. Pick the role you would like to give permission. 31 | 3. Scroll down to the section **Images**. 32 | 4. Check the `get` endpoint, and press save. 33 | 34 | ## Usage 35 | 36 | When the plugin is installed, it will add a `resize_url` field to each attachment in the response. When the attachment is of a supported mime-type, the field will contain a url for the resizing endpoint. 37 | 38 | ### Resizing endpoint 39 | 40 | This endpoint can be called with some transformation parameters. 41 | 42 | Examples: 43 | 44 | ```HOST_NAME/images/${image_id}?size=120x120``` 45 | This example will resize the image to 120px by 120px. It will by default use the "cover" mode. 46 | 47 | ```HOST_NAME/images/${image_id}?size=120x120&mode=contain``` 48 | This example will resize the image to 120px by 120px. This is explicitly configured to use the contain mode. 49 | 50 | ## Credits 51 | 52 | This plugin is basically a strapi implementation of [Jimp](https://github.com/oliver-moran/jimp) 53 | 54 | Checkout the docs. 55 | -------------------------------------------------------------------------------- /admin/build/main.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"main.js","sources":["webpack:///main.js"],"mappings":"AAAA;;;;;AAKA;;;;;;;;AAQA;;;;;AAKA","sourceRoot":""} -------------------------------------------------------------------------------- /admin/src/containers/App/actions.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * App actions 4 | * 5 | */ 6 | -------------------------------------------------------------------------------- /admin/src/containers/App/constants.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * App constants 4 | * 5 | */ 6 | -------------------------------------------------------------------------------- /admin/src/containers/App/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * This component is the skeleton around the actual pages, and should only 4 | * contain code that should be seen on all pages. (e.g. navigation bar) 5 | * 6 | */ 7 | 8 | import React from 'react'; 9 | import PropTypes from 'prop-types'; 10 | import { connect } from 'react-redux'; 11 | import { createStructuredSelector } from 'reselect'; 12 | import { Switch, Route } from 'react-router-dom'; 13 | import { bindActionCreators, compose } from 'redux'; 14 | 15 | // Utils 16 | import { pluginId } from 'app'; 17 | 18 | // Containers 19 | import HomePage from 'containers/HomePage'; 20 | import NotFoundPage from 'containers/NotFoundPage'; 21 | 22 | 23 | class App extends React.Component { 24 | 25 | render() { 26 | return ( 27 |
28 | 29 | 30 | 31 | 32 |
33 | ); 34 | } 35 | } 36 | 37 | App.contextTypes = { 38 | plugins: PropTypes.object, 39 | router: PropTypes.object.isRequired, 40 | updatePlugin: PropTypes.func, 41 | }; 42 | 43 | App.propTypes = { 44 | history: PropTypes.object.isRequired, 45 | }; 46 | 47 | export function mapDispatchToProps(dispatch) { 48 | return bindActionCreators( 49 | {}, 50 | dispatch, 51 | ); 52 | } 53 | 54 | const mapStateToProps = createStructuredSelector({}); 55 | 56 | // Wrap the component to inject dispatch and state into it 57 | const withConnect = connect(mapStateToProps, mapDispatchToProps); 58 | 59 | export default compose( 60 | withConnect, 61 | )(App); 62 | -------------------------------------------------------------------------------- /admin/src/containers/App/reducer.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * App reducer 4 | * 5 | */ 6 | 7 | import { fromJS } from 'immutable'; 8 | 9 | const initialState = fromJS({}); 10 | 11 | function appReducer(state = initialState, action) { 12 | switch (action.type) { 13 | default: 14 | return state; 15 | } 16 | } 17 | 18 | export default appReducer; 19 | -------------------------------------------------------------------------------- /admin/src/containers/App/selectors.js: -------------------------------------------------------------------------------- 1 | // import { createSelector } from 'reselect'; 2 | 3 | /** 4 | * Direct selector to the list state domain 5 | */ 6 | 7 | // const selectGlobalDomain = () => state => state.get('global'); 8 | 9 | export {}; 10 | -------------------------------------------------------------------------------- /admin/src/containers/HomePage/actions.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * HomePage actions 4 | * 5 | */ 6 | 7 | import { DEFAULT_ACTION } from './constants'; 8 | 9 | export function defaultAction() { 10 | return { 11 | type: DEFAULT_ACTION, 12 | }; 13 | } 14 | -------------------------------------------------------------------------------- /admin/src/containers/HomePage/constants.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * HomePage constants 4 | * 5 | */ 6 | 7 | export const DEFAULT_ACTION = 'HomePage/DEFAULT_ACTION'; 8 | -------------------------------------------------------------------------------- /admin/src/containers/HomePage/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * HomePage 4 | * 5 | */ 6 | 7 | import React from 'react'; 8 | import PropTypes from 'prop-types'; 9 | import { connect } from 'react-redux'; 10 | import { createStructuredSelector } from 'reselect'; 11 | import { injectIntl } from 'react-intl'; 12 | import { bindActionCreators, compose } from 'redux'; 13 | 14 | import injectReducer from 'utils/injectReducer'; 15 | import injectSaga from 'utils/injectSaga'; 16 | 17 | // Selectors 18 | import selectHomePage from './selectors'; 19 | 20 | // Styles 21 | import styles from './styles.scss'; 22 | 23 | import reducer from './reducer'; 24 | import saga from './saga'; 25 | 26 | export class HomePage extends React.Component { 27 | render() { 28 | return ( 29 |
30 |

Images plugin

31 |
32 | ); 33 | } 34 | } 35 | 36 | HomePage.contextTypes = { 37 | router: PropTypes.object, 38 | }; 39 | 40 | HomePage.propTypes = { 41 | // homePage: PropTypes.object, 42 | }; 43 | 44 | function mapDispatchToProps(dispatch) { 45 | return bindActionCreators( 46 | { 47 | // Your actions here 48 | }, 49 | dispatch, 50 | ); 51 | } 52 | 53 | const mapStateToProps = createStructuredSelector({ 54 | homePage: selectHomePage(), 55 | }); 56 | 57 | const withConnect = connect(mapStateToProps, mapDispatchToProps); 58 | 59 | const withReducer = injectReducer({ key: 'homePage', reducer }); 60 | const withSaga = injectSaga({ key: 'homePage', saga }); 61 | 62 | export default compose( 63 | withReducer, 64 | withSaga, 65 | withConnect, 66 | )(injectIntl(HomePage)); 67 | -------------------------------------------------------------------------------- /admin/src/containers/HomePage/reducer.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * HomePage reducer 4 | * 5 | */ 6 | 7 | import { fromJS } from 'immutable'; 8 | 9 | import { DEFAULT_ACTION } from './constants'; 10 | 11 | const initialState = fromJS({}); 12 | 13 | function homePageReducer(state = initialState, action) { 14 | switch (action.type) { 15 | case DEFAULT_ACTION: 16 | return state; 17 | default: 18 | return state; 19 | } 20 | } 21 | 22 | export default homePageReducer; 23 | -------------------------------------------------------------------------------- /admin/src/containers/HomePage/saga.js: -------------------------------------------------------------------------------- 1 | // import { LOCATION_CHANGE } from 'react-router-redux'; 2 | // import { takeLatest, put, fork, take, cancel } from 'redux-saga/effects'; 3 | 4 | // Individual exports for testing 5 | export function* defaultSaga() { 6 | } 7 | 8 | // All sagas to be loaded 9 | export default defaultSaga; 10 | -------------------------------------------------------------------------------- /admin/src/containers/HomePage/selectors.js: -------------------------------------------------------------------------------- 1 | import { createSelector } from 'reselect'; 2 | 3 | /** 4 | * Direct selector to the homePage state domain 5 | */ 6 | const selectHomePageDomain = () => state => state.get('homePage'); 7 | 8 | /** 9 | * Default selector used by HomePage 10 | */ 11 | 12 | const selectHomePage = () => createSelector( 13 | selectHomePageDomain(), 14 | (substate) => substate.toJS(), 15 | ); 16 | 17 | export default selectHomePage; 18 | -------------------------------------------------------------------------------- /admin/src/containers/HomePage/styles.scss: -------------------------------------------------------------------------------- 1 | .homePage { 2 | 3 | } 4 | -------------------------------------------------------------------------------- /admin/src/containers/NotFoundPage/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * NotFoundPage 3 | * 4 | * This is the page we show when the user visits a url that doesn't have a route 5 | * 6 | * NOTE: while this component should technically be a stateless functional 7 | * component (SFC), hot reloading does not currently support SFCs. If hot 8 | * reloading is not a neccessity for you then you can refactor it and remove 9 | * the linting exception. 10 | */ 11 | 12 | import React from 'react'; 13 | 14 | import NotFound from 'components/NotFound'; 15 | 16 | export default class NotFoundPage extends React.Component { 17 | render() { 18 | return ; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /admin/src/translations/ar.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /admin/src/translations/de.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /admin/src/translations/en.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /admin/src/translations/es.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /admin/src/translations/fr.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /admin/src/translations/it.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /admin/src/translations/ko.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /admin/src/translations/nl.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /admin/src/translations/pl.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /admin/src/translations/pt-BR.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /admin/src/translations/pt.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /admin/src/translations/ru.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /admin/src/translations/tr.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /admin/src/translations/zh-Hans.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /admin/src/translations/zh.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /config/functions/bootstrap.js: -------------------------------------------------------------------------------- 1 | module.exports = async cb => { 2 | cb(); 3 | }; -------------------------------------------------------------------------------- /config/routes.json: -------------------------------------------------------------------------------- 1 | { 2 | "routes": [ 3 | { 4 | "method": "GET", 5 | "path": "/:_id", 6 | "handler": "Images.get", 7 | "config": { 8 | "policies": [] 9 | } 10 | } 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /controllers/Images.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Images.js controller 5 | * 6 | * @description: A set of functions called "actions" of the `images` plugin. 7 | */ 8 | 9 | module.exports = { 10 | 11 | /** 12 | * Get and process an image 13 | * 14 | * @return {Object} 15 | */ 16 | 17 | get: async (ctx) => { 18 | const ImagesService = strapi.plugins['images'].services.images; 19 | const { 20 | size, 21 | mode = 'cover', 22 | } = ctx.query; 23 | const entity = await strapi.plugins['upload'].services.upload.fetch(ctx.params); 24 | const { 25 | mime, 26 | } = entity; 27 | if (!entity) return ctx.notFound(); // Upload was not found 28 | if (!ImagesService.supportedMime(mime)) return ctx.noContent(); // Mime of the upload is not supported 29 | 30 | try { 31 | const image = await ImagesService.read(entity); 32 | await ImagesService 33 | .process(image, { 34 | size, 35 | mode, 36 | }); 37 | const imageBuffer = await ImagesService.getBuffer(image, mime); 38 | ctx.set('Content-Type', mime); 39 | ctx.send(imageBuffer); 40 | } catch (error) { 41 | ctx.internalServerError(); 42 | } 43 | } 44 | }; 45 | -------------------------------------------------------------------------------- /middlewares/images/defaults.json: -------------------------------------------------------------------------------- 1 | { 2 | "images": { 3 | "enabled": true 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /middlewares/images/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const _ = require('lodash'); 3 | 4 | function extractAttachmentKeys(jsonBody) { 5 | const recursiveMap = (value, key) => { 6 | if (_.isArray(value)) { 7 | return _.map(value, recursiveMap).map((keyIndex) => [key, keyIndex].join(',')); 8 | } else { 9 | if (_.has(value, 'mime')) return key; 10 | } 11 | }; 12 | 13 | return _(jsonBody) 14 | .mapValues(recursiveMap) 15 | .filter((imageKey) => !!imageKey) 16 | .flatten() 17 | .value(); 18 | } 19 | 20 | module.exports = strapi => ({ 21 | initialize: (cb) => { 22 | const ImagesService = strapi.plugins['images'].services.images; 23 | strapi.app.use(async (ctx, next) => { 24 | await next(); 25 | if (ctx.get('Content-Type') != 'application/json' || !ctx.body) return; 26 | const responseBody = JSON.parse(JSON.stringify(ctx.body)); 27 | const attachmentKeys = extractAttachmentKeys(responseBody); 28 | if (_.size(attachmentKeys) < 1) return; 29 | _.forEach(attachmentKeys, (key) => { 30 | const objectPath = key.split(','); 31 | const attachment = _.get(responseBody, objectPath); 32 | if ( ImagesService.supportedMime(_.get(attachment, ['mime']))) { 33 | const resizeRoute = ImagesService.resizeURL(_.get(attachment, ['_id'])); 34 | _.set(responseBody, [...objectPath, 'resize_url'], resizeRoute); 35 | } else { 36 | _.set(responseBody, [...objectPath, 'resize_url'], null); 37 | } 38 | }); 39 | ctx.body = responseBody; 40 | }); 41 | cb(); 42 | } 43 | }); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "strapi-plugin-images", 3 | "version": "0.0.5", 4 | "description": "This plugin delivers more image-features to your apis.", 5 | "strapi": { 6 | "name": "images", 7 | "icon": "plug", 8 | "description": "Description of images plugin." 9 | }, 10 | "scripts": { 11 | "analyze:clean": "node ./node_modules/strapi-helper-plugin/node_modules/.bin/rimraf stats.json", 12 | "preanalyze": "npm run analyze:clean", 13 | "analyze": "node ./node_modules/strapi-helper-plugin/lib/internals/scripts/analyze.js", 14 | "prebuild": "npm run build:clean", 15 | "build:dev": "node ./node_modules/strapi-helper-plugin/node_modules/.bin/cross-env NODE_ENV=development node ./node_modules/strapi-helper-plugin/node_modules/.bin/webpack --config node_modules/strapi-helper-plugin/lib/internals/webpack/webpack.prod.babel.js --color -p --progress", 16 | "build": "node ./node_modules/strapi-helper-plugin/node_modules/.bin/cross-env NODE_ENV=production node node_modules/strapi-helper-plugin/node_modules/.bin/webpack --config node_modules/strapi-helper-plugin/lib/internals/webpack/webpack.prod.babel.js --color -p --progress", 17 | "build:clean": "node ./node_modules/strapi-helper-plugin/node_modules/.bin/rimraf admin/build", 18 | "start": "node ./node_modules/strapi-helper-plugin/node_modules/.bin/cross-env NODE_ENV=development node ./node_modules/strapi-helper-plugin/lib/server", 19 | "generate": "node ./node_modules/plop/plop.js --plopfile node_modules/strapi-helper-plugin/lib/internals/generators/index.js", 20 | "lint": "node ./node_modules/strapi-helper-plugin/node_modules/.bin/eslint --ignore-path .gitignore --ignore-pattern '/admin/build/' --config ./node_modules/strapi-helper-plugin/lib/internals/eslint/.eslintrc.json admin", 21 | "prettier": "node ./node_modules/strapi-helper-plugin/node_modules/.bin/prettier --single-quote --trailing-comma es5 --write \"{admin,__{tests,mocks}__}/**/*.js\"", 22 | "test": "npm run lint", 23 | "prepublishOnly": "npm run build" 24 | }, 25 | "dependencies": { 26 | "jimp": "^0.5.4", 27 | "lodash": "^4.17.11" 28 | }, 29 | "devDependencies": { 30 | "strapi-helper-plugin": "3.0.0-alpha.14.3" 31 | }, 32 | "author": { 33 | "name": "Kristian Froelund", 34 | "email": "", 35 | "url": "" 36 | }, 37 | "repository": { 38 | "type": "git", 39 | "url": "https://github.com/Froelund/strapi-plugin-images.git" 40 | }, 41 | "maintainers": [ 42 | { 43 | "name": "Kristian Froelund", 44 | "email": "", 45 | "url": "" 46 | } 47 | ], 48 | "engines": { 49 | "node": ">= 9.0.0", 50 | "npm": ">= 5.3.0" 51 | }, 52 | "license": "MIT" 53 | } 54 | -------------------------------------------------------------------------------- /services/Images.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const Jimp = require('jimp'); 3 | const path = require('path'); 4 | const _ = require('lodash'); 5 | /** 6 | * Images.js service 7 | * 8 | * @description: 9 | */ 10 | const SUPPORTED_MIMES = [Jimp.MIME_PNG, Jimp.MIME_TIFF, Jimp.MIME_JPEG, Jimp.MIME_BMP, Jimp.MIME_X_MS_BMP, Jimp.MIME_GIF]; 11 | module.exports = { 12 | SUPPORTED_MIMES, 13 | supportedMime: (mime) => _.isString(mime) && _.includes(SUPPORTED_MIMES, mime), 14 | read: async (entity) => { 15 | const { 16 | provider, 17 | url, 18 | } = entity; 19 | if (provider == 'local') { 20 | const filePath = path.join(strapi.config.appPath, 'public', url); 21 | return Jimp.read(filePath); 22 | } else { 23 | return Jimp.read(url); 24 | } 25 | }, 26 | process: async (image, options) => { 27 | const { 28 | size, 29 | mode = 'cover' 30 | } = options; 31 | if (size) { 32 | const [width, height] = size.split('x').map((dimString) => parseInt(dimString)); 33 | switch (mode) { 34 | case 'contain': 35 | await image.contain(width, height); 36 | break; 37 | case 'cover': 38 | await image.cover(width, height); 39 | break; 40 | } 41 | } 42 | }, 43 | getBuffer: async (image, mime) => { 44 | return new Promise((resolve, reject) => { 45 | image.getBuffer(mime, (err, buffer) => { 46 | if (err) return reject(err); 47 | return resolve(buffer); 48 | }); 49 | }); 50 | }, 51 | resizeURL: (imageID) => { 52 | return `/images/${imageID}`; 53 | } 54 | }; 55 | --------------------------------------------------------------------------------