module profile.service; import handy_httpd; import handy_httpd.components.optional; 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 context. This will attempt to * extract a "profile" path parameter and the authenticated user, and combine * them into the ProfileContext. * Params: * ctx = The request context to read. * Returns: An optional profile context. */ Optional!ProfileContext getProfileContext(in HttpRequestContext ctx) { import auth.service : AuthContext, getAuthContext; if ("profile" !in ctx.request.pathParams) return Optional!ProfileContext.empty; string profileName = ctx.request.pathParams["profile"]; if (!validateProfileName(profileName)) return Optional!ProfileContext.empty; AuthContext authCtx = getAuthContext(ctx); 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)); } /** * Similar to `getProfileContext`, but throws an HttpStatusException with a * 404 NOT FOUND status if no profile context could be obtained. * Params: * ctx = The request context to read. * Returns: The profile context that was obtained. */ ProfileContext getProfileContextOrThrow(in HttpRequestContext ctx) { return getProfileContext(ctx).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: * ctx = The request context. * Returns: The profile data source. */ ProfileDataSource getProfileDataSource(in HttpRequestContext ctx) { ProfileContext pc = getProfileContextOrThrow(ctx); return getProfileDataSource(pc); }