Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

πŸ”€ :: [#122] 곡지 상세 Feature κ΅¬ν˜„ #132

Merged
merged 15 commits into from
Jul 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
af9e543
:seedling: :: [#122] DetailNoticeFeature / κΈ°λ³Έ 파일 μ„ΈνŒ…
baekteun Jul 27, 2023
8c2322d
:lipstick: :: [#122] DetailNoticeFeature / 곡지 상세 UI
baekteun Jul 28, 2023
1c533d5
:sparkles: :: [#122] NoticeDomain / FetchNoticeUseCase Implementation
baekteun Jul 28, 2023
2c94613
:sparkles: :: [#122] DetailNoticeFeature / fetchNoticeUseCase DI
baekteun Jul 29, 2023
a8a818c
:sparkles: :: [#122] DetailNoticeFeature / 곡지 상세 데이터 바인딩
baekteun Jul 29, 2023
50dc26f
:sparkles: :: [#122] NoticeFeature / DetailNotice ν™”λ©΄μ „ν™˜
baekteun Jul 29, 2023
80b7473
:sparkles: :: [#122] NoticeDomain / 곡지 μ‚­μ œ UseCase
baekteun Jul 29, 2023
c62ec3e
:sparkles: :: [#122] DetailNoticeFeature / κΆŒν•œμ— 따라 곡지 μ‚­μ œ κΈ°λŠ₯ λΆ€μ—¬
baekteun Jul 29, 2023
efba3ba
:sparkles: :: [#122] DetailNoticeFeature / 상세 곡지 제λͺ©, μˆ˜μ •μΌ 말고 생성일 ν‘œμ‹œ
baekteun Jul 29, 2023
e961e8f
:white_check_mark: :: [#122] DetailNoticeFeature / 곡지 선택 및 ν•΄μ œ μ•‘μ…˜ Tests
baekteun Jul 29, 2023
3c10e52
:white_check_mark: :: [#122] DetailNoticeFeature / removeNoticeUseCas…
baekteun Jul 29, 2023
9fb2c8f
:sparkles: :: [#122] MusicFeature / λ‚ μ§œ 선택 Bar λ²„νŠΌ μ œμ™Έμ‹œμΌœλ†“κΈ°
baekteun Jul 29, 2023
bad5f59
:pencil2: :: [#122] DetailNoticeFeature / DemoApp에도 μžŠμ–΄λ¨Ήμ€ DI
baekteun Jul 29, 2023
3d4fd6e
Merge branch 'master' of https://github.com/Team-Ampersand/Dotori-iOS…
baekteun Jul 30, 2023
cf1b396
:pencil2: :: [#122] MusicFeature / λ³€μˆ˜λͺ… λ°”κΎΌκ±° λŒ€μ‘
baekteun Jul 30, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ public extension TargetDependency {
}

public extension TargetDependency.SPM {
static let Nuke = TargetDependency.external(name: "Nuke")
static let Anim = TargetDependency.external(name: "Anim")
static let CombineMiniature = TargetDependency.external(name: "CombineMiniature")
static let AsyncNeiSwift = TargetDependency.external(name: "AsyncNeiSwift")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ public extension ModulePaths {
public extension ModulePaths {
enum Feature: String, MicroTargetPathConvertable {
case ProposeMusicFeature
case DetailNoticeFeature
case MyViolationListFeature
case SplashFeature
case ConfirmationDialogFeature
Expand Down
2 changes: 2 additions & 0 deletions Projects/App/Sources/Application/AppDelegate.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import AuthDomain
import ConfirmationDialogFeature
import Database
import DetailNoticeFeature
import HomeFeature
import IQKeyboardManagerSwift
import JwtStore
Expand Down Expand Up @@ -48,6 +49,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
HomeAssembly(),
MyViolationListAssembly(),
NoticeAssembly(),
DetailNoticeAssembly(),
SelfStudyAssembly(),
ConfirmationDialogAssembly(),
MassageAssembly(),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
public protocol RemoteNoticeDataSource {
func fetchNoticeList() async throws -> [NoticeEntity]
func fetchNotice(id: Int) async throws -> DetailNoticeEntity
func removeNotice(id: Int) async throws
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import BaseDomainInterface
import Foundation

public struct DetailNoticeEntity: Equatable {
public let id: Int
public let title: String
public let content: String
public let role: UserRoleType
public let images: [NoticeImage]
public let createdDate: Date
public let modifiedDate: Date?

public init(
id: Int,
title: String,
content: String,
role: UserRoleType,
images: [DetailNoticeEntity.NoticeImage],
createdDate: Date,
modifiedDate: Date? = nil
) {
self.id = id
self.title = title
self.content = content
self.role = role
self.images = images
self.createdDate = createdDate
self.modifiedDate = modifiedDate
}

public struct NoticeImage: Equatable {
public let id: Int
public let imageURL: String

public init(id: Int, imageURL: String) {
self.id = id
self.imageURL = imageURL
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
public typealias DetailNoticeModel = DetailNoticeEntity
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
public protocol NoticeRepository {
func fetchNoticeList() async throws -> [NoticeEntity]
func fetchNotice(id: Int) async throws -> DetailNoticeEntity
func removeNotice(id: Int) async throws
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
public protocol FetchNoticeUseCase {
func callAsFunction(id: Int) async throws -> DetailNoticeModel
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
public protocol RemoveNoticeUseCase {
func callAsFunction(id: Int) async throws
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,13 @@ public final class NoticeDomainAssembly: Assembly {
container.register(FetchNoticeListUseCase.self) { resolver in
FetchNoticeListUseCaseImpl(noticeRepository: resolver.resolve(NoticeRepository.self)!)
}

container.register(FetchNoticeUseCase.self) { resolver in
FetchNoticeUseCaseImpl(noticeRepository: resolver.resolve(NoticeRepository.self)!)
}

container.register(RemoveNoticeUseCase.self) { resolver in
RemoveNoticeUseCaseImpl(noticeRepository: resolver.resolve(NoticeRepository.self)!)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import BaseDomainInterface
import DateUtility
import Foundation
import NoticeDomainInterface

struct FetchNoticeResponseDTO: Decodable {
let id: Int
let title: String
let content: String
let role: UserRoleType
let boardImage: [BoardImage]
let createdDate: String
let modifiedDate: String?

struct BoardImage: Decodable {
let id: Int
let url: String

init(id: Int, url: String) {
self.id = id
self.url = url
}
}
}

extension FetchNoticeResponseDTO {
func toDomain() -> DetailNoticeEntity {
DetailNoticeEntity(
id: id,
title: title,
content: content,
role: role,
images: boardImage.map { $0.toDomain() },
createdDate: createdDate.toDateWithCustomFormat("yyyy-MM-dd'T'HH:mm:ss.SSS"),
modifiedDate: modifiedDate?.toDateWithCustomFormat("yyyy-MM-dd'T'HH:mm:ss.SSS")
)
}
}

extension FetchNoticeResponseDTO.BoardImage {
func toDomain() -> DetailNoticeEntity.NoticeImage {
.init(id: id, imageURL: url)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import NetworkingInterface

enum NoticeEndpoint {
case fetchNoticeList
case fetchNotice(id: Int)
case removeNotice(id: Int)
}

extension NoticeEndpoint: DotoriEndpoint {
Expand All @@ -14,6 +16,12 @@ extension NoticeEndpoint: DotoriEndpoint {
switch self {
case .fetchNoticeList:
return .get("")

case let .fetchNotice(id):
return .get("/\(id)")

case let .removeNotice(id):
return .delete("/\(id)")
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,16 @@ final class RemoteNoticeDataSourceImpl: RemoteNoticeDataSource {
)
.toDomain()
}

func fetchNotice(id: Int) async throws -> DetailNoticeEntity {
try await networking.request(
NoticeEndpoint.fetchNotice(id: id),
dto: FetchNoticeResponseDTO.self
)
.toDomain()
}

func removeNotice(id: Int) async throws {
try await networking.request(NoticeEndpoint.removeNotice(id: id))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,12 @@ final class NoticeRepositoryImpl: NoticeRepository {
func fetchNoticeList() async throws -> [NoticeEntity] {
try await remoteNoticeDataSource.fetchNoticeList()
}

func fetchNotice(id: Int) async throws -> DetailNoticeEntity {
try await remoteNoticeDataSource.fetchNotice(id: id)
}

func removeNotice(id: Int) async throws {
try await remoteNoticeDataSource.removeNotice(id: id)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import NoticeDomainInterface

struct FetchNoticeUseCaseImpl: FetchNoticeUseCase {
private let noticeRepository: any NoticeRepository

init(noticeRepository: any NoticeRepository) {
self.noticeRepository = noticeRepository
}

func callAsFunction(id: Int) async throws -> DetailNoticeModel {
try await noticeRepository.fetchNotice(id: id)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import NoticeDomainInterface

struct RemoveNoticeUseCaseImpl: RemoveNoticeUseCase {
private let noticeRepository: any NoticeRepository

init(noticeRepository: any NoticeRepository) {
self.noticeRepository = noticeRepository
}

func callAsFunction(id: Int) async throws {
try await noticeRepository.removeNotice(id: id)
}
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import BaseDomainInterface
import NoticeDomainInterface

final class RemoteNoticeDataSourceSpy: RemoteNoticeDataSource {
Expand All @@ -7,4 +8,27 @@ final class RemoteNoticeDataSourceSpy: RemoteNoticeDataSource {
fetchNoticeListCallCount += 1
return fetchNoticeListReturn
}

var fetchNoticeCallCount = 0
var fetchNoticeHandler: (Int) async throws -> DetailNoticeEntity = { _ in
.init(
id: 1,
title: "",
content: "",
role: .member,
images: [],
createdDate: .init()
)
}
func fetchNotice(id: Int) async throws -> DetailNoticeEntity {
fetchNoticeCallCount += 1
return try await fetchNoticeHandler(id)
}

var removeNoticeCallCount = 0
var removeNoticeHandler: (Int) async throws -> Void = { _ in }
func removeNotice(id: Int) async throws {
removeNoticeCallCount += 1
try await removeNoticeHandler(id)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,27 @@ final class NoticeRepositorySpy: NoticeRepository {
fetchNoticeListCallCount += 1
return fetchNoticeListReturn
}

var fetchNoticeCallCount = 0
var fetchNoticeHandler: (Int) async throws -> DetailNoticeEntity = { _ in
.init(
id: 1,
title: "",
content: "",
role: .member,
images: [],
createdDate: .init()
)
}
func fetchNotice(id: Int) async throws -> DetailNoticeEntity {
fetchNoticeCallCount += 1
return try await fetchNoticeHandler(id)
}

var removeNoticeCallCount = 0
var removeNoticeHandler: (Int) async throws -> Void = { _ in }
func removeNotice(id: Int) async throws {
removeNoticeCallCount += 1
try await removeNoticeHandler(id)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import NoticeDomainInterface

final class FetchNoticeUseCaseSpy: FetchNoticeUseCase {
var fetchNoticeCallCount = 0
var fetchNoticeHandler: (Int) async throws -> DetailNoticeModel = { _ in
.init(
id: 1,
title: "",
content: "",
role: .member,
images: [],
createdDate: .init()
)
}
func callAsFunction(id: Int) async throws -> DetailNoticeModel {
fetchNoticeCallCount += 1
return try await fetchNoticeHandler(id)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import NoticeDomainInterface

final class RemoveNoticeUseCaseSpy: RemoveNoticeUseCase {
var removeNoticeCallCount = 0
var removeNoticeHandler: (Int) async throws -> Void = { _ in }
func callAsFunction(id: Int) async throws {
removeNoticeCallCount += 1
try await removeNoticeHandler(id)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import NoticeDomainInterface
import XCTest
@testable import NoticeDomain
@testable import NoticeDomainTesting

final class FetchNoticeUseCaseTests: XCTestCase {
var noticeRepository: NoticeRepositorySpy!
var sut: FetchNoticeUseCaseImpl!

override func setUp() {
noticeRepository = .init()
sut = .init(noticeRepository: noticeRepository)
}

override func tearDown() {
noticeRepository = nil
sut = nil
}

func testCallAsFunction() async throws {
let date = Date()
XCTAssertEqual(noticeRepository.fetchNoticeCallCount, 0)
let expected = DetailNoticeEntity(
id: 1,
title: "b",
content: "aj",
role: .councillor,
images: [],
createdDate: .init(),
modifiedDate: nil
)
noticeRepository.fetchNoticeHandler = { _ in expected }

let actual = try await sut(id: 1)

XCTAssertEqual(noticeRepository.fetchNoticeCallCount, 1)
XCTAssertEqual(actual, expected)
}
}
22 changes: 21 additions & 1 deletion Projects/Domain/NoticeDomain/Tests/NoticeRepositoryTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,32 @@ final class NoticeRepositoryTests: XCTestCase {
func testFetchNoticeList() async throws {
let date = Date()
XCTAssertEqual(remoteNoticeDataSource.fetchNoticeListCallCount, 0)
let expected = [NoticeModel(id: 1, title: "title2", content: "contents", roles: .member, createdTime: date)]
let expected = [NoticeEntity(id: 1, title: "title2", content: "contents", roles: .member, createdTime: date)]
remoteNoticeDataSource.fetchNoticeListReturn = expected

let actual = try await sut.fetchNoticeList()

XCTAssertEqual(remoteNoticeDataSource.fetchNoticeListCallCount, 1)
XCTAssertEqual(actual, expected)
}

func testFetchNotice() async throws {
let date = Date()
XCTAssertEqual(remoteNoticeDataSource.fetchNoticeCallCount, 0)
let expected = DetailNoticeEntity(id: 1, title: "title2", content: "contents", role: .member, images: [], createdDate: date)
remoteNoticeDataSource.fetchNoticeHandler = { _ in expected }

let actual = try await sut.fetchNotice(id: 1)

XCTAssertEqual(remoteNoticeDataSource.fetchNoticeCallCount, 1)
XCTAssertEqual(actual, expected)
}

func testRemoveNotice() async throws {
XCTAssertEqual(remoteNoticeDataSource.removeNoticeCallCount, 0)

try await sut.removeNotice(id: 1)

XCTAssertEqual(remoteNoticeDataSource.removeNoticeCallCount, 1)
}
}
Loading
Loading