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")