Git Product home page Git Product logo

ghost-storage-cloudinary's Introduction

Ghost Storage Cloudinary

npm version Test Maintainability Test Coverage

A fully featured and deeply tested Cloudinary Ghost storage adapter.

Features

  • Up to date with latest Ghost versions 🚀
  • Latest Cloudinary NodeJS SDK
  • Image upload, existence check & deletion (when Ghost will support it)
  • Ability to upload in dated sub-directories (alike first Ghost default Local storage adapter YYYY/MM)
  • Ability to upload images into a specific directory
  • Ability to tag images
  • Cool plugins!

Installation

Install using yarn

  • Go into Ghost root directory
  • Download the adapter:
yarn add ghost-storage-cloudinary
mv node_modules/ghost-storage-cloudinary content/adapters/storage/ghost-storage-cloudinary
  • Done, go configure

Install on Docker

Here's an example of using this adapter with a containerized Ghost:

FROM ghost:5-alpine as cloudinary
RUN apk add g++ make python3
RUN su-exec node yarn add ghost-storage-cloudinary

FROM ghost:5-alpine
COPY --chown=node:node --from=cloudinary $GHOST_INSTALL/node_modules $GHOST_INSTALL/node_modules
COPY --chown=node:node --from=cloudinary $GHOST_INSTALL/node_modules/ghost-storage-cloudinary $GHOST_INSTALL/content/adapters/storage/ghost-storage-cloudinary
# Here, we use the Ghost CLI to set some pre-defined values.
RUN set -ex; \
    su-exec node ghost config storage.active ghost-storage-cloudinary; \
    su-exec node ghost config storage.ghost-storage-cloudinary.upload.use_filename true; \
    su-exec node ghost config storage.ghost-storage-cloudinary.upload.unique_filename false; \
    su-exec node ghost config storage.ghost-storage-cloudinary.upload.overwrite false; \
    su-exec node ghost config storage.ghost-storage-cloudinary.fetch.quality auto; \
    su-exec node ghost config storage.ghost-storage-cloudinary.fetch.cdn_subdomain true;

Make sure to set the content path right in the Ghost config as well:

"paths": {
    "contentPath": "/var/lib/ghost/content/"
}

Configuration

Check out configuration.json.dist for a complete example.

  • Ensure to disable Ghost Image Optimisation
  • The optional useDatedFolder = false to upload images in dated sub-directories (alike default Ghost Local storage adapter)
  • The auth property is optional if you use the CLOUDINARY_URL environment variable authentification method
  • The optional upload property contains Cloudinary API upload options
  • The optional fetch property contains Cloudinary API image transformation options

Recommended configuration

  • upload.use_filename = true in order use the original image name
  • upload.unique_filename = false unlikely Ghost local storage adapter which auto-dedup an existing file name, Cloudinary will return the existing image URL instead of deduping the image
  • upload.overwrite = false goes along with previous option: returns existing image instead of overwriting it
  • upload.folder = "my-blog" allows to upload all your images into a specific directory instead of Cloudinary media library root
  • upload.tags = ["blog", "photography"] if you want to add some taxonomy to your uploaded images
  • fetch.quality = "auto" equals auto:good (see doc)
  • fetch.secure = false set to true if you want to serve images over SSL (not recommended for performances)
  • fetch.cdn_subdomain = true to really use the benefit of Cloudinary CDN

❤️ Don't forget to checkout the plugins!

Development

Run yarn install without the --production flag.

Runs the tests and generate coverage:

yarn coverage

Runs the linter:

yarn eslint

To enable debug logs, set the following environment variable:

DEBUG=ghost-storage-cloudinary:*

Integration testing:

docker build \
    --build-arg CLOUDINARY_URL=cloudinary://.... \
    --file Dockerfile.test \
    --tag test \
    .
docker run -itp 2368:2368 test

Go to http://localhost:2368.


Many thanks to @mmornati, @sethbrasile and all other contributors for their work. In the continuation of this project, don't hesitate to fork, contribute and add more features.

ghost-storage-cloudinary's People

Contributors

dependabot[bot] avatar edshee avatar eexit avatar greenkeeper[bot] avatar greenkeeperio-bot avatar mmornati avatar naz avatar sesser avatar sethbrasile avatar snyk-bot avatar zavrelj avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

ghost-storage-cloudinary's Issues

