all repos — litestore @ 6363a58f31488ccfbcff2433bc426d1977b5ab3f

A minimalist nosql document store.

Started implementing $http API.
h3rald h3rald@h3rald.com
Fri, 05 Jan 2024 13:47:25 +0100
commit

6363a58f31488ccfbcff2433bc426d1977b5ab3f

parent

9d6df9a69446ad4967d04dceba2061a9a838dda9

2 files changed, 171 insertions(+), 72 deletions(-)

jump to
M .gitignore.gitignore

@@ -25,3 +25,4 @@ litestore_linkerArgs.txt

token*.txt x5c*.cert jwt +middleware
M src/litestorepkg/lib/api_v8.nimsrc/litestorepkg/lib/api_v8.nim

@@ -1,5 +1,6 @@

import - asynchttpserver, + std/[asynchttpserver, + httpclient, strutils, sequtils, cgi,

@@ -9,7 +10,7 @@ json,

os, uri, tables, - times + times] import types, contenttypes,

@@ -154,11 +155,13 @@ case pair[0]:

of "filter": filterClauses(pair[1], options) if options.jsonFilter == "": - raise newException(EInvalidRequest, "Invalid filter clause: $1" % pair[1].replace("\"", "\\\"")) + raise newException(EInvalidRequest, "Invalid filter clause: $1" % pair[ + 1].replace("\"", "\\\"")) of "select": selectClause(pair[1], options) if options.jsonSelect.len == 0: - raise newException(EInvalidRequest, "Invalid select clause: $1" % pair[1].replace("\"", "\\\"")) + raise newException(EInvalidRequest, "Invalid select clause: $1" % pair[ + 1].replace("\"", "\\\"")) of "like": options.like = pair[1] of "search":

@@ -169,32 +172,38 @@ of "created-after":

try: options.createdAfter = pair[1].parseInt.fromUnix.utc.format("yyyy-MM-dd'T'HH:mm:ss'Z'") except CatchableError: - raise newException(EInvalidRequest, "Invalid created-after value: $1" % getCurrentExceptionMsg()) + raise newException(EInvalidRequest, "Invalid created-after value: $1" % + getCurrentExceptionMsg()) of "created-before": try: options.createdBefore = pair[1].parseInt.fromUnix.utc.format("yyyy-MM-dd'T'HH:mm:ss'Z'") except CatchableError: - raise newException(EInvalidRequest, "Invalid created-before value: $1" % getCurrentExceptionMsg()) + raise newException(EInvalidRequest, "Invalid created-before value: $1" % + getCurrentExceptionMsg()) of "modified-after": try: options.modifiedAfter = pair[1].parseInt.fromUnix.utc.format("yyyy-MM-dd'T'HH:mm:ss'Z'") except CatchableError: - raise newException(EInvalidRequest, "Invalid modified.after value: $1" % getCurrentExceptionMsg()) + raise newException(EInvalidRequest, "Invalid modified.after value: $1" % + getCurrentExceptionMsg()) of "modified-before": try: options.modifiedBefore = pair[1].parseInt.fromUnix.utc.format("yyyy-MM-dd'T'HH:mm:ss'Z'") except CatchableError: - raise newException(EInvalidRequest, "Invalid modified-before value: $1" % getCurrentExceptionMsg()) + raise newException(EInvalidRequest, + "Invalid modified-before value: $1" % getCurrentExceptionMsg()) of "limit": try: options.limit = pair[1].parseInt except CatchableError: - raise newException(EInvalidRequest, "Invalid limit value: $1" % getCurrentExceptionMsg()) + raise newException(EInvalidRequest, "Invalid limit value: $1" % + getCurrentExceptionMsg()) of "offset": try: options.offset = pair[1].parseInt except CatchableError: - raise newException(EInvalidRequest, "Invalid offset value: $1" % getCurrentExceptionMsg()) + raise newException(EInvalidRequest, "Invalid offset value: $1" % + getCurrentExceptionMsg()) of "sort": let orderby = pair[1].orderByClauses() if orderby != "":

