can create and delete files in blogdir, add todos
This commit is contained in:
parent
9c961ec834
commit
011ff5718a
110
README.md
110
README.md
|
@ -18,6 +18,59 @@ Node (optional) - if you'd prefer to go headless, you can.
|
||||||
|
|
||||||
The server is *very* minimal and could easily be implemented in any language (such as ruby or python).
|
The server is *very* minimal and could easily be implemented in any language (such as ruby or python).
|
||||||
|
|
||||||
|
Widgets
|
||||||
|
=======
|
||||||
|
|
||||||
|
All widgets should export an object with a `create(widgetConf, desiState)` function that returns a promise.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
widgets:
|
||||||
|
foogizmo:
|
||||||
|
# only stuff that is intensely specific to foogizmo goes here
|
||||||
|
# stuff like google ad and disqus ids should go in config.yml or data.yml
|
||||||
|
config:
|
||||||
|
foobeep: boop
|
||||||
|
|
||||||
|
handle:
|
||||||
|
- html
|
||||||
|
- markdown
|
||||||
|
handlers:
|
||||||
|
post: fooposter
|
||||||
|
page: foopager
|
||||||
|
```
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
module.exports.Foogizmo.create = function (foogizmoConf, desiState) {
|
||||||
|
return new Promise(function (resolve) {
|
||||||
|
|
||||||
|
function pager(desiPageState) {
|
||||||
|
// Do processing
|
||||||
|
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
function poster(desiPostState) {
|
||||||
|
// Do processing
|
||||||
|
|
||||||
|
desiPostState.fooembedinator = function (fooval) {
|
||||||
|
// figure out what type of link fooval is and return iframe html
|
||||||
|
return '<iframe src="http://embedinator.com/"' + foovalProcessed + '></iframe>'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve({ foopager: pager, fooposter: poster });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Overlays
|
||||||
|
--------
|
||||||
|
|
||||||
|
For any config a widget uses, it should also check on post.fooconfig and theme.fooconfig to make sure that they don't override the foogizmo.config.fooconfig
|
||||||
|
|
||||||
|
|
||||||
Server
|
Server
|
||||||
======
|
======
|
||||||
|
|
||||||
|
@ -25,7 +78,7 @@ Obviously there has to be a server with some sort of storage and retrieval mecha
|
||||||
|
|
||||||
I've implemented a very simple node server using the filesystem.
|
I've implemented a very simple node server using the filesystem.
|
||||||
|
|
||||||
/api/fs/walk
|
GET /api/fs/walk
|
||||||
------------
|
------------
|
||||||
|
|
||||||
`GET http://local.dear.desi:8080/api/fs/walk?dir=posts&dotfiles=true&extensions=md,markdown,jade,htm,html`
|
`GET http://local.dear.desi:8080/api/fs/walk?dir=posts&dotfiles=true&extensions=md,markdown,jade,htm,html`
|
||||||
|
@ -76,7 +129,7 @@ POST http://local.dear.desi:8080/api/fs/walk?dotfiles=true&extensions=md,markdow
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
/api/fs/files
|
GET /api/fs/files
|
||||||
-------------
|
-------------
|
||||||
|
|
||||||
`GET http://local.dear.desi:8080/api/fs/files?path=posts/happy-new-year.md`
|
`GET http://local.dear.desi:8080/api/fs/files?path=posts/happy-new-year.md`
|
||||||
|
@ -113,3 +166,56 @@ POST http://local.dear.desi:8080/api/fs/files?dotfiles=true&extensions=md,markdo
|
||||||
, ...
|
, ...
|
||||||
]
|
]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
POST /api/fs/files
|
||||||
|
------------------
|
||||||
|
|
||||||
|
By default this should assume that you intended to write to the compiled directory
|
||||||
|
and return an error if you try to write to any other directory, unless `compiled=false` (not yet implemented).
|
||||||
|
|
||||||
|
`_method=PUT` is just for funzies.
|
||||||
|
|
||||||
|
Including `sha1` is optional, but recommended.
|
||||||
|
|
||||||
|
`lastModifiedDate` is optional and may or may not make any difference.
|
||||||
|
|
||||||
|
`strict` (not yet implemented) fail immediately and completely on any error
|
||||||
|
|
||||||
|
```json
|
||||||
|
POST http://local.dear.desi:8080/api/fs/files?compiled=true&_method=PUT
|
||||||
|
|
||||||
|
{
|
||||||
|
"files": [
|
||||||
|
{ "path": "posts/foo.md"
|
||||||
|
, "name": "foo.md"
|
||||||
|
, "relativePath": "posts"
|
||||||
|
, "lastModifiedDate": "2013-08-01T22:47:37.000Z"
|
||||||
|
, "contents": "..."
|
||||||
|
, "sha1": "6eae3a5b062c6d0d79f070c26e6d62486b40cb46"
|
||||||
|
, "delete": false
|
||||||
|
}
|
||||||
|
, ...
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The response may include errors of all shapes and sizes.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{ "error": { message: "any top-level error", ... }
|
||||||
|
, "errors": [
|
||||||
|
{ "type": "file|directory"
|
||||||
|
, "message": "maybe couldn't create the directory, but maybe still wrote the file. Maybe not"
|
||||||
|
}
|
||||||
|
, ...
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**TODO** Allow rename and delete?
|
||||||
|
|
||||||
|
TODO
|
||||||
|
----
|
||||||
|
|
||||||
|
option for client write to a hidden `.desi-revisions` (as well as indexeddb)
|
||||||
|
to safeguard against accidental blow-ups for people who aren't using git.
|
||||||
|
|
85
lib/fsapi.js
85
lib/fsapi.js
|
@ -8,6 +8,7 @@ var PromiseA = require('bluebird').Promise
|
||||||
, escapeRegExp = require('./deardesi-utils').escapeRegExp
|
, escapeRegExp = require('./deardesi-utils').escapeRegExp
|
||||||
, safeResolve = require('./deardesi-utils').safeResolve
|
, safeResolve = require('./deardesi-utils').safeResolve
|
||||||
, sha1sum = function (str) { return require('secret-utils').hashsum('sha1', str); }
|
, sha1sum = function (str) { return require('secret-utils').hashsum('sha1', str); }
|
||||||
|
, mkdirp = PromiseA.promisify(require('mkdirp'))
|
||||||
;
|
;
|
||||||
|
|
||||||
function strip(prefix, pathname) {
|
function strip(prefix, pathname) {
|
||||||
|
@ -155,6 +156,89 @@ function getfs(blogdir, filepaths) {
|
||||||
return files;
|
return files;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function putfs(blogdir, files) {
|
||||||
|
var results = { errors: [] }
|
||||||
|
, dirpaths = {}
|
||||||
|
;
|
||||||
|
|
||||||
|
return forEachAsync(files, function (file) {
|
||||||
|
var filepath = safeResolve(blogdir, file.path || path.join(file.relativePath, file.name))
|
||||||
|
, pathname = path.dirname(filepath)
|
||||||
|
, filename = file.name || path.basename(filepath)
|
||||||
|
;
|
||||||
|
|
||||||
|
file.realPath = filepath;
|
||||||
|
file.name = filename;
|
||||||
|
|
||||||
|
dirpaths[pathname] = true;
|
||||||
|
|
||||||
|
return Promise.resolve();
|
||||||
|
}).then(function () {
|
||||||
|
// TODO is it better to do this lazy-like or as a batch?
|
||||||
|
// I figure as batch when there may be hundreds of files,
|
||||||
|
// likely within 2 or 3 directories
|
||||||
|
return forEachAsync(Object.keys(dirpaths), function (pathname) {
|
||||||
|
return mkdirp(pathname).catch(function (e) {
|
||||||
|
// TODO exclude attempting to write files to this dir?
|
||||||
|
results.errors.push({
|
||||||
|
type: 'directory'
|
||||||
|
|
||||||
|
, directory: pathname
|
||||||
|
|
||||||
|
, message: e.message
|
||||||
|
, code: e.code
|
||||||
|
, errno: e.errno
|
||||||
|
, status: e.status
|
||||||
|
, syscall: e.syscall
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}).then(function () {
|
||||||
|
// TODO sort deletes last
|
||||||
|
return forEachAsync(files, function (file) {
|
||||||
|
var p
|
||||||
|
;
|
||||||
|
|
||||||
|
// TODO use lastModifiedDate as per client request?
|
||||||
|
// TODO compare sha1 sums for integrity
|
||||||
|
if (file.delete || !file.contents) {
|
||||||
|
p = fs.unlinkAsync(file.realPath);
|
||||||
|
} else {
|
||||||
|
p = fs.writeFileAsync(file.realPath, file.contents, 'utf8');
|
||||||
|
}
|
||||||
|
|
||||||
|
return p.catch(function (e) {
|
||||||
|
results.errors.push({
|
||||||
|
type: 'file'
|
||||||
|
|
||||||
|
, file: file.realPath
|
||||||
|
, delete: !file.contents
|
||||||
|
, path: file.path
|
||||||
|
, relativePath: file.relativePath
|
||||||
|
, name: file.name
|
||||||
|
|
||||||
|
, message: e.message
|
||||||
|
, code: e.code
|
||||||
|
, errno: e.errno
|
||||||
|
, status: e.status
|
||||||
|
, syscall: e.syscall
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}).catch(function (e) {
|
||||||
|
results.error = {
|
||||||
|
message: e.message
|
||||||
|
, code: e.code
|
||||||
|
, errno: e.errno
|
||||||
|
, status: e.status
|
||||||
|
, syscall: e.syscall
|
||||||
|
};
|
||||||
|
}).then(function () {
|
||||||
|
return results;
|
||||||
|
});
|
||||||
|
}
|
||||||
/*
|
/*
|
||||||
walkDirs('blog', ['posts'], { contents: false }).then(function (stats) {
|
walkDirs('blog', ['posts'], { contents: false }).then(function (stats) {
|
||||||
console.log(JSON.stringify(stats, null, ' '));
|
console.log(JSON.stringify(stats, null, ' '));
|
||||||
|
@ -163,5 +247,6 @@ walkDirs('blog', ['posts'], { contents: false }).then(function (stats) {
|
||||||
|
|
||||||
module.exports.walk = { walkDirs: walkDirs, walkDir: walkDir };
|
module.exports.walk = { walkDirs: walkDirs, walkDir: walkDir };
|
||||||
module.exports.getfs = getfs;
|
module.exports.getfs = getfs;
|
||||||
|
module.exports.putfs = putfs;
|
||||||
module.exports.walkDir = walkDir;
|
module.exports.walkDir = walkDir;
|
||||||
module.exports.walkDirs = walkDirs;
|
module.exports.walkDirs = walkDirs;
|
||||||
|
|
|
@ -36,10 +36,11 @@
|
||||||
"connect-query": "^0.2.0",
|
"connect-query": "^0.2.0",
|
||||||
"connect-send-json": "^1.0.0",
|
"connect-send-json": "^1.0.0",
|
||||||
"escape-string-regexp": "^1.0.2",
|
"escape-string-regexp": "^1.0.2",
|
||||||
"foreachasync": "^5.0.2",
|
"foreachasync": "^5.0.5",
|
||||||
"json2yaml": "^1.0.3",
|
"json2yaml": "^1.0.3",
|
||||||
"markdown": "^0.5.0",
|
"markdown": "^0.5.0",
|
||||||
"marked": "^0.3.2",
|
"marked": "^0.3.2",
|
||||||
|
"mkdirp": "^0.5.0",
|
||||||
"mustache": "^1.0.0",
|
"mustache": "^1.0.0",
|
||||||
"require-yaml": "0.0.1",
|
"require-yaml": "0.0.1",
|
||||||
"require-yamljs": "^1.0.1",
|
"require-yamljs": "^1.0.1",
|
||||||
|
|
73
server.js
73
server.js
|
@ -12,6 +12,7 @@ var connect = require('connect')
|
||||||
, app = connect()
|
, app = connect()
|
||||||
, walk = require('./lib/fsapi').walk
|
, walk = require('./lib/fsapi').walk
|
||||||
, getfs = require('./lib/fsapi').getfs
|
, getfs = require('./lib/fsapi').getfs
|
||||||
|
, putfs = require('./lib/fsapi').putfs
|
||||||
|
|
||||||
, config = require('./config.yml')
|
, config = require('./config.yml')
|
||||||
, path = require('path')
|
, path = require('path')
|
||||||
|
@ -23,40 +24,14 @@ app
|
||||||
.use(send.json())
|
.use(send.json())
|
||||||
.use(query())
|
.use(query())
|
||||||
.use(bodyParser.json())
|
.use(bodyParser.json())
|
||||||
|
|
||||||
.use('/api/fs/files', function (req, res, next) {
|
|
||||||
if (!(/^GET$/i.test(req.method) || /^GET$/i.test(req.query._method))) {
|
|
||||||
next();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var filepaths = req.query.path && [req.query.path] || (req.query.paths && req.query.paths.split(/,/g)) || req.body.paths
|
|
||||||
;
|
|
||||||
|
|
||||||
if (!filepaths || !filepaths.length) {
|
|
||||||
res.json({ error: "please specify GET w/ req.query.path or POST _method=GET&paths=path/to/thing,..." });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
return getfs(blogdir, filepaths).then(function (files) {
|
|
||||||
if (!req.body.paths && !req.query.paths) {
|
|
||||||
res.json(files[0]);
|
|
||||||
} else {
|
|
||||||
res.send(files);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
})
|
|
||||||
|
|
||||||
.use('/api/fs/walk', function (req, res, next) {
|
.use('/api/fs/walk', function (req, res, next) {
|
||||||
var opts = {}
|
|
||||||
;
|
|
||||||
|
|
||||||
if (!(/^GET$/i.test(req.method) || /^GET$/i.test(req.query._method))) {
|
if (!(/^GET$/i.test(req.method) || /^GET$/i.test(req.query._method))) {
|
||||||
next();
|
next();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var dirnames = req.query.dir && [req.query.dir] || (req.query.dirs && req.query.dirs.split(/,/g)) || req.body.dirs
|
var opts = {}
|
||||||
|
, dirnames = req.query.dir && [req.query.dir] || (req.query.dirs && req.query.dirs.split(/,/g)) || req.body.dirs
|
||||||
;
|
;
|
||||||
|
|
||||||
if (!dirnames || !dirnames.length) {
|
if (!dirnames || !dirnames.length) {
|
||||||
|
@ -87,6 +62,47 @@ app
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
|
.use('/api/fs/files', function (req, res, next) {
|
||||||
|
if (!(/^GET$/i.test(req.method) || /^GET$/i.test(req.query._method))) {
|
||||||
|
next();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var filepaths = req.query.path && [req.query.path] || (req.query.paths && req.query.paths.split(/,/g)) || req.body.paths
|
||||||
|
;
|
||||||
|
|
||||||
|
if (!filepaths || !filepaths.length) {
|
||||||
|
res.json({ error: "please specify GET w/ req.query.path or POST _method=GET&paths=path/to/thing,..." });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return getfs(blogdir, filepaths).then(function (files) {
|
||||||
|
if (!req.body.paths && !req.query.paths) {
|
||||||
|
res.json(files[0]);
|
||||||
|
} else {
|
||||||
|
res.send(files);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.use('/api/fs/files', function (req, res, next) {
|
||||||
|
if (!(/^POST|PUT$/i.test(req.method) || /^POST|PUT$/i.test(req.query._method))) {
|
||||||
|
next();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var opts = {}
|
||||||
|
, files = req.body.files
|
||||||
|
;
|
||||||
|
|
||||||
|
if (!files || !files.length) {
|
||||||
|
res.json({ error: "please specify POST w/ req.body.files" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return putfs(blogdir, files, opts).then(function (results) {
|
||||||
|
res.json(results);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
|
||||||
.use('/api/fs', function (req, res, next) {
|
.use('/api/fs', function (req, res, next) {
|
||||||
next();
|
next();
|
||||||
|
@ -94,6 +110,7 @@ app
|
||||||
})
|
})
|
||||||
.use('/api/fs/static', serveStatic('.'))
|
.use('/api/fs/static', serveStatic('.'))
|
||||||
|
|
||||||
|
.use(serveStatic(blogdir))
|
||||||
.use(serveStatic('.'))
|
.use(serveStatic('.'))
|
||||||
;
|
;
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue