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).
|
||||
|
||||
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
|
||||
======
|
||||
|
||||
|
@ -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.
|
||||
|
||||
/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`
|
||||
|
@ -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`
|
||||
|
@ -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
|
||||
, safeResolve = require('./deardesi-utils').safeResolve
|
||||
, sha1sum = function (str) { return require('secret-utils').hashsum('sha1', str); }
|
||||
, mkdirp = PromiseA.promisify(require('mkdirp'))
|
||||
;
|
||||
|
||||
function strip(prefix, pathname) {
|
||||
|
@ -155,6 +156,89 @@ function getfs(blogdir, filepaths) {
|
|||
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) {
|
||||
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.getfs = getfs;
|
||||
module.exports.putfs = putfs;
|
||||
module.exports.walkDir = walkDir;
|
||||
module.exports.walkDirs = walkDirs;
|
||||
|
|
|
@ -36,10 +36,11 @@
|
|||
"connect-query": "^0.2.0",
|
||||
"connect-send-json": "^1.0.0",
|
||||
"escape-string-regexp": "^1.0.2",
|
||||
"foreachasync": "^5.0.2",
|
||||
"foreachasync": "^5.0.5",
|
||||
"json2yaml": "^1.0.3",
|
||||
"markdown": "^0.5.0",
|
||||
"marked": "^0.3.2",
|
||||
"mkdirp": "^0.5.0",
|
||||
"mustache": "^1.0.0",
|
||||
"require-yaml": "0.0.1",
|
||||
"require-yamljs": "^1.0.1",
|
||||
|
|
73
server.js
73
server.js
|
@ -12,6 +12,7 @@ var connect = require('connect')
|
|||
, app = connect()
|
||||
, walk = require('./lib/fsapi').walk
|
||||
, getfs = require('./lib/fsapi').getfs
|
||||
, putfs = require('./lib/fsapi').putfs
|
||||
|
||||
, config = require('./config.yml')
|
||||
, path = require('path')
|
||||
|
@ -23,40 +24,14 @@ app
|
|||
.use(send.json())
|
||||
.use(query())
|
||||
.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) {
|
||||
var opts = {}
|
||||
;
|
||||
|
||||
if (!(/^GET$/i.test(req.method) || /^GET$/i.test(req.query._method))) {
|
||||
next();
|
||||
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) {
|
||||
|
@ -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) {
|
||||
next();
|
||||
|
@ -94,6 +110,7 @@ app
|
|||
})
|
||||
.use('/api/fs/static', serveStatic('.'))
|
||||
|
||||
.use(serveStatic(blogdir))
|
||||
.use(serveStatic('.'))
|
||||
;
|
||||
|
||||
|
|
Loading…
Reference in New Issue