@@ -214,9 +223,11 @@ var fragments = q.split('&')

for f in fragments: f.parseQueryOption(options) -proc validate*(req: LSRequest, LS: LiteStore, resource: string, id: string, cb: proc(req: LSRequest, LS: LiteStore, resource: string, id: string):LSResponse): LSResponse = +proc validate*(req: LSRequest, LS: LiteStore, resource: string, id: string, + cb: proc(req: LSRequest, LS: LiteStore, resource: string, + id: string): LSResponse): LSResponse = if req.reqMethod == HttpPost or req.reqMethod == HttpPut or req.reqMethod == HttpPatch: - var ct = "" + var ct = "" let body = req.body.strip if body == "": return resError(Http400, "Bad request: No content specified for document.")

@@ -227,13 +238,16 @@ of "application/json":

try: discard body.parseJson() except CatchableError: - return resError(Http400, "Invalid JSON content - $1" % getCurrentExceptionMsg()) + return resError(Http400, "Invalid JSON content - $1" % + getCurrentExceptionMsg()) else: discard return cb(req, LS, resource, id) -proc patchTag(tags: var seq[string], index: int, op, path, value: string): bool = - LOG.debug("- PATCH -> $1 tag['$2'] = \"$3\" - Total tags: $4." % [op, $index, $value, $tags.len]) +proc patchTag(tags: var seq[string], index: int, op, path, + value: string): bool = + LOG.debug("- PATCH -> $1 tag['$2'] = \"$3\" - Total tags: $4." % [op, $index, + $value, $tags.len]) case op: of "remove": let tag = tags[index]

@@ -252,7 +266,8 @@ raise newException(EInvalidRequest, "invalid tag: $1" % value)

of "replace": if value.match(PEG_USER_TAG): if tags[index].startsWith("$"): - raise newException(EInvalidRequest, "cannot replace system tag: $1" % tags[index]) + raise newException(EInvalidRequest, "cannot replace system tag: $1" % + tags[index]) else: tags[index] = value else:

@@ -267,7 +282,8 @@ else:

raise newException(EInvalidRequest, "invalid patch operation: $1" % op) return true -proc patchData*(data: var JsonNode, origData: JsonNode, op: string, path: string, value: JsonNode): bool = +proc patchData*(data: var JsonNode, origData: JsonNode, op: string, + path: string, value: JsonNode): bool = LOG.debug("- PATCH -> $1 path $2 with $3" % [op, path, $value]) var keys = path.replace(peg"^\/data\/", "").split("/") if keys.len == 0:

@@ -292,12 +308,14 @@ of "test":

if d.elems[index] != value: return false else: - raise newException(EInvalidRequest, "invalid patch operation: $1" % op) + raise newException(EInvalidRequest, + "invalid patch operation: $1" % op) else: d = d[index] dorig = dorig[index] except CatchableError: - raise newException(EInvalidRequest, "invalid index key '$1' in path '$2'" % [key, path]) + raise newException(EInvalidRequest, + "invalid index key '$1' in path '$2'" % [key, path]) else: if c >= keys.len: case op:

@@ -305,20 +323,23 @@ of "remove":

if d.hasKey(key): d.delete(key) else: - raise newException(EInvalidRequest, "key '$1' not found in path '$2'" % [key, path]) + raise newException(EInvalidRequest, + "key '$1' not found in path '$2'" % [key, path]) of "add": d[key] = value of "replace": if d.hasKey(key): d[key] = value else: - raise newException(EInvalidRequest, "key '$1' not found in path '$2'" % [key, path]) + raise newException(EInvalidRequest, + "key '$1' not found in path '$2'" % [key, path]) of "test": if dorig.hasKey(key): if dorig[key] != value: return false else: - raise newException(EInvalidRequest, "key '$1' not found in path '$2'" % [key, path]) + raise newException(EInvalidRequest, + "key '$1' not found in path '$2'" % [key, path]) else: raise newException(EInvalidRequest, "invalid patch operation: $1" % op) else:

