module api_mapping; import handy_http_primitives; import handy_http_handlers.path_handler; import handy_http_handlers.filtered_handler; /// The base path to all API endpoints. private const API_PATH = "/api"; /** * Defines the Finnow API mapping with a main PathHandler. * Returns: The handler to plug into an HttpServer. */ HttpRequestHandler mapApiHandlers() { PathHandler h = new PathHandler(); // Generic, public endpoints: h.map(HttpMethod.GET, "/status", &getStatus); h.map(HttpMethod.OPTIONS, "/**", &getOptions); // Dev endpoint for sample data: REMOVE BEFORE DEPLOYING!!! h.map(HttpMethod.POST, "/sample-data", &sampleDataEndpoint); // Auth endpoints: import auth.api; h.map(HttpMethod.POST, "/login", &postLogin); h.map(HttpMethod.POST, "/register", &postRegister); h.map(HttpMethod.GET, "/register/username-availability", &getUsernameAvailability); // Authenticated endpoints: PathHandler a = new PathHandler(); a.map(HttpMethod.GET, "/me", &getMyUser); a.map(HttpMethod.DELETE, "/me", &deleteMyUser); a.map(HttpMethod.GET, "/me/token", &getNewToken); a.map(HttpMethod.POST, "/me/password", &changeMyPassword); import profile.api; a.map(HttpMethod.GET, "/profiles", &handleGetProfiles); a.map(HttpMethod.POST, "/profiles", &handleCreateNewProfile); /// URL path to a specific profile, with the :profile path parameter. const PROFILE_PATH = "/profiles/:profile"; a.map(HttpMethod.GET, PROFILE_PATH, &handleGetProfile); a.map(HttpMethod.DELETE, PROFILE_PATH, &handleDeleteProfile); a.map(HttpMethod.GET, PROFILE_PATH ~ "/properties", &handleGetProperties); // Account endpoints: import account.api; a.map(HttpMethod.GET, PROFILE_PATH ~ "/accounts", &handleGetAccounts); a.map(HttpMethod.POST, PROFILE_PATH ~ "/accounts", &handleCreateAccount); a.map(HttpMethod.GET, PROFILE_PATH ~ "/accounts/:accountId:ulong", &handleGetAccount); a.map(HttpMethod.PUT, PROFILE_PATH ~ "/accounts/:accountId:ulong", &handleUpdateAccount); a.map(HttpMethod.DELETE, PROFILE_PATH ~ "/accounts/:accountId:ulong", &handleDeleteAccount); import transaction.api; // Transaction vendor endpoints: a.map(HttpMethod.GET, PROFILE_PATH ~ "/vendors", &getVendors); a.map(HttpMethod.GET, PROFILE_PATH ~ "/vendors/:vendorId:ulong", &getVendor); a.map(HttpMethod.POST, PROFILE_PATH ~ "/vendors", &createVendor); a.map(HttpMethod.PUT, PROFILE_PATH ~ "/vendors/:vendorId:ulong", &updateVendor); a.map(HttpMethod.DELETE, PROFILE_PATH ~ "/vendors/:vendorId:ulong", &deleteVendor); a.map(HttpMethod.GET, PROFILE_PATH ~ "/transactions", &getTransactions); // Protect all authenticated paths with a filter. import auth.service : AuthenticationFilter; HttpRequestFilter authenticationFilter = new AuthenticationFilter(); h.addMapping(API_PATH ~ "/**", new FilteredHandler( [authenticationFilter], a )); return new CorsHandler(h); } private void getStatus(ref ServerHttpRequest request, ref ServerHttpResponse response) { response.writeBodyString("online", ContentTypes.TEXT_PLAIN); } private void getOptions(ref ServerHttpRequest request, ref ServerHttpResponse response) { // Do nothing, just return 200 OK. } private void sampleDataEndpoint(ref ServerHttpRequest request, ref ServerHttpResponse response) { import slf4d; import util.sample_data; import core.thread; Thread t = new Thread(() { try { generateSampleData(); } catch (Exception e) { error("Error while generating sample data.", e); } }); t.start(); info("Started new thread to generate sample data."); } private void map( PathHandler handler, HttpMethod method, string subPath, void function(ref ServerHttpRequest, ref ServerHttpResponse) fn ) { handler.addMapping(method, API_PATH ~ subPath, HttpRequestHandler.of(fn)); } private class CorsHandler : HttpRequestHandler { private HttpRequestHandler handler; this(HttpRequestHandler handler) { this.handler = handler; } void handle(ref ServerHttpRequest request, ref ServerHttpResponse response) { response.headers.add("Access-Control-Allow-Origin", "http://localhost:5173"); response.headers.add("Access-Control-Allow-Methods", "*"); response.headers.add("Access-Control-Allow-Headers", "Authorization, Content-Type"); try { this.handler.handle(request, response); } catch (HttpStatusException e) { response.status = e.status; response.writeBodyString(e.message.idup); } } }