"Unable to find storage adapter" in Ghost 4 / API compatibility issue?

On a brand new Ghost 4 install using the same config.production.json that I'd used previously with 2.* and 3.0.3 I get the following error message:

Debug Information:
    OS: Fedora, v34
    Node Version: v14.17.0
    Ghost Version: 4.8.4
    Ghost-CLI Version: 1.17.3
    Environment: production
    Command: 'ghost start'
Message: Ghost was able to start, but errored during boot with: Unable to find storage adapter ghost-storage-cloudinary in ,/var/www/myurl.com/ghost/content/adapters/,/var/www/myurl.com/ghost/versions/4.8.4/core/server/adapters/.
Stack: Error: Ghost was able to start, but errored during boot with: Unable to find storage adapter ghost-storage-cloudinary in ,/var/www/myurl.com/ghost/content/adapters/,/var/www/myurl.com/ghost/versions/4.8.4/core/server/adapters/.
    at ChildProcess.<anonymous> (/home/kevin/ghost-cli/node_modules/ghost-cli/lib/utils/local-process.js:75:35)
    at ChildProcess.emit (events.js:376:20)
    at emit (internal/child_process.js:910:12)
    at processTicksAndRejections (internal/process/task_queues.js:83:21)

I've tried putting the adapter in both /var/www/myurl.com/ghost/content/adapters/ and /var/www/myurl.com/ghost/versions/4.8.4/core/server/adapters/, directly in that path as well as the storage/ subdirectory, I've triple-checked the permissions of all files, but I simply can't get the storage adapter to load.

In the Ghost forum I found this suggestion https://forum.ghost.org/t/updating-from-3-20-3-to-latest-cloudinary-content-store/16699/2 that it might have to do with the adapter crashing because it uses internal API/exports that have become unavailable with Ghost 4?
So following KINGMJ/qn-store@bde0b56 I tried removing all usage of the require('ghost-ignition').debug('adapter') as well as GhostError instances from the code but it still produces the same error message.

All help/hints would be appreciated. I'm also curious/confused about how @adikari apparently managed to get the adapter to start in #123?

Would be awesome with webp support

You have the option to enable fetch.quality = "auto" would be awesome if you also could use
fetch.format = "auto" for the webp functionality.

An in-range update of npm is breaking the build 🚨

The dependency npm was updated from 6.9.1 to 6.9.2.

🚨 View failing branch.

This version is covered by your current version range and after updating it in your project the build failed.

npm is a direct dependency of this project, and it is very likely causing it to break. If other packages depend on yours, this update is probably also breaking those in turn.

Status Details
  • continuous-integration/travis-ci/push: The Travis CI build could not complete due to an error (Details).

Commits

The new version differs by 2 commits.

See the full diff

FAQ and help

There is a collection of frequently asked questions. If those don’t help, you can always ask the humans behind Greenkeeper.


Your Greenkeeper Bot 🌴

[Question] How do I check if this is working

Hi,

I'm using digitalocean to host my ghost blog site. I've successfully installed this adapter, How do I configure this? As I checked configuration.json.dist file it is already configured and I've set cloude-name, api-key and secret key, It would be great if you could guide me as what to do next?

Responsive image transformation

I was trying to setup responsive image transformations following the official Cloudinary Node.js documentation.

I changed my configuration file as follows:

"fetch": {
   "fetch_format": "auto",
   "responsive": true,
   "width": "auto",
   "crop": "scale",
   "quality": "auto",
   "secure": false,
   "cdn_subdomain": true
}

The default Ghost Image Optimisation has been disabled as required.

"imageOptimisation": {
    "resize": false
}

However, there seems to be no change in the widths on Pagespeed Insights.
While the image URL shows the parameters:

https://res-4.cloudinary.com/da9mvldpq/image/upload/c_scale,f_auto,q_auto,w_auto/v1/blog-images/2--Resource.jpg

The attribute for the image has the original width and height

<img src="https://res-4.cloudinary.com/da9mvldpq/image/upload/c_scale,f_auto,q_auto,…" class="kg-image" alt="" loading="lazy" width="1500" height="741">

I do not know if the problem is in my configuration or if the adapter does not support these functions.
Any help is much appreciated!

post banner is not serving in https mode

Hi, I am serving my ghost site in https mode,
But during create new post, upload an image in right hand side panel ( as know as post banner)
It's successful to upload, and show correctly in panel.

