module profile.service; import handy_http_primitives; import handy_http_handlers.path_handler; import profile.model; import profile.data; import profile.data_impl_sqlite; import auth.model; /** * Validates a profile name. * Params: * name = The name to check. * Returns: True if the profile name is valid. */ bool validateProfileName(string name) { import std.regex; import std.uni : toLower; if (name is null || name.length < 3) return false; auto r = ctRegex!(`^[a-zA-Z]+[a-zA-Z0-9_-]+$`); return !matchFirst(name, r).empty; } /// Contextual information that's available when handling requests under a profile. struct ProfileContext { const Profile profile; const User user; } /** * Tries to get a profile context from a request. This will attempt to * extract a "profile" path parameter and the authenticated user, and combine * them into the ProfileContext. * Params: * request = The request to read. * Returns: An optional profile context. */ Optional!ProfileContext getProfileContext(in ServerHttpRequest request) { import auth.service : AuthContext, getAuthContext; foreach (param; getPathParams(request)) { if (param.name == "profile") { string profileName = param.value; if (!validateProfileName(profileName)) return Optional!ProfileContext.empty; AuthContext authCtx = getAuthContext(request); if (authCtx is null) return Optional!ProfileContext.empty; User user = authCtx.user; ProfileRepository repo = new FileSystemProfileRepository(user.username); return repo.findByName(profileName) .mapIfPresent!(p => ProfileContext(p, user)); } } return Optional!ProfileContext.empty; } /** * Similar to `getProfileContext`, but throws an HttpStatusException with a * 404 NOT FOUND status if no profile context could be obtained. * Params: * request = The request to read. * Returns: The profile context that was obtained. */ ProfileContext getProfileContextOrThrow(in ServerHttpRequest request) { return getProfileContext(request).orElseThrow(() => new HttpStatusException(HttpStatus.NOT_FOUND)); } /** * Obtains a ProfileDataSource from a ProfileContext. Use this to get access * to all profile data when handling an HTTP request, for example. * Params: * pc = The profile context. * Returns: The profile data source. */ ProfileDataSource getProfileDataSource(in ProfileContext pc) { ProfileRepository profileRepo = new FileSystemProfileRepository(pc.user.username); return profileRepo.getDataSource(pc.profile); } /** * Obtains a ProfileDataSource from an HTTP request context. Use this to * access all profile data when handling the request. * Params: * request = The request. * Returns: The profile data source. */ ProfileDataSource getProfileDataSource(in ServerHttpRequest request) { ProfileContext pc = getProfileContextOrThrow(request); return getProfileDataSource(pc); }