feat: Adds file client.
This commit is contained in:
@@ -9,6 +9,7 @@ let package = Package(
|
|||||||
.library(name: "ApiController", targets: ["ApiController"]),
|
.library(name: "ApiController", targets: ["ApiController"]),
|
||||||
.library(name: "AuthClient", targets: ["AuthClient"]),
|
.library(name: "AuthClient", targets: ["AuthClient"]),
|
||||||
.library(name: "DatabaseClient", targets: ["DatabaseClient"]),
|
.library(name: "DatabaseClient", targets: ["DatabaseClient"]),
|
||||||
|
.library(name: "FileClient", targets: ["FileClient"]),
|
||||||
.library(name: "PdfClient", targets: ["PdfClient"]),
|
.library(name: "PdfClient", targets: ["PdfClient"]),
|
||||||
.library(name: "ProjectClient", targets: ["ProjectClient"]),
|
.library(name: "ProjectClient", targets: ["ProjectClient"]),
|
||||||
.library(name: "ManualDCore", targets: ["ManualDCore"]),
|
.library(name: "ManualDCore", targets: ["ManualDCore"]),
|
||||||
@@ -76,6 +77,13 @@ let package = Package(
|
|||||||
.product(name: "Vapor", package: "vapor"),
|
.product(name: "Vapor", package: "vapor"),
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
|
.target(
|
||||||
|
name: "FileClient",
|
||||||
|
dependencies: [
|
||||||
|
.product(name: "Dependencies", package: "swift-dependencies"),
|
||||||
|
.product(name: "DependenciesMacros", package: "swift-dependencies"),
|
||||||
|
]
|
||||||
|
),
|
||||||
.target(
|
.target(
|
||||||
name: "PdfClient",
|
name: "PdfClient",
|
||||||
dependencies: [
|
dependencies: [
|
||||||
|
|||||||
@@ -14,8 +14,7 @@ private let viewRouteMiddleware: [any Middleware] = [
|
|||||||
extension SiteRoute.View {
|
extension SiteRoute.View {
|
||||||
var middleware: [any Middleware]? {
|
var middleware: [any Middleware]? {
|
||||||
switch self {
|
switch self {
|
||||||
// TODO: Should pdf require authentication, just here now for testing.
|
case .login, .signup, .test:
|
||||||
case .project(.detail(_, .pdf)), .login, .signup, .test:
|
|
||||||
return nil
|
return nil
|
||||||
case .project, .user:
|
case .project, .user:
|
||||||
return viewRouteMiddleware
|
return viewRouteMiddleware
|
||||||
|
|||||||
@@ -120,7 +120,7 @@ func handlePdf(_ projectID: Project.ID, on request: Request) async throws -> Res
|
|||||||
|
|
||||||
let html = try await projectClient.toHTML(projectID)
|
let html = try await projectClient.toHTML(projectID)
|
||||||
let url = "/tmp/\(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 process = Process()
|
||||||
let standardInput = Pipe()
|
let standardInput = Pipe()
|
||||||
@@ -129,21 +129,24 @@ func handlePdf(_ projectID: Project.ID, on request: Request) async throws -> Res
|
|||||||
process.standardOutput = standardOutput
|
process.standardOutput = standardOutput
|
||||||
process.executableURL = URL(fileURLWithPath: "/bin/pandoc")
|
process.executableURL = URL(fileURLWithPath: "/bin/pandoc")
|
||||||
process.arguments = [
|
process.arguments = [
|
||||||
"\(url).html", "--pdf-engine=weasyprint", "-f", "html",
|
"\(url).html",
|
||||||
|
"--pdf-engine=weasyprint",
|
||||||
|
"--from=html",
|
||||||
"--css=Public/css/pdf.css",
|
"--css=Public/css/pdf.css",
|
||||||
"-o", "\(url).pdf",
|
"-o", "\(url).pdf",
|
||||||
]
|
]
|
||||||
try process.run()
|
try process.run()
|
||||||
process.waitUntilExit()
|
process.waitUntilExit()
|
||||||
|
|
||||||
var headers = HTTPHeaders()
|
let response = try await request.fileio.asyncStreamFile(at: "\(url).pdf", mediaType: .pdf) { _ in
|
||||||
headers.add(name: .contentType, value: "application/octet-stream")
|
// Remove files here.
|
||||||
headers.add(name: .contentDisposition, value: "attachment")
|
try FileManager.default.removeItem(atPath: "\(url).pdf")
|
||||||
|
try FileManager.default.removeItem(atPath: "\(url).html")
|
||||||
let response = try await request.fileio.asyncStreamFile(at: "\(url).pdf", mediaType: .pdf)
|
}
|
||||||
response.headers.replaceOrAdd(name: .contentType, value: "application/octet-stream")
|
response.headers.replaceOrAdd(name: .contentType, value: "application/octet-stream")
|
||||||
response.headers.replaceOrAdd(
|
response.headers.replaceOrAdd(
|
||||||
name: .contentDisposition, value: "attachment; filename=Duct-Calc.pdf")
|
name: .contentDisposition, value: "attachment; filename=Duct-Calc.pdf"
|
||||||
|
)
|
||||||
return response
|
return response
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
29
Sources/FileClient/Interface.swift
Normal file
29
Sources/FileClient/Interface.swift
Normal file
@@ -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)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user