After publish the post, image wont show in post, in chrome developer tools, it show the error as below:

Mixed Content: The page at 'https://<mydomain>/' was loaded over HTTPS, but requested an insecure image 'http://<cloudinary url link>'. This request has been blocked; the content must be served over HTTPS.

Addon details:
Normal image upload in content is work fine.

Any idea on this?
Thank you for help!

Crashes in Ghost 4

I am trying to use this in Ghost 4 and seems like when I upload the image, it crashes the entire ghost instance.

I am running this in the dockerized version. I can't really see the logs to figure out what the actual problem is.

Would be cool if this worked with Ghost 4.

Possibility to specify the fetch flags AFTER upload

Hi,

I'm curious if there is a way to specify the fetch flags once the images are uploaded and a post is saved. It currently saves the fetch options that just generates ULR which cannot be changed unless the image is re-added again.

I am building a custom theme and using for rather smaller feature images to display a grid on the index, and when in the post is rendered then you can click on the smaller thumb and get a full screen image. Ideally I'd like to upload a full-quality asset to Cloudinary (via Ghost) and in my theme somehow specify the requested size (Cloudinary will do the rest).

It needs to happen in the theme - in HBS - in order to serve small image where it needs to be. I know this is more of a role for CMS and Ghost is not really excelling here.

I was thinking -- if this storage provider registered a Handlebars helper like cloudinary <<Cloudinary params here>> imgUrl then as someone creating a custom theme on your Ghost with this storage adapter you could use that to actually have HTML with for example resized (smaller) images if you wanted to.

SSL warning

Hello,

When using ghost-storage-cloudinary as default storage adapter, the image url automatic use "http" instead of "https" and that create warning in Firefox and Chrome about the security of pages.
Is there anyway that we can config the link will be "https" automatically?

Thank you.
Here is some picture about the issues
ssl warning
ssl warning 2

Not tested on Ghost version newer than 3

Hello,

I appreciate your report and I am very pleased to see that this plugin is used. However, please be patient as I need more time to test it against the latest versions of Ghost.

Thanks a lot for your support ❤️

Docker Ghost 2.0 can't find `ghost-ignition`

I tried to follow the example of Dockerfile for the newest version and this message appears when I try to start it.

Not sure if your project isn't 2.0 compatible or where is the issue.

ghost                | module.js:550
ghost                |     throw err;
ghost                |     ^
ghost                | 
ghost                | Error: Cannot find module 'ghost-ignition'
ghost                |     at Function.Module._resolveFilename (module.js:548:15)
ghost                |     at Function.Module._load (module.js:475:25)
ghost                |     at Module.require (module.js:597:17)
ghost                |     at require (internal/module.js:11:18)
ghost                |     at Object.<anonymous> (/var/lib/ghost/versions/2.4.0/index.js:5:13)
ghost                |     at Module._compile (module.js:653:30)
ghost                |     at Object.Module._extensions..js (module.js:664:10)
ghost                |     at Module.load (module.js:566:32)
ghost                |     at tryModuleLoad (module.js:506:12)
ghost                |     at Function.Module._load (module.js:498:3)

fetch.secure is only applied at time of upload, not actually when fetching

More an idea for an enhancement rather than a bug: I started off using the recommended fetch.secure: false but ended up having to change to secure: true because our site is served over https and most browsers didn't like us dynamically requesting content from a non-secured site. I assumed that the storage adapter would just change all picture URLs from http:// to https:// but to my surprise all previously uploaded pictures were still requested over http, only those uploaded after the config change got a https:// path by default.
I understand that this probably has to do with how storage adapters fit into the Ghost architecture (storing a fixed path to the image at the time of upload) but especially given the name of the config setting I found it deeply unintuitive that previously uploaded images were unaffected. I wonder if there is a safe way for the storage adapter to check all the URLs in the Ghost storage and update any cloudinary URLs which have the wrong protocol? It seems like the storage adapter isn't actually queried when Ghost generates the <img> tags, so this might have to happen during the adapter initialisation...

How difficult would it be to proxy all images through the node.js server?

I'm serving my Ghost blog on a subdirectory on my root domain, so I proxy all the requests through my application server. I also route all the traffic on my root domain through Cloudflare, and set it to cache every request.

