Skip to content

Commit

Permalink
feat: 파일 업로드 제작
Browse files Browse the repository at this point in the history
  • Loading branch information
kimsh153 committed Jul 9, 2024
1 parent 506e37a commit 3cf7aa0
Show file tree
Hide file tree
Showing 19 changed files with 133 additions and 49 deletions.
60 changes: 32 additions & 28 deletions Projects/App/Sources/Application/NeedleGenerated.swift
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,9 @@ private class GSMAuthenticationDependencydf257bda3051cc91534fProvider: GSMAuthen
var authenticationDomainBuildable: any AuthenticationDomainBuildable {
return appComponent.authenticationDomainBuildable
}
var fileDomainBuildable: any FileDomainBuildable {
return appComponent.fileDomainBuildable
}
private let appComponent: AppComponent
init(appComponent: AppComponent) {
self.appComponent = appComponent
Expand Down Expand Up @@ -471,33 +474,33 @@ extension JwtStoreComponent: Registration {
extension AppComponent: Registration {
public func registerItems() {

localTable["rootComponent-RootComponent"] = { [unowned self] in self.rootComponent as Any }
localTable["signinBuildable-any SigninBuildable"] = { [unowned self] in self.signinBuildable as Any }
localTable["inputInformationBuildable-any InputInformationBuildable"] = { [unowned self] in self.inputInformationBuildable as Any }
localTable["inputProfileInfoBuildable-any InputProfileInfoBuildable"] = { [unowned self] in self.inputProfileInfoBuildable as Any }
localTable["inputSchoolLifeInfoBuildable-any InputSchoolLifeInfoBuildable"] = { [unowned self] in self.inputSchoolLifeInfoBuildable as Any }
localTable["inputWorkInfoBuildable-any InputWorkInfoBuildable"] = { [unowned self] in self.inputWorkInfoBuildable as Any }
localTable["inputMilitaryInfoBuildable-any InputMilitaryInfoBuildable"] = { [unowned self] in self.inputMilitaryInfoBuildable as Any }
localTable["inputCertificateInfoBuildable-any InputCertificateInfoBuildable"] = { [unowned self] in self.inputCertificateInfoBuildable as Any }
localTable["inputLanguageInfoBuildable-any InputLanguageInfoBuildable"] = { [unowned self] in self.inputLanguageInfoBuildable as Any }
localTable["inputPrizeInfoBuildable-any InputPrizeInfoBuildable"] = { [unowned self] in self.inputPrizeInfoBuildable as Any }
localTable["inputProjectInfoBuildable-any InputProjectInfoBuildable"] = { [unowned self] in self.inputProjectInfoBuildable as Any }
localTable["mainBuildable-any MainBuildable"] = { [unowned self] in self.mainBuildable as Any }
localTable["myPageBuildable-any MyPageBuildable"] = { [unowned self] in self.myPageBuildable as Any }
localTable["techStackAppendBuildable-any TechStackAppendBuildable"] = { [unowned self] in self.techStackAppendBuildable as Any }
localTable["studentDetailBuildable-any StudentDetailBuildable"] = { [unowned self] in self.studentDetailBuildable as Any }
localTable["filterBuildable-any FilterBuildable"] = { [unowned self] in self.filterBuildable as Any }
localTable["splashBuildable-any SplashBuildable"] = { [unowned self] in self.splashBuildable as Any }
localTable["gsmAuthenticationBuildable-any GSMAuthenticationBuildable"] = { [unowned self] in self.gsmAuthenticationBuildable as Any }
localTable["authDomainBuildable-any AuthDomainBuildable"] = { [unowned self] in self.authDomainBuildable as Any }
localTable["studentDomainBuildable-any StudentDomainBuildable"] = { [unowned self] in self.studentDomainBuildable as Any }
localTable["majorDomainBuildable-any MajorDomainBuildable"] = { [unowned self] in self.majorDomainBuildable as Any }
localTable["fileDomainBuildable-any FileDomainBuildable"] = { [unowned self] in self.fileDomainBuildable as Any }
localTable["userDomainBuildable-any UserDomainBuildable"] = { [unowned self] in self.userDomainBuildable as Any }
localTable["techStackDomainBuildable-any TechStackDomainBuildable"] = { [unowned self] in self.techStackDomainBuildable as Any }
localTable["jwtStoreBuildable-any JwtStoreBuildable"] = { [unowned self] in self.jwtStoreBuildable as Any }
localTable["keychainBuildable-any KeychainBuildable"] = { [unowned self] in self.keychainBuildable as Any }
localTable["authenticationDomainBuildable-any AuthenticationDomainBuildable"] = { [unowned self] in self.authenticationDomainBuildable as Any }
localTable["rootComponent-RootComponent"] = { self.rootComponent as Any }
localTable["signinBuildable-any SigninBuildable"] = { self.signinBuildable as Any }
localTable["inputInformationBuildable-any InputInformationBuildable"] = { self.inputInformationBuildable as Any }
localTable["inputProfileInfoBuildable-any InputProfileInfoBuildable"] = { self.inputProfileInfoBuildable as Any }
localTable["inputSchoolLifeInfoBuildable-any InputSchoolLifeInfoBuildable"] = { self.inputSchoolLifeInfoBuildable as Any }
localTable["inputWorkInfoBuildable-any InputWorkInfoBuildable"] = { self.inputWorkInfoBuildable as Any }
localTable["inputMilitaryInfoBuildable-any InputMilitaryInfoBuildable"] = { self.inputMilitaryInfoBuildable as Any }
localTable["inputCertificateInfoBuildable-any InputCertificateInfoBuildable"] = { self.inputCertificateInfoBuildable as Any }
localTable["inputLanguageInfoBuildable-any InputLanguageInfoBuildable"] = { self.inputLanguageInfoBuildable as Any }
localTable["inputPrizeInfoBuildable-any InputPrizeInfoBuildable"] = { self.inputPrizeInfoBuildable as Any }
localTable["inputProjectInfoBuildable-any InputProjectInfoBuildable"] = { self.inputProjectInfoBuildable as Any }
localTable["mainBuildable-any MainBuildable"] = { self.mainBuildable as Any }
localTable["myPageBuildable-any MyPageBuildable"] = { self.myPageBuildable as Any }
localTable["techStackAppendBuildable-any TechStackAppendBuildable"] = { self.techStackAppendBuildable as Any }
localTable["studentDetailBuildable-any StudentDetailBuildable"] = { self.studentDetailBuildable as Any }
localTable["filterBuildable-any FilterBuildable"] = { self.filterBuildable as Any }
localTable["splashBuildable-any SplashBuildable"] = { self.splashBuildable as Any }
localTable["gsmAuthenticationBuildable-any GSMAuthenticationBuildable"] = { self.gsmAuthenticationBuildable as Any }
localTable["authDomainBuildable-any AuthDomainBuildable"] = { self.authDomainBuildable as Any }
localTable["studentDomainBuildable-any StudentDomainBuildable"] = { self.studentDomainBuildable as Any }
localTable["majorDomainBuildable-any MajorDomainBuildable"] = { self.majorDomainBuildable as Any }
localTable["fileDomainBuildable-any FileDomainBuildable"] = { self.fileDomainBuildable as Any }
localTable["userDomainBuildable-any UserDomainBuildable"] = { self.userDomainBuildable as Any }
localTable["techStackDomainBuildable-any TechStackDomainBuildable"] = { self.techStackDomainBuildable as Any }
localTable["jwtStoreBuildable-any JwtStoreBuildable"] = { self.jwtStoreBuildable as Any }
localTable["keychainBuildable-any KeychainBuildable"] = { self.keychainBuildable as Any }
localTable["authenticationDomainBuildable-any AuthenticationDomainBuildable"] = { self.authenticationDomainBuildable as Any }
}
}
extension KeychainComponent: Registration {
Expand All @@ -508,6 +511,7 @@ extension KeychainComponent: Registration {
extension GSMAuthenticationComponent: Registration {
public func registerItems() {
keyPathToName[\GSMAuthenticationDependency.authenticationDomainBuildable] = "authenticationDomainBuildable-any AuthenticationDomainBuildable"
keyPathToName[\GSMAuthenticationDependency.fileDomainBuildable] = "fileDomainBuildable-any FileDomainBuildable"
}
}
extension SplashComponent: Registration {
Expand Down Expand Up @@ -665,7 +669,7 @@ private func registerProviderFactory(_ componentPath: String, _ factory: @escapi

#if !NEEDLE_DYNAMIC

@inline(never) private func register1() {
private func register1() {
registerProviderFactory("^->AppComponent->JwtStoreComponent", factoryb27d5aae1eb7e73575a6f47b58f8f304c97af4d5)
registerProviderFactory("^->AppComponent", factoryEmptyDependencyProvider)
registerProviderFactory("^->AppComponent->KeychainComponent", factoryEmptyDependencyProvider)
Expand Down
4 changes: 0 additions & 4 deletions Projects/Core/DesignSystem/Sources/File/SMSFileField.swift
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,6 @@ public struct SMSFileField: View {
isOnClear: false
)
.disabled(true)
.overlay(alignment: .trailing) {
SMSIcon(.folder)
.padding(.trailing, 12)
}
.simultaneousGesture(
TapGesture()
.onEnded {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
public protocol FileDomainBuildable {
var imageUploadUseCase: any ImageUploadUseCase { get }
var fileUploadUseCase: any FileUploadUseCase { get }
var fileRepository: any FileRepository { get }
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ import Foundation

public protocol RemoteFileDataSource {
func imageUpload(image: Data, fileName: String) async throws -> String
func fileUpload(file: Data, fileName: String) async throws -> String
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import Foundation

public enum FileDomainError: Error {
case notImageType
case notFileType
case internalServerError
}

Expand All @@ -11,6 +12,9 @@ extension FileDomainError: LocalizedError {
case .notImageType:
return "이미지 형식이 jpg, jpeg, png, heic인 이미지가 아닙니다."

case .notFileType:
return "파일 형식이 hw, hwpx, png인 파일이 아닙니다."

case .internalServerError:
return "서버에서 문제가 발생하였습니다. 지속될 시 문의해주세요."
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ import Foundation

public protocol FileRepository {
func imageUpload(image: Data, fileName: String) async throws -> String
func fileUpload(file: Data, fileName: String) async throws -> String
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import Foundation

public protocol FileUploadUseCase {
func execute(file: Data, fileName: String) async throws -> String
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ public protocol FileDomainDependency: Dependency {
}

public final class FileDomainComponent: Component<FileDomainDependency>, FileDomainBuildable {
public var fileUploadUseCase: any FileUploadUseCase {
FileUploadUseCaseImpl(fileRepository: fileRepository)
}
public var imageUploadUseCase: any ImageUploadUseCase {
ImageUploadUseCaseImpl(fileRepository: fileRepository)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import Foundation

public struct FileUploadResponseDTO: Decodable {
public let fileURL: String

enum CodingKeys: String, CodingKey {
case fileURL = "fileUrl"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,11 @@ final class RemoteFileDataSourceImpl: BaseRemoteDataSource<FileEndpoint>, Remote
)
.fileURL
}

func fileUpload(file: Data, fileName: String) async throws -> String {
try await request(
.fileUpload(file: file, fileName: fileName),
dto: FileUploadResponseDTO.self
).fileURL
}
}
17 changes: 17 additions & 0 deletions Projects/Domain/FileDomain/Sources/Endpoint/FileEndpoint.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import Foundation

enum FileEndpoint {
case imageUpload(image: Data, fileName: String)
case fileUpload(file: Data, fileName: String)
}

extension FileEndpoint: SMSEndpoint {
Expand All @@ -18,6 +19,9 @@ extension FileEndpoint: SMSEndpoint {
switch self {
case .imageUpload:
return .post("/image")

case .fileUpload:
return .post("")
}
}

Expand All @@ -28,6 +32,11 @@ extension FileEndpoint: SMSEndpoint {
MultiPartFormData(field: "file", data: image, fileName: fileName)
])

case let .fileUpload(file, fileName):
return .uploadMultipart([
MultiPartFormData(field: "file", data: file, fileName: fileName)
])

default:
return .requestPlain
}
Expand All @@ -38,6 +47,9 @@ extension FileEndpoint: SMSEndpoint {
case .imageUpload:
return .accessToken

case .fileUpload:
return .accessToken

default:
return .none
}
Expand All @@ -54,6 +66,11 @@ extension FileEndpoint: SMSEndpoint {
400: .notImageType,
500: .internalServerError
]
case .fileUpload:
return [
400: .notFileType,
500: .internalServerError
]
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,8 @@ struct FileRepositoryImpl: FileRepository {
func imageUpload(image: Data, fileName: String) async throws -> String {
try await remoteFileDataSource.imageUpload(image: image, fileName: fileName)
}

func fileUpload(file: Data, fileName: String) async throws -> String {
try await remoteFileDataSource.fileUpload(file: file, fileName: fileName)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import FileDomainInterface
import Foundation

struct FileUploadUseCaseImpl: FileUploadUseCase {
private let fileRepository: any FileRepository

init(fileRepository: any FileRepository) {
self.fileRepository = fileRepository
}

func execute(file: Data, fileName: String) async throws -> String {
try await fileRepository.fileUpload(file: file, fileName: fileName)
}
}
3 changes: 2 additions & 1 deletion Projects/Feature/GSMAuthenticationFormFeature/Project.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ let project = Project.makeModule(
targets: [.interface, .unitTest, .demo],
internalDependencies: [
.Feature.BaseFeature,
.Domain.AuthenticationDomainInterface
.Domain.AuthenticationDomainInterface,
.Domain.FileDomainInterface
],
demoDependencies: [
.Domain.AuthenticationDomainTesting
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ import NeedleFoundation
import SwiftUI
import GSMAuthenticationFormFeatureInterface
import AuthenticationDomainInterface
import FileDomainInterface

public protocol GSMAuthenticationDependency: Dependency {
var authenticationDomainBuildable: any AuthenticationDomainBuildable { get }
var fileDomainBuildable: any FileDomainBuildable { get }
}

public final class GSMAuthenticationComponent: Component<GSMAuthenticationDependency>, GSMAuthenticationBuildable {
Expand All @@ -15,7 +17,8 @@ public final class GSMAuthenticationComponent: Component<GSMAuthenticationDepend
let intent = GSMAuthenticationFormIntent(
model: model,
fetchAuthenticationFormUseCase: dependency.authenticationDomainBuildable.fetchAuthenticationFormUseCase,
inputAuthenticationUseCase: dependency.authenticationDomainBuildable.inputAuthenticationUseCase
inputAuthenticationUseCase: dependency.authenticationDomainBuildable.inputAuthenticationUseCase,
fileUploadUseCase: dependency.fileDomainBuildable.fileUploadUseCase
)
let container = MVIContainer(
intent: intent as GSMAuthenticationFormIntentProtocol,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import AuthenticationDomainInterface
import FileDomainInterface
import Foundation
import ConcurrencyUtil
import DateUtil
Expand All @@ -7,15 +8,18 @@ final class GSMAuthenticationFormIntent: GSMAuthenticationFormIntentProtocol {
weak var model: (any GSMAuthenticationFormActionProtocol)?
private let fetchAuthenticationFormUseCase: any FetchAuthenticationFormUseCase
private let inputAuthenticationUseCase: any InputAuthenticationUseCase
private let fileUploadUseCase: any FileUploadUseCase

init(
model: any GSMAuthenticationFormActionProtocol,
fetchAuthenticationFormUseCase: any FetchAuthenticationFormUseCase,
inputAuthenticationUseCase: any InputAuthenticationUseCase
inputAuthenticationUseCase: any InputAuthenticationUseCase,
fileUploadUseCase: any FileUploadUseCase
) {
self.model = model
self.fetchAuthenticationFormUseCase = fetchAuthenticationFormUseCase
self.inputAuthenticationUseCase = inputAuthenticationUseCase
self.fileUploadUseCase = fileUploadUseCase
}

func appendField(
Expand Down Expand Up @@ -134,8 +138,13 @@ final class GSMAuthenticationFormIntent: GSMAuthenticationFormIntentProtocol {
model?.updateBoolField(area: area, sectionIndex: sectionIndex, groupIndex: groupIndex, fieldIndex: fieldIndex, select: select)
}

func updateFileField(area: Int, sectionIndex: Int, groupIndex: Int, fieldIndex: Int, file: String) {
model?.updateFileField(area: area, sectionIndex: sectionIndex, groupIndex: groupIndex, fieldIndex: fieldIndex, file: file)
func updateFileField(area: Int, sectionIndex: Int, groupIndex: Int, fieldIndex: Int, file: Data, fileName: String) {
Task {
do {
let fileUrl = try await fileUploadUseCase.execute(file: file, fileName: fileName)
model?.updateFileField(area: area, sectionIndex: sectionIndex, groupIndex: groupIndex, fieldIndex: fieldIndex, file: fileUrl)
}
}
}

func updateSelectField(area: Int, sectionIndex: Int, groupIndex: Int, fieldIndex: Int, select: String) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,10 @@ protocol GSMAuthenticationFormIntentProtocol {
sectionIndex: Int,
groupIndex: Int,
fieldIndex: Int,
file: String
file: Data,
fileName: String
)

func updateSelectField(
area: Int,
sectionIndex: Int,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ enum FieldChanges {
case text(String)
case number(Int)
case boolean(String)
case file(String)
case file(Data, String)
case select(String)
}

Expand Down Expand Up @@ -332,15 +332,18 @@ struct GSMAuthenticationFormBuilderView: View {
) { result in
switch result {
case let .success(url):
onFieldInteraction(
.fieldChanges(
area: areaIndex,
sectionIndex: sectionIndex,
groupIndex: groupIndex,
fieldIndex: fieldIndex,
fieldChanges: .file(url.absoluteString)
if let fileData = try? Data(contentsOf: url) {
let fileName = url.lastPathComponent
onFieldInteraction(
.fieldChanges(
area: areaIndex,
sectionIndex: sectionIndex,
groupIndex: groupIndex,
fieldIndex: fieldIndex,
fieldChanges: .file(fileData, fileName)
)
)
)
}

default:
return
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ struct GSMAuthenticationFormView: View {
intent.updateNumberField(area: area, sectionIndex: section, groupIndex: group, fieldIndex: field, number: number)
case let .boolean(select):
intent.updateBoolField(area: area, sectionIndex: section, groupIndex: group, fieldIndex: field, select: select)
case let .file(file):
intent.updateFileField(area: area, sectionIndex: section, groupIndex: group, fieldIndex: field, file: file)
case let .file(file, fileName):
intent.updateFileField(area: area, sectionIndex: section, groupIndex: group, fieldIndex: field, file: file, fileName: fileName)
case let .select(select):
intent.updateSelectField(area: area, sectionIndex: section, groupIndex: group, fieldIndex: field, select: select)
}
Expand Down

0 comments on commit 3cf7aa0

Please sign in to comment.