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…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user