I can see that this Ghost plugin is loading the images directly from the cloudinary domain, so this bypasses all of my proxies and caches, and it means I have to be more careful about bandwidth for my image requests. I would prefer to send everything through CloudFlare if possible so that I can cache and serve everything for free.

How difficult would it be to proxy all the image requests through the Node.js server, instead of serving directly from Cloudinary?

I'm thinking that I could return a simple path from the uploader function, e.g. /cloudinary/{image_id}. Then I could set up a handler in serve to respond to the /cloudinary/{image_id} requests.

Would anyone else be interested in a proxyImages option?

Image appears duplicated on upload

Hello @eexit!

I found out a little problem while uploading images using ghost-cloudinary-storage. Every time I upload a new image from Ghost, it appears uploaded twice on the Cloudinary dashboard.

I've been looking at the Console to see if the request was made twice but seems that is firing only once 🤔

Screenshot 2019-09-11 at 22 44 37

Screenshot 2019-09-11 at 22 44 50

This is my configuration file:

"storage": {
    "active": "ghost-storage-cloudinary",
    "ghost-storage-cloudinary": {
      "cloud_name": "removed",
      "api_key": "removed",
      "api_secret": "removed",
      "configuration": {
        "image": {
          "secure": true
        },
        "file": {}
      }
    }
  },

Thanks!

Version 10 of node.js has been released

Version 10 of Node.js (code name Dubnium) has been released! 🎊

To see what happens to your code in Node.js 10, Greenkeeper has created a branch with the following changes:

  • Added the new Node.js version to your .travis.yml

If you’re interested in upgrading this repo to Node.js 10, you can open a PR with these changes. Please note that this issue is just intended as a friendly reminder and the PR as a possible starting point for getting your code running on Node.js 10.

More information on this issue

