finnow/finnow-api/source/api_mapping.d

128 lines
4.3 KiB
D

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