141 lines
3.8 KiB
Nim
141 lines
3.8 KiB
Nim
## Personal Issue Tracker API Interface
|
|
## ====================================
|
|
#
|
|
# **NOTE** This is currently not being built as it no longer works under Nim
|
|
# 2.x due to the inability to call system calls (invoke pit via cli) in a
|
|
# gc-safe manner. It should be rewritten to use the functionality exposed by
|
|
# libpit directly rather than calling the pit cli executable. Unfortunately
|
|
# this would require a non-trivial rewrite.
|
|
|
|
import asyncdispatch, cliutils, docopt, jester, json, logging, options, sequtils, strutils
|
|
import nre except toSeq
|
|
|
|
import pit/libpit
|
|
import pit/cliconstants
|
|
|
|
type
|
|
PitApiCfg* = object
|
|
apiKeys*: seq[string]
|
|
global*: PitConfig
|
|
port*: int
|
|
|
|
const TXT = "text/plain"
|
|
|
|
proc raiseEx(reason: string): void = raise newException(Exception, reason)
|
|
|
|
template halt(code: HttpCode,
|
|
headers: RawHeaders,
|
|
content: string): void =
|
|
## Immediately replies with the specified request. This means any further
|
|
## code will not be executed after calling this template in the current
|
|
## route.
|
|
bind TCActionSend, newHttpHeaders
|
|
result[0] = CallbackAction.TCActionSend
|
|
result[1] = code
|
|
result[2] = some(headers)
|
|
result[3] = content
|
|
result.matched = true
|
|
break allRoutes
|
|
|
|
template checkAuth(cfg: PitApiCfg) =
|
|
## Check this request for authentication and authorization information.
|
|
## If the request is not authorized, this template sets up the 401 response
|
|
## correctly. The calling context needs only to return from the route.
|
|
|
|
var authed {.inject.} = false
|
|
|
|
try:
|
|
if not headers(request).hasKey("Authorization"):
|
|
raiseEx "No auth token."
|
|
|
|
let headerVal = headers(request)["Authorization"]
|
|
if not headerVal.startsWith("Bearer "):
|
|
raiseEx "Invalid Authentication type (only 'Bearer' is supported)."
|
|
|
|
if not cfg.apiKeys.contains(headerVal[7..^1]):
|
|
raiseEx "Invalid API key."
|
|
|
|
authed = true
|
|
|
|
except:
|
|
stderr.writeLine "Auth failed: " & getCurrentExceptionMsg()
|
|
halt(
|
|
Http401,
|
|
@{"Content-Type": TXT},
|
|
getCurrentExceptionMsg())
|
|
|
|
proc start*(cfg: PitApiCfg) =
|
|
|
|
var stopFuture = newFuture[void]()
|
|
|
|
settings:
|
|
port = Port(cfg.port)
|
|
appName = "/api"
|
|
|
|
routes:
|
|
|
|
get "/ping":
|
|
resp("pong", TXT)
|
|
|
|
get "/issues":
|
|
checkAuth(cfg)
|
|
|
|
var args = queryParamsToCliArgs(request.params)
|
|
args = @["list"] & args
|
|
|
|
info "args: \n" & args.join(" ")
|
|
let execResult = execWithOutput("pit", ".", args)
|
|
if execResult[2] != 0: resp(Http500, stripAnsi($execResult[0] & "\n" & $execResult[1]), TXT)
|
|
else: resp(stripAnsi(execResult[0]), TXT)
|
|
|
|
post "/issues":
|
|
checkAuth(cfg)
|
|
|
|
get "/issue/@issueId":
|
|
checkAuth(cfg)
|
|
|
|
var args = queryParamsToCliArgs(request.params)
|
|
args = @["list", @"issueId"] & args
|
|
|
|
info "args: \n" & args.join(" ")
|
|
let execResult = execWithOutput("pit", ".", args)
|
|
if execResult[2] != 0: resp(Http500, stripAnsi($execResult[0] & "\n" & $execResult[1]), TXT)
|
|
else: resp(stripAnsi(execResult[0]), TXT)
|
|
|
|
waitFor(stopFuture)
|
|
|
|
proc loadApiConfig(args: Table[string, Value]): PitApiCfg =
|
|
let pitCfg = loadConfig(args)
|
|
let apiJson =
|
|
if pitCfg.cfg.json.hasKey("api"): pitCfg.cfg.json["api"]
|
|
else: newJObject()
|
|
|
|
let apiCfg = CombinedConfig(docopt: args, json: apiJson)
|
|
|
|
let apiKeysArray =
|
|
if apiJson.hasKey("apiKeys"): apiJson["apiKeys"]
|
|
else: newJArray()
|
|
|
|
result = PitApiCfg(
|
|
apiKeys: toSeq(apiKeysArray).mapIt(it.getStr),
|
|
global: pitCfg,
|
|
port: parseInt(apiCfg.getVal("port", "8123")))
|
|
|
|
when isMainModule:
|
|
|
|
let doc = """\
|
|
Usage:
|
|
pit_api [options]
|
|
|
|
Options:
|
|
|
|
-c, --config <cfgFile> Path to the pit_api config file.
|
|
-d, --tasks-dir Path to the tasks directory.
|
|
-p, --port Port to listen on (defaults to 8123)
|
|
"""
|
|
|
|
let args = docopt(doc, version = PIT_VERSION)
|
|
|
|
let apiCfg = loadApiConfig(args)
|
|
start(apiCfg)
|