diff --git a/finnow-api/source/api_mapping.d b/finnow-api/source/api_mapping.d index 8ff9612..a817e1f 100644 --- a/finnow-api/source/api_mapping.d +++ b/finnow-api/source/api_mapping.d @@ -152,6 +152,9 @@ private void map( handler.addMapping(method, API_PATH ~ subPath, HttpRequestHandler.of(fn)); } +/** + * A filter that adds CORS response headers. + */ private class CorsFilter : HttpRequestFilter { private string webOrigin; @@ -168,6 +171,10 @@ private class CorsFilter : HttpRequestFilter { } } +/** + * A filter that rejects requests with a body that's too large, to avoid issues + * later with handling such large objects in memory. + */ private class ContentLengthFilter : HttpRequestFilter { const MAX_LENGTH = 1024 * 1024 * 20; // 2MB limit @@ -192,6 +199,10 @@ private class ContentLengthFilter : HttpRequestFilter { } } +/** + * A filter that catches any exception thrown by the filter chain, and nicely + * formats the response status and message. + */ private class ExceptionHandlingFilter : HttpRequestFilter { void doFilter(ref ServerHttpRequest request, ref ServerHttpResponse response, FilterChain filterChain) { try { diff --git a/finnow-api/source/app.d b/finnow-api/source/app.d index ab9cf71..3fcc63f 100644 --- a/finnow-api/source/app.d +++ b/finnow-api/source/app.d @@ -1,37 +1,73 @@ import handy_http_transport; import slf4d; import slf4d.default_provider; -import scheduled; -import std.datetime; import api_mapping; import util.config; -import analytics; +import scheduled_jobs; +/** + * Starts the Finnow API. + */ void main() { + // testUDA(); + const config = readConfig(); + configureSlf4d(config); + startScheduledJobs(); + startWebServer(config); +} + +void configureSlf4d(in AppConfig config) { Level logLevel = getConfiguredLoggingLevel(config); auto provider = new DefaultProvider(logLevel); configureLoggingProvider(provider); - infoF!"Loaded app config: port = %d, webOrigin = %s"(config.port, config.webOrigin); - - // Start scheduled tasks in a separate thread: - JobSchedule analyticsSchedule = new FixedIntervalSchedule( - hours(1), - Clock.currTime(UTC()) + seconds(10) - ); - - JobScheduler jobScheduler = new TaskPoolScheduler(); - jobScheduler.addJob(() { - info("Computing account balance time series analytics for all users..."); - doForAllUserProfiles(&computeAccountBalanceTimeSeries); - doForAllUserProfiles(&computeCategorySpendTimeSeries); - info("Done computing analytics!"); - }, analyticsSchedule); - jobScheduler.start(); +} +void startWebServer(in AppConfig config) { Http1TransportConfig transportConfig = defaultConfig(); transportConfig.port = config.port; HttpTransport transport = new TaskPoolHttp1Transport(mapApiHandlers(config.webOrigin), transportConfig); transport.start(); } + +// void testUDA() { +// import std.traits : getSymbolsByUDA; +// import std.stdio; +// static assert(getSymbolsByUDA!(app, Attr).length == 2); +// Attr a; +// static foreach(symbol; getSymbolsByUDA!(app, Attr)) { +// pragma(msg, symbol); +// pragma(msg, __traits(identifier, symbol)); +// pragma(msg, "------------------"); +// static foreach(attr; __traits(getAttributes, symbol)) { +// pragma(msg, "Attribute:"); +// pragma(msg, attr); +// pragma(msg, __traits(identifier, attr)); +// static if (is(typeof(attr) == Attr)) { +// pragma(msg, "Found target attribute!"); +// pragma(msg, attr.val); +// a = attr; +// writefln!"Function %s has attr val = %d"((__traits(identifier, symbol)), a.val); +// } else { +// pragma(msg, "Other attribute :("); +// } +// } +// } +// } + +// struct Attr { +// int val; +// } + +// enum OtherAttr; + +// @Attr(5) @OtherAttr +// void testMethod1() { +// int x = 5; +// } + +// @Attr(42) +// void testMethod2() { +// int y = 5; +// } diff --git a/finnow-api/source/scheduled_jobs.d b/finnow-api/source/scheduled_jobs.d new file mode 100644 index 0000000..44a01b5 --- /dev/null +++ b/finnow-api/source/scheduled_jobs.d @@ -0,0 +1,23 @@ +module scheduled_jobs; + +import scheduled; +import std.datetime; +import slf4d; + +import analytics; + +void startScheduledJobs() { + JobSchedule analyticsSchedule = new FixedIntervalSchedule( + hours(1), + Clock.currTime(UTC()) + seconds(5) + ); + + JobScheduler jobScheduler = new TaskPoolScheduler(); + jobScheduler.addJob(() { + info("Computing account balance time series analytics for all users..."); + doForAllUserProfiles(&computeAccountBalanceTimeSeries); + doForAllUserProfiles(&computeCategorySpendTimeSeries); + info("Done computing analytics!"); + }, analyticsSchedule); + jobScheduler.start(); +} \ No newline at end of file diff --git a/finnow-api/source/util/config.d b/finnow-api/source/util/config.d index 75a1831..43ee19a 100644 --- a/finnow-api/source/util/config.d +++ b/finnow-api/source/util/config.d @@ -2,12 +2,34 @@ module util.config; import std.stdio; import slf4d; +import asdf; +/** + * The path of the config file to attempt to load. + */ private const CONFIG_FILE = "finnow-api-config.json"; +/** + * Finnow API configuration struct, which contains properties used to + * configure various parts of the API. + */ struct AppConfig { + /** + * The port on which the HTTP server will accept requests. + */ ushort port; + /** + * The address at which the corresponding Finnow website is available to + * clients. In development, this will be another localhost address, but in + * deployed environments, it'll be the actual website with a legitimate + * domain name. This is used to supply the Access-Control-Allow-Origin + * CORS header, which can help to prevent cross-site-scripting attacks. + */ string webOrigin; + /** + * The logging level to use globally in the application. See SLF4D for more: + * https://andrewlalis.github.io/slf4d/guide/using-slf4d.html#logging-levels + */ string logLevel; } @@ -31,6 +53,8 @@ AppConfig readConfig() { ); // Local dev environment if no config is given. if (!exists(CONFIG_FILE)) { + const defaultConfigJson = serializeToJson(defaultConfig); + infoF!"No config file \"%s\" found. Using defaults: %s."(CONFIG_FILE, defaultConfigJson); return defaultConfig; } JSONValue obj;