feat: Adds pdf client tests, updates view controller snapshots that have changed.
This commit is contained in:
@@ -36,6 +36,7 @@ struct DependenciesMiddleware: AsyncMiddleware {
|
||||
// $0.dateFormatter = .liveValue
|
||||
$0.viewController = viewController
|
||||
$0.pdfClient = .liveValue
|
||||
$0.fileClient = .live(fileIO: request.fileio)
|
||||
} operation: {
|
||||
try await next.respond(to: request)
|
||||
}
|
||||
|
||||
@@ -114,43 +114,6 @@ extension SiteRoute {
|
||||
|
||||
extension DuctSizes: Content {}
|
||||
|
||||
// FIX: Move
|
||||
func handlePdf(_ projectID: Project.ID, on request: Request) async throws -> Response {
|
||||
@Dependency(\.projectClient) var projectClient
|
||||
return try await projectClient.generatePdf(projectID, request.fileio)
|
||||
|
||||
// let html = try await projectClient.toHTML(projectID)
|
||||
// let url = "/tmp/\(projectID)"
|
||||
// try await request.fileio.writeFile(.init(string: html.render()), at: "\(url).html")
|
||||
//
|
||||
// let process = Process()
|
||||
// let standardInput = Pipe()
|
||||
// let standardOutput = Pipe()
|
||||
// process.standardInput = standardInput
|
||||
// process.standardOutput = standardOutput
|
||||
// process.executableURL = URL(fileURLWithPath: "/bin/pandoc")
|
||||
// process.arguments = [
|
||||
// "\(url).html",
|
||||
// "--pdf-engine=weasyprint",
|
||||
// "--from=html",
|
||||
// "--css=Public/css/pdf.css",
|
||||
// "-o", "\(url).pdf",
|
||||
// ]
|
||||
// try process.run()
|
||||
// process.waitUntilExit()
|
||||
//
|
||||
// let response = try await request.fileio.asyncStreamFile(at: "\(url).pdf", mediaType: .pdf) { _ in
|
||||
// // Remove files here.
|
||||
// try FileManager.default.removeItem(atPath: "\(url).pdf")
|
||||
// try FileManager.default.removeItem(atPath: "\(url).html")
|
||||
// }
|
||||
// response.headers.replaceOrAdd(name: .contentType, value: "application/octet-stream")
|
||||
// response.headers.replaceOrAdd(
|
||||
// name: .contentDisposition, value: "attachment; filename=Duct-Calc.pdf"
|
||||
// )
|
||||
// return response
|
||||
}
|
||||
|
||||
@Sendable
|
||||
private func siteHandler(
|
||||
request: Request,
|
||||
@@ -165,9 +128,10 @@ private func siteHandler(
|
||||
return try await apiController.respond(route, request: request)
|
||||
case .health:
|
||||
return HTTPStatus.ok
|
||||
// FIX: Move
|
||||
// Generating a pdf return's a `Response` instead of `HTML` like other views, so we
|
||||
// need to handle it seperately.
|
||||
case .view(.project(.detail(let projectID, .pdf))):
|
||||
return try await handlePdf(projectID, on: request)
|
||||
return try await projectClient.generatePdf(projectID)
|
||||
case .view(let route):
|
||||
return try await viewController.respond(route: route, request: request)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import Dependencies
|
||||
import DependenciesMacros
|
||||
import Foundation
|
||||
import Vapor
|
||||
|
||||
extension DependencyValues {
|
||||
public var fileClient: FileClient {
|
||||
@@ -11,19 +12,29 @@ extension DependencyValues {
|
||||
|
||||
@DependencyClient
|
||||
public struct FileClient: Sendable {
|
||||
public typealias OnCompleteHandler = @Sendable () async throws -> Void
|
||||
|
||||
public var writeFile: @Sendable (String, String) async throws -> Void
|
||||
public var removeFile: @Sendable (String) async throws -> Void
|
||||
public var streamFile: @Sendable (String, @escaping OnCompleteHandler) async throws -> Response
|
||||
}
|
||||
|
||||
extension FileClient: DependencyKey {
|
||||
extension FileClient: TestDependencyKey {
|
||||
public static let testValue = Self()
|
||||
|
||||
public static let liveValue = Self(
|
||||
writeFile: { contents, path in
|
||||
try contents.write(to: URL(fileURLWithPath: path), atomically: true, encoding: .utf8)
|
||||
},
|
||||
removeFile: { path in
|
||||
try FileManager.default.removeItem(atPath: path)
|
||||
}
|
||||
)
|
||||
public static func live(fileIO: FileIO) -> Self {
|
||||
.init(
|
||||
writeFile: { contents, path in
|
||||
try await fileIO.writeFile(ByteBuffer(string: contents), at: path)
|
||||
},
|
||||
removeFile: { path in
|
||||
try FileManager.default.removeItem(atPath: path)
|
||||
},
|
||||
streamFile: { path, onComplete in
|
||||
try await fileIO.asyncStreamFile(at: path) { _ in
|
||||
try await onComplete()
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,8 @@ import ManualDCore
|
||||
|
||||
extension DependencyValues {
|
||||
|
||||
/// Access the pdf client dependency that can be used to generate pdf's for
|
||||
/// a project.
|
||||
public var pdfClient: PdfClient {
|
||||
get { self[PdfClient.self] }
|
||||
set { self[PdfClient.self] = newValue }
|
||||
@@ -16,9 +18,19 @@ extension DependencyValues {
|
||||
|
||||
@DependencyClient
|
||||
public struct PdfClient: Sendable {
|
||||
/// Generate the html used to convert to pdf for a project.
|
||||
public var html: @Sendable (Request) async throws -> (any HTML & Sendable)
|
||||
|
||||
/// Converts the generated html to a pdf.
|
||||
///
|
||||
/// **NOTE:** This is generally not used directly, instead use the overload that accepts a request,
|
||||
/// which generates the html and does the conversion all in one step.
|
||||
public var generatePdf: @Sendable (Project.ID, any HTML & Sendable) async throws -> Response
|
||||
|
||||
/// Generate a pdf for the given project request.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - request: The project data used to generate the pdf.
|
||||
public func generatePdf(request: Request) async throws -> Response {
|
||||
let html = try await self.html(request)
|
||||
return try await self.generatePdf(request.project.id, html)
|
||||
@@ -64,7 +76,7 @@ extension PdfClient: DependencyKey {
|
||||
}
|
||||
|
||||
extension PdfClient {
|
||||
|
||||
/// Container for the data required to generate a pdf for a given project.
|
||||
public struct Request: Codable, Equatable, Sendable {
|
||||
|
||||
public let project: Project
|
||||
|
||||
@@ -28,12 +28,7 @@ public struct ProjectClient: Sendable {
|
||||
@Sendable (User.ID, Project.Create) async throws -> CreateProjectResponse
|
||||
|
||||
public var frictionRate: @Sendable (Project.ID) async throws -> FrictionRateResponse
|
||||
|
||||
// FIX: Name to something to do with generating a pdf, just experimenting now.
|
||||
// public var toMarkdown: @Sendable (Project.ID) async throws -> String
|
||||
// public var toHTML: @Sendable (Project.ID) async throws -> (any HTML & Sendable)
|
||||
|
||||
public var generatePdf: @Sendable (Project.ID, FileIO) async throws -> Response
|
||||
public var generatePdf: @Sendable (Project.ID) async throws -> Response
|
||||
}
|
||||
|
||||
extension ProjectClient: TestDependencyKey {
|
||||
|
||||
@@ -37,14 +37,18 @@ extension ProjectClient: DependencyKey {
|
||||
frictionRate: { projectID in
|
||||
try await manualD.frictionRate(projectID: projectID)
|
||||
},
|
||||
generatePdf: { projectID, fileIO in
|
||||
generatePdf: { projectID in
|
||||
let pdfResponse = try await pdfClient.generatePdf(
|
||||
request: database.makePdfRequest(projectID))
|
||||
request: database.makePdfRequest(projectID)
|
||||
)
|
||||
|
||||
let response = try await fileIO.asyncStreamFile(at: pdfResponse.pdfPath) { _ in
|
||||
try await fileClient.removeFile(pdfResponse.htmlPath)
|
||||
try await fileClient.removeFile(pdfResponse.pdfPath)
|
||||
}
|
||||
let response = try await fileClient.streamFile(
|
||||
pdfResponse.pdfPath,
|
||||
{
|
||||
try await fileClient.removeFile(pdfResponse.htmlPath)
|
||||
try await fileClient.removeFile(pdfResponse.pdfPath)
|
||||
}
|
||||
)
|
||||
|
||||
response.headers.replaceOrAdd(name: .contentType, value: "application/octet-stream")
|
||||
response.headers.replaceOrAdd(
|
||||
|
||||
Reference in New Issue
Block a user