all repos — litestore @ 5294b1bd9a05c0cbdf21a95e490a1abb45f3e14a

A minimalist nosql document store.

Documented tags resource; Other improvements.
h3rald h3rald@h3rald.com
Sun, 23 Sep 2018 11:47:11 +0200
commit

5294b1bd9a05c0cbdf21a95e490a1abb45f3e14a

parent

3298b52f01d5588aaddb4677f6733cafc9e28edf

M build_guidebuild_guide

@@ -11,6 +11,7 @@ md/api.md

md/api_info.md md/api_dir.md md/api_docs.md + md/api_tags.md md/nim-api.md md/nim-api_high.md md/nim-api_low.md
M src/admin/js/components/navbar.jssrc/admin/js/components/navbar.js

@@ -22,9 +22,7 @@ {path: "/guide/api", title: "HTTP API Reference"},

{path: "/guide/api_info", title: caret+"info (LiteStore Information)"}, {path: "/guide/api_dir", title: caret+"dir (LiteStore Directory)"}, {path: "/guide/api_docs", title: caret+"docs (LiteStore Documents)"}, - {path: "/guide/nim-api", title: "Nim API Reference"}, - {path: "/guide/nim-api_high", title: caret+"High-Level Nim API"}, - {path: "/guide/nim-api_low", title: caret+"Low-Level Nim API"}, + {path: "/guide/api_tags", title: caret+"tags (LiteStore Tags)"}, {path: "/guide/credits", title: "Credits"} ]; vm.taglinks = function(info){

@@ -120,4 +118,4 @@ ])

]); } }; -}()); +}());
M src/admin/md/api_docs.mdsrc/admin/md/api_docs.md

@@ -27,7 +27,7 @@ ```

#### OPTIONS docs -Returns the allowed HTTP verbs for the this resource. +Returns the allowed HTTP verbs for this resource. ##### Example

@@ -238,6 +238,50 @@

Retrieve JSON documents containing only the specified fields. Fields must be specified by comma-separated path/alias expression. Example: http://127.0.0.1:9500/docs/?select=$.name.first%20as%20FirstName,$.age%20as%20Age + +##### `created-after` option + +Retrieve only documents created after a the specified timestamp. + +Example: http://127.0.0.1:9500/?created-after=1537695677 + +> %note% +> API v4 Required +> +> This query string option has been introduced in version 4 of the LiteStore API. + +##### `created-before` option + +Retrieve only documents created before a the specified timestamp. + +Example: http://127.0.0.1:9500/?created-before=1537695677 + +> %note% +> API v4 Required +> +> This query string option has been introduced in version 4 of the LiteStore API. + +##### `modified-after` option + +Retrieve only documents modified after a the specified timestamp. + +Example: http://127.0.0.1:9500/?modified-after=1537695677 + +> %note% +> API v4 Required +> +> This query string option has been introduced in version 4 of the LiteStore API. + +##### `modified-before` option + +Retrieve only documents modified before a the specified timestamp. + +Example: http://127.0.0.1:9500/?modified-before=1537695677 + +> %note% +> API v4 Required +> +> This query string option has been introduced in version 4 of the LiteStore API. ##### `sort` option

@@ -507,4 +551,4 @@ Content-Length: 0

Access-Control-Allow-Headers: Content-Type Access-Control-Allow-Origin: * Server: LiteStore/1.0.3 -``` +```
A src/admin/md/api_tags.md

@@ -0,0 +1,107 @@

+### tags (LiteStore Tags) + +This resource can be queried to retrieve the total of documents associated to a tag, or a list of tags matching a string. + +> %note% +> API v4 Required +> +> This resource has been introduced in version 4 of the LiteStore API. + +#### OPTIONS tags + +Returns the allowed HTTP verbs for this resource. + +##### Example + +``` +$ curl -i -X OPTIONS http://127.0.0.1:9500/tags +HTTP/1.1 200 OK +server: LiteStore/1.5.0 +access-control-allow-origin: http://localhost:9500 +access-control-allow-headers: Content-Type +allow: GET,OPTIONS +access-control-allow-methods: GET,OPTIONS +content-length: 0 +``` + +#### OPTIONS tags/:id + +Returns the allowed HTTP verbs for this resource. + +##### Example + +``` +$ curl -i -X OPTIONS http://127.0.0.1:9500/tags/$type:text +HTTP/1.1 200 OK +server: LiteStore/1.5.0 +access-control-allow-origin: http://localhost:9500 +access-control-allow-headers: Content-Type +allow: GET,OPTIONS +access-control-allow-methods: GET,OPTIONS +Content-Length: 0 +``` + +#### GET tags + +Retrieves all tags and the total of their associated documents. + +##### `like` option + +If this option is specified, retrieves all tags matching the specified string. + +> %tip% +> Wildcards +> +> You can use asterisks (\*) as wildcards. + +##### Example + +``` +$ curl -i http://localhost:9500/tags/?like=%24type:%2A +HTTP/1.1 200 OK +server: LiteStore/1.5.0 +access-control-allow-origin: http://localhost:9500 +content-type: application/json +vary: Origin +access-control-allow-headers: Content-Type +Content-Length: 290 + +{ + "like": "$type:*", + "total": 3, + "execution_time": 0.0008190000000000003, + "results": [ + { + "id": "$type:application", + "documents": 43 + }, + { + "id": "$type:image", + "documents": 10 + }, + { + "id": "$type:text", + "documents": 32 + } + ] +} +``` + +#### GET tags/:id + +Retrieves the specified tag and corresponding document total. + +##### Example + +``` +$ curl -i http://localhost:9500/tags/%24type%3Atext +HTTP/1.1 200 OK +server: LiteStore/1.5.0 +access-control-allow-origin: http://localhost:9500 +content-type: application/json +vary: Origin +access-control-allow-headers: Content-Type +Content-Length: 34 + +{"id":"$type:text","documents":32} +```
M src/litestorepkg/lib/api_v4.nimsrc/litestorepkg/lib/api_v4.nim

