module attachment.api; import handy_http_primitives; import handy_http_data.json; import handy_http_handlers.path_handler : PathMapping; import std.conv; import profile.data; import profile.data_impl_sqlite; import profile.service; import auth.service; import auth.model; import util.data; import attachment.data; import attachment.model; /** * Handles downloading of an attachment. Because the browser is doing the * downloading instead of a Javascript client, this means we have to embed the * user's auth token in the query parameters and deal with it differently from * normal authenticated requests. * Params: * request = The HTTP request. * response = The HTTP response. */ @PathMapping(HttpMethod.GET, "/api/profiles/:profile/attachments/:attachmentId/download") void handleDownloadAttachment(ref ServerHttpRequest request, ref ServerHttpResponse response) { Optional!AuthContext authCtx = extractAuthContextFromQueryParam(request, response); if (authCtx.isNull) return; User user = authCtx.value.user; string profileName = request.getPathParamOrThrow!string("profile"); ProfileRepository repo = new FileSystemProfileRepository(user.username); ProfileContext profileCtx = repo.findByName(profileName) .mapIfPresent!(p => ProfileContext(p, user)) .orElseThrow(() => new HttpStatusException(HttpStatus.NOT_FOUND)); ProfileDataSource ds = getProfileDataSource(profileCtx); ulong attachmentId = request.getPathParamOrThrow!ulong("attachmentId"); AttachmentRepository attachmentRepo = ds.getAttachmentRepository(); Attachment attachment = attachmentRepo.findById(attachmentId) .orElseThrow(() => new HttpStatusException(HttpStatus.NOT_FOUND, "Attachment not found.")); ubyte[] content = attachmentRepo.getContent(attachment.id) .orElseThrow(() => new HttpStatusException( HttpStatus.INTERNAL_SERVER_ERROR, "Couldn't get content for attachment." )); response.headers.add("Content-Disposition", "attachment; filename=\"" ~ attachment.filename ~ "\""); response.writeBodyBytes(content, attachment.contentType); }