all repos — litestore @ 56a0d5f135e28ecc25eb532b8c8cca78ee68f21c

A minimalist nosql document store.

Implemented GET/OPTIONS /v1/info.
h3rald h3rald@h3rald.com
Sun, 01 Feb 2015 13:07:41 +0100
commit

56a0d5f135e28ecc25eb532b8c8cca78ee68f21c

parent

55556dcb64a73be868f2385f3eae0905e2c2fad2

4 files changed, 109 insertions(+), 49 deletions(-)

jump to
M api_v1.nimapi_v1.nim

@@ -1,4 +1,4 @@

-import asynchttpserver2, asyncdispatch, strutils, cgi, strtabs, pegs, json +import asynchttpserver2, asyncdispatch, strutils, cgi, strtabs, pegs, json, os import types, core, utils # Helper procs

@@ -51,7 +51,7 @@ for f in fragments:

f.parseQueryOption(options) -proc validate(req: Request, LS: LiteStore, id: string, cb: proc(req: Request, LS: LiteStore, id: string):Response): Response = +proc validate(req: Request, LS: LiteStore, resource: string, id: string, cb: proc(req: Request, LS: LiteStore, resource: string, id: string):Response): Response = if req.reqMethod == "POST" or req.reqMethod == "PUT" or req.reqMethod == "PATCH": var ct = "" let body = req.body.strip

@@ -67,7 +67,7 @@ except:

return resError(Http400, "Invalid JSON content - $1" % getCurrentExceptionMsg()) else: discard - return cb(req, LS, id) + return cb(req, LS, resource, id) proc applyPatchOperation(tags: var seq[string], op: string, path: string, value: string): bool = var matches = @[""]

@@ -171,9 +171,23 @@ content["sort"] = %options.orderby

content["total"] = %total content["results"] = docs result.headers = ctJsonHeader() - result.content = $content + result.content = content.pretty result.code = Http200 +proc getInfo(LS: LiteStore): Response = + let total_docs = LS.store.countDocuments() + let total_tags = LS.store.countTags() + let tags = LS.store.retrieveTagsWithTotals() + var content = newJObject() + content["version"] = %(LS.appname & " v" & LS.appversion) + content["size"] = %($((LS.file.getFileSize().float/(1024*1024)).formatFloat(ffDecimal, 2)) & " MB") + content["total_documents"] = %total_docs + content["total_tags"] = %total_tags + content["tags"] = tags + result.headers = ctJsonHeader() + result.content = content.pretty + result.code = Http200 + proc postDocument(LS: LiteStore, body: string, ct: string): Response = try: var doc = LS.store.createDocument("", body, ct)

@@ -248,23 +262,34 @@ return LS.getRawDocument(id)

# Main routing -proc options(req: Request, LS: LiteStore, id = ""): Response = - if id != "": - result.code = Http204 - result.content = "" - if LS.readonly: - result.headers = {"Allow": "HEAD,GET"}.newStringTable - else: - result.headers = {"Allow": "HEAD,GET,PUT,PATCH,DELETE"}.newStringTable - else: - result.code = Http204 - result.content = "" - if LS.readonly: - result.headers = {"Allow": "HEAD,GET"}.newStringTable +proc options(req: Request, LS: LiteStore, resource: string, id = ""): Response = + case resource: + of "info": + if id != "": + return resError(Http404, "Info '$1' not found." % id) + else: + result.code = Http204 + result.content = "" + result.headers = {"Allow": "GET"}.newStringTable + of "docs": + if id != "": + result.code = Http204 + result.content = "" + if LS.readonly: + result.headers = {"Allow": "HEAD,GET"}.newStringTable + else: + result.headers = {"Allow": "HEAD,GET,PUT,PATCH,DELETE"}.newStringTable + else: + result.code = Http204 + result.content = "" + if LS.readonly: + result.headers = {"Allow": "HEAD,GET"}.newStringTable + else: + result.headers = {"Allow": "HEAD,GET,POST"}.newStringTable else: - result.headers = {"Allow": "HEAD,GET,POST"}.newStringTable + discard # never happens really. -proc head(req: Request, LS: LiteStore, id = ""): Response = +proc head(req: Request, LS: LiteStore, resource: string, id = ""): Response = var options = newQueryOptions() options.select = "id, content_type, binary, searchable, created, modified" try:

@@ -276,21 +301,30 @@ return LS.getRawDocuments(options)

except: return resError(Http400, "Bad request - $1" % getCurrentExceptionMsg()) -proc get(req: Request, LS: LiteStore, id = ""): Response = - var options = newQueryOptions() - try: - parseQueryOptions(req.url.query, options); - if id != "": - if req.url.query.contains("raw=true") or req.headers["Content-Type"] == "application/json": - return LS.getRawDocument(id, options) - else: - return LS.getDocument(id, options) +proc get(req: Request, LS: LiteStore, resource: string, id = ""): Response = + case resource: + of "docs": + var options = newQueryOptions() + try: + parseQueryOptions(req.url.query, options); + if id != "": + if req.url.query.contains("raw=true") or req.headers["Content-Type"] == "application/json": + return LS.getRawDocument(id, options) + else: + return LS.getDocument(id, options) + else: + return LS.getRawDocuments(options) + except: + return resError(Http400, "Bad request - $1" % getCurrentExceptionMsg()) + of "info": + if id != "": + return resError(Http404, "Info '$1' not found." % id) + return LS.getInfo() else: - return LS.getRawDocuments(options) - except: - return resError(Http400, "Bad request - $1" % getCurrentExceptionMsg()) + discard # never happens really. -proc post(req: Request, LS: LiteStore, id = ""): Response = + +proc post(req: Request, LS: LiteStore, resource: string, id = ""): Response = if id == "": var ct = "text/plain" if req.headers.hasKey("Content-type"):

