148 lines
4.3 KiB
Nim

import jester, json, logging, options, sequtils, strutils
from re import re, find
import ./models, ./notion_client, ./service, ./version
const JSON = "application/json"
## Response Utilities
template halt(code: HttpCode,
headers: RawHeaders,
content: string) =
## 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] = if isSome(result[2]): some(result[2].get() & headers)
else: some(headers)
result[3] = content
result.matched = true
break allRoutes
template jsonResp(code: HttpCode,
body: string = "",
headersToSend: RawHeaders = @{:} ) =
## Immediately send a JSON response and stop processing the request.
let reqOrigin =
if request.headers.hasKey("Origin"): $(request.headers["Origin"])
else: ""
let corsHeaders =
if cfg.knownOrigins.contains(reqOrigin):
@{
"Access-Control-Allow-Origin": reqOrigin,
"Access-Control-Allow-Credentials": "true",
"Access-Control-Allow-Methods": $(request.reqMethod),
"Access-Control-Allow-Headers": "Authorization,X-CSRF-TOKEN"
}
else: @{:}
halt(
code,
headersToSend & corsHeaders & @{
"Content-Type": JSON,
"Cache-Control": "no-cache"
},
body
)
template dataResp(code: HttpCode,
data: JsonNode,
headersToSend: RawHeaders = @{:} ) =
jsonResp(
code,
$(%* {
"details": "",
"data": data
}),
headersToSend)
template dataResp(data: JsonNode) = dataResp(Http200, data)
template statusResp(code: HttpCode,
details: string = "",
headersToSend: RawHeaders = @{:} ) =
## Helper to send a JSON response based on a given HTTP code.
jsonResp(
code,
$(%* {
"details": details
}),
headersToSend)
template errorResp(err: ref ApiError): void =
debug err.respMsg & ( if err.msg.len > 0: ": " & err.msg else: "")
if not err.parent.isNil: debug " original exception: " & err.parent.msg
statusResp(err.respCode, err.respMsg)
## CORS support
template optionsResp(allowedMethods: seq[HttpMethod]) =
let reqOrigin =
if request.headers.hasKey("Origin"): $(request.headers["Origin"])
else: ""
let corsHeaders =
if cfg.knownOrigins.contains(reqOrigin):
@{
"Access-Control-Allow-Origin": reqOrigin,
"Access-Control-Allow-Credentials": "true",
"Access-Control-Allow-Methods": allowedMethods.mapIt($it).join(", "),
"Access-Control-Allow-Headers": "DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization,X-CSRF-TOKEN"
}
else: @{:}
halt(
Http200,
corsHeaders,
""
)
template withApiErrors(actions: untyped) =
try: actions
except JsonParsingError, ValueError:
echo getCurrentException().getStackTrace()
statusResp(Http400, getCurrentExceptionMsg())
except ApiError: errorResp(cast[ref ApiError](getCurrentException()))
except:
let ex = getCurrentException()
debug ex.getStackTrace
errorResp(newApiError(ex, Http500, "Internal server error", ex.msg))
proc start*(cfg: HffEntryFormsApiConfig): void =
if cfg.debug: setLogFilter(lvlDebug)
settings:
port = Port(cfg.port)
appName = "/v1"
routes:
options "/version": optionsResp(@[HttpGet])
get "/version":
jsonResp(Http200, $(%("hff_entry_forms_api v" & HFF_ENTRY_FORMS_API_VERSION)))
options "/event-proposals/config": optionsResp(@[HttpGet])
get "/event-proposals/config":
withApiErrors:
dataResp(%getEventProposalConfig(cfg))
options "/event-proposals": optionsResp(@[HttpPost])
post "/event-proposals":
withApiErrors:
let ep = parseEventProposal(parseJson(request.body))
if createProposedEvent(cfg, ep): statusResp(Http200)
else: statusResp(Http500)
options re".*": statusResp(Http404, "Not Found")
get re".*": statusResp(Http404, "Not Found")
post re".*": statusResp(Http404, "Not Found")
put re".*": statusResp(Http404, "Not Found")
delete re".*": statusResp(Http404, "Not Found")