rest-easy-loki
Advanced tools
Comparing version 0.6.0 to 0.7.0
@@ -0,3 +1,8 @@ | ||
/// <reference types="node" /> | ||
import * as http from 'http'; | ||
import Koa from 'koa'; | ||
import { ICommandOptions } from './models/command-options'; | ||
export declare const createApi: (config: Partial<ICommandOptions>) => Koa<Koa.DefaultState, Koa.DefaultContext>; | ||
export declare const createApi: (config: ICommandOptions) => { | ||
api: Koa<Koa.DefaultState, Koa.DefaultContext>; | ||
server?: http.Server | undefined; | ||
}; |
"use strict"; | ||
var __importStar = (this && this.__importStar) || function (mod) { | ||
if (mod && mod.__esModule) return mod; | ||
var result = {}; | ||
if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; | ||
result["default"] = mod; | ||
return result; | ||
}; | ||
var __importDefault = (this && this.__importDefault) || function (mod) { | ||
@@ -8,8 +15,12 @@ return (mod && mod.__esModule) ? mod : { "default": mod }; | ||
const cors = require('@koa/cors'); | ||
const fs = __importStar(require("fs")); | ||
const koa_1 = __importDefault(require("koa")); | ||
const koa_body_1 = __importDefault(require("koa-body")); | ||
const koa_static_1 = __importDefault(require("koa-static")); | ||
const path = __importStar(require("path")); | ||
const authorization_1 = require("./authorization"); | ||
const logging_1 = require("./logging"); | ||
const routes_1 = require("./routes"); | ||
const socket_service_1 = require("./socket-service"); | ||
const upload_service_1 = require("./upload-service"); | ||
const state = { | ||
@@ -29,14 +40,42 @@ pretty: false, | ||
const api = new koa_1.default(); | ||
// custom 404 | ||
// api.use(async (ctx, next) => { | ||
// await next(); | ||
// if (ctx.body || !ctx.idempotent) { | ||
// return; | ||
// } | ||
// ctx.redirect('/404.html'); | ||
// }); | ||
if (config.cors) { | ||
console.log('Enabling CORS.'); | ||
// api.use(cors({ credentials: true })); | ||
api.use(cors()); | ||
} | ||
api.use(koa_body_1.default({ formLimit: config.sizeLimit, jsonLimit: config.sizeLimit })); | ||
const ss = config.io ? socket_service_1.createSocketService(api) : undefined; | ||
api.use(koa_body_1.default({ | ||
formLimit: config.sizeLimit, | ||
jsonLimit: config.sizeLimit, | ||
multipart: true, | ||
})); | ||
api.use(logging_1.logger); | ||
api.use(koa_static_1.default('./public')); | ||
// Serve public folder | ||
if (config.public) { | ||
api.use(koa_static_1.default(path.resolve(process.cwd(), config.public))); | ||
} | ||
api.use(authorization_1.pep); | ||
api.use(routes_1.router.routes()); | ||
api.use(routes_1.router.allowedMethods()); | ||
return api; | ||
// Allow uploading files to 'config.upload' folder. Files can be uploaded to /upload/:CONTEXT. | ||
if (config.upload) { | ||
const uploadPath = path.resolve(process.cwd(), config.upload); | ||
if (!fs.existsSync(uploadPath)) { | ||
fs.mkdirSync(uploadPath); | ||
} | ||
api.use(koa_static_1.default(uploadPath)); | ||
api.use(upload_service_1.uploadService(uploadPath)); | ||
console.log(`Uploading files enabled: POST to /upload/:CONTEXT and the files will be saved in ${uploadPath}.`); | ||
} | ||
const router = routes_1.createRouter(ss ? ss.io : undefined); | ||
api.use(router.routes()); | ||
api.use(router.allowedMethods()); | ||
return { api, server: ss ? ss.server : undefined }; | ||
}; | ||
//# sourceMappingURL=api.js.map |
@@ -7,4 +7,4 @@ "use strict"; | ||
const command_line_args_1 = __importDefault(require("command-line-args")); | ||
const config_1 = require("./config"); | ||
const serve_1 = require("./serve"); | ||
const config_1 = require("./config"); | ||
// tslint:disable-next-line: no-var-requires | ||
@@ -22,3 +22,3 @@ const npm = require('../package.json'); | ||
typeLabel: 'Boolean', | ||
description: 'Show the help manual', | ||
description: 'Show the help manual.', | ||
}, | ||
@@ -31,5 +31,13 @@ { | ||
defaultValue: config_1.config.cors, | ||
description: 'Enable CORS (default true)', | ||
description: `Enable CORS ($LOKI_CORS ${config_1.config.cors}).`, | ||
}, | ||
{ | ||
name: 'io', | ||
alias: 'i', | ||
type: Boolean, | ||
typeLabel: 'Boolean', | ||
defaultValue: config_1.config.io, | ||
description: `Enable socket.io ($LOKI_IO ${config_1.config.io}).`, | ||
}, | ||
{ | ||
name: 'pretty', | ||
@@ -40,3 +48,3 @@ alias: 'v', | ||
typeLabel: 'Boolean', | ||
description: 'Enable pretty output, default taken from environment settings, $LOKI_PRETTY, default true.', | ||
description: `Enable pretty output ($LOKI_PRETTY ${config_1.config.pretty}).`, | ||
}, | ||
@@ -49,3 +57,3 @@ { | ||
typeLabel: 'Boolean', | ||
description: 'Port to use, default taken from environment settings, $LOKI_PORT, otherwise 3000', | ||
description: `Port to use ($LOKI_PORT ${config_1.config.port}).`, | ||
}, | ||
@@ -58,5 +66,20 @@ { | ||
typeLabel: 'String', | ||
description: 'Message size limit for body parser, $LOKI_SIZE_LIMIT, default 25mb', | ||
description: `Message size limit for body parser ($LOKI_SIZE_LIMIT ${config_1.config.sizeLimit}).`, | ||
}, | ||
{ | ||
name: 'upload', | ||
alias: 'u', | ||
type: String, | ||
typeLabel: 'String', | ||
description: 'Optional relative path to the `upload` folder to upload files to `/upload/:CONTEXT` URL.', | ||
}, | ||
{ | ||
name: 'public', | ||
alias: 'b', | ||
defaultValue: 'public', | ||
type: String, | ||
typeLabel: 'String', | ||
description: 'Relative path to a `public` folder to share your files, default \'public\'.', | ||
}, | ||
{ | ||
name: 'db', | ||
@@ -67,3 +90,3 @@ alias: 'd', | ||
typeLabel: 'String', | ||
description: 'Name of the database taken from environment settings, $LOKI_DB, otherwise `rest_easy_loki.db`.', | ||
description: `Name of the database taken from environment settings ($LOKI_DB ${config_1.config.db}).`, | ||
}, | ||
@@ -91,2 +114,10 @@ ]; | ||
}, | ||
{ | ||
desc: '03. Start the service on port 3456, allowing uploading files to the upload folder.', | ||
example: `$ ${cmdName} -p 3456 -u ./upload`, | ||
}, | ||
{ | ||
desc: '04. Start the service and allow clients to subscribe to updates via socket.io.', | ||
example: `$ ${cmdName} -i`, | ||
}, | ||
], | ||
@@ -93,0 +124,0 @@ }, |
@@ -12,3 +12,4 @@ "use strict"; | ||
port: process.env.LOKI_PORT || 3000, | ||
cors: process.env.LOKI_CORS || true, | ||
cors: process.env.LOKI_CORS ? process.env.LOKI_CORS : true, | ||
io: process.env.LOKI_IO ? process.env.LOKI_IO : true, | ||
db: process.env.LOKI_DB || 'rest_easy_loki.db', | ||
@@ -15,0 +16,0 @@ sizeLimit: process.env.LOKI_SIZE_LIMIT || '25mb', |
export interface ICommandOptions { | ||
/** Show the manual */ | ||
help: boolean; | ||
help?: boolean; | ||
/** Show verbose output */ | ||
pretty: boolean; | ||
pretty?: boolean; | ||
/** Port to use */ | ||
port: number; | ||
port?: number; | ||
/** Enable CORS */ | ||
cors: boolean; | ||
cors?: boolean; | ||
/** Enable Socket.io */ | ||
io?: boolean; | ||
/** Static public folder name, default 'public' */ | ||
public?: string; | ||
/** Allow file uploads to upload folder, default not activated */ | ||
upload?: string; | ||
/** Database name */ | ||
db: string; | ||
db?: string; | ||
/** Message size limit for URL-encoded or JSON messages. Used in bodyparser, e.g. 1mb. Default 25mb */ | ||
sizeLimit: string; | ||
sizeLimit?: string; | ||
} |
import Router from 'koa-router'; | ||
export declare const router: Router<any, {}>; | ||
import IO from 'socket.io'; | ||
export declare const createRouter: (io?: IO.Server | undefined) => Router<any, {}>; |
@@ -11,88 +11,101 @@ "use strict"; | ||
const utils_1 = require("./utils"); | ||
exports.router = new koa_router_1.default(); | ||
exports.router.get('/api/env', async (ctx) => { | ||
ctx.body = environment_1.environment(); | ||
}); | ||
exports.router.get('/api/collections', async (ctx) => { | ||
ctx.status = 201; | ||
ctx.body = database_1.collections(); | ||
}); | ||
/** | ||
* Request the whole collection but only returns a subset of all properties | ||
* - Specify `props` containing a comma separted array of top-level properties. | ||
* - Optionally, specify `from` and `to` as query params for pagination, e.g. ?from=0&to=5 | ||
*/ | ||
exports.router.get('/api/:collection/view', async (ctx) => { | ||
const { collection } = ctx.params; | ||
const map = utils_1.propertyMap(ctx.query); | ||
const filter = utils_1.paginationFilter(ctx.query); | ||
const results = database_1.all(collection, ctx.query.q); | ||
ctx.body = | ||
map && results | ||
? filter | ||
? results.filter(filter).map(map) | ||
: results.map(map) | ||
: results; | ||
}); | ||
/** Get by ID */ | ||
exports.router.get('/api/:collection/:id', async (ctx) => { | ||
const { collection, id } = ctx.params; | ||
ctx.body = database_1.get(collection, +id); | ||
}); | ||
/** | ||
* Request the whole collection | ||
* - Optionally, specify from and to as query params for pagination, e.g. ?from=0&to=5 | ||
*/ | ||
exports.router.get('/api/:collection', async (ctx) => { | ||
const { collection } = ctx.params; | ||
const pages = utils_1.paginationFilter(ctx.query); | ||
const results = database_1.all(collection, ctx.query.q); | ||
ctx.body = pages && results ? results.filter(pages) : results; | ||
}); | ||
exports.router.post('/api/:collection', async (ctx) => { | ||
const { collection } = ctx.params; | ||
const item = ctx.request.body; | ||
ctx.body = database_1.post(collection, item); | ||
}); | ||
exports.router.put('/api/:collection/:id', async (ctx) => { | ||
const { collection, id } = ctx.params; | ||
const item = ctx.request.body; | ||
ctx.body = database_1.update(collection, +id, item); | ||
}); | ||
exports.router.patch('/api/:collection/:id', async (ctx) => { | ||
const { collection, id } = ctx.params; | ||
if (id) { | ||
const item = database_1.get(collection, +id); | ||
const mutation = ctx.request.body; | ||
if (item && mutation && mutation.patch) { | ||
const { saveChanges, patch } = mutation; | ||
const errors = rfc6902_1.applyPatch(item, patch); | ||
const hasErrors = errors.some(e => e !== null); | ||
if (hasErrors) { | ||
errors.forEach(e => e && console.error(e)); | ||
ctx.status = 409; | ||
ctx.body = errors; | ||
} | ||
else { | ||
if (saveChanges) { | ||
delete mutation.saveChanges; | ||
database_1.post(saveChanges, mutation); | ||
exports.createRouter = (io) => { | ||
const router = new koa_router_1.default(); | ||
router.get('/api/env', async (ctx) => { | ||
ctx.body = environment_1.environment(); | ||
}); | ||
router.get('/api/collections', async (ctx) => { | ||
ctx.status = 201; | ||
ctx.body = database_1.collections(); | ||
}); | ||
/** | ||
* Request the whole collection but only returns a subset of all properties | ||
* - Specify `props` containing a comma separted array of top-level properties. | ||
* - Optionally, specify `from` and `to` as query params for pagination, e.g. ?from=0&to=5 | ||
*/ | ||
router.get('/api/:collection/view', async (ctx) => { | ||
const { collection } = ctx.params; | ||
const map = utils_1.propertyMap(ctx.query); | ||
const filter = utils_1.paginationFilter(ctx.query); | ||
const results = database_1.all(collection, ctx.query.q); | ||
ctx.body = map && results ? (filter ? results.filter(filter).map(map) : results.map(map)) : results; | ||
}); | ||
/** Get by ID */ | ||
router.get('/api/:collection/:id', async (ctx) => { | ||
const { collection, id } = ctx.params; | ||
ctx.body = database_1.get(collection, +id); | ||
}); | ||
/** | ||
* Request the whole collection | ||
* - Optionally, specify from and to as query params for pagination, e.g. ?from=0&to=5 | ||
*/ | ||
router.get('/api/:collection', async (ctx) => { | ||
const { collection } = ctx.params; | ||
const pages = utils_1.paginationFilter(ctx.query); | ||
const results = database_1.all(collection, ctx.query.q); | ||
ctx.body = pages && results ? results.filter(pages) : results; | ||
}); | ||
router.post('/api/:collection', async (ctx) => { | ||
const { collection } = ctx.params; | ||
const item = ctx.request.body; | ||
ctx.body = database_1.post(collection, item); | ||
if (io) { | ||
io.emit(collection, ctx.body); | ||
} | ||
}); | ||
router.put('/api/:collection/:id', async (ctx) => { | ||
const { collection, id } = ctx.params; | ||
const item = ctx.request.body; | ||
ctx.body = database_1.update(collection, +id, item); | ||
if (io) { | ||
io.emit(`${collection}/${id}`, ctx.body); | ||
} | ||
}); | ||
router.patch('/api/:collection/:id', async (ctx) => { | ||
const { collection, id } = ctx.params; | ||
if (id) { | ||
const item = database_1.get(collection, +id); | ||
const mutation = ctx.request.body; | ||
if (item && mutation && mutation.patch) { | ||
const { saveChanges, patch } = mutation; | ||
const errors = rfc6902_1.applyPatch(item, patch); | ||
const hasErrors = errors.some(e => e !== null); | ||
if (hasErrors) { | ||
errors.forEach(e => e && console.error(e)); | ||
ctx.status = 409; | ||
ctx.body = errors; | ||
} | ||
ctx.body = database_1.update(collection, +id, item); | ||
else { | ||
if (saveChanges) { | ||
delete mutation.saveChanges; | ||
database_1.post(saveChanges, mutation); | ||
} | ||
ctx.body = database_1.update(collection, +id, item); | ||
if (io) { | ||
io.emit(`${collection}/${id}`, ctx.body); | ||
} | ||
} | ||
} | ||
} | ||
} | ||
}); | ||
exports.router.put('/api/:collection', async (ctx) => { | ||
const { collection } = ctx.params; | ||
const item = ctx.request.body; | ||
ctx.body = database_1.updateItem(collection, item); | ||
}); | ||
exports.router.delete('/api/:collection/:id', async (ctx) => { | ||
const { collection, id } = ctx.params; | ||
ctx.body = database_1.del(collection, +id); | ||
}); | ||
// export const routes: compose.Middleware< | ||
// Application.ParameterizedContext<any, Router.IRouterParamContext<any, {}>> | ||
// > = router.routes(); | ||
}); | ||
router.put('/api/:collection', async (ctx) => { | ||
const { collection } = ctx.params; | ||
const item = ctx.request.body; | ||
ctx.body = database_1.updateItem(collection, item); | ||
if (io && item.id) { | ||
io.emit(`${collection}/${item.id}`, ctx.body); | ||
} | ||
}); | ||
router.delete('/api/:collection/:id', async (ctx) => { | ||
const { collection, id } = ctx.params; | ||
ctx.body = database_1.del(collection, +id); | ||
if (io) { | ||
io.emit(`${collection}/${id}`); | ||
} | ||
}); | ||
// export const routes: compose.Middleware< | ||
// Application.ParameterizedContext<any, Router.IRouterParamContext<any, {}>> | ||
// > = router.routes(); | ||
return router; | ||
}; | ||
//# sourceMappingURL=routes.js.map |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
// import http from 'http'; | ||
// import IO from 'socket.io'; | ||
const config_1 = require("./config"); | ||
@@ -7,4 +9,4 @@ const index_1 = require("./index"); | ||
index_1.db.startDatabase(configuration.db, () => { | ||
const api = index_1.createApi(configuration); | ||
api.listen(configuration.port); | ||
const { api, server } = index_1.createApi(configuration); | ||
(server || api).listen(configuration.port); | ||
console.log(`Server running on port ${configuration.port}.`); | ||
@@ -11,0 +13,0 @@ }); |
{ | ||
"name": "rest-easy-loki", | ||
"version": "0.6.0", | ||
"version": "0.7.0", | ||
"description": "A REST interface for lokijs supporting CRUD operations and automatic creation of new collections.", | ||
@@ -15,3 +15,3 @@ "main": "./dist/index.js", | ||
"watch": "tsc -w", | ||
"start": "tsc-watch --onSuccess \"node ./dist/cli.js\" --onFailure \"echo Beep! Compilation Failed\" --compiler typescript/bin/tsc", | ||
"start": "tsc-watch --onSuccess \"node ./dist/cli.js --upload upload\" --onFailure \"echo Beep! Compilation Failed\" --compiler typescript/bin/tsc", | ||
"dry-run": "npm publish --dry-run", | ||
@@ -47,16 +47,18 @@ "patch-release": "npm run clean && npm run build && npm version patch --force -m \"Patch release\" && npm publish && git push --follow-tags", | ||
"lokijs": "^1.5.8", | ||
"rfc6902": "^3.0.4" | ||
"rfc6902": "^3.0.4", | ||
"socket.io": "^2.3.0" | ||
}, | ||
"devDependencies": { | ||
"@types/command-line-args": "^5.0.0", | ||
"@types/koa": "^2.0.52", | ||
"@types/koa": "^2.11.0", | ||
"@types/koa-compose": "^3.2.5", | ||
"@types/koa-router": "^7.0.42", | ||
"@types/koa-router": "^7.4.0", | ||
"@types/koa-static": "^4.0.1", | ||
"@types/lokijs": "^1.5.2", | ||
"@types/node": "^12.12.11", | ||
"@types/lokijs": "^1.5.3", | ||
"@types/node": "^13.1.8", | ||
"@types/socket.io": "^2.1.4", | ||
"rimraf": "^3.0.0", | ||
"tsc-watch": "^4.0.0", | ||
"typescript": "^3.7.2" | ||
"tsc-watch": "^4.1.0", | ||
"typescript": "^3.7.5" | ||
} | ||
} |
@@ -83,7 +83,15 @@ # REST-EASY-LOKI | ||
- You can use the `public` folder for sharing static files or your own web application. | ||
You can use the `public` folder for sharing static files or your own web application. | ||
### Uploading files | ||
You can use the `upload` folder for uploading files to a (automatically created) CONTEXT folder, if enabled on start-up using the `-u` instruction. Test it via `curl -F "file=@filename.jpg" http://localhost:3030/upload/:CONTEXT`. Files will be served `http://localhost:3030/`. | ||
### Socket.io support | ||
If enabled using the `io` flag (or -i) so clients can subscribe to receive updates when a value has changed. Clients can either subscribe to a collection `socket.subscribe('COLLECTION_NAME')`, or to a collection item `socket.subscribe('COLLECTION_NAME/$LOKI')`. The latter is, for example, useful when you have multiple editors. Subscribers receive the updated item. | ||
### Serving environment variables | ||
- The [http://localhost:3000/api/env](http://localhost:3000/api/env) serves all environment variables that start with `LOKI_` (except the `LOKI_AUTHZ_`, so you don't accidentally share secrets). Since all key-value pairs are strings, a type conversion to boolean, number and arrays (using the , as separator) is performed. | ||
The [http://localhost:3000/api/env](http://localhost:3000/api/env) serves all environment variables that start with `LOKI_` (except the `LOKI_AUTHZ_`, so you don't accidentally share secrets). Since all key-value pairs are strings, a type conversion to boolean, number and arrays (using the , as separator) is performed. | ||
@@ -90,0 +98,0 @@ ### Authorization |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
162170
64
964
101
11
11
11
1
+ Addedsocket.io@^2.3.0
+ Addedafter@0.8.2(transitive)
+ Addedarraybuffer.slice@0.0.7(transitive)
+ Addedbacko2@1.0.2(transitive)
+ Addedbase64-arraybuffer@0.1.4(transitive)
+ Addedbase64id@2.0.0(transitive)
+ Addedblob@0.0.5(transitive)
+ Addedcomponent-bind@1.0.0(transitive)
+ Addedcomponent-emitter@1.2.11.3.1(transitive)
+ Addedcomponent-inherit@0.0.3(transitive)
+ Addedcookie@0.4.2(transitive)
+ Addeddebug@3.1.04.1.1(transitive)
+ Addedengine.io@3.6.2(transitive)
+ Addedengine.io-client@3.5.4(transitive)
+ Addedengine.io-parser@2.2.1(transitive)
+ Addedhas-binary2@1.0.3(transitive)
+ Addedhas-cors@1.1.0(transitive)
+ Addedindexof@0.0.1(transitive)
+ Addedisarray@2.0.1(transitive)
+ Addedms@2.0.0(transitive)
+ Addedparseqs@0.0.6(transitive)
+ Addedparseuri@0.0.6(transitive)
+ Addedsocket.io@2.5.1(transitive)
+ Addedsocket.io-adapter@1.1.2(transitive)
+ Addedsocket.io-client@2.5.0(transitive)
+ Addedsocket.io-parser@3.3.43.4.3(transitive)
+ Addedto-array@0.1.4(transitive)
+ Addedws@7.5.10(transitive)
+ Addedxmlhttprequest-ssl@1.6.3(transitive)
+ Addedyeast@0.1.2(transitive)