diff --git a/backend/docs/api_data.js b/backend/docs/api_data.js index e251608..82c43d1 100644 --- a/backend/docs/api_data.js +++ b/backend/docs/api_data.js @@ -3,7 +3,6 @@ define({ "api": [ "type": "post", "url": "/api/login", "title": "Authenticate the user", - "name": "Login", "group": "Authenticate", "parameter": { "fields": { @@ -33,13 +32,13 @@ define({ "api": [ "type": "String", "optional": false, "field": "token", - "description": "

User token, valid for 20 minutes .

" + "description": "

User token, valid for 20 minutes.

" } ] }, "examples": [ { - "title": "Sucess", + "title": "Success", "content": "HTTP/1.1 200 OK\n{\n \"token\": \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImRhbmllbHJjIiwiaWF0IjoxNTI3MTY1NTg2LCJleHAiOjE1MjcxNjY3ODZ9.VEvOcyPa-LKSr0kJXTa6TvpCqyKmenJRbEgdxNKJjik\" \n}", "type": "json" } @@ -77,6 +76,101 @@ define({ "api": [ }, "version": "0.0.0", "filename": "routes/login.js", - "groupTitle": "Authenticate" + "groupTitle": "Authenticate", + "name": "PostApiLogin" + }, + { + "type": "get", + "url": "/api/directories", + "title": "List items", + "group": "Directories", + "description": "

List items from specified path. The token is needed.

", + "header": { + "fields": { + "Header": [ + { + "group": "Header", + "type": "String", + "optional": false, + "field": "Authorization", + "description": "

Authorization token.

" + } + ] + } + }, + "parameter": { + "fields": { + "Parameter": [ + { + "group": "Parameter", + "type": "String", + "optional": false, + "field": "path", + "description": "

The directory path.

" + } + ] + } + }, + "examples": [ + { + "title": "Example usage", + "content": "curl -H \"Authorization: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImRhbmllbCIsImlhdCI6MTUyNzI5Mjk2NSwiZXhwIjoxNTI3Mjk0MTY1fQ.M8HAJAjq5E8k-e4LzxMXccG7z5ay4Yrs05ZmhXhMv6g\" http://127.0.0.1:3000/api/directories?path=Documents/example", + "type": "curl" + } + ], + "success": { + "examples": [ + { + "title": "Success", + "content": "HTTP/1.1 200 OK\n{\n \"items\": [\n {\n \"name\": \"dir1\",\n \"path\": \"/dir1\",\n \"isFile\": false\n },\n {\n \"name\": \"file1.txt\",\n \"path\": \"/file1.txt\",\n \"isFile\": true\n }\n ]\n}", + "type": "json" + } + ] + }, + "error": { + "fields": { + "Error 4xx": [ + { + "group": "Error 4xx", + "optional": false, + "field": "ENOENT", + "description": "

No such file or directory.

" + }, + { + "group": "Error 4xx", + "optional": false, + "field": "NoTokenProvided", + "description": "

No token provided.

" + }, + { + "group": "Error 4xx", + "optional": false, + "field": "InvalidToken", + "description": "

Invalid token.

" + } + ] + }, + "examples": [ + { + "title": "ENOENT", + "content": "HTTP/1.1 404 NotFound\n{\n \"error\": \"ENOENT: no such file or directory, scandir ''\"\n}", + "type": "json" + }, + { + "title": "NoTokenProvided", + "content": "HTTP/1.1 401 Unauthorized\n{\n \"error\": \"Access denied. No token provided.\"\n}", + "type": "json" + }, + { + "title": "InvalidToken", + "content": "HTTP/1.1 400 BadRequest\n{\n \"error\": \"Invalid token\"\n}", + "type": "json" + } + ] + }, + "version": "0.0.0", + "filename": "routes/directories.js", + "groupTitle": "Directories", + "name": "GetApiDirectories" } ] }); diff --git a/backend/docs/api_data.json b/backend/docs/api_data.json index 70874a0..c3edada 100644 --- a/backend/docs/api_data.json +++ b/backend/docs/api_data.json @@ -3,7 +3,6 @@ "type": "post", "url": "/api/login", "title": "Authenticate the user", - "name": "Login", "group": "Authenticate", "parameter": { "fields": { @@ -33,13 +32,13 @@ "type": "String", "optional": false, "field": "token", - "description": "

User token, valid for 20 minutes .

" + "description": "

User token, valid for 20 minutes.

" } ] }, "examples": [ { - "title": "Sucess", + "title": "Success", "content": "HTTP/1.1 200 OK\n{\n \"token\": \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImRhbmllbHJjIiwiaWF0IjoxNTI3MTY1NTg2LCJleHAiOjE1MjcxNjY3ODZ9.VEvOcyPa-LKSr0kJXTa6TvpCqyKmenJRbEgdxNKJjik\" \n}", "type": "json" } @@ -77,6 +76,101 @@ }, "version": "0.0.0", "filename": "routes/login.js", - "groupTitle": "Authenticate" + "groupTitle": "Authenticate", + "name": "PostApiLogin" + }, + { + "type": "get", + "url": "/api/directories", + "title": "List items", + "group": "Directories", + "description": "

List items from specified path. The token is needed.

", + "header": { + "fields": { + "Header": [ + { + "group": "Header", + "type": "String", + "optional": false, + "field": "Authorization", + "description": "

Authorization token.

" + } + ] + } + }, + "parameter": { + "fields": { + "Parameter": [ + { + "group": "Parameter", + "type": "String", + "optional": false, + "field": "path", + "description": "

The directory path.

" + } + ] + } + }, + "examples": [ + { + "title": "Example usage", + "content": "curl -H \"Authorization: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImRhbmllbCIsImlhdCI6MTUyNzI5Mjk2NSwiZXhwIjoxNTI3Mjk0MTY1fQ.M8HAJAjq5E8k-e4LzxMXccG7z5ay4Yrs05ZmhXhMv6g\" http://127.0.0.1:3000/api/directories?path=Documents/example", + "type": "curl" + } + ], + "success": { + "examples": [ + { + "title": "Success", + "content": "HTTP/1.1 200 OK\n{\n \"items\": [\n {\n \"name\": \"dir1\",\n \"path\": \"/dir1\",\n \"isFile\": false\n },\n {\n \"name\": \"file1.txt\",\n \"path\": \"/file1.txt\",\n \"isFile\": true\n }\n ]\n}", + "type": "json" + } + ] + }, + "error": { + "fields": { + "Error 4xx": [ + { + "group": "Error 4xx", + "optional": false, + "field": "ENOENT", + "description": "

No such file or directory.

" + }, + { + "group": "Error 4xx", + "optional": false, + "field": "NoTokenProvided", + "description": "

No token provided.

" + }, + { + "group": "Error 4xx", + "optional": false, + "field": "InvalidToken", + "description": "

Invalid token.

" + } + ] + }, + "examples": [ + { + "title": "ENOENT", + "content": "HTTP/1.1 404 NotFound\n{\n \"error\": \"ENOENT: no such file or directory, scandir ''\"\n}", + "type": "json" + }, + { + "title": "NoTokenProvided", + "content": "HTTP/1.1 401 Unauthorized\n{\n \"error\": \"Access denied. No token provided.\"\n}", + "type": "json" + }, + { + "title": "InvalidToken", + "content": "HTTP/1.1 400 BadRequest\n{\n \"error\": \"Invalid token\"\n}", + "type": "json" + } + ] + }, + "version": "0.0.0", + "filename": "routes/directories.js", + "groupTitle": "Directories", + "name": "GetApiDirectories" } ] diff --git a/backend/docs/api_project.js b/backend/docs/api_project.js index c1cf591..89c9f1d 100644 --- a/backend/docs/api_project.js +++ b/backend/docs/api_project.js @@ -8,7 +8,7 @@ define({ "apidoc": "0.3.0", "generator": { "name": "apidoc", - "time": "2018-05-24T16:32:08.705Z", + "time": "2018-05-26T00:22:17.461Z", "url": "http://apidocjs.com", "version": "0.17.6" } diff --git a/backend/docs/api_project.json b/backend/docs/api_project.json index c11b6c1..688ccf4 100644 --- a/backend/docs/api_project.json +++ b/backend/docs/api_project.json @@ -8,7 +8,7 @@ "apidoc": "0.3.0", "generator": { "name": "apidoc", - "time": "2018-05-24T16:32:08.705Z", + "time": "2018-05-26T00:22:17.461Z", "url": "http://apidocjs.com", "version": "0.17.6" } diff --git a/backend/docs/vendor/path-to-regexp/index.js b/backend/docs/vendor/path-to-regexp/index.js new file mode 100644 index 0000000..5cb8ed8 --- /dev/null +++ b/backend/docs/vendor/path-to-regexp/index.js @@ -0,0 +1,204 @@ +var isArray = Array.isArray || function (arr) { + return Object.prototype.toString.call(arr) == '[object Array]'; +}; + +/** + * Expose `pathToRegexp`. + */ +// module.exports = pathToRegexp + +/** + * The main path matching regexp utility. + * + * @type {RegExp} + */ +var PATH_REGEXP = new RegExp([ + // Match escaped characters that would otherwise appear in future matches. + // This allows the user to escape special characters that won't transform. + '(\\\\.)', + // Match Express-style parameters and un-named parameters with a prefix + // and optional suffixes. Matches appear as: + // + // "/:test(\\d+)?" => ["/", "test", "\d+", undefined, "?"] + // "/route(\\d+)" => [undefined, undefined, undefined, "\d+", undefined] + '([\\/.])?(?:\\:(\\w+)(?:\\(((?:\\\\.|[^)])*)\\))?|\\(((?:\\\\.|[^)])*)\\))([+*?])?', + // Match regexp special characters that are always escaped. + '([.+*?=^!:${}()[\\]|\\/])' +].join('|'), 'g'); + +/** + * Escape the capturing group by escaping special characters and meaning. + * + * @param {String} group + * @return {String} + */ +function escapeGroup (group) { + return group.replace(/([=!:$\/()])/g, '\\$1'); +} + +/** + * Attach the keys as a property of the regexp. + * + * @param {RegExp} re + * @param {Array} keys + * @return {RegExp} + */ +function attachKeys (re, keys) { + re.keys = keys; + return re; +} + +/** + * Get the flags for a regexp from the options. + * + * @param {Object} options + * @return {String} + */ +function flags (options) { + return options.sensitive ? '' : 'i'; +} + +/** + * Pull out keys from a regexp. + * + * @param {RegExp} path + * @param {Array} keys + * @return {RegExp} + */ +function regexpToRegexp (path, keys) { + // Use a negative lookahead to match only capturing groups. + var groups = path.source.match(/\((?!\?)/g); + + if (groups) { + for (var i = 0; i < groups.length; i++) { + keys.push({ + name: i, + delimiter: null, + optional: false, + repeat: false + }); + } + } + + return attachKeys(path, keys); +} + +/** + * Transform an array into a regexp. + * + * @param {Array} path + * @param {Array} keys + * @param {Object} options + * @return {RegExp} + */ +function arrayToRegexp (path, keys, options) { + var parts = []; + + for (var i = 0; i < path.length; i++) { + parts.push(pathToRegexp(path[i], keys, options).source); + } + + var regexp = new RegExp('(?:' + parts.join('|') + ')', flags(options)); + return attachKeys(regexp, keys); +} + +/** + * Replace the specific tags with regexp strings. + * + * @param {String} path + * @param {Array} keys + * @return {String} + */ +function replacePath (path, keys) { + var index = 0; + + function replace (_, escaped, prefix, key, capture, group, suffix, escape) { + if (escaped) { + return escaped; + } + + if (escape) { + return '\\' + escape; + } + + var repeat = suffix === '+' || suffix === '*'; + var optional = suffix === '?' || suffix === '*'; + + keys.push({ + name: key || index++, + delimiter: prefix || '/', + optional: optional, + repeat: repeat + }); + + prefix = prefix ? ('\\' + prefix) : ''; + capture = escapeGroup(capture || group || '[^' + (prefix || '\\/') + ']+?'); + + if (repeat) { + capture = capture + '(?:' + prefix + capture + ')*'; + } + + if (optional) { + return '(?:' + prefix + '(' + capture + '))?'; + } + + // Basic parameter support. + return prefix + '(' + capture + ')'; + } + + return path.replace(PATH_REGEXP, replace); +} + +/** + * Normalize the given path string, returning a regular expression. + * + * An empty array can be passed in for the keys, which will hold the + * placeholder key descriptions. For example, using `/user/:id`, `keys` will + * contain `[{ name: 'id', delimiter: '/', optional: false, repeat: false }]`. + * + * @param {(String|RegExp|Array)} path + * @param {Array} [keys] + * @param {Object} [options] + * @return {RegExp} + */ +function pathToRegexp (path, keys, options) { + keys = keys || []; + + if (!isArray(keys)) { + options = keys; + keys = []; + } else if (!options) { + options = {}; + } + + if (path instanceof RegExp) { + return regexpToRegexp(path, keys, options); + } + + if (isArray(path)) { + return arrayToRegexp(path, keys, options); + } + + var strict = options.strict; + var end = options.end !== false; + var route = replacePath(path, keys); + var endsWithSlash = path.charAt(path.length - 1) === '/'; + + // In non-strict mode we allow a slash at the end of match. If the path to + // match already ends with a slash, we remove it for consistency. The slash + // is valid at the end of a path match, not in the middle. This is important + // in non-ending mode, where "/test/" shouldn't match "/test//route". + if (!strict) { + route = (endsWithSlash ? route.slice(0, -2) : route) + '(?:\\/(?=$))?'; + } + + if (end) { + route += '$'; + } else { + // In non-ending mode, we need the capturing groups to match as much as + // possible by using a positive lookahead to the end or next path segment. + route += strict && endsWithSlash ? '' : '(?=\\/|$)'; + } + + return attachKeys(new RegExp('^' + route, flags(options)), keys); +} diff --git a/backend/routes/directories.js b/backend/routes/directories.js index 3fc4e62..0c597a4 100644 --- a/backend/routes/directories.js +++ b/backend/routes/directories.js @@ -2,5 +2,58 @@ module.exports = function (app) { var auth = app.middlewares.auth var directoriesREST = app.controllers.directories + /** + * @api {get} /api/directories List items + * @apiGroup Directories + * + * @apiDescription + * List items from specified path. + * The token is needed. + * + * @apiHeader {String} Authorization Authorization token. + * + * @apiParam {String} path The directory path. + * + * @apiExample {curl} Example usage + * curl -H "Authorization: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImRhbmllbCIsImlhdCI6MTUyNzI5Mjk2NSwiZXhwIjoxNTI3Mjk0MTY1fQ.M8HAJAjq5E8k-e4LzxMXccG7z5ay4Yrs05ZmhXhMv6g" http://127.0.0.1:3000/api/directories?path=Documents/example + * + * @apiSuccessExample {json} Success + * HTTP/1.1 200 OK + * { + * "items": [ + * { + * "name": "dir1", + * "path": "/dir1", + * "isFile": false + * }, + * { + * "name": "file1.txt", + * "path": "/file1.txt", + * "isFile": true + * } + * ] + * } + * + * @apiError ENOENT No such file or directory. + * @apiError NoTokenProvided No token provided. + * @apiError InvalidToken Invalid token. + * + * @apiErrorExample {json} ENOENT + * HTTP/1.1 404 NotFound + * { + * "error": "ENOENT: no such file or directory, scandir ''" + * } + * @apiErrorExample {json} NoTokenProvided + * HTTP/1.1 401 Unauthorized + * { + * "error": "Access denied. No token provided." + * } + * @apiErrorExample {json} InvalidToken + * HTTP/1.1 400 BadRequest + * { + * "error": "Invalid token" + * } + * + */ app.get('/api/directories', auth, directoriesREST.get) } diff --git a/backend/routes/login.js b/backend/routes/login.js index 85c0b50..412689a 100644 --- a/backend/routes/login.js +++ b/backend/routes/login.js @@ -3,7 +3,6 @@ module.exports = (app) => { /** * @api {post} /api/login Authenticate the user - * @apiName Login * @apiGroup Authenticate * * @apiParam {String} username Username of LCCs. diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..48e341a --- /dev/null +++ b/package-lock.json @@ -0,0 +1,3 @@ +{ + "lockfileVersion": 1 +}