@@ -342,34 +342,37 @@ raise newException(EInvalidRequest, "cannot patch data of a non-JSON document.")

# Low level procs -proc getTag*(LS: LiteStore, id: string, options = newQueryOptions()): LSResponse = +proc getTag*(LS: LiteStore, id: string, options = newQueryOptions(), req: LSRequest): LSResponse = let doc = LS.store.retrieveTag(id, options) result.headers = ctJsonHeader() + setOrigin(LS, req, result.headers) if doc == newJObject(): result = resTagNotFound(id) else: result.content = $doc result.code = Http200 -proc getRawDocument*(LS: LiteStore, id: string, options = newQueryOptions()): LSResponse = +proc getRawDocument*(LS: LiteStore, id: string, options = newQueryOptions(), req: LSRequest): LSResponse = let doc = LS.store.retrieveRawDocument(id, options) result.headers = ctJsonHeader() + setOrigin(LS, req, result.headers) if doc == "": result = resDocumentNotFound(id) else: result.content = doc result.code = Http200 -proc getDocument*(LS: LiteStore, id: string, options = newQueryOptions()): LSResponse = +proc getDocument*(LS: LiteStore, id: string, options = newQueryOptions(), req: LSRequest): LSResponse = let doc = LS.store.retrieveDocument(id, options) if doc.data == "": result = resDocumentNotFound(id) else: result.headers = doc.contenttype.ctHeader + setOrigin(LS, req, result.headers) result.content = doc.data result.code = Http200 -proc deleteDocument*(LS: LiteStore, id: string): LSResponse = +proc deleteDocument*(LS: LiteStore, id: string, req: LSRequest): LSResponse = let doc = LS.store.retrieveDocument(id) if doc.data == "": result = resDocumentNotFound(id)

@@ -380,13 +383,14 @@ if res == 0:

result = resError(Http500, "Unable to delete document '$1'" % id) else: result.headers = newHttpHeaders(TAB_HEADERS) + setOrigin(LS, req, result.headers) result.headers["Content-Length"] = "0" result.content = "" result.code = Http204 except: result = resError(Http500, "Unable to delete document '$1'" % id) -proc getTags*(LS: LiteStore, options: QueryOptions = newQueryOptions()): LSResponse = +proc getTags*(LS: LiteStore, options: QueryOptions = newQueryOptions(), req: LSRequest): LSResponse = var options = options let t0 = cpuTime() let docs = LS.store.retrieveTags(options)

@@ -407,10 +411,11 @@ content["total"] = %total

content["execution_time"] = %(cputime()-t0) content["results"] = docs result.headers = ctJsonHeader() + setOrigin(LS, req, result.headers) result.content = content.pretty result.code = Http200 -proc getRawDocuments*(LS: LiteStore, options: QueryOptions = newQueryOptions()): LSResponse = +proc getRawDocuments*(LS: LiteStore, options: QueryOptions = newQueryOptions(), req: LSRequest): LSResponse = var options = options let t0 = cpuTime() let docs = LS.store.retrieveRawDocuments(options)

