Documented tags resource; Other improvements.
@@ -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
@@ -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 -``` +```
@@ -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:
@@ -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()
@@ -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] == '/')