Skip to content
This repository has been archived by the owner on Apr 27, 2024. It is now read-only.

Commit

Permalink
Finished application document upload
Browse files Browse the repository at this point in the history
  • Loading branch information
carlobortolan committed Sep 12, 2023
1 parent 3bb3c16 commit bc54470
Show file tree
Hide file tree
Showing 8 changed files with 98 additions and 30 deletions.
4 changes: 4 additions & 0 deletions mobile.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
227E9C992AABBB4100049CAB /* JobManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 227E9C982AABBB4100049CAB /* JobManager.swift */; };
227E9C9B2AABBB5500049CAB /* ApplicationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 227E9C9A2AABBB5500049CAB /* ApplicationManager.swift */; };
227E9C9D2AABECBA00049CAB /* OwnApplicationButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 227E9C9C2AABECBA00049CAB /* OwnApplicationButton.swift */; };
228800CE2AB10C4300AE9A4B /* FileFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 228800CD2AB10C4300AE9A4B /* FileFormatter.swift */; };
228E0CEB2AADFF8100AEF8A2 /* JobsMapAnnotation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 228E0CEA2AADFF8100AEF8A2 /* JobsMapAnnotation.swift */; };
22B8041B2AA890BC008D7108 /* URLImage in Frameworks */ = {isa = PBXBuildFile; productRef = 22B8041A2AA890BC008D7108 /* URLImage */; };
22B8041D2AA891ED008D7108 /* JobDetail.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22B8041C2AA891ED008D7108 /* JobDetail.swift */; };
Expand Down Expand Up @@ -148,6 +149,7 @@
227E9C982AABBB4100049CAB /* JobManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JobManager.swift; sourceTree = "<group>"; };
227E9C9A2AABBB5500049CAB /* ApplicationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApplicationManager.swift; sourceTree = "<group>"; };
227E9C9C2AABECBA00049CAB /* OwnApplicationButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OwnApplicationButton.swift; sourceTree = "<group>"; };
228800CD2AB10C4300AE9A4B /* FileFormatter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileFormatter.swift; sourceTree = "<group>"; };
228E0CEA2AADFF8100AEF8A2 /* JobsMapAnnotation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JobsMapAnnotation.swift; sourceTree = "<group>"; };
22B8041C2AA891ED008D7108 /* JobDetail.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JobDetail.swift; sourceTree = "<group>"; };
22B8041E2AA8F813008D7108 /* APIHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = APIHandler.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -369,6 +371,7 @@
isa = PBXGroup;
children = (
227E9C902AAB9A5500049CAB /* DateFormatter.swift */,
228800CD2AB10C4300AE9A4B /* FileFormatter.swift */,
22D927872AA7FAE90017678C /* HtmlView.swift */,
22430F972AAA5CEC00747827 /* ImagePicker.swift */,
22D927822AA7B4FE0017678C /* JobListView.swift */,
Expand Down Expand Up @@ -634,6 +637,7 @@
227E9C9B2AABBB5500049CAB /* ApplicationManager.swift in Sources */,
2216EA832A8D762400C970DD /* ExploreView.swift in Sources */,
22B8046D2AA91D54008D7108 /* OwnApplicationsView.swift in Sources */,
228800CE2AB10C4300AE9A4B /* FileFormatter.swift in Sources */,
22B8046F2AA92781008D7108 /* AccountInfo.swift in Sources */,
22430FA22AAAB52900747827 /* AppInfoView.swift in Sources */,
22B8041D2AA891ED008D7108 /* JobDetail.swift in Sources */,
Expand Down
Binary file not shown.
10 changes: 7 additions & 3 deletions mobile/Controllers/ApplicationManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -136,17 +136,18 @@ class ApplicationManager: ObservableObject {
}
}