@@ -328,7 +349,8 @@ c += 1

return true -proc applyPatchOperation*(data: var JsonNode, origData: JsonNode, tags: var seq[string], op: string, path: string, value: JsonNode): bool = +proc applyPatchOperation*(data: var JsonNode, origData: JsonNode, tags: var seq[ + string], op: string, path: string, value: JsonNode): bool = var matches = @[""] let p = peg""" path <- ^tagPath / fieldPath$

@@ -351,7 +373,8 @@ raise newException(EInvalidRequest, "cannot patch data of a non-JSON document.")

# Low level procs -proc getTag*(LS: LiteStore, id: string, options = newQueryOptions(), req: LSRequest): 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)

@@ -361,7 +384,8 @@ else:

result.content = $doc result.code = Http200 -proc getStore*(LS: LiteStore, id: string, options = newQueryOptions(), req: LSRequest): LSResponse = +proc getStore*(LS: LiteStore, id: string, options = newQueryOptions(), + req: LSRequest): LSResponse = if (not LSDICT.hasKey(id)): return resStoreNotFound(id) let store = LSDICT[id]

@@ -374,7 +398,8 @@ setOrigin(LS, req, result.headers)

result.content = $doc result.code = Http200 -proc getIndex*(LS: LiteStore, id: string, options = newQueryOptions(), req: LSRequest): LSResponse = +proc getIndex*(LS: LiteStore, id: string, options = newQueryOptions(), + req: LSRequest): LSResponse = let doc = LS.store.retrieveIndex(id, options) result.headers = ctJsonHeader() setOrigin(LS, req, result.headers)

@@ -384,7 +409,8 @@ else:

result.content = $doc result.code = Http200 -proc getRawDocument*(LS: LiteStore, id: string, options = newQueryOptions(), req: LSRequest): 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)

@@ -394,7 +420,8 @@ else:

result.content = doc result.code = Http200 -proc getDocument*(LS: LiteStore, id: string, options = newQueryOptions(), req: LSRequest): 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)

@@ -422,7 +449,8 @@ result.code = Http204

except CatchableError: result = resError(Http500, "Unable to delete document '$1'" % id) -proc getTags*(LS: LiteStore, options: QueryOptions = newQueryOptions(), req: LSRequest): LSResponse = +proc getTags*(LS: LiteStore, options: QueryOptions = newQueryOptions(), + req: LSRequest): LSResponse = var options = options let t0 = cpuTime() let docs = LS.store.retrieveTags(options)

@@ -431,7 +459,8 @@ let orig_offset = options.offset

options.limit = 0 options.offset = 0 options.select = @["COUNT(tag_id)"] - let total = LS.store.countTags(prepareSelectTagsQuery(options), options.like.replace("*", "%")) + let total = LS.store.countTags(prepareSelectTagsQuery(options), + options.like.replace("*", "%")) var content = newJObject() if options.like != "": content["like"] = %(options.like.decodeURL)

@@ -447,7 +476,8 @@ setOrigin(LS, req, result.headers)

result.content = content.pretty result.code = Http200 -proc getStores(LS: LiteStore, options: QueryOptions = newQueryOptions(), req: LSRequest): LSResponse = +proc getStores(LS: LiteStore, options: QueryOptions = newQueryOptions(), + req: LSRequest): LSResponse = let t0 = cpuTime() var docs = newJArray() for k, v in LSDICT.pairs:

@@ -465,7 +495,8 @@ setOrigin(LS, req, result.headers)

result.content = content.pretty result.code = Http200 -proc getIndexes*(LS: LiteStore, options: QueryOptions = newQueryOptions(), req: LSRequest): LSResponse = +proc getIndexes*(LS: LiteStore, options: QueryOptions = newQueryOptions(), + req: LSRequest): LSResponse = var options = options let t0 = cpuTime() let docs = LS.store.retrieveIndexes(options)

@@ -474,7 +505,8 @@ let orig_offset = options.offset

options.limit = 0 options.offset = 0 options.select = @["COUNT(name)"] - let total = LS.store.countIndexes(prepareSelectIndexesQuery(options), options.like.replace("*", "%")) + let total = LS.store.countIndexes(prepareSelectIndexesQuery(options), + options.like.replace("*", "%")) var content = newJObject() if options.like != "": content["like"] = %(options.like.decodeURL)

@@ -490,7 +522,8 @@ setOrigin(LS, req, result.headers)

result.content = content.pretty result.code = Http200 -proc getRawDocuments*(LS: LiteStore, options: QueryOptions = newQueryOptions(), req: LSRequest): LSResponse = +proc getRawDocuments*(LS: LiteStore, options: QueryOptions = newQueryOptions(), + req: LSRequest): LSResponse = var options = options let t0 = cpuTime() let docs = LS.store.retrieveRawDocuments(options)

@@ -533,7 +566,8 @@ var content = newJObject()

content["version"] = %(LS.appname & " v" & LS.appversion) content["datastore_version"] = %version content["api_version"] = %7 - content["size"] = %($((LS.file.getFileSize().float/(1024*1024)).formatFloat(ffDecimal, 2)) & " MB") + content["size"] = %($((LS.file.getFileSize().float/(1024*1024)).formatFloat( + ffDecimal, 2)) & " MB") content["read_only"] = %LS.readonly content["log_level"] = %LS.loglevel if LS.directory.len == 0:

@@ -541,7 +575,8 @@ content["directory"] = newJNull()

else: content["directory"] = %LS.directory content["mount"] = %LS.mount - if LS.config != newJNull() and LS.config.hasKey("stores") and LS.config["stores"].len > 0: + if LS.config != newJNull() and LS.config.hasKey("stores") and LS.config[ + "stores"].len > 0: content["additional_stores"] = %toSeq(LS.config["stores"].keys) else: content["additional_stores"] = newJArray()

@@ -574,7 +609,8 @@ except CatchableError:

eWarn() result = resError(Http500, "Unable to create index.") -proc putStore*(LS: LiteStore, id: string, config: JsonNode, req: LSRequest): LSResponse = +proc putStore*(LS: LiteStore, id: string, config: JsonNode, + req: LSRequest): LSResponse = try: if (not id.match(PEG_STORE) or id == "master"): return resError(Http400, "invalid store ID: $1" % id)

@@ -624,7 +660,8 @@ except CatchableError:

eWarn() result = resError(Http500, "Unable to delete index.") -proc postDocument*(LS: LiteStore, body: string, ct: string, folder="", req: LSRequest): 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:

@@ -640,7 +677,8 @@ except CatchableError:

eWarn() result = resError(Http500, "Unable to create document.") -proc putDocument*(LS: LiteStore, id: string, body: string, ct: string, req: LSRequest): 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)

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

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

@@ -698,13 +737,14 @@ if item.hasKey("op") and item.hasKey("path"):

if not item.hasKey("value"): item["value"] = %"" try: - apply = applyPatchOperation(data, origData, tags, item["op"].str, item["path"].str, item["value"]) + apply = applyPatchOperation(data, origData, tags, item["op"].str, item[ + "path"].str, item["value"]) if not apply: break except CatchableError: return resError(Http400, "Bad request - $1" % getCurrentExceptionMsg()) else: - return resError(Http400, "Bad request: patch operation #$1 is malformed." % $c) + return resError(Http400, "Bad request: patch operation #$1 is malformed." % $c) c.inc if apply: # when document is not JSON the origData is not defined

@@ -715,7 +755,8 @@ var doc = LS.store.updateDocument(id, data.pretty, "application/json")

if doc == "": return resError(Http500, "Unable to patch document '$1'." % id) except CatchableError: - return resError(Http500, "Unable to patch document '$1' - $2" % id, getCurrentExceptionMsg()) + return resError(Http500, "Unable to patch document '$1' - $2" % id, + getCurrentExceptionMsg()) if origTags != tags: try: for t1 in jdoc["tags"].items:

@@ -724,12 +765,14 @@ for t2 in tags:

if t2 != "": LS.store.createTag(t2, id, true) except CatchableError: - return resError(Http500, "Unable to patch document '$1' - $2" % [id, getCurrentExceptionMsg()]) + return resError(Http500, "Unable to patch document '$1' - $2" % [id, + getCurrentExceptionMsg()]) return LS.getRawDocument(id, newQueryOptions(), req) # Main routing -proc options*(req: LSRequest, LS: LiteStore, resource: string, id = ""): LSResponse = +proc options*(req: LSRequest, LS: LiteStore, resource: string, + id = ""): LSResponse = case resource: of "info": result.headers = newHttpHeaders(TAB_HEADERS)

@@ -863,7 +906,8 @@ result.headers["Access-Control-Allow-Methods"] = "GET, OPTIONS"

else: discard # never happens really. -proc head*(req: LSRequest, LS: LiteStore, resource: string, id = ""): LSResponse = +proc head*(req: LSRequest, LS: LiteStore, resource: string, + id = ""): LSResponse = var options = newQueryOptions() options.select = @["documents.id AS id", "created", "modified"] if id.isFolder:

@@ -879,7 +923,8 @@ result.content = ""

except CatchableError: return resError(Http400, "Bad request - $1" % getCurrentExceptionMsg()) -proc get*(req: LSRequest, LS: LiteStore, resource: string, id = ""): LSResponse = +proc get*(req: LSRequest, LS: LiteStore, resource: string, + id = ""): LSResponse = case resource: of "docs": var options = newQueryOptions()

@@ -890,7 +935,8 @@ options.select = @["documents.id AS id", "created", "modified"]

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": + if req.url.query.contains("raw=true") or req.headers.hasKey( + "Accept") and req.headers["Accept"] == "application/json": return LS.getRawDocument(id, options, req) else: return LS.getDocument(id, options, req)

@@ -913,7 +959,7 @@ else:

options.folder = "" result = LS.getDocument(id & "index.html", options, req) if result.code == Http404: - result = LS.getDocument(id & "index.htm", options, req) + result = LS.getDocument(id & "index.htm", options, req) return result except CatchableError: let e = getCurrentException()

@@ -957,27 +1003,31 @@ return LS.getInfo(req)

else: discard # never happens really. -proc post*(req: LSRequest, LS: LiteStore, resource: string, id = ""): LSResponse = +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, req) -proc put*(req: LSRequest, LS: LiteStore, resource: string, id = ""): LSResponse = +proc put*(req: LSRequest, LS: LiteStore, resource: string, + id = ""): LSResponse = if id != "": if resource == "indexes": var field = "" try: field = parseJson(req.body.strip)["field"].getStr except CatchableError: - return resError(Http400, "Bad Request - Invalid JSON body - $1" % getCurrentExceptionMsg()) + return resError(Http400, "Bad Request - Invalid JSON body - $1" % + getCurrentExceptionMsg()) return LS.putIndex(id, field, req) elif resource == "stores": var config = newJNull() try: config = parseJson(req.body) except CatchableError: - return resError(Http400, "Bad Request - Invalid JSON body - $1" % getCurrentExceptionMsg()) + return resError(Http400, "Bad Request - Invalid JSON body - $1" % + getCurrentExceptionMsg()) return LS.putStore(id, config, req) else: # Assume docs var ct = "text/plain"

@@ -987,7 +1037,8 @@ 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 = +proc delete*(req: LSRequest, LS: LiteStore, resource: string, + id = ""): LSResponse = if id != "": if resource == "indexes": return LS.deleteIndex(id, req)

@@ -998,7 +1049,8 @@ 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 = +proc patch*(req: LSRequest, LS: LiteStore, resource: string, + id = ""): LSResponse = if id != "": return LS.patchDocument(id, req.body, req) else:

@@ -1031,7 +1083,8 @@ return resError(Http404, "File '$1' not found." % path)

else: return resError(Http405, "Method not allowed: $1" % $req.reqMethod) -proc route*(req: LSRequest, LS: LiteStore, resource = "docs", id = ""): LSResponse = +proc route*(req: LSRequest, LS: LiteStore, resource = "docs", + id = ""): LSResponse = var reqMethod = $req.reqMethod if req.headers.hasKey("X-HTTP-Method-Override"): reqMethod = req.headers["X-HTTP-Method-Override"]

@@ -1072,31 +1125,37 @@ discard path.find(PEG_URL, matches)

return req.route(LSDICT[id], matches[1], matches[2]) return req.route(LS, resource, id) -proc newSimpleLSRequest(meth: HttpMethod, resource = "", id = "", body = "", params = "", headers = newHttpHeaders()): LSRequest = +proc newSimpleLSRequest(meth: HttpMethod, resource = "", id = "", body = "", + params = "", headers = newHttpHeaders()): LSRequest = result.reqMethod = meth result.body = body result.headers = headers - result.url = parseUri("$1://$2:$3/$4/$5?$6" % @["http", "localhost", "9500", resource, id, params]) - + result.url = parseUri("$1://$2:$3/$4/$5?$6" % @["http", "localhost", "9500", + resource, id, params]) + proc get(resource, id: string, params = ""): LSResponse = - return newSimpleLSRequest(HttpGet, resource, id, "", params).multiRoute(resource, id) + return newSimpleLSRequest(HttpGet, resource, id, "", params).multiRoute( + resource, id) proc post(resource, folder, body: string, ct = ""): LSResponse = var headers = newHttpHeaders() if ct != "": headers["Content-Type"] = ct - return newSimpleLSRequest(HttpPost, resource, folder, body, "", headers).multiRoute(resource, folder & "/") + return newSimpleLSRequest(HttpPost, resource, folder, body, "", + headers).multiRoute(resource, folder & "/") proc put(resource, id, body: string, ct = ""): LSResponse = var headers = newHttpHeaders() if ct != "": headers["Content-Type"] = ct - return newSimpleLSRequest(HttpPut, resource, id, body, "", headers).multiRoute(resource, id) + return newSimpleLSRequest(HttpPut, resource, id, body, "", + headers).multiRoute(resource, id) proc patch(resource, id, body: string): LSResponse = var headers = newHttpHeaders() headers["Content-Type"] = "application/json" - return newSimpleLSRequest(HttpPatch, resource, id, body, "", headers).multiRoute(resource, id) + return newSimpleLSRequest(HttpPatch, resource, id, body, "", + headers).multiRoute(resource, id) proc delete(resource, id: string): LSResponse = return newSimpleLSRequest(HttpDelete, resource, id).multiRoute(resource, id)

@@ -1104,7 +1163,41 @@

proc head(resource, id: string): LSResponse = return newSimpleLSRequest(HttpHead, resource, id).multiRoute(resource, id) -proc registerStoreApi(LS: LiteStore, ctx: DTContext, origResource, origId: string) = +proc toHeaders(obj: JsonNode): HttpHeaders = + var headers = newSeq[tuple[key: string, val: string]](0) + for k, v in obj.pairs: + headers.add (key: k, val: v.getstr) + return newHttpHeaders(headers) + +proc toJson(headers: HttpHeaders): JsonNode = + result = newJObject() + for k, v in headers.pairs: + result[k] = newJString(v) + +proc registerHttpApi(LS: LiteStore, ctx: DTContext) = + var api_idx = ctx.duk_push_object() + # GET + var get: DTCFunction = (proc (ctx: DTContext): cint{.stdcall.} = + let url = $duk_get_string(ctx, 0) + let headers = $duk_get_string(ctx, 1) + let client = newHttpClient(headers = ( + headers).parseJson.toHeaders) + let resp = client.get(url) + var res_idx = ctx.duk_push_object() + ctx.duk_push_int(cast[cint](resp.code)) + discard ctx.duk_put_prop_string(res_idx, "code") + discard ctx.duk_push_string(resp.body.cstring) + discard ctx.duk_put_prop_string(res_idx, "content") + discard ctx.duk_push_string(resp.headers.toJson.pretty.cstring) + discard ctx.duk_put_prop_string(res_idx, "headers") + return 1 + ) + discard duk_push_c_function(ctx, get, 2) + discard ctx.duk_put_prop_string(api_idx, "get") + discard ctx.duk_put_global_string("$http") + +proc registerStoreApi(LS: LiteStore, ctx: DTContext, origResource, + origId: string) = var api_idx = ctx.duk_push_object() # GET var get: DTCFunction = (proc (ctx: DTContext): cint{.stdcall.} =

@@ -1198,7 +1291,7 @@ discard duk_push_c_function(ctx, head, 2)

discard ctx.duk_put_prop_string(api_idx, "head") discard ctx.duk_put_global_string("$store") -proc jError(ctx: DTContext): LSResponse = +proc jError(ctx: DTContext): LSResponse = return resError(Http500, "Middleware Error: " & $ctx.duk_safe_to_string(-1)) proc getMiddleware*(LS: LiteStore, id: string): string =

@@ -1213,9 +1306,9 @@ else:

result = LS.middleware[id] proc getMiddlewareSeq(LS: LiteStore, resource, id, meth: string): seq[string] = - result = newSeq[string]() + result = newSeq[string]() if LS.config.kind != JObject or not LS.config.hasKey("resources"): - return + return var reqUri = "/" & resource & "/" & id if reqUri[^1] == '/': reqUri.removeSuffix({'/'})

@@ -1226,24 +1319,28 @@ var currentPaths = ""

for p in ancestors: currentPath &= "/" & p currentPaths = currentPath & "/*" - if LS.config["resources"].hasKey(currentPaths) and LS.config["resources"][currentPaths].hasKey(meth) and LS.config["resources"][currentPaths][meth].hasKey("middleware"): + if LS.config["resources"].hasKey(currentPaths) and LS.config["resources"][ + currentPaths].hasKey(meth) and LS.config["resources"][currentPaths][ + meth].hasKey("middleware"): let mw = LS.config["resources"][currentPaths][meth]["middleware"] if (mw.kind == JArray): for m in mw: result.add m.getStr - if LS.config["resources"].hasKey(reqUri) and LS.config["resources"][reqUri].hasKey(meth) and LS.config["resources"][reqUri][meth].hasKey("middleware"): + if LS.config["resources"].hasKey(reqUri) and LS.config["resources"][ + reqUri].hasKey(meth) and LS.config["resources"][reqUri][meth].hasKey("middleware"): let mw = LS.config["resources"][reqUri][meth]["middleware"] if (mw.kind == JArray): for m in mw: result.add m.getStr -proc execute*(req: var LSRequest, LS: LiteStore, resource, id: string): LSResponse = +proc execute*(req: var LSRequest, LS: LiteStore, resource, + id: string): LSResponse = let middleware = getMiddlewareSeq(LS, resource, id, $req.reqMethod) if middleware.len > 0: LOG.debug("Middleware: " & middleware.join(" -> ")); if middleware.len == 0: return route(req, LS, resource, id) - var jReq = $(%* req) + var jReq = $( %* req) LOG.debug("Request: " & jReq) var jRes = """{ "code": 200,

@@ -1262,6 +1359,7 @@ var ctx = duk_create_heap_default()

duk_console_init(ctx) duk_print_alert_init(ctx) LS.registerStoreApi(ctx, resource, id) + LS.registerHttpApi(ctx) if ctx.duk_peval_string(cstring("($1)" % $jReq)) != 0: return jError(ctx) discard ctx.duk_put_global_string("$req")

@@ -1276,7 +1374,7 @@ var i = 0

var abort = 0 while abort != 1 and i < middleware.len: let code = LS.getMiddleware(middleware[i]) - LOG.debug("Evaluating middleware '$1'" % middleware[i]) + LOG.debug("Evaluating middleware '$1'" % middleware[i]) if ctx.duk_peval_string(code.cstring) != 0: return jError(ctx) abort = ctx.duk_get_boolean(-1)