diff --git a/bin/ldnode.js b/bin/ldnode.js index 1ae3d6337..adabd5ec1 100644 --- a/bin/ldnode.js +++ b/bin/ldnode.js @@ -97,7 +97,12 @@ var argv = require('nomnom') full: 'default-app', help: 'URI to use as a default app for resources (default: https://linkeddata.github.io/warp/#/list/)' }) - .parse() + .option('signup', { + full: 'signup', + flag: true, + help: 'Allow a user to set up its initial identity' + }) + .parse(); function bin (argv) { // Print version and leave diff --git a/lib/create-app.js b/lib/create-app.js index bcc18a45b..ec7a2e452 100644 --- a/lib/create-app.js +++ b/lib/create-app.js @@ -1,67 +1,78 @@ -module.exports = createApp +module.exports = createApp; -var express = require('express') -var session = require('express-session') -var uuid = require('node-uuid') -var cors = require('cors') -var LDP = require('./ldp') -var LdpMiddleware = require('./ldp-middleware') +var express = require('express'); +var session = require('express-session'); +var uuid = require('node-uuid'); +var cors = require('cors'); +var debug = require('./debug'); +var LDP = require('./ldp'); +var LdpMiddleware = require('./ldp-middleware'); var proxy = require('./handlers/proxy') -var IdentityProvider = require('./identity-provider') -var vhost = require('vhost') - +var IdentityProvider = require('./identity-provider'); +var vhost = require('vhost'); +var path = require('path'); var corsSettings = cors({ - methods: [ - 'OPTIONS', 'HEAD', 'GET', - 'PATCH', 'POST', 'PUT', 'DELETE' - ], - exposedHeaders: 'User, Location, Link, Vary, Last-Modified, Content-Length', - credentials: true, - maxAge: 1728000, - origin: true -}) + methods: [ + 'OPTIONS', 'HEAD', 'GET', + 'PATCH', 'POST', 'PUT', 'DELETE' + ], + exposedHeaders: 'User, Location, Link, Vary, Last-Modified, Content-Length', + credentials: true, + maxAge: 1728000, + origin: true +}); function createApp (argv) { - var ldp = new LDP(argv) - var app = express() + var ldp = new LDP(argv); + var app = express(); - // Setting options as local variable - app.locals.ldp = ldp + // Setting options as local variable + app.locals.ldp = ldp; - var sessionSettings = { - secret: ldp.secret || uuid.v1(), - saveUninitialized: false, - resave: false - } + var sessionSettings = { + secret: ldp.secret || uuid.v1(), + saveUninitialized: false, + resave: false + } - // Cookies should set to be secure if https is on - if (ldp.webid || ldp.idp) { - sessionSettings.cookie = { - secure: true + // Cookies should set to be secure if https is on + if (ldp.webid || ldp.idp) { + sessionSettings.cookie = { + secure: true + } } - } - // Session - app.use(session(sessionSettings)) + // Session + app.use(session(sessionSettings)); - // Adding proxy - if (ldp.proxy) { - proxy(app, ldp.proxy) - } + // Adding proxy + if (ldp.proxy) { + proxy(app, ldp.proxy); + } - // Adding Multi-user support - if (ldp.idp) { - var idp = IdentityProvider({ - store: ldp, - suffixAcl: ldp.suffixAcl - }) - app.use('/accounts', idp.middleware(corsSettings)) - app.use('/', corsSettings, idp.get.bind(idp)) - app.use(vhost('*', LdpMiddleware(corsSettings))) - } + // Adding Multi-user support + if (ldp.idp || ldp.signup) { + var idp = IdentityProvider({ + store: ldp, + suffixAcl: ldp.suffixAcl, + overwrite: ldp.signup + }) + app.use('/accounts', idp.middleware(corsSettings)); + app.use('/', corsSettings, idp.get.bind(idp)) + } - // Setting up routes - app.use('/', LdpMiddleware(corsSettings)) + if (ldp.idp) { + app.use(vhost('*', LdpMiddleware(corsSettings))); + } + + if (ldp.signup) { + app.get('/', function (req, res) { + res.set('Content-Type', 'text/html'); + var signup = path.resolve(__dirname + '/../static/signup.html') + res.sendFile(signup); + }); + } + app.use('/', LdpMiddleware(corsSettings)); - return app -} + return app; +} \ No newline at end of file diff --git a/lib/identity-provider.js b/lib/identity-provider.js index 13bdf43b3..43e44590a 100644 --- a/lib/identity-provider.js +++ b/lib/identity-provider.js @@ -35,6 +35,7 @@ function IdentityProvider (options) { this.suffixURI = options.suffixURI || '#me' this.buildURI = options.buildURI || defaultBuildURI this.suffixAcl = options.suffixAcl + this.overwrite = options.overwrite } // Generate the future webid from the options and the IdentityProvider Settings @@ -78,7 +79,7 @@ IdentityProvider.prototype.create = function (options, cert, callback) { var subdomain = options.host.split(':')[0] self.store.exists(subdomain, '/', function (err) { // if page exists - if (!err || err.status !== 404) { + if (!self.overwrite && (!err || err.status !== 404)) { debug('Cannot create ' + subdomain + ', it already exists') var error = new Error('Account already exists') error.status = 406 @@ -94,10 +95,10 @@ IdentityProvider.prototype.create = function (options, cert, callback) { ], function (err) { if (err) { err.status = 500 - debug('Error creating account ' + options.username + ': ' + err.message) + debug('Error creating account ' + options.agent + ': ' + err.message) } - debug('Created files for ' + options.username) + debug('Created files for ' + options.agent) callback(err) }) }) @@ -405,9 +406,9 @@ IdentityProvider.prototype.post = function (req, res, next) { var self = this var options = req.body - if (!options || !options.username) { - debug('Account creation is missing username field') - var err = new Error('You must enter an account name!') + if (!options) { + debug('Options missing') + var err = new Error('Settings to create the account have not been passed!') err.status = 406 return next(err) } diff --git a/lib/ldp.js b/lib/ldp.js index 4cd51c007..1a94fb59f 100644 --- a/lib/ldp.js +++ b/lib/ldp.js @@ -1,465 +1,608 @@ -module.exports = LDP +module.exports = LDP; var mime = require('mime') -var path = require('path') -var S = require('string') -var fs = require('fs') -var $rdf = require('rdflib') -var async = require('async') -var url = require('url') -var mkdirp = require('fs-extra').mkdirp -var uuid = require('node-uuid') -var debug = require('./debug') -var utils = require('./utils') -var ns = require('./vocab/ns').ns -var error = require('./http-error') +var path = require('path'); +var regexp = require('node-regexp'); +var S = require('string'); +var fs = require('fs'); +var $rdf = require('rdflib'); +var async = require('async'); +var fs = require('fs'); +var url = require('url'); +var mkdirp = require('fs-extra').mkdirp; +var uuid = require('node-uuid'); +var debug = require('./debug'); +var utils = require('./utils'); +var ns = require('./vocab/ns').ns; +var HttpError = require('./http-error'); var stringToStream = require('./utils').stringToStream -var extend = require('extend') -var doWhilst = require('async').doWhilst var turtleExtension = '.ttl' -function LDP (argv) { - argv = argv || {} - extend(this, argv) - - // Setting root - if (!this.root) { - this.root = process.cwd() - } - if (!(S(this.root).endsWith('/'))) { - this.root += '/' - } - - // Suffixes - if (!this.suffixAcl) { - this.suffixAcl = '.acl' +function LDP(argv) { + argv = argv || {}; + var ldp = this; + // From input + ldp.forceUser = argv.forceUser; + ldp.cache = argv.cache; + ldp.live = argv.live; + ldp.host = argv.host; // TODO maybe deprecated + ldp.root = argv.root || process.cwd(); + // Add trailing / + if (!(S(ldp.root).endsWith('/'))) { + ldp.root += '/'; } - if (!this.suffixMeta) { - this.suffixMeta = '.meta' - } - this.turtleExtensions = ['.ttl', this.suffixAcl, this.suffixMeta] - + ldp.secret = argv.secret; + ldp.signup = argv.signup; + ldp.webid = argv.webid; + ldp.idp = argv.idp; + ldp.leavePatchConnectionOpen = false; + ldp.suffixAcl = argv.suffixAcl || ".acl"; + ldp.suffixMeta = argv.suffixMeta || ".meta"; + ldp.suffixSSE = argv.suffixSSE || '.events'; + ldp.turtleExtensions = ['.ttl', ldp.suffixAcl, ldp.suffixMeta, ldp.suffixSSE] + ldp.proxy = argv.proxy; + // Cache of usedURIs + ldp.usedURIs = {}; // Error pages folder - this.errorPages = null - if (!this.noErrorPages) { - this.errorPages = argv.errorPages - if (!this.errorPages) { - // TODO: For now disable error pages if errorPages parameter is not explicitly passed - this.noErrorPages = true - } else if (!S(this.errorPages).endsWith('/')) { - this.errorPages += '/' + ldp.noErrorPages = argv.noErrorPages; + if (!ldp.noErrorPages) { + ldp.errorPages = argv.errorPages; + if (!ldp.errorPages) { + // For now disable error pages if errorPages parameter is not explicitly passed + ldp.noErrorPages = true; + } else if (!S(ldp.errorPages).endsWith('/')) { + ldp.errorPages += '/'; } } - - if (this.defaultApp !== false) { - this.defaultApp = argv.defaultApp || 'https://linkeddata.github.io/warp/#/list/' + ldp.errorHandler = argv.errorHandler; + if (argv.defaultApp !== false) { + ldp.defaultApp = argv.defaultApp || 'https://linkeddata.github.io/warp/#/list/'; } - - debug.settings('Suffix Acl: ' + this.suffixAcl) - debug.settings('Suffix Meta: ' + this.suffixMeta) - debug.settings('Filesystem Root: ' + this.root) - debug.settings('WebID support: ' + !!this.webid) - debug.settings('Live-updates: ' + !!this.live) - debug.settings('Identity Provider: ' + !!this.idp) - debug.settings('Default App: ' + this.defaultApp) - - return this + debug.settings("root: " + ldp.root); + debug.settings("URI path filter regexp: " + ldp.pathFilter); + debug.settings("Verbose: " + !!ldp.verbose); + debug.settings("WebID: " + !!ldp.webid); + debug.settings("Live: " + !!ldp.live); + debug.settings("Identity Provider: " + ldp.idp); + debug.settings("Default App: " + ldp.defaultApp); + + return ldp; } -LDP.prototype.stat = function (file, callback) { - fs.stat(file, function (err, stats) { +LDP.prototype.stat = function(file, callback) { + fs.stat(file, function(err, stats) { if (err) { - return callback(error(err)) - } - - return callback(null, stats) - }) -} + return callback(new HttpError({ + status: err.code === 'ENOENT' ? 404 : 500, + message: err.message + })); + } + return callback(null, stats); + }); +}; LDP.prototype.createReadStream = function (filename) { return fs.createReadStream(filename, { 'encoding': 'utf8' }) } LDP.prototype.readFile = function (filename, callback) { - fs.readFile( - filename, - { 'encoding': 'utf8' }, - function (err, data) { - if (err) { - return callback(error(err, 'Can\'t read file: ' + err.message)) - } + fs.readFile(filename, { + 'encoding': 'utf8' + }, function(err, data) { + if (err) { + return callback(new HttpError({ + status: err.code === 'ENOENT' ? 404 : 500, + message: "Can't read file: " + err + })); + } - return callback(null, data) - }) -} + return callback(null, data); + }); +}; LDP.prototype.readContainerMeta = function (directory, callback) { - var ldp = this + var ldp = this; - if (directory[directory.length - 1] !== '/') { - directory += '/' + if (directory[directory.length-1] !== '/') { + directory += '/'; } - ldp.readFile(directory + ldp.suffixMeta, function (err, data) { + ldp.readFile(directory + ldp.suffixMeta, function(err, data) { if (err) { - return callback(error(err, 'Can\'t read meta file')) + return callback(new HttpError({ + status: err.status, + message: 'Can\'t read metafile' + })); } - - return callback(null, data) - }) -} + return callback(null, data); + }); +}; LDP.prototype.listContainer = function (filename, uri, containerData, contentType, callback) { var ldp = this var host = url.parse(uri).hostname - var root = !ldp.idp ? ldp.root : ldp.root + host + '/' + var root = !ldp.idp ? ldp.root : ldp.root + host + '/'; function addStats (resourceGraph, baseUri, stats) { resourceGraph.add( resourceGraph.sym(baseUri), ns.stat('mtime'), - stats.mtime.getTime() / 1000) + stats.mtime.getTime() / 1000); resourceGraph.add( resourceGraph.sym(baseUri), ns.stat('size'), - stats.size) + stats.size); } - function readdir (filename, callback) { - debug.handlers('GET -- Reading directory') + function readdir(filename, callback) { + debug.handlers("GET -- Reading directory"); fs.readdir(filename, function (err, files) { if (err) { - debug.handlers('GET -- Error reading files: ' + err) - return callback(error(err)) + debug.handlers("GET -- Error reading files: " + err); + return callback(new HttpError({ + status: err.code === 'ENOENT' ? 404 : 500, + message: err.message + })); } - debug.handlers('Files in directory: ' + files) - return callback(null, files) - }) + debug.handlers("Files in directory: " + files); + return callback(null, files); + }); } - function getMetadataGraph (metaFile, fileBaseUri, callback) { - ldp.stat(metaFile, function (err, metaStats) { + function getMetadataGraph(metaFile, fileBaseUri, callback) { + ldp.stat(metaFile, function(err, metaStats) { if (err) { - return callback(err) + return callback(err); } if (metaStats && metaStats.isFile()) { - ldp.readFile(metaFile, function (err, rawMetadata) { + ldp.readFile(metaFile, function(err, rawMetadata) { if (err) { - return callback(err) + return callback(err); } - var metadataGraph = $rdf.graph() + var metadataGraph = $rdf.graph(); try { $rdf.parse( rawMetadata, metadataGraph, fileBaseUri, - 'text/turtle') + 'text/turtle'); } catch (dirErr) { - return callback(error(err, dirErr.message)) + return callback(new HttpError({ + status: err.code === 'ENOENT' ? 404 : 500, + message: dirErr.message + })); } - return callback(null, metadataGraph) - }) + return callback(null, metadataGraph); + }); } else { - return callback(null, $rdf.graph()) + return callback(null, $rdf.graph()); } - }) + }); } - function addFile (ldp, resourceGraph, baseUri, uri, container, file, callback) { - // Skip .meta and .acl - if (S(file).endsWith(ldp.suffixMeta) || S(file).endsWith(ldp.suffixAcl)) { - return callback(null) - } - - // Get file stats - ldp.stat(container + file, function (err, stats) { - if (err) { - // File does not exist, skip - return callback(null) + function addFile(ldp, resourceGraph, baseUri, uri, container, file, callback) { + // Skip .meta and .acl + if (S(file).endsWith(ldp.suffixMeta) || S(file).endsWith(ldp.suffixAcl)) { + return callback(null); } - var fileSubject = file + (stats.isDirectory() ? '/' : '') - // var fileBaseUri = utils.filenameToBaseUri(fileSubject, uri, root) + // Get file stats + ldp.stat(container + file, function (err, stats) { + if (err) { + // File does not exist, skip + return callback(null); + } + + var fileSubject = file + (stats.isDirectory() ? '/' : ''); + var fileBaseUri = utils.filenameToBaseUri(fileSubject, uri, root); - // Add fileStats to resource Graph - addStats(resourceGraph, fileSubject, stats) + // Add fileStats to resource Graph + addStats(resourceGraph, fileSubject, stats); - // Add to `contains` list - resourceGraph.add( - resourceGraph.sym(''), - ns.ldp('contains'), - resourceGraph.sym(fileSubject)) + // Add to `contains` list + resourceGraph.add( + resourceGraph.sym(''), + ns.ldp('contains'), + resourceGraph.sym(fileSubject)); - // Set up a metaFile path - var metaFile = container + file + - (stats.isDirectory() ? '/' : '') + - (S(file).endsWith(turtleExtension) ? '' : ldp.suffixMeta) + // Set up a metaFile path + var metaFile = container + file + + (stats.isDirectory() ? '/' : '') + + (S(file).endsWith(turtleExtension) ? '' : ldp.suffixMeta); - getMetadataGraph(metaFile, baseUri, function (err, metadataGraph) { - if (err) { - metadataGraph = $rdf.graph() - } + getMetadataGraph(metaFile, baseUri, function(err, metadataGraph) { + if (err) { + metadataGraph = $rdf.graph(); + } - // Add Container or BasicContainer types - if (stats.isDirectory()) { - resourceGraph.add( - metadataGraph.sym(fileSubject), - ns.rdf('type'), - ns.ldp('BasicContainer')) + // Add Container or BasicContainer types + if (stats.isDirectory()) { + resourceGraph.add( + metadataGraph.sym(fileSubject), + ns.rdf('type'), + ns.ldp('BasicContainer')); + resourceGraph.add( + metadataGraph.sym(fileSubject), + ns.rdf('type'), + ns.ldp('Container')); + } + // Add generic LDP type resourceGraph.add( metadataGraph.sym(fileSubject), ns.rdf('type'), - ns.ldp('Container')) - } - // Add generic LDP type - resourceGraph.add( - metadataGraph.sym(fileSubject), - ns.rdf('type'), - ns.ldp('Resource')) - - // Add type from metadataGraph - metadataGraph - .statementsMatching( - metadataGraph.sym(baseUri), - ns.rdf('type'), - undefined) - .forEach(function (typeStatement) { - // If the current is a file and its type is BasicContainer, - // This is not possible, so do not infer its type! - if ( - ( - typeStatement.object.uri !== ns.ldp('BasicContainer').uri && - typeStatement.object.uri !== ns.ldp('Container').uri - ) || - !stats.isFile() - ) { - resourceGraph.add( - resourceGraph.sym(fileSubject), - typeStatement.predicate, - typeStatement.object) - } - }) + ns.ldp('Resource')); - return callback(null) - }) - }) + // Add type from metadataGraph + var typeStatements = metadataGraph + .statementsMatching( + metadataGraph.sym(baseUri), + ns.rdf('type'), + undefined) + .forEach(function (typeStatement) { + // If the current is a file and its type is BasicContainer, + // This is not possible, so do not infer its type! + if ( + ( + typeStatement.object.uri !== ns.ldp('BasicContainer').uri && + typeStatement.object.uri !== ns.ldp('Container').uri + ) || + !stats.isFile() + ) { + resourceGraph.add( + resourceGraph.sym(fileSubject), + typeStatement.predicate, + typeStatement.object); + } + }); + + + return callback(null); + }); + }); } - var baseUri = utils.filenameToBaseUri(filename, uri, root) - var resourceGraph = $rdf.graph() + var baseUri = utils.filenameToBaseUri(filename, uri, root); + var resourceGraph = $rdf.graph(); try { - $rdf.parse(containerData, resourceGraph, baseUri, 'text/turtle') + $rdf.parse(containerData, resourceGraph, baseUri, 'text/turtle'); } catch (err) { - debug.handlers('GET -- Error parsing data: ' + err) - return callback(error(500, err.message)) + debug.handlers("GET -- Error parsing data: " + err); + return callback(new HttpError({ + status: 500, + message: err.message + })); } async.waterfall([ // add container stats function (next) { - ldp.stat(filename, function (err, containerStats) { + ldp.stat(filename, function(err, containerStats) { if (!err) { - addStats(resourceGraph, '', containerStats) + addStats(resourceGraph, '', containerStats); resourceGraph.add( resourceGraph.sym(''), ns.rdf('type'), - ns.ldp('BasicContainer')) + ns.ldp('BasicContainer')); resourceGraph.add( resourceGraph.sym(''), ns.rdf('type'), - ns.ldp('Container')) + ns.ldp('Container')); } - next() - }) + next(); + }); }, // reading directory function (next) { - readdir(filename, next) + readdir(filename, next); }, // Iterate through all the files function (files, next) { async.each( files, - function (file, cb) { - addFile(ldp, resourceGraph, baseUri, uri, filename, file, cb) + function(file, cb) { + addFile(ldp, resourceGraph, baseUri, uri, filename, file, cb); }, - next) + next); } ], function (err, data) { - if (err) { - return callback(error(500, err.message)) - } - var turtleData + var turtleData; try { turtleData = $rdf.serialize( undefined, resourceGraph, null, - contentType) + contentType); } catch (parseErr) { - debug.handlers('GET -- Error serializing container: ' + parseErr) - return callback(error(500, parseErr.message)) + debug.handlers("GET -- Error serializing container: " + parseErr); + return callback(new HttpError({ + status: 500, + message: parseErr.message + })); } - return callback(null, turtleData) - }) -} + return callback(null, turtleData); + }); +}; LDP.prototype.put = function (host, resourcePath, contents, callback) { - var ldp = this - var root = !ldp.idp ? ldp.root : ldp.root + host + '/' - var filePath = utils.uriToFilename(resourcePath, root, host) + var ldp = this; + var root = !ldp.idp ? ldp.root : ldp.root + host + '/'; + var filePath = utils.uriToFilename(resourcePath, root, host); // PUT requests not supported on containers. Use POST instead if (filePath[filePath.length - 1] === '/') { - return callback(error(409, 'PUT not supported on containers, use POST instead')) + return callback(new HttpError({ + status: 409, + message: "PUT to containers not supported. Use POST method instead" + })); } mkdirp(path.dirname(filePath), function (err) { - if (err) { - debug.handlers('PUT -- Error creating directory: ' + err) - return callback(error(err)) - } - return fs.writeFile(filePath, contents, function (err) { if (err) { - debug.handlers('PUT -- Error writing file: ' + err) - return callback(error(err)) + debug.handlers("PUT -- Error creating directory: " + err); + return callback(new HttpError({ + status: err.code === 'ENOENT' ? 404 : 500, + message: err.message + })); } - // Success! - return callback(null) - }) - }) -} + return fs.writeFile(filePath, contents, function (err) { + if (err) { + debug.handlers("PUT -- Error writing file: " + err); + return callback(new HttpError({ + status: err.code === 'ENOENT' ? 404 : 500, + message: err.message + })); + } + // Success! + return callback(null); + }); + }); +}; LDP.prototype.exists = function (host, reqPath, callback) { this.get(host, reqPath, undefined, false, undefined, callback) } LDP.prototype.get = function (host, reqPath, baseUri, includeBody, contentType, callback) { - var ldp = this - var root = !ldp.idp ? ldp.root : ldp.root + host + '/' - var filename = utils.uriToFilename(reqPath, root) + var ldp = this; + var root = !ldp.idp ? ldp.root : ldp.root + host + '/'; + var filename = utils.uriToFilename(reqPath, root); + ldp.stat(filename, function (err, stats) { + // File does not exist + if (err) { + return callback(new HttpError({ + status: err.status, + message: "Can't read file: " + err.message + })); + } - ldp.stat(filename, function (err, stats) { - // File does not exist - if (err) { - return callback(error(err, 'Can\t find resource requested')) - } + // Just return, since resource exists + if (!includeBody) { + return callback(null); + } + + // Found a container + if (stats.isDirectory()) { + return ldp.readContainerMeta(filename, function (err, metaFile) { + if (err) { + metaFile = ''; + } + + ldp.listContainer(filename, baseUri, metaFile, contentType, function (err, data) { + if (err) { + debug.handlers('GET container -- Read error:' + err.message); + return callback(err) + } + var stream = stringToStream(data) + return callback(null, stream, contentType, true); + }); + }); + } else { + var stream = ldp.createReadStream(filename) + stream + .on('error', function (err) { + debug.handlers('GET -- Read error:' + err.message); + return new HttpError({ + status: err.code === 'ENOENT' ? 404 : 500, + message: "Can't read file: " + err + }) + }) + .on('open', function () { + debug.handlers('GET -- Read Start.'); + var contentType = mime.lookup(filename); + if (utils.hasSuffix(filename, ldp.turtleExtensions)) { + contentType = 'text/turtle'; + } + callback(null, stream, contentType, false) + }) + } + }); +}; + +LDP.prototype.delete = function(host, resourcePath, callback) { + var ldp = this; + var root = !ldp.idp ? ldp.root : ldp.root + host + '/'; + var filename = utils.uriToFilename(resourcePath, root); + ldp.stat(filename, function(err, stats) { + if (err) { + return callback(new HttpError({ + status: 404, + message: "Can't find file: " + err + })); + } - // Just return, since resource exists - if (!includeBody) { - return callback(null, stats) + if (stats.isDirectory()) { + return ldp.deleteContainerMetadata(filename, callback); + } else { + return ldp.deleteResource(filename, callback); + } + }); +}; + +LDP.prototype.deleteContainerMetadata = function(directory, callback) { + if (directory[directory.length-1] !== '/') { + directory += '/'; } - // Found a container - if (stats.isDirectory()) { - return ldp.readContainerMeta(filename, function (err, metaFile) { + return fs.unlink(directory + this.suffixMeta, function(err, data) { if (err) { - metaFile = '' + debug.container("DELETE -- unlink() error: " + err); + return callback(new HttpError({ + status: err.code === 'ENOENT' ? 404 : 500, + message: "Can't delete container: " + err + })); } + return callback(null, data); + }); +}; - ldp.listContainer(filename, baseUri, metaFile, contentType, function (err, data) { - if (err) { - debug.handlers('GET container -- Read error:' + err.message) - return callback(err) - } - var stream = stringToStream(data) - return callback(null, stream, contentType, true) - }) - }) +LDP.prototype.deleteResource = function(filename, callback) { + return fs.unlink(filename, function(err, data) { + if (err) { + debug.container("DELETE -- unlink() error: " + err); + return callback(new HttpError({ + status: err.code === 'ENOENT' ? 404 : 500, + message: "Can't delete container: " + err + })); + } + return callback(null, data); + }); +}; + + +var addUriTriple = function(kb, s, o, p) { + kb.add(kb.sym(s), kb.sym(o), kb.sym(p)); +}; + +LDP.prototype.createResourceUri = function(containerURI, slug, isBasicContainer) { + var ldp = this; + + var newPath; + if (slug) { + if (S(slug).endsWith(turtleExtension)) { + newPath = path.join(containerURI, slug); + } else { + if (isBasicContainer) { + newPath = path.join(containerURI, slug); + } else { + newPath = path.join(containerURI, slug + turtleExtension); + } + } } else { - var stream = ldp.createReadStream(filename) - stream - .on('error', function (err) { - debug.handlers('GET -- Read error:' + err.message) - return callback(error(err, 'Can\'t create file ' + err)) - }) - .on('open', function () { - debug.handlers('GET -- Read Start.') - var contentType = mime.lookup(filename) - if (utils.hasSuffix(filename, ldp.turtleExtensions)) { - contentType = 'text/turtle' - } - callback(null, stream, contentType, false) - }) - } - }) -} - -LDP.prototype.delete = function (host, resourcePath, callback) { - var ldp = this - var root = !ldp.idp ? ldp.root : ldp.root + host + '/' - var filename = utils.uriToFilename(resourcePath, root) - ldp.stat(filename, function (err, stats) { - if (err) { - return callback(error(404, 'Can\'t find ' + err)) + if (isBasicContainer) { + newPath = path.join(containerURI, uuid.v1()); + } else { + newPath = path.join(containerURI, uuid.v1() + turtleExtension); + } } - - if (stats.isDirectory()) { - return ldp.deleteContainerMetadata(filename, callback) + if (!(fs.existsSync(newPath) || containerURI in ldp.usedURIs)) { + ldp.usedURIs[newPath] = true; } else { - return ldp.deleteResource(filename, callback) + return null; } - }) -} + return newPath; +}; + +LDP.prototype.releaseResourceUri = function (uri) { + delete this.usedURIs[uri]; +}; + +LDP.prototype.createNewResource = function(host, uri, resourcePath, resourceGraph, callback) { + var ldp = this; + var root = !ldp.idp ? ldp.root : ldp.root + host + '/'; + var resourceURI = path.relative(root, resourcePath); + var rawResource = $rdf.serialize( + undefined, + resourceGraph, + uri + resourceURI, + 'text/turtle'); -LDP.prototype.deleteContainerMetadata = function (directory, callback) { - if (directory[directory.length - 1] !== '/') { - directory += '/' - } + debug.container("Writing new resource to " + resourcePath); + debug.container("Resource:\n" + rawResource); - return fs.unlink(directory + this.suffixMeta, function (err, data) { - if (err) { - debug.container('DELETE -- unlink() error: ' + err) - return callback(error(err, 'Failed to delete container')) - } - return callback(null, data) - }) -} + fs.writeFile( + resourcePath, + rawResource, + writeResourceCallback); -LDP.prototype.deleteResource = function (filename, callback) { - return fs.unlink(filename, function (err, data) { - if (err) { - debug.container('DELETE -- unlink() error: ' + err) - return callback(error(err, 'Failed to delete resource')) + function writeResourceCallback(err) { + if (err) { + debug.container("Error writing resource: " + err); + ldp.releaseResourceUri(resourcePath); + return callback(new HttpError({ + message: "Cannot create new resource", + status: err.code === 'ENOENT' ? 404 : 500 + })); + } + + return callback(null); } - return callback(null, data) - }) -} +}; -LDP.prototype.getAvailablePath = function (host, containerURI, slug, callback) { - var self = this +LDP.prototype.createNewContainer = function (uri, containerPath, containerGraph, callback) { + var ldp = this; + fs.mkdir(containerPath, mkdirCallback); - if (!slug) { - slug = uuid.v1() - } - var exists - var newPath = path.join(containerURI, slug) + function mkdirCallback(err) { + if (err) { + debug.container("Error creating directory for new container: " + err); + ldp.releaseResourceUri(containerPath); + return callback(new HttpError({ + message: "Cannot create new container", + status: err.code === 'ENOENT' ? 404 : 500 + })); + } - // TODO: maybe a nicer code - doWhilst( - function (next) { - self.exists(host, newPath, function (err) { - exists = !err + var rawContainer = $rdf.serialize( + undefined, + containerGraph, + uri, + 'text/turtle'); - if (exists) { - var id = uuid.v1().split('-')[0] + '-' - newPath = path.join(containerURI, id + slug) + debug.container("rawContainer " + rawContainer); + + ldp.writeContainerMetadata( + containerPath, + rawContainer, + writeContainerCallback); + } + + function writeContainerCallback(err) { + if (err) { + debug.container("Error writing container: " + err); + ldp.releaseResourceUri(containerPath); + return callback(new HttpError({ + message: "Cannot create new container", + status: err.code === 'ENOENT' ? 404 : 500 + })); } - next() - }) - }, - function () { - return exists === true - }, - function () { - callback(newPath) - }) -} + debug.container("Wrote container to " + containerPath); + ldp.releaseResourceUri(containerPath); + return callback(null); + + } +}; + +LDP.prototype.writeContainerMetadata = function (directory, container, callback) { + if (directory[directory.length-1] !== '/') { + directory += '/'; + } + return fs.writeFile(directory + this.suffixMeta, container, callback); +}; + +LDP.prototype.isMetadataFile = function (filename) { + if (path.extname(filename) === this.suffixMeta) + return true; + return false; +}; + +LDP.prototype.hasContainerMetadata = function(directory) { + return fs.existsSync(directory + this.suffixMeta); +}; \ No newline at end of file diff --git a/static/signup.html b/static/signup.html new file mode 100644 index 000000000..5bfe36594 --- /dev/null +++ b/static/signup.html @@ -0,0 +1,112 @@ + + + + Ldnode: Admin Signup + + + + + + + +
+

Create your local account.

+ +
+ +
+ +
+ + + + + + \ No newline at end of file diff --git a/test/identity-provider.js b/test/identity-provider.js index c7cd3f8dd..7644a2dff 100644 --- a/test/identity-provider.js +++ b/test/identity-provider.js @@ -94,11 +94,6 @@ describe('Identity Provider', function () { rm('accounts/nicola.localhost') }) - it('should return 406 if username is not given', function (done) { - var subdomain = supertest('https://nicola.' + host) - subdomain.post('/accounts/new') - .expect(406, done) - }) it('should return create WebID if only username is given', function (done) { var subdomain = supertest('https://nicola.' + host) subdomain.post('/accounts/new')