func submitApplication(iteration: Int, jobId: Int, userId: Int, message: String, cv: Data?) {
func submitApplication(iteration: Int, jobId: Int, userId: Int, message: String, cv: Data?, format: [String]?, completion: @escaping () -> Void) {
print("Iteration \(iteration)")
let application = Application(jobId: jobId, userId: userId, createdAt: "", updatedAt: "", status: "0", applicationText: message, applicationDocuments: nil, response: nil)
if let accessToken = authenticationManager.getAccessToken() {
APIManager.createApplication(accessToken: accessToken, application: application, cv: cv) { tokenResponse in
APIManager.createApplication(accessToken: accessToken, application: application, cv: cv, format: format) { tokenResponse in
switch tokenResponse {
case .success(let apiResponse):
DispatchQueue.main.async {
print("case .success \(apiResponse)")
self.errorHandlingManager.errorMessage = nil
self.ownApplications.append(application)
completion()
}
case .failure(let error):
DispatchQueue.main.async {
Expand All @@ -158,18 +159,21 @@ class ApplicationManager: ObservableObject {
// Refresh the access token and retry the request
self.authenticationManager.requestAccessToken() { accessTokenSuccess in
if accessTokenSuccess{
self.submitApplication(iteration: 1, jobId: jobId, userId: userId, message: message, cv: cv)
self.submitApplication(iteration: 1, jobId: jobId, userId: userId, message: message, cv: cv, format: format, completion: completion)
} else {
self.errorHandlingManager.errorMessage = error.localizedDescription
completion()
}
}
} else {
print("case .else")
self.errorHandlingManager.errorMessage = error.localizedDescription
completion()
}
} else {
self.authenticationManager.isAuthenticated = false
self.errorHandlingManager.errorMessage = "Tokens expired. Log in to refresh tokens."
completion()
}
}
}
Expand Down
3 changes: 3 additions & 0 deletions mobile/Controllers/ErrorHandlingManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ enum APIError: Error {
case forbidden // 403
case notFound // 404
case noContent(String)
case fileFormatError(String)

// TODO: Add other error cases as needed

Expand Down Expand Up @@ -57,6 +58,8 @@ enum APIError: Error {
return "Not found. This page does not exist."
case .noContent(let cause):
return "No \(cause) found"
case .fileFormatError(let message):
return "File Format Error: \(message)"
}
}
}
6 changes: 3 additions & 3 deletions mobile/Networking/APIManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -167,9 +167,9 @@ class APIManager {
/// - Parameters:
/// - accessToken: The user's access token for authentication.
/// - completion: A closure that receives a `Result` with a `APIResponse` or an `APIError`.
static func createApplication(accessToken: String, application: Application, cv: Data?, completion: @escaping (Result<APIResponse, APIError>) -> Void) {
if let data = cv {
ApplicationHandler.createAttachmentApplication(accessToken: accessToken, application: application, attachment: data, completion: completion)
static func createApplication(accessToken: String, application: Application, cv: Data?, format: [String]?, completion: @escaping (Result<APIResponse, APIError>) -> Void) {
if let data = cv, let format = format {
ApplicationHandler.createAttachmentApplication(accessToken: accessToken, application: application, attachment: data, format: format, completion: completion)
} else {
ApplicationHandler.createNormalApplication(accessToken: accessToken, application: application, completion: completion)
}
Expand Down
18 changes: 10 additions & 8 deletions mobile/Networking/ApplicationHandler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ class ApplicationHandler {
}
}

static func createAttachmentApplication(accessToken: String, application: Application, attachment: Data, completion: @escaping (Result<APIResponse, APIError>) -> Void) {
static func createAttachmentApplication(accessToken: String, application: Application, attachment: Data, format: [String], completion: @escaping (Result<APIResponse, APIError>) -> Void) {
print("Started creating application with: \naccess_token: \(accessToken)\njobId: \(application.jobId)\nuserId: \(application.userId)")

// Create a unique boundary string
Expand Down Expand Up @@ -133,14 +133,16 @@ class ApplicationHandler {
body.append("Content-Disposition: form-data; name=\"application_text\"\r\n\r\n".data(using: .utf8)!)
body.append("\(application.applicationText)\r\n".data(using: .utf8)!)

body.append("--\(boundary)\r\n".data(using: .utf8)!)
body.append("Content-Disposition: form-data; name=\"application_attachment\"; filename=\"\(application.jobId)_\(application.userId)_cv.pdf\"\r\n".data(using: .utf8)!)
body.append("Content-Type: application/pdf\r\n\r\n".data(using: .utf8)!)

// Append the file data here
body.append(attachment)
// Update the "Content-Type" and filename based on the specified format
for formatItem in format {
body.append("--\(boundary)\r\n".data(using: .utf8)!)
let filename = "\(application.jobId)_\(application.userId)_cv\(formatItem)"
body.append("Content-Disposition: form-data; name=\"application_attachment\"; filename=\"\(filename)\"\r\n".data(using: .utf8)!)
body.append("Content-Type: application/\(formatItem)\r\n\r\n".data(using: .utf8)!)
body.append(attachment)
body.append("\r\n".data(using: .utf8)!)
}

body.append("\r\n".data(using: .utf8)!)
body.append("--\(boundary)--\r\n".data(using: .utf8)!)

request.httpBody = body
Expand Down
38 changes: 22 additions & 16 deletions mobile/Views/Components/ApplicationPopup.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ struct ApplicationPopup: View {
@Binding var isVisible: Bool
@Binding var message: String
@State var cvData: Data?
@State var fileName: String = ""
@State private var isLoading = false
@State private var isPickingDocument = false
var job: Job
Expand Down Expand Up @@ -60,11 +61,11 @@ struct ApplicationPopup: View {
.cornerRadius(10)
.overlay(
RoundedRectangle(cornerRadius: 10)
.foregroundColor(Color("SuccessColor"))
.foregroundColor(Color("SecondaryColor"))
.border(Color("FgColor"), width: 3)
.padding(.horizontal, 10.0)
.overlay(
Text("UPLOAD CV [\(String(describing: job.allowedCvFormat))]")
Text(fileName == "" ? "UPLOAD CV \(job.allowedCvFormat.joined(separator: ", "))" : fileName)
.font(.headline)
.fontWeight(.black)
.foregroundColor(Color.white)
Expand All @@ -73,50 +74,55 @@ struct ApplicationPopup: View {
}
.padding()
.cornerRadius(10)
.fileImporter(isPresented: $isPickingDocument, allowedContentTypes: [.pdf], onCompletion: handleDocumentSelection)
.fileImporter(isPresented: $isPickingDocument, allowedContentTypes: FileFormatter.toUTType(allowedCvFormat: job.allowedCvFormat), onCompletion: handleDocumentSelection)
}


Button(action: {
applicationManager.submitApplication(iteration: 0, jobId: job.jobId, userId: authenticationManager.current.userId, message: message, cv: cvData)
isVisible = false
}) {
isLoading = true
DispatchQueue.main.async {
applicationManager.submitApplication(iteration: 0, jobId: job.jobId, userId: authenticationManager.current.userId, message: message, cv: cvData, format: job.allowedCvFormat) {
isLoading = false
isVisible = false
}
} }) {
RoundedRectangle(cornerRadius: 10)
.foregroundColor(Color("FeedBgColor"))
.cornerRadius(10)
.overlay(
RoundedRectangle(cornerRadius: 10)
.foregroundColor(Color("SuccessColor"))
.foregroundColor(cvData != nil || !job.cvRequired ? Color("SuccessColor") : Color.gray)
.border(Color("FgColor"), width: 3)
.padding(.horizontal, 10.0)
.overlay(
Text("APPLY")
.font(/*@START_MENU_TOKEN@*/.title/*@END_MENU_TOKEN@*/)
.font(.title)
.fontWeight(.black)
.foregroundColor(Color.white)
)
)
}
.padding()
.cornerRadius(10)

.disabled(cvData == nil && job.cvRequired)
}
)
)
}
}

private func handleDocumentSelection(result: Result<URL, Error>) {
if case .success(let url) = result {
func handleDocumentSelection(result: Result<URL, Error>) {
switch result {
case .success(let selectedURL):
do {
cvData = try Data(contentsOf: url)
cvData = try Data(contentsOf: selectedURL)
} catch {
// Handle error while reading file data
print("Error reading file data: \(error)")
}
} else if case .failure(let error) = result {
// Handle document picker error
print("Document picker error: \(error)")
fileName = selectedURL.lastPathComponent
case .failure(let error):
print("File Selection Error: \(error.localizedDescription)")
}
}

}
49 changes: 49 additions & 0 deletions mobile/Views/Shared/FileFormatter.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
//
// FileFormatter.swift
// mobile
//
// Created by cb on 12.09.23.
//

import Foundation
import UniformTypeIdentifiers

struct FileFormatter {

static func toUTType(allowedCvFormat: [String]) -> [UTType] {
let recognizedFileExtensions: [String: UTType] = [
".pdf": .pdf,
".xml": .xml,
".txt": .plainText,
".docx": UTType("org.openxmlformats.wordprocessingml.document")!
]

var allowedCvTypes: [UTType] = []

for fileExtension in allowedCvFormat {
let lowercasedExtension = fileExtension.lowercased()

if let utType = recognizedFileExtensions[lowercasedExtension] {
allowedCvTypes.append(utType)
} else {
// Handle custom or unsupported file extensions here
print("Custom or unsupported file extension: \(fileExtension)")
// Instead of fatalError, you can choose to handle it differently
}
}
print("STRINGTypes: \(allowedCvFormat)")
print("UTTypes: \(allowedCvTypes)")
return allowedCvTypes
}

func handleDocumentSelection(fileName: String, result: Result<URL, Error>, completion: @escaping (Result<String, APIError>) -> Void) {
switch result {
case .success(let selectedURL):
return completion(.success(selectedURL.lastPathComponent))

case .failure(let error):
print("File Selection Error: \(error.localizedDescription)")
return completion(.failure(APIError.fileFormatError(error.localizedDescription)))
}
}
}

0 comments on commit bc54470

Please sign in to comment.