Cleaned up the code a bit, added some testing code for UDAs for path handler stuff.
Build and Deploy API / build-and-deploy (push) Has been cancelled Details
Build and Deploy Web App / build-and-deploy (push) Has been cancelled Details

This commit is contained in:
andrewlalis 2026-01-10 19:10:18 -05:00
parent b7da823ddb
commit d9310d5979
4 changed files with 113 additions and 19 deletions

View File

@ -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 {

View File

@ -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;
// }

View File

@ -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();
}

View File

@ -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:
* <a>https://andrewlalis.github.io/slf4d/guide/using-slf4d.html#logging-levels</a>
*/
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;