diff --git a/Package.swift b/Package.swift index 1daa49f..ff70882 100644 --- a/Package.swift +++ b/Package.swift @@ -9,6 +9,7 @@ let package = Package( .library(name: "ApiController", targets: ["ApiController"]), .library(name: "AuthClient", targets: ["AuthClient"]), .library(name: "DatabaseClient", targets: ["DatabaseClient"]), + .library(name: "FileClient", targets: ["FileClient"]), .library(name: "PdfClient", targets: ["PdfClient"]), .library(name: "ProjectClient", targets: ["ProjectClient"]), .library(name: "ManualDCore", targets: ["ManualDCore"]), @@ -76,6 +77,13 @@ let package = Package( .product(name: "Vapor", package: "vapor"), ] ), + .target( + name: "FileClient", + dependencies: [ + .product(name: "Dependencies", package: "swift-dependencies"), + .product(name: "DependenciesMacros", package: "swift-dependencies"), + ] + ), .target( name: "PdfClient", dependencies: [ diff --git a/Sources/App/Middleware/ViewRoute+middleware.swift b/Sources/App/Middleware/ViewRoute+middleware.swift index 663542d..4f79011 100644 --- a/Sources/App/Middleware/ViewRoute+middleware.swift +++ b/Sources/App/Middleware/ViewRoute+middleware.swift @@ -14,8 +14,7 @@ private let viewRouteMiddleware: [any Middleware] = [ extension SiteRoute.View { var middleware: [any Middleware]? { switch self { - // TODO: Should pdf require authentication, just here now for testing. - case .project(.detail(_, .pdf)), .login, .signup, .test: + case .login, .signup, .test: return nil case .project, .user: return viewRouteMiddleware diff --git a/Sources/App/configure.swift b/Sources/App/configure.swift index 88c7611..9498442 100644 --- a/Sources/App/configure.swift +++ b/Sources/App/configure.swift @@ -120,7 +120,7 @@ func handlePdf(_ projectID: Project.ID, on request: Request) async throws -> Res let html = try await projectClient.toHTML(projectID) let url = "/tmp/\(projectID)" - try await request.fileio.writeFile(.init(string: html.renderFormatted()), at: "\(url).html") + try await request.fileio.writeFile(.init(string: html.render()), at: "\(url).html") let process = Process() let standardInput = Pipe() @@ -129,21 +129,24 @@ func handlePdf(_ projectID: Project.ID, on request: Request) async throws -> Res process.standardOutput = standardOutput process.executableURL = URL(fileURLWithPath: "/bin/pandoc") process.arguments = [ - "\(url).html", "--pdf-engine=weasyprint", "-f", "html", + "\(url).html", + "--pdf-engine=weasyprint", + "--from=html", "--css=Public/css/pdf.css", "-o", "\(url).pdf", ] try process.run() process.waitUntilExit() - var headers = HTTPHeaders() - headers.add(name: .contentType, value: "application/octet-stream") - headers.add(name: .contentDisposition, value: "attachment") - - let response = try await request.fileio.asyncStreamFile(at: "\(url).pdf", mediaType: .pdf) + 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") + name: .contentDisposition, value: "attachment; filename=Duct-Calc.pdf" + ) return response } diff --git a/Sources/FileClient/Interface.swift b/Sources/FileClient/Interface.swift new file mode 100644 index 0000000..f29ff42 --- /dev/null +++ b/Sources/FileClient/Interface.swift @@ -0,0 +1,29 @@ +import Dependencies +import DependenciesMacros +import Foundation + +extension DependencyValues { + public var fileClient: FileClient { + get { self[FileClient.self] } + set { self[FileClient.self] = newValue } + } +} + +@DependencyClient +public struct FileClient: Sendable { + public var writeFile: @Sendable (String, String) async throws -> Void + public var removeFile: @Sendable (String) async throws -> Void +} + +extension FileClient: DependencyKey { + 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) + } + ) +}