148 lines
5.5 KiB
D
148 lines
5.5 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.
|
|
* Params:
|
|
* webOrigin: The origin to use when configuring CORS headers.
|
|
* Returns: The handler to plug into an HttpServer.
|
|
*/
|
|
HttpRequestHandler mapApiHandlers(string webOrigin) {
|
|
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", &handleGetVendors);
|
|
a.map(HttpMethod.GET, PROFILE_PATH ~ "/vendors/:vendorId:ulong", &handleGetVendor);
|
|
a.map(HttpMethod.POST, PROFILE_PATH ~ "/vendors", &handleCreateVendor);
|
|
a.map(HttpMethod.PUT, PROFILE_PATH ~ "/vendors/:vendorId:ulong", &handleUpdateVendor);
|
|
a.map(HttpMethod.DELETE, PROFILE_PATH ~ "/vendors/:vendorId:ulong", &handleDeleteVendor);
|
|
// Transaction category endpoints:
|
|
a.map(HttpMethod.GET, PROFILE_PATH ~ "/categories", &handleGetCategories);
|
|
a.map(HttpMethod.GET, PROFILE_PATH ~ "/categories/:categoryId:ulong", &handleGetCategory);
|
|
a.map(HttpMethod.POST, PROFILE_PATH ~ "/categories", &handleCreateCategory);
|
|
a.map(HttpMethod.PUT, PROFILE_PATH ~ "/categories/:categoryId:ulong", &handleUpdateCategory);
|
|
a.map(HttpMethod.DELETE, PROFILE_PATH ~ "/categories/:categoryId:ulong", &handleDeleteCategory);
|
|
// Transaction endpoints:
|
|
a.map(HttpMethod.GET, PROFILE_PATH ~ "/transactions", &handleGetTransactions);
|
|
a.map(HttpMethod.GET, PROFILE_PATH ~ "/transactions/:transactionId:ulong", &handleGetTransaction);
|
|
a.map(HttpMethod.POST, PROFILE_PATH ~ "/transactions", &handleAddTransaction);
|
|
a.map(HttpMethod.PUT, PROFILE_PATH ~ "/transactions/:transactionId:ulong", &handleUpdateTransaction);
|
|
a.map(HttpMethod.DELETE, PROFILE_PATH ~ "/transactions/:transactionId:ulong", &handleDeleteTransaction);
|
|
|
|
a.map(HttpMethod.GET, PROFILE_PATH ~ "/transaction-tags", &handleGetAllTags);
|
|
|
|
import data_api;
|
|
// Various other data endpoints:
|
|
a.map(HttpMethod.GET, "/currencies", &handleGetCurrencies);
|
|
|
|
// 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, webOrigin);
|
|
}
|
|
|
|
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;
|
|
private string webOrigin;
|
|
|
|
this(HttpRequestHandler handler, string webOrigin) {
|
|
this.handler = handler;
|
|
this.webOrigin = webOrigin;
|
|
}
|
|
|
|
void handle(ref ServerHttpRequest request, ref ServerHttpResponse response) {
|
|
response.headers.add("Access-Control-Allow-Origin", webOrigin);
|
|
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);
|
|
}
|
|
}
|
|
}
|