@@ -439,10 +444,11 @@ content["total"] = %total

content["execution_time"] = %(cputime()-t0) content["results"] = docs result.headers = ctJsonHeader() + setOrigin(LS, req, result.headers) result.content = content.pretty result.code = Http200 -proc getInfo*(LS: LiteStore): LSResponse = +proc getInfo*(LS: LiteStore, req: LSRequest): LSResponse = let info = LS.store.retrieveInfo() let version = info[0] let total_documents = info[1]

@@ -463,16 +469,18 @@ content["total_documents"] = %total_documents

content["total_tags"] = %total_tags content["tags"] = tags result.headers = ctJsonHeader() + setOrigin(LS, req, result.headers) result.content = content.pretty result.code = Http200 -proc postDocument*(LS: LiteStore, body: string, ct: string, folder=""): LSResponse = +proc postDocument*(LS: LiteStore, body: string, ct: string, folder="", req: LSRequest): LSResponse = if not folder.isFolder: return resError(Http400, "Invalid folder specified when creating document: $1" % folder) try: var doc = LS.store.createDocument(folder, body, ct) if doc != "": result.headers = ctJsonHeader() + setOrigin(LS, req, result.headers) result.content = doc result.code = Http201 else:

@@ -480,7 +488,7 @@ result = resError(Http500, "Unable to create document.")

except: result = resError(Http500, "Unable to create document.") -proc putDocument*(LS: LiteStore, id: string, body: string, ct: string): LSResponse = +proc putDocument*(LS: LiteStore, id: string, body: string, ct: string, req: LSRequest): LSResponse = if id.isFolder: return resError(Http400, "Invalid ID '$1' (Document IDs cannot end with '/')." % id) let doc = LS.store.retrieveDocument(id)

@@ -489,6 +497,7 @@ # Create a new document

var doc = LS.store.createDocument(id, body, ct) if doc != "": result.headers = ctJsonHeader() + setOrigin(LS, req, result.headers) result.content = doc result.code = Http201 else:

@@ -499,6 +508,7 @@ try:

var doc = LS.store.updateDocument(id, body, ct) if doc != "": result.headers = ctJsonHeader() + setOrigin(LS, req, result.headers) result.content = doc result.code = Http200 else:

@@ -506,7 +516,7 @@ result = resError(Http500, "Unable to update document '$1'." % id)

except: result = resError(Http500, "Unable to update document '$1'." % id) -proc patchDocument*(LS: LiteStore, id: string, body: string): LSResponse = +proc patchDocument*(LS: LiteStore, id: string, body: string, req: LSRequest): LSResponse = var apply = true let jbody = body.parseJson if jbody.kind != JArray:

@@ -561,7 +571,7 @@ if t2 != "":

LS.store.createTag(t2, id, true) except: return resError(Http500, "Unable to patch document '$1' - $2" % [id, getCurrentExceptionMsg()]) - return LS.getRawDocument(id) + return LS.getRawDocument(id, newQueryOptions(), req) # Main routing

@@ -574,12 +584,21 @@ else:

result.code = Http200 result.content = "" result.headers = newHttpHeaders(TAB_HEADERS) + setOrigin(LS, req, result.headers) result.headers["Allow"] = "GET,OPTIONS" result.headers["Access-Control-Allow-Methods"] = "GET,OPTIONS" of "dir": result.code = Http200 result.content = "" result.headers = newHttpHeaders(TAB_HEADERS) + setOrigin(LS, req, result.headers) + result.headers["Allow"] = "GET,OPTIONS" + result.headers["Access-Control-Allow-Methods"] = "GET,OPTIONS" + of "tags": + result.code = Http200 + result.content = "" + result.headers = newHttpHeaders(TAB_HEADERS) + setOrigin(LS, req, result.headers) result.headers["Allow"] = "GET,OPTIONS" result.headers["Access-Control-Allow-Methods"] = "GET,OPTIONS" of "docs":

@@ -591,10 +610,12 @@ result.code = Http200