Greenkeeper has checked the engines key in any package.json file, the .nvmrc file, and the .travis.yml file, if present.

  • engines was only updated if it defined a single version, not a range.
  • .nvmrc was updated to Node.js 10
  • .travis.yml was only changed if there was a root-level node_js that didn’t already include Node.js 10, such as node or lts/*. In this case, the new version was appended to the list. We didn’t touch job or matrix configurations because these tend to be quite specific and complex, and it’s difficult to infer what the intentions were.

For many simpler .travis.yml configurations, this PR should suffice as-is, but depending on what you’re doing it may require additional work or may not be applicable at all. We’re also aware that you may have good reasons to not update to Node.js 10, which is why this was sent as an issue and not a pull request. Feel free to delete it without comment, I’m a humble robot and won’t feel rejected 🤖


FAQ and help

There is a collection of frequently asked questions. If those don’t help, you can always ask the humans behind Greenkeeper.


Your Greenkeeper Bot 🌴

Fall back to images in content/images/ if file exists?

Hello,

I'm setting up Ghost on Heroku via the awesome ghost-on-heroku. This provisions the Cloudinary resource and sets up ghost-storage-cloudinary by default.

My problem is that I have already migrated a lot of my blog posts to a Ghost(Pro) instance before I decided to host my own instance on Heroku. I was able to export all of my posts from Ghost(Pro) as JSON, and I downloaded all of the images by scraping the blog using wget --recursive. So I copied all of these historical images into content/images/. If I disable Cloudinary by unsetting the CLOUDINARY_URL, then everything is working great and all the images load properly.

However, when the ghost-storage-cloudinary storage is configured in this code, the server can no longer serve any of my images in the content/images/ folder.

I was wondering if it would be possible to support a hybrid approach where any new images are uploaded to Cloudinary, but this library also checks for any hard-coded images inside the content/images/ folder?

node version incompatibility issue

I'm getting the following error on installation:

[email protected]: The engine "node" is incompatible with this module. Expected version "^12.22.1 || ^14.17.0 || ^16.13.0". Got "18.19.1"

Dockerfile looks like:

FROM amd64/node:18 as builder

WORKDIR /scratch
COPY ./theme/ ./
RUN yarn add ghost-storage-cloudinary

Support Node 18

ghost-storage-cloudinary cannot be installed with Node.js v18 due to the following error:

error [email protected]: The engine "node" is incompatible with this module. Expected version "^14.17.0 || ^16.13.0". Got "18.16.1"

Would it be possible to add support for Node.js v18?

Support for store all uploaded files such as video,pdf etc.

@eexit I am using this plugin with ghost self-hosted sever. I noticed that this plugin only manage images with cloudinary. Ghost also provide facility of uploading video, audio etc. to sever. When I upload other file it get uploaded to local file storage of my server. But in order to make my app state less I have to store all the uploaded files to cloudinary. I thinks its best add support for it to the library.

Thanks!

Trying to serve images as webp

Hello,

I tried to change the config.production.json, but adding the fetch format as .webp didn't work. Is there any workaround to this?
Thanks in advance!

"ghost-storage-cloudinary": { "useDatedFolder": false, "auth": { "cloud_name": "...", "api_key": "...", "api_secret": "..." }, "upload": { "use_filename": true, "unique_filename": false, "overwrite": false, "folder": "blog-images", "tags": [ "blog" ] }, "fetch": { "quality": "auto", "secure": true, "cdn_subdomain": true, "format": "webp" }, "plugins": {} }

public_id is always set

I think this is a problem if you want to be able to be to upload images with the same name and still have them uniquely named - using the unique_filename option. Because the public_id is always set, Cloudinary will use that and never generate the unique filename.

I've solved this locally by modifying the source code (commenting out index.js:72, but I believe this might affect more people. Would public_id need to be set anyway? The upload options should be able to solve all of this automagically, right?

How to integrate with existing docker-compose?

How can I integrate into an existing project? I'm currently running Ghost using the following docker-compose.yml:

version: '3.1'
services:
  ghost:
    image: arm64v8/ghost
    restart: unless-stopped
    environment:
      url: http://localhost:3001
    volumes:
      - ./ghost-data:/var/lib/ghost/content
    ports:
      - 3001:2368

I've tried creating a new Dockerfile based on your example on the readme, and then modifying docker-compose.yml to the following:

FROM arm64v8/ghost as cloudinary
WORKDIR $GHOST_INSTALL/current
RUN su-exec node yarn add ghost-storage-cloudinary@2

FROM arm64v8/ghost
COPY --chown=node:node --from=cloudinary $GHOST_INSTALL/current/node_modules $GHOST_INSTALL/current/node_modules
COPY --chown=node:node --from=cloudinary $GHOST_INSTALL/current/node_modules/ghost-storage-cloudinary $GHOST_INSTALL/current/core/server/adapters/storage/ghost-storage-cloudinary
RUN set -ex; \
  su-exec node ghost config storage.active ghost-storage-cloudinary; \
  su-exec node ghost config storage.ghost-storage-cloudinary.upload.use_filename true; \
  su-exec node ghost config storage.ghost-storage-cloudinary.upload.unique_filename false; \
  su-exec node ghost config storage.ghost-storage-cloudinary.upload.overwrite false; \
  su-exec node ghost config storage.ghost-storage-cloudinary.fetch.quality auto; \
  su-exec node ghost config storage.ghost-storage-cloudinary.fetch.cdn_subdomain true;
version: '3.1'
services:
  ghost:
    build:
      context: ./
      dockerfile: Dockerfile
    image: ghost
    restart: unless-stopped
    environment:
      url: http://localhost:3001
    volumes:
      - ./ghost-data:/var/lib/ghost/content
    ports:
      - 3001:2368

However, this causes a generic error with NextJS and GraphQL: Error: Index creation failed.

Any ideas? I'm using the latest version of Ghost (v4).

Images uploaded before activating this adapter return 404

Hello!

I've been using the default Ghost storage adapter for a while, and have got a few year's worth of images in my content/images/ folder.

Recently though I've switched to using Cloudinary for my image storage, and I've activated this adapter.

However all my old images no longer work. Let's say I'm requesting image 2022/05/Untitled.png. This is what happens:

  • Ghost responds with 301 for {domain}/content/images/2022/05/Untitled.png, redirecting to {domain}/content/images/2022/05/untitled.png.
  • Ghost responds with 404 for {domain}/content/images/2022/05/untitled.png (makes sense, the image doesn't exist)

What I'm not sure about is why the original 301 response!

Deactivating this storage adapter and going back to the default one fixes the problem. No more 301s, and therefore no more 404s.

Looking through the code, I can't see anywhere in this storage adapter that would fall back to the filesystem (but also I don't know much about the code).

Should it fall back to the filesystem if the image is not found in Cloudinary? Or are we expected to migrate all our in-post images to Cloudinary before switching the adapter?

Looking at previous issues and PRs I found #90 which was closed without merging, seems like it adds this missing functionality.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.