@@ -299,7 +333,7 @@ return LS.postDocument(req.body.strip, ct)

else: return resError(Http400, "Bad request: document ID cannot be specified in POST requests.") -proc put(req: Request, LS: LiteStore, id = ""): Response = +proc put(req: Request, LS: LiteStore, resource: string, id = ""): Response = if id != "": var ct = "text/plain" if req.headers.hasKey("Content-type"):

@@ -308,19 +342,19 @@ return LS.putDocument(id, req.body.strip, ct)

else: return resError(Http400, "Bad request: document ID must be specified in PUT requests.") -proc delete(req: Request, LS: LiteStore, id = ""): Response = +proc delete(req: Request, LS: LiteStore, resource: string, id = ""): Response = if id != "": return LS.deleteDocument(id) else: return resError(Http400, "Bad request: document ID must be specified in DELETE requests.") -proc patch(req: Request, LS: LiteStore, id = ""): Response = +proc patch(req: Request, LS: LiteStore, resource: string, id = ""): Response = if id != "": return LS.patchDocument(id, req.body) else: return resError(Http400, "Bad request: document ID must be specified in PATCH requests.") -proc route*(req: Request, LS: LiteStore, id = ""): Response = +proc route*(req: Request, LS: LiteStore, resource = "docs", id = ""): Response = var reqMethod = req.reqMethod if req.headers.hasKey("X-HTTP-Method-Override"): reqMethod = req.headers["X-HTTP-Method-Override"]

@@ -328,24 +362,24 @@ case reqMethod.toUpper:

of "POST": if LS.readonly: return resError(Http405, "Method not allowed: $1" % req.reqMethod) - return validate(req, LS, id, post) + return validate(req, LS, resource, id, post) of "PUT": if LS.readonly: return resError(Http405, "Method not allowed: $1" % req.reqMethod) - return validate(req, LS, id, put) + return validate(req, LS, resource, id, put) of "DELETE": if LS.readonly: return resError(Http405, "Method not allowed: $1" % req.reqMethod) - return validate(req, LS, id, delete) + return validate(req, LS, resource, id, delete) of "HEAD": - return validate(req, LS, id, head) + return validate(req, LS, resource, id, head) of "OPTIONS": - return validate(req, LS, id, options) + return validate(req, LS, resource, id, options) of "GET": - return validate(req, LS, id, get) + return validate(req, LS, resource, id, get) of "PATCH": if LS.readonly: return resError(Http405, "Method not allowed: $1" % req.reqMethod) - return validate(req, LS, id, patch) + return validate(req, LS, resource, id, patch) else: return resError(Http405, "Method not allowed: $1" % req.reqMethod)
M core.nimcore.nim

@@ -119,6 +119,10 @@ for doc in raw_documents:

documents.add store.prepareJsonDocument(doc) return %documents +proc countDocuments*(store: Datastore): int64 = + return store.db.getRow(SQL_COUNT_DOCUMENTS)[0].parseInt + + # Manage Tags proc createTag*(store: Datastore, tagid, documentid: string, system=false) =

@@ -147,6 +151,19 @@ var tags = newSeq[JsonNode](0)

for tag in raw_tags: tags.add(%[("id", %tag[0]), ("documents", %(tag[1].parseInt))]) return $(%tags) + +proc countTags*(store: Datastore): int64 = + return store.db.getRow(SQL_COUNT_TAGS)[0].parseInt + +proc retrieveTagsWithTotals*(store: Datastore): JsonNode = + var data = store.db.getAllRows(SQL_SELECT_TAGS_WITH_TOTALS) + var tag_array = newSeq[JsonNode](0) + for row in data: + var obj = newJObject() + obj[row[0]] = %row[1].parseInt + tag_array.add(obj) + return %tag_array + proc packDir*(store: Datastore, dir: string) = if not dir.dirExists:
M queries.nimqueries.nim

@@ -3,10 +3,6 @@

# SQL QUERIES -const SQL_COUNT_TAGS* = sql""" -SELECT COUNT(DISTINCT tag_id) FROM tags -""" - const SQL_CREATE_DOCUMENTS_TABLE* = sql""" CREATE TABLE documents ( id TEXT PRIMARY KEY,

@@ -112,3 +108,16 @@ SELECT id FROM documents, tags

WHERE documents.id = tags.document_id AND tag_id = ? """ + +const SQL_SELECT_TAGS_WITH_TOTALS* = sql""" +SELECT DISTINCT tag_id, COUNT(document_id) +FROM tags GROUP BY tag_id ORDER BY tag_id ASC +""" + +const SQL_COUNT_TAGS* = sql""" +SELECT COUNT(DISTINCT tag_id) FROM tags +""" + +const SQL_COUNT_DOCUMENTS* = sql""" +SELECT COUNT(id) FROM documents +"""
M server.nimserver.nim

@@ -20,8 +20,8 @@

proc route(req: Request, LS: LiteStore): Response = try: var info = req.parseApiUrl - if info.version == "v1" and info.resource == "docs": - return api_v1.route(req, LS, info.id) + if info.version == "v1" and info.resource.match(peg"^docs / info$"): + return api_v1.route(req, LS, info.resource, info.id) else: if info.version != "v1": return resError(Http400, "Bad request - Invalid API version: $1" % info.version)