result.content = "" if LS.readonly: result.headers = newHttpHeaders(TAB_HEADERS) + setOrigin(LS, req, result.headers) result.headers["Allow"] = "HEAD,GET,OPTIONS" result.headers["Access-Control-Allow-Methods"] = "HEAD,GET,OPTIONS" else: result.headers = newHttpHeaders(TAB_HEADERS) + setOrigin(LS, req, result.headers) result.headers["Allow"] = "HEAD,GET,OPTIONS,POST,PUT" result.headers["Access-Control-Allow-Methods"] = "HEAD,GET,OPTIONS,POST,PUT" elif id != "":

@@ -602,10 +623,12 @@ result.code = Http200

result.content = "" if LS.readonly: result.headers = newHttpHeaders(TAB_HEADERS) + setOrigin(LS, req, result.headers) result.headers["Allow"] = "HEAD,GET,OPTIONS" result.headers["Access-Control-Allow-Methods"] = "HEAD,GET,OPTIONS" else: result.headers = newHttpHeaders(TAB_HEADERS) + setOrigin(LS, req, result.headers) result.headers["Allow"] = "HEAD,GET,OPTIONS,PUT,PATCH,DELETE" result.headers["Allow-Patch"] = "application/json-patch+json" result.headers["Access-Control-Allow-Methods"] = "HEAD,GET,OPTIONS,PUT,PATCH,DELETE"

@@ -614,10 +637,12 @@ result.code = Http200

result.content = "" if LS.readonly: result.headers = newHttpHeaders(TAB_HEADERS) + setOrigin(LS, req, result.headers) result.headers["Allow"] = "HEAD,GET,OPTIONS" result.headers["Access-Control-Allow-Methods"] = "HEAD,GET,OPTIONS" else: result.headers = newHttpHeaders(TAB_HEADERS) + setOrigin(LS, req, result.headers) result.headers["Allow"] = "HEAD,GET,OPTIONS,POST" result.headers["Access-Control-Allow-Methods"] = "HEAD,GET,OPTIONS,POST" else:

@@ -631,10 +656,10 @@ options.folder = id

try: parseQueryOptions(req.url.query, options); if id != "" and options.folder == "": - result = LS.getRawDocument(id, options) + result = LS.getRawDocument(id, options, req) result.content = "" else: - result = LS.getRawDocuments(options) + result = LS.getRawDocuments(options, req) result.content = "" except: return resError(Http400, "Bad request - $1" % getCurrentExceptionMsg())

@@ -652,11 +677,11 @@ try:

parseQueryOptions(req.url.query, options); if id != "" and options.folder == "": if req.url.query.contains("raw=true") or req.headers.hasKey("Accept") and req.headers["Accept"] == "application/json": - return LS.getRawDocument(id, options) + return LS.getRawDocument(id, options, req) else: - return LS.getDocument(id, options) + return LS.getDocument(id, options, req) else: - return LS.getRawDocuments(options) + return LS.getRawDocuments(options, req) except: return resError(Http400, "Bad Request - $1" % getCurrentExceptionMsg()) of "tags":

@@ -664,15 +689,15 @@ var options = newQueryOptions()

try: parseQueryOptions(req.url.query, options); if id != "": - return LS.getTag(id, options) + return LS.getTag(id, options, req) else: - return LS.getTags(options) + return LS.getTags(options, req) except: return resError(Http400, "Bad Request - $1" % getCurrentExceptionMsg()) of "info": if id != "": return resError(Http404, "Info '$1' not found." % id) - return LS.getInfo() + return LS.getInfo(req) else: discard # never happens really.

@@ -680,26 +705,26 @@ proc post*(req: LSRequest, LS: LiteStore, resource: string, id = ""): LSResponse =

var ct = "text/plain" if req.headers.hasKey("Content-Type"): ct = req.headers["Content-Type"] - return LS.postDocument(req.body.strip, ct, id) + return LS.postDocument(req.body.strip, ct, id, req) proc put*(req: LSRequest, LS: LiteStore, resource: string, id = ""): LSResponse = if id != "": var ct = "text/plain" if req.headers.hasKey("Content-Type"): ct = req.headers["Content-Type"] - return LS.putDocument(id, req.body.strip, ct) + return LS.putDocument(id, req.body.strip, ct, req) else: return resError(Http400, "Bad request: document ID must be specified in PUT requests.") proc delete*(req: LSRequest, LS: LiteStore, resource: string, id = ""): LSResponse = if id != "": - return LS.deleteDocument(id) + return LS.deleteDocument(id, req) else: return resError(Http400, "Bad request: document ID must be specified in DELETE requests.") proc patch*(req: LSRequest, LS: LiteStore, resource: string, id = ""): LSResponse = if id != "": - return LS.patchDocument(id, req.body) + return LS.patchDocument(id, req.body, req) else: return resError(Http400, "Bad request: document ID must be specified in PATCH requests.")

@@ -720,6 +745,7 @@ if CONTENT_TYPES.hasKey(parts.ext):

result.headers = CONTENT_TYPES[parts.ext].ctHeader else: result.headers = ctHeader("text/plain") + setOrigin(LS, req, result.headers) result.content = contents result.code = Http200 except:
M src/litestorepkg/lib/server.nimsrc/litestorepkg/lib/server.nim

@@ -42,7 +42,7 @@ return api_v4.serveFile(req, LS, info.id)

else: return resError(Http400, "Bad Request - Not serving any directory." % info.version) else: - return resError(Http400, "Bad Request - Invalid resource: $1" % info.resource) + return resError(Http404, "Resource Not Found: $1" % info.resource) elif info.version == "v3": if info.resource.match(peg"^docs / info$"): return api_v3.route(req, LS, info.resource, info.id)

@@ -52,7 +52,7 @@ return api_v3.serveFile(req, LS, info.id)

else: return resError(Http400, "Bad Request - Not serving any directory." % info.version) else: - return resError(Http400, "Bad Request - Invalid resource: $1" % info.resource) + return resError(Http404, "Resource Not Found: $1" % info.resource) elif info.version == "v2": if info.resource.match(peg"^docs / info$"): return api_v2.route(req, LS, info.resource, info.id)

@@ -62,7 +62,7 @@ return api_v2.serveFile(req, LS, info.id)

else: return resError(Http400, "Bad Request - Not serving any directory." % info.version) else: - return resError(Http400, "Bad Request - Invalid resource: $1" % info.resource) + return resError(Http404, "Resource Not Found: $1" % info.resource) elif info.version == "v1": if info.resource.match(peg"^docs / info$"): return api_v1.route(req, LS, info.resource, info.id)

@@ -72,7 +72,7 @@ return api_v1.serveFile(req, LS, info.id)

else: return resError(Http400, "Bad Request - Not serving any directory." % info.version) else: - return resError(Http400, "Bad Request - Invalid resource: $1" % info.resource) + return resError(Http404, "Resource Not Found: $1" % info.resource) else: if info.version == "v1" or info.version == "v2" or info.version == "v3" or info.version == "v4": return resError(Http400, "Bad Request - Invalid API version: $1" % info.version)

@@ -80,7 +80,7 @@ else:

if info.resource.decodeURL.strip == "": return resError(Http400, "Bad Request - No resource specified." % info.resource) else: - return resError(Http400, "Bad Request - Invalid resource: $1" % info.resource) + return resError(Http404, "Resource Not Found: $1" % info.resource) proc process*(req: LSRequest, LS: LiteStore): LSResponse {.gcsafe.}= var matches = @["", "", ""]

@@ -112,7 +112,7 @@ raise newException(EInvalidRequest, req.getReqInfo())

except EInvalidRequest: let e = (ref EInvalidRequest)(getCurrentException()) let trace = e.getStackTrace() - return resError(Http400, "Bad Request: $1" % getCurrentExceptionMsg(), trace) + return resError(Http404, "Resource Not Found: $1" % getCurrentExceptionMsg().split(" ")[2], trace) except: let e = getCurrentException() let trace = e.getStackTrace()
M src/litestorepkg/lib/utils.nimsrc/litestorepkg/lib/utils.nim

@@ -16,6 +16,29 @@ queries,

contenttypes, logger +proc setOrigin*(LS: LiteStore, req: LSRequest, headers: var HttpHeaders) = + var host = "" + var port = "" + var protocol = "http" + if req.url.hostname != "" and req.url.port != "": + host = req.url.hostname + port = req.url.port + elif req.headers.hasKey("Host"): + var parts = req.headers["Host"].split(":") + if (parts.len >= 2): + host = parts[0] + port = parts[1] + else: + host = parts[0] + port = "80" + else: + headers["Access-Control-Allow-Origin"] = "*" + return + if req.url.scheme != "": + protocol = req.url.scheme + headers["Vary"] = "Origin" + headers["Access-Control-Allow-Origin"] = "$1://$2:$3" % [protocol, host, port] + proc isFolder*(id: string): bool = return (id.len == 0 or id.len > 0 and id[id.len-1] == '/')