├── LICENSE ├── README.md ├── config.example.js ├── index.js └── package.json /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Klion Xu 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Ghost Upyun Storage 2 | 将图片储存于又拍云。仅限于 Ghost 0.6.0 及以后版本使用。 3 | 4 | 目前版本:1.1.0 5 | 6 | 已通过 0.10.0 测试 7 | 8 | 七牛的储存模块请移步:[Minwe/qn-store](https://github.com/Minwe/qn-store) 9 | 10 | **1.1.0 以前的版本在 0.10.0 将无法使用,请您及时升级到新版** 11 | **1.1.0 不支持直接通过模块启动,请您修改 index.js 中的 BaseStore 到对应的地址** 12 | **1.0.1 有 BUG,不影响正常使用,但不推荐使用** 13 | 14 | ## 安装 15 | * 在 `content` 文件夹中创建一个名为 `storage` 的文件夹 16 | * 将本仓库克隆至 `storage` 文件夹下 17 | 18 | ``` 19 | cd path/to/your/ghost/content/storage 20 | git clone https://github.com/sanddudu/upyun-ghost-store.git 21 | ``` 22 | 23 | 或者 [下载 zip 包](https://github.com/sanddudu/upyun-ghost-store/archive/master.zip) 后解压至 storage 文件夹下(请将解压出的文件夹名改名为 `upyun-ghost-store`) 24 | * 安装依赖包 25 | 26 | ``` 27 | cd upyun-ghost-store 28 | npm install 29 | ``` 30 | 31 | ## 配置 32 | 在 `upyun-ghost-store` 文件夹下自带已经修改好的 `config.example.js`(无备注),您只需要复制到主目录后修改相应内容,并改名为 `config.js` ,您也可以自行根据以下默认配置自行添加(请不要忘记根据实际情况在末尾添加逗号)。 33 | 34 | ``` 35 | storage: { 36 | active: 'upyun-ghost-store', 37 | 'upyun-ghost-store': { 38 | bucket: 'my-bucket', //bucket 名称 39 | operator: 'somebody', //操作员用户名 40 | password: 'secret', //操作员密码 41 | domain: 'http://bucket.b0.upaiyun.com', //空间绑定的域名,必须带 "http(s)://" ,结尾不能有 "/" 42 | filePath: 'YYYY/MM/', //文件远端保存地址,默认为日期格式,由 moment 解析后填充, 43 | 您可以修改为 "[您的目录地址]",开头不能加 "/",结尾必须加 "/" 44 | 具体格式参见: [http://momentjs.com/](http://momentjs.com/) 45 | imgVersion: '' // 自定义版本,需带上分隔符(如 _large )。使用原图则留空或删除该选项 46 | } 47 | } 48 | ``` 49 | 50 | ##许可证 51 | 源代码: 52 | The MIT License (MIT) 53 | 54 | Copyright (c) 2015-2016 Klion Xu 55 | 56 | Permission is hereby granted, free of charge, to any person obtaining a copy 57 | of this software and associated documentation files (the "Software"), to deal 58 | in the Software without restriction, including without limitation the rights 59 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 60 | copies of the Software, and to permit persons to whom the Software is 61 | furnished to do so, subject to the following conditions: 62 | 63 | The above copyright notice and this permission notice shall be included in all 64 | copies or substantial portions of the Software. 65 | 66 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 67 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 68 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 69 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 70 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 71 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 72 | SOFTWARE. 73 | 74 | 依赖包: 75 | moment: 76 | Copyright (c) 2011-2016 Tim Wood, Iskren Chernev, Moment.js contributors 77 | 78 | Permission is hereby granted, free of charge, to any person 79 | obtaining a copy of this software and associated documentation 80 | files (the "Software"), to deal in the Software without 81 | restriction, including without limitation the rights to use, 82 | copy, modify, merge, publish, distribute, sublicense, and/or sell 83 | copies of the Software, and to permit persons to whom the 84 | Software is furnished to do so, subject to the following 85 | conditions: 86 | 87 | The above copyright notice and this permission notice shall be 88 | included in all copies or substantial portions of the Software. 89 | 90 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 91 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 92 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 93 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 94 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 95 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 96 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 97 | OTHER DEALINGS IN THE SOFTWARE. 98 | 99 | bluebird: 100 | The MIT License (MIT) 101 | 102 | Copyright (c) 2013-2015 Petka Antonov 103 | 104 | Permission is hereby granted, free of charge, to any person obtaining a copy 105 | of this software and associated documentation files (the "Software"), to deal 106 | in the Software without restriction, including without limitation the rights 107 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 108 | copies of the Software, and to permit persons to whom the Software is 109 | furnished to do so, subject to the following conditions: 110 | 111 | The above copyright notice and this permission notice shall be included in 112 | all copies or substantial portions of the Software. 113 | 114 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 115 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 116 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 117 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 118 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 119 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 120 | THE SOFTWARE. 121 | 122 | fs-extra: 123 | (The MIT License) 124 | 125 | Copyright (c) 2011-2016 JP Richardson 126 | 127 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files 128 | (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, 129 | merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is 130 | furnished to do so, subject to the following conditions: 131 | 132 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 133 | 134 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE 135 | WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS 136 | OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 137 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 138 | 139 | upyun: 140 | MIT License -------------------------------------------------------------------------------- /config.example.js: -------------------------------------------------------------------------------- 1 | // # Ghost Configuration 2 | // Setup your Ghost install for various [environments](http://support.ghost.org/config/#about-environments). 3 | 4 | // Ghost runs in `development` mode by default. Full documentation can be found at http://support.ghost.org/config/ 5 | 6 | var path = require('path'), 7 | config; 8 | 9 | config = { 10 | // ### Production 11 | // When running Ghost in the wild, use the production environment. 12 | // Configure your URL and mail settings here 13 | production: { 14 | url: 'http://my-ghost-blog.com', 15 | mail: {}, 16 | database: { 17 | client: 'sqlite3', 18 | connection: { 19 | filename: path.join(__dirname, '/content/data/ghost.db') 20 | }, 21 | debug: false 22 | }, 23 | 24 | server: { 25 | host: '127.0.0.1', 26 | port: '2368' 27 | } 28 | }, 29 | 30 | // ### Development **(default)** 31 | development: { 32 | // The url to use when providing links to the site, E.g. in RSS and email. 33 | // Change this to your Ghost blog's published URL. 34 | url: 'http://localhost:2368', 35 | 36 | // Example refferer policy 37 | // Visit https://www.w3.org/TR/referrer-policy/ for instructions 38 | // default 'origin-when-cross-origin', 39 | // referrerPolicy: 'origin-when-cross-origin', 40 | 41 | // Example mail config 42 | // Visit http://support.ghost.org/mail for instructions 43 | // ``` 44 | // mail: { 45 | // transport: 'SMTP', 46 | // options: { 47 | // service: 'Mailgun', 48 | // auth: { 49 | // user: '', // mailgun username 50 | // pass: '' // mailgun password 51 | // } 52 | // } 53 | // }, 54 | // ``` 55 | 56 | // #### Database 57 | // Ghost supports sqlite3 (default), MySQL & PostgreSQL 58 | database: { 59 | client: 'sqlite3', 60 | connection: { 61 | filename: path.join(__dirname, '/content/data/ghost-dev.db') 62 | }, 63 | debug: false 64 | }, 65 | // #### Server 66 | // Can be host & port (default), or socket 67 | server: { 68 | // Host to be passed to node's `net.Server#listen()` 69 | host: '127.0.0.1', 70 | // Port to be passed to node's `net.Server#listen()`, for iisnode set this to `process.env.PORT` 71 | port: '2368' 72 | }, 73 | // #### Paths 74 | // Specify where your content directory lives 75 | paths: { 76 | contentPath: path.join(__dirname, '/content/') 77 | }, 78 | storage: { 79 | active: 'upyun-ghost-store', 80 | 'upyun-ghost-store': { 81 | bucket: 'my-bucket', 82 | operator: 'somebody', 83 | password: 'secret', 84 | domain: 'http://bucket.b0.upaiyun.com', 85 | filePath: 'YYYY/MM/', 86 | imgVersion: '' 87 | } 88 | } 89 | }, 90 | 91 | // **Developers only need to edit below here** 92 | 93 | // ### Testing 94 | // Used when developing Ghost to run tests and check the health of Ghost 95 | // Uses a different port number 96 | testing: { 97 | url: 'http://127.0.0.1:2369', 98 | database: { 99 | client: 'sqlite3', 100 | connection: { 101 | filename: path.join(__dirname, '/content/data/ghost-test.db') 102 | }, 103 | pool: { 104 | afterCreate: function (conn, done) { 105 | conn.run('PRAGMA synchronous=OFF;' + 106 | 'PRAGMA journal_mode=MEMORY;' + 107 | 'PRAGMA locking_mode=EXCLUSIVE;' + 108 | 'BEGIN EXCLUSIVE; COMMIT;', done); 109 | } 110 | } 111 | }, 112 | server: { 113 | host: '127.0.0.1', 114 | port: '2369' 115 | }, 116 | logging: false, 117 | storage: { 118 | active: 'upyun-ghost-store', 119 | 'upyun-ghost-store': { 120 | bucket: 'my-bucket', 121 | operator: 'somebody', 122 | password: 'secret', 123 | domain: 'http://bucket.b0.upaiyun.com', 124 | filePath: 'YYYY/MM/', 125 | imgVersion: '' 126 | } 127 | } 128 | }, 129 | 130 | // ### Testing MySQL 131 | // Used by Travis - Automated testing run through GitHub 132 | 'testing-mysql': { 133 | url: 'http://127.0.0.1:2369', 134 | database: { 135 | client: 'mysql', 136 | connection: { 137 | host : '127.0.0.1', 138 | user : 'root', 139 | password : '', 140 | database : 'ghost_testing', 141 | charset : 'utf8' 142 | } 143 | }, 144 | server: { 145 | host: '127.0.0.1', 146 | port: '2369' 147 | }, 148 | logging: false 149 | }, 150 | 151 | // ### Testing pg 152 | // Used by Travis - Automated testing run through GitHub 153 | 'testing-pg': { 154 | url: 'http://127.0.0.1:2369', 155 | database: { 156 | client: 'pg', 157 | connection: { 158 | host : '127.0.0.1', 159 | user : 'postgres', 160 | password : '', 161 | database : 'ghost_testing', 162 | charset : 'utf8' 163 | } 164 | }, 165 | server: { 166 | host: '127.0.0.1', 167 | port: '2369' 168 | }, 169 | logging: false 170 | } 171 | }; 172 | 173 | module.exports = config; 174 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | // # Upyun storage module for Ghost blog 2 | 3 | var path = require('path'); 4 | fs = require('fs-extra'); 5 | Promise = require('bluebird'); 6 | UPYUN = require('upyun'); 7 | moment = require('moment'); 8 | util = require('util'); 9 | BaseStore = require('../../../core/server/storage/base'); 10 | 11 | 12 | function UpyunStore(config) { 13 | BaseStore.call(this); 14 | this.options = config || {}; 15 | } 16 | 17 | util.inherits(UpyunStore, BaseStore); 18 | 19 | // ### Save 20 | // Saves the image to Upyun 21 | // - image is the express image object 22 | // - returns a promise which ultimately returns the full url to the uploaded image 23 | UpyunStore.prototype.save = function(image) { 24 | var _this = this; 25 | 26 | var upyun = new UPYUN (this.options.bucket, this.options.operator, this.options.password, 'v0.api.upyun.com', {apiVersion: 'v2'}); 27 | 28 | return new Promise (function (resolve, reject) { 29 | var remotePath = _this.getRemotePath(image); 30 | var remoteURL = _this.options.domain; 31 | upyun.putFile(remotePath, image.path, null, false, {}, function (err, result) { 32 | if (err || result.statusCode!=200) { 33 | reject('[' + result.data.code + '] ' + result.data.msg); 34 | } else { 35 | if (_this.options.imgVersion != undefined) { 36 | resolve(remoteURL + remotePath + _this.options.imgVersion); 37 | } else { 38 | resolve(remoteURL + remotePath); 39 | } 40 | } 41 | }); 42 | }); 43 | }; 44 | 45 | // middleware for serving the files 46 | UpyunStore.prototype.serve = function () { 47 | // a no-op, these are absolute URLs 48 | return function (req, res, next) { 49 | next(); 50 | }; 51 | }; 52 | 53 | UpyunStore.prototype.exists = function () { 54 | // Server side will automatically replace the file. 55 | return; 56 | }; 57 | 58 | UpyunStore.prototype.delete = function (target) { 59 | //For backup and security purposes there is no way to delete files 60 | //whatever on local or server side through Ghost, please do it manually. 61 | return; 62 | }; 63 | 64 | UpyunStore.prototype.getRemotePath = function (image) { 65 | var prefix = moment().format(this.options.filePath || 'YYYY/MM/').replace(/^\//, ''); 66 | 67 | return '/' + prefix + image.name; 68 | }; 69 | 70 | module.exports = UpyunStore; 71 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ghost-upyun-store", 3 | "version": "1.1.0", 4 | "description": "Upyun store for Ghost blog.", 5 | "main": "index.js", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/sanddudu/ghost-upyun-store.git" 9 | }, 10 | "keywords": [ 11 | "Ghost", 12 | "upyun", 13 | "ghost-store" 14 | ], 15 | "author": "Klion Xu", 16 | "license": "MIT", 17 | "bugs": { 18 | "url": "https://github.com/sanddudu/ghost-upyun-store/issues" 19 | }, 20 | "homepage": "https://github.com/sanddudu/ghost-upyun-store", 21 | "dependencies": { 22 | "upyun": "2.0.4", 23 | "bluebird": "3.4.1", 24 | "moment": "2.14.1", 25 | "fs-extra": "0.30.0" 26 | } 27 | } 28 | --------------------------------------------------------------------------------