Jun 24 - Update Resolution Details upload photos section

This commit is contained in:
Nguyen Ngo
2026-06-24 16:34:05 -04:00
parent fc571b3faa
commit e6de1f01b6
6 changed files with 297 additions and 6 deletions
+53
View File
@@ -258,6 +258,59 @@ actor APIClient {
)
}
// Upload a resolution photo (entity_type = issue_result)
// Saves to issue_result_photos subfolder on the server same bucket
// as photos uploaded via the web update form.
func uploadResultPhoto(localPath: String, retrying: Bool = false) async throws -> String {
let url = try buildURL("/api/v1/photos/upload")
guard let imageData = FileManager.default.contents(atPath: localPath) else {
throw APIError.networkError("Could not read photo: \(localPath)")
}
let boundary = "Boundary-\(UUID().uuidString)"
let filename = URL(fileURLWithPath: localPath).lastPathComponent
let ext = (filename as NSString).pathExtension.lowercased()
let mime = ext == "png" ? "image/png" : "image/jpeg"
var body = Data()
body.append("--\(boundary)\r\nContent-Disposition: form-data; name=\"entity_type\"\r\n\r\nissue_result\r\n".data(using: .utf8)!)
body.append("--\(boundary)\r\nContent-Disposition: form-data; name=\"file\"; filename=\"\(filename)\"\r\nContent-Type: \(mime)\r\n\r\n".data(using: .utf8)!)
body.append(imageData)
body.append("\r\n--\(boundary)--\r\n".data(using: .utf8)!)
var req = URLRequest(url: url)
req.httpMethod = "POST"
req.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")
injectToken(&req)
req.httpBody = body
let (data, response) = try await performRequest(req)
if shouldRefresh(response, retrying: retrying) {
let refreshed = await refreshAccessToken()
if refreshed { return try await uploadResultPhoto(localPath: localPath, retrying: true) }
throw APIError.notAuthenticated
}
struct PhotoResult: Decodable, Sendable { let serverPath: String }
if let env = try? decoder.decode(_Envelope<PhotoResult>.self, from: data),
env.ok, let r = env.data { return r.serverPath }
throw APIError.serverError("Result photo upload failed")
}
// Attach resolution photos to an existing issue
// PATCHes /api/v1/issues/{id}/result_photos writes to Issue.result_photos
// (Resolution Details on web), not mobile_photo_paths (Photo Evidence).
func updateIssueResultPhotos(issueId: Int, resultPhotos: [String]) async throws {
struct R: Decodable, Sendable { let issueId: Int; let resultPhotosCount: Int }
let _: R = try await request(
"/api/v1/issues/\(issueId)/result_photos",
method: "PATCH",
body: ["result_photos": resultPhotos]
)
}
// Fetch Issue Detail (status + assigned_to)
func fetchIssueDetail(issueId: Int) async throws -> APIIssueDetail {
+4 -1
View File
@@ -403,6 +403,8 @@ struct APIIssueDetail: Decodable, Sendable {
let verifiedAt: String?
let verificationNote: String?
let reportedByName: String?
// Resolution photos uploaded via web or mobile resolve flow
let resultPhotos: [String]
// Phase E area and assignee context
let areaName: String?
let assignedToName: String?
@@ -422,13 +424,14 @@ struct APIIssueDetail: Decodable, Sendable {
verifiedAt = try? c.decode(String.self, forKey: .verifiedAt)
verificationNote = try? c.decode(String.self, forKey: .verificationNote)
reportedByName = try? c.decode(String.self, forKey: .reportedByName)
resultPhotos = (try? c.decode([String].self, forKey: .resultPhotos)) ?? []
areaName = try? c.decode(String.self, forKey: .areaName)
assignedToName = try? c.decode(String.self, forKey: .assignedToName)
}
private enum CodingKeys: String, CodingKey {
case id, status, severity, description, assignedTo
case facilityId, facilityName, reportedAt, resolvedAt
case resultNotes, verifiedAt, verificationNote, reportedByName
case resultNotes, verifiedAt, verificationNote, reportedByName, resultPhotos
case areaName, assignedToName
}
}