can create and delete files in blogdir, add todos

This commit is contained in:
AJ ONeal 2015-01-07 07:58:09 +00:00
parent 9c961ec834
commit 011ff5718a
4 changed files with 240 additions and 31 deletions

110
README.md
View File

@ -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.

View File

@ -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;

View File

@ -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",

View File

@ -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('.'))
; ;