diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml
index 27b9d3af5..167de5273 100644
--- a/.github/workflows/CI.yml
+++ b/.github/workflows/CI.yml
@@ -1,13 +1,14 @@
name: CI
on:
+ pull_request:
+ paths:
+ - ".github/workflows/CI.yml"
+ - "**/*.swift"
push:
branches:
- develop
- master
- pull_request:
- branches:
- - "*"
env:
CACHED_PACKAGE_DEPENDENCY_PATHS: ${{ github.workspace }}/.build
diff --git a/Package.swift b/Package.swift
index 01bc6f408..1e7a5772c 100644
--- a/Package.swift
+++ b/Package.swift
@@ -11,6 +11,7 @@ let packageSetting = PackageSettings(
baseSettings: .settings(
configurations: [
.debug(name: .debug),
+ .debug(name: .qa),
.release(name: .release)
]
)
diff --git a/Plugin/ConfigurationPlugin/ProjectDescriptionHelpers/Configuration+Ext.swift b/Plugin/ConfigurationPlugin/ProjectDescriptionHelpers/Configuration+Ext.swift
index b300374f0..96724758f 100644
--- a/Plugin/ConfigurationPlugin/ProjectDescriptionHelpers/Configuration+Ext.swift
+++ b/Plugin/ConfigurationPlugin/ProjectDescriptionHelpers/Configuration+Ext.swift
@@ -3,6 +3,11 @@ import ProjectDescription
public extension Array where Element == Configuration {
static let `default`: [Configuration] = [
.debug(name: .debug, xcconfig: .relativeToRoot("Projects/App/XCConfig/Secrets.xcconfig")),
+ .debug(name: .qa, xcconfig: .relativeToRoot("Projects/App/XCConfig/Secrets.xcconfig")),
.release(name: .release, xcconfig: .relativeToRoot("Projects/App/XCConfig/Secrets.xcconfig"))
]
}
+
+public extension ProjectDescription.ConfigurationName {
+ static let qa = ConfigurationName.configuration("QA")
+}
diff --git a/Projects/App/Project.swift b/Projects/App/Project.swift
index e51acd313..c73afcdfe 100644
--- a/Projects/App/Project.swift
+++ b/Projects/App/Project.swift
@@ -8,6 +8,7 @@ let settinges: Settings =
base: env.baseSetting,
configurations: [
.debug(name: .debug),
+ .debug(name: .qa),
.release(name: .release)
],
defaultSettings: .recommended
@@ -56,9 +57,11 @@ let targets: [Target] = [
base: env.baseSetting,
configurations: [
.debug(name: .debug, xcconfig: "XCConfig/Secrets.xcconfig"),
+ .debug(name: .qa, xcconfig: "XCConfig/Secrets.xcconfig"),
.release(name: .release, xcconfig: "XCConfig/Secrets.xcconfig")
]
- )
+ ),
+ environmentVariables: ["NETWORK_LOG_LEVEL": "short"]
),
.target(
name: "\(env.name)Tests",
@@ -76,7 +79,7 @@ let targets: [Target] = [
let schemes: [Scheme] = [
.scheme(
- name: "\(env.name)Tests-DEBUG",
+ name: "\(env.name)-DEBUG",
shared: true,
buildAction: .buildAction(targets: ["\(env.name)"]),
testAction: TestAction.targets(
@@ -92,6 +95,16 @@ let schemes: [Scheme] = [
profileAction: .profileAction(configuration: .debug),
analyzeAction: .analyzeAction(configuration: .debug)
),
+ .scheme(
+ name: "\(env.name)-QA",
+ shared: true,
+ buildAction: .buildAction(targets: ["\(env.name)"]),
+ testAction: nil,
+ runAction: .runAction(configuration: .qa),
+ archiveAction: .archiveAction(configuration: .qa),
+ profileAction: .profileAction(configuration: .qa),
+ analyzeAction: .analyzeAction(configuration: .qa)
+ ),
.scheme(
name: "\(env.name)-RELEASE",
shared: true,
diff --git a/Projects/App/Sources/Application/AppComponent+Base.swift b/Projects/App/Sources/Application/AppComponent+Base.swift
index d2a763c8e..5387df404 100644
--- a/Projects/App/Sources/Application/AppComponent+Base.swift
+++ b/Projects/App/Sources/Application/AppComponent+Base.swift
@@ -3,12 +3,12 @@ import BaseFeatureInterface
import Foundation
public extension AppComponent {
- var multiPurposePopUpFactory: any MultiPurposePopupFactory {
+ var multiPurposePopupFactory: any MultiPurposePopupFactory {
MultiPurposePopupComponent(parent: self)
}
- var textPopUpFactory: any TextPopUpFactory {
- TextPopUpComponent(parent: self)
+ var textPopupFactory: any TextPopupFactory {
+ TextPopupComponent(parent: self)
}
var containSongsFactory: any ContainSongsFactory {
diff --git a/Projects/App/Sources/Application/AppComponent+Credit.swift b/Projects/App/Sources/Application/AppComponent+Credit.swift
index 84a4a88f6..c59d3f88c 100644
--- a/Projects/App/Sources/Application/AppComponent+Credit.swift
+++ b/Projects/App/Sources/Application/AppComponent+Credit.swift
@@ -24,6 +24,12 @@ public extension AppComponent {
}
}
+ var fetchCreditProfileUseCase: any FetchCreditProfileUseCase {
+ shared {
+ FetchCreditProfileUseCaseImpl(creditRepository: creditRepository)
+ }
+ }
+
var songCreditFactory: any SongCreditFactory {
SongCreditComponent(parent: self)
}
diff --git a/Projects/App/Sources/Application/AppComponent+MyInfo.swift b/Projects/App/Sources/Application/AppComponent+MyInfo.swift
index 442aabc5a..9535416f7 100644
--- a/Projects/App/Sources/Application/AppComponent+MyInfo.swift
+++ b/Projects/App/Sources/Application/AppComponent+MyInfo.swift
@@ -34,4 +34,8 @@ extension AppComponent {
var profilePopupFactory: any ProfilePopupFactory {
ProfilePopupComponent(parent: self)
}
+
+ var playTypeTogglePopupFactory: any PlayTypeTogglePopupFactory {
+ PlayTypeTogglePopupComponent(parent: self)
+ }
}
diff --git a/Projects/App/Sources/Application/AppComponent+Playlist.swift b/Projects/App/Sources/Application/AppComponent+Playlist.swift
index e6534e14a..cf39453fb 100644
--- a/Projects/App/Sources/Application/AppComponent+Playlist.swift
+++ b/Projects/App/Sources/Application/AppComponent+Playlist.swift
@@ -80,6 +80,12 @@ public extension AppComponent {
}
}
+ var fetchWMPlaylistDetailUseCase: any FetchWMPlaylistDetailUseCase {
+ shared {
+ FetchWMPlaylistDetailUseCaseImpl(playlistRepository: playlistRepository)
+ }
+ }
+
var createPlaylistUseCase: any CreatePlaylistUseCase {
shared {
CreatePlaylistUseCaseImpl(playlistRepository: playlistRepository)
diff --git a/Projects/App/Sources/Application/AppDelegate.swift b/Projects/App/Sources/Application/AppDelegate.swift
index 4ffa39c90..162350f4b 100644
--- a/Projects/App/Sources/Application/AppDelegate.swift
+++ b/Projects/App/Sources/Application/AppDelegate.swift
@@ -24,6 +24,7 @@ final class AppDelegate: UIResponder, UIApplicationDelegate {
} else {
LogManager.setUserID(userID: nil)
}
+ initializeUserProperty()
Analytics.logEvent(AnalyticsEventAppOpen, parameters: nil)
@@ -56,20 +57,6 @@ final class AppDelegate: UIResponder, UIApplicationDelegate {
}
extension AppDelegate {
- func application(
- _ application: UIApplication,
- didReceiveRemoteNotification userInfo: [AnyHashable: Any]
- ) {
- // If you are receiving a notification message while your app is in the background,
- // this callback will not be fired till the user taps on the notification launching the application.
-
- // With swizzling disabled you must let Messaging know about the message, for Analytics
- Messaging.messaging().appDidReceiveMessage(userInfo)
-
- // Print full message.
- LogManager.printDebug("๐:: \(userInfo)")
- }
-
/// [START receive_message]
func application(
_ application: UIApplication,
@@ -116,9 +103,29 @@ extension AppDelegate {
}
}
}
+
+ private func initializeUserProperty() {
+ if let loginPlatform = PreferenceManager.userInfo?.platform {
+ LogManager.setUserProperty(property: .loginPlatform(platform: loginPlatform))
+ } else {
+ LogManager.clearUserProperty(property: .loginPlatform(platform: ""))
+ }
+
+ if let fruitTotal = PreferenceManager.userInfo?.itemCount {
+ LogManager.setUserProperty(property: .fruitTotal(count: fruitTotal))
+ } else {
+ LogManager.clearUserProperty(property: .fruitTotal(count: -1))
+ }
+
+ if let playPlatform = PreferenceManager.songPlayPlatformType {
+ LogManager.setUserProperty(property: .songPlayPlatform(platform: playPlatform.display))
+ }
+
+ LogManager.setUserProperty(property: .playlistSongTotal(count: PlayState.shared.count))
+ }
}
-#if DEBUG
+#if DEBUG || QA
extension UIWindow {
override open func motionEnded(_ motion: UIEvent.EventSubtype, with event: UIEvent?) {
super.motionEnded(motion, with: event)
diff --git a/Projects/App/Sources/Application/SceneDelegate.swift b/Projects/App/Sources/Application/SceneDelegate.swift
index 17be8fd5c..42cc4a2b5 100644
--- a/Projects/App/Sources/Application/SceneDelegate.swift
+++ b/Projects/App/Sources/Application/SceneDelegate.swift
@@ -65,8 +65,7 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
}
LogManager.printDebug("URL: \(webpageURL.absoluteString)")
- guard webpageURL.host == WM_UNIVERSALLINK_DOMAIN() ||
- webpageURL.host == WM_UNIVERSALLINK_TEST_DOMAIN() else {
+ guard webpageURL.host == WM_UNIVERSALLINK_DOMAIN() else {
return
}
handleUniversalLink(url: webpageURL)
diff --git a/Projects/App/Support/Info.plist b/Projects/App/Support/Info.plist
index 8b9cb9383..118c93f85 100644
--- a/Projects/App/Support/Info.plist
+++ b/Projects/App/Support/Info.plist
@@ -55,8 +55,10 @@
LSApplicationQueriesSchemes
- naversearchapp
- naversearchthirdlogin
+ naversearchapp
+ naversearchthirdlogin
+ youtube
+ youtubemusic
LSRequiresIPhoneOS
@@ -70,10 +72,10 @@
BASE_DEV_URL
$(BASE_DEV_URL)
- BASE_IMAGE_URL
- $(BASE_IMAGE_URL)
BASE_PROD_URL
$(BASE_PROD_URL)
+ CDN_DOMAIN_URL
+ $(CDN_DOMAIN_URL)
GOOGLE_CLIENT_ID
$(GOOGLE_CLIENT_ID)
GOOGLE_URL_SCHEME
@@ -100,22 +102,6 @@
$(WMDOMAIN_FAQ)
WMDOMAIN_IMAGE
$(WMDOMAIN_IMAGE)
- WMDOMAIN_IMAGE_ARTIST_ROUND
- $(WMDOMAIN_IMAGE_ARTIST_ROUND)
- WMDOMAIN_IMAGE_ARTIST_SQUARE
- $(WMDOMAIN_IMAGE_ARTIST_SQUARE)
- WMDOMAIN_IMAGE_NEWS
- $(WMDOMAIN_IMAGE_NEWS)
- WMDOMAIN_IMAGE_NOTICE
- $(WMDOMAIN_IMAGE_NOTICE)
- WMDOMAIN_IMAGE_PLAYLIST
- $(WMDOMAIN_IMAGE_PLAYLIST)
- WMDOMAIN_IMAGE_PROFILE
- $(WMDOMAIN_IMAGE_PROFILE)
- WMDOMAIN_IMAGE_RECOMMEND_PLAYLIST_ROUND
- $(WMDOMAIN_IMAGE_RECOMMEND_PLAYLIST_ROUND)
- WMDOMAIN_IMAGE_RECOMMEND_PLAYLIST_SQUARE
- $(WMDOMAIN_IMAGE_RECOMMEND_PLAYLIST_SQUARE)
WMDOMAIN_LIKE
$(WMDOMAIN_LIKE)
WMDOMAIN_NOTICE
diff --git a/Projects/Domains/ArtistDomain/Sources/ResponseDTO/ArtistDetailResponseDTO.swift b/Projects/Domains/ArtistDomain/Sources/ResponseDTO/ArtistDetailResponseDTO.swift
index dbb50ca3d..90d255977 100644
--- a/Projects/Domains/ArtistDomain/Sources/ResponseDTO/ArtistDetailResponseDTO.swift
+++ b/Projects/Domains/ArtistDomain/Sources/ResponseDTO/ArtistDetailResponseDTO.swift
@@ -29,7 +29,7 @@ public extension ArtistDetailResponseDTO {
let enName: String
private enum CodingKeys: String, CodingKey {
- case krName = "krShort"
+ case krName = "kr"
case enName = "en"
}
}
diff --git a/Projects/Domains/ArtistDomain/Sources/ResponseDTO/ArtistListResponseDTO.swift b/Projects/Domains/ArtistDomain/Sources/ResponseDTO/ArtistListResponseDTO.swift
index 98f1ca88a..eec4fdb18 100644
--- a/Projects/Domains/ArtistDomain/Sources/ResponseDTO/ArtistListResponseDTO.swift
+++ b/Projects/Domains/ArtistDomain/Sources/ResponseDTO/ArtistListResponseDTO.swift
@@ -29,7 +29,7 @@ public extension ArtistListResponseDTO {
let enName: String
private enum CodingKeys: String, CodingKey {
- case krName = "krShort"
+ case krName = "kr"
case enName = "en"
}
}
diff --git a/Projects/Domains/BaseDomain/Sources/DataSource/BaseRemoteDataSource.swift b/Projects/Domains/BaseDomain/Sources/DataSource/BaseRemoteDataSource.swift
index 1c4501b4e..ad03cb542 100644
--- a/Projects/Domains/BaseDomain/Sources/DataSource/BaseRemoteDataSource.swift
+++ b/Projects/Domains/BaseDomain/Sources/DataSource/BaseRemoteDataSource.swift
@@ -25,7 +25,7 @@ open class BaseRemoteDataSource {
) {
self.keychain = keychain
- #if DEBUG
+ #if DEBUG || QA
self.provider = provider ?? MoyaProvider(plugins: [
JwtPlugin(keychain: keychain),
BasePlugin(keychain: keychain),
@@ -107,7 +107,15 @@ private extension BaseRemoteDataSource {
}
func reissueToken() -> Completable {
- let provider = refreshProvider ?? MoyaProvider(plugins: [JwtPlugin(keychain: keychain), CustomLoggingPlugin()])
+ #if DEBUG || QA
+ let provider = refreshProvider ?? MoyaProvider(plugins: [
+ JwtPlugin(keychain: keychain),
+ CustomLoggingPlugin()
+ ])
+ #else
+ let provider = refreshProvider ?? MoyaProvider(plugins: [JwtPlugin(keychain: keychain)])
+ #endif
+
if refreshProvider == nil {
refreshProvider = provider
}
diff --git a/Projects/Domains/BaseDomain/Sources/Logging/CustomLoggingPlugin.swift b/Projects/Domains/BaseDomain/Sources/Logging/CustomLoggingPlugin.swift
index 7403fb91f..dc06aa1b5 100644
--- a/Projects/Domains/BaseDomain/Sources/Logging/CustomLoggingPlugin.swift
+++ b/Projects/Domains/BaseDomain/Sources/Logging/CustomLoggingPlugin.swift
@@ -1,18 +1,20 @@
-//
-// CustomLoggingPlugin.swift
-// BaseDomain
-//
-// Created by KTH on 2024/03/04.
-// Copyright ยฉ 2024 yongbeomkwak. All rights reserved.
-//
-
import Foundation
import Moya
+import OSLog
-#if DEBUG
+#if DEBUG || QA
+ private enum NetworkLogLevel: String {
+ case short
+ case detail
+ }
public final class CustomLoggingPlugin: PluginType {
- public init() {}
+ let logger = Logger(subsystem: Bundle.main.bundleIdentifier ?? "", category: "NETWORK")
+ private let logLevel: NetworkLogLevel
+
+ public init() {
+ self.logLevel = CustomLoggingPlugin.getLogLevelFromArguments() ?? .detail
+ }
public func willSend(_ request: RequestType, target: TargetType) {
guard let httpRequest = request.request else {
@@ -30,7 +32,14 @@ import Moya
log.append("\(bodyString)\n")
}
log.append("---------------- END \(method) -----------------------\n")
- print(log)
+
+ switch logLevel {
+ case .short:
+ let log = "[๐ Request] [\(method)] [\(target)] \(url)"
+ logger.log(level: .debug, "\(log)")
+ case .detail:
+ logger.log(level: .debug, "\(log)")
+ }
}
public func didReceive(_ result: Result, target: TargetType) {
@@ -56,7 +65,14 @@ import Moya
log.append("\(reString)\n")
}
log.append("------------------- END HTTP (\(response.data.count)-byte body) -------------------\n")
- print(log)
+
+ switch logLevel {
+ case .short:
+ let log = "[๐ Response] [\(statusCode)] [\(target)] \(url)"
+ logger.log(level: .debug, "\(log)")
+ case .detail:
+ logger.log(level: .debug, "\(log)")
+ }
}
func onFail(_ error: MoyaError, target: TargetType) {
@@ -68,7 +84,18 @@ import Moya
log.append("<-- \(error.errorCode) \(target)\n")
log.append("\(error.failureReason ?? error.errorDescription ?? "unknown error")\n")
log.append("<-- END HTTP\n")
- print(log)
+
+ logger.log("\(log)")
+ }
+ }
+
+ extension CustomLoggingPlugin {
+ /// Environment Variables ์์ ๋ก๊ทธ ๋ ๋ฒจ์ ๊ฐ์ ธ์ค๋ ๋ฉ์๋
+ /// Scheme์ Environment Variables ์ key : NETWORK_LOG_LEVEL, value : short ๋๋ detail
+ private static func getLogLevelFromArguments() -> NetworkLogLevel? {
+ guard let logLevelValue = ProcessInfo.processInfo.environment["NETWORK_LOG_LEVEL"] else { return nil }
+ guard let networkLogLevel = NetworkLogLevel(rawValue: logLevelValue) else { return nil }
+ return networkLogLevel
}
}
diff --git a/Projects/Domains/BaseDomain/Sources/WMAPI/SecretURL.swift b/Projects/Domains/BaseDomain/Sources/WMAPI/SecretURL.swift
index 41539e68a..9c1c42c18 100644
--- a/Projects/Domains/BaseDomain/Sources/WMAPI/SecretURL.swift
+++ b/Projects/Domains/BaseDomain/Sources/WMAPI/SecretURL.swift
@@ -17,7 +17,7 @@ public func config(key: String) -> String {
// MARK: - BASE_URL
public func BASE_URL() -> String {
- #if DEBUG
+ #if DEBUG || QA
return config(key: "BASE_DEV_URL")
#else
return config(key: "BASE_PROD_URL")
diff --git a/Projects/Domains/ChartDomain/Sources/ResponseDTO/FetchChartRankingResponseDTO.swift b/Projects/Domains/ChartDomain/Sources/ResponseDTO/FetchChartRankingResponseDTO.swift
index ba0c36850..5b8cc3795 100644
--- a/Projects/Domains/ChartDomain/Sources/ResponseDTO/FetchChartRankingResponseDTO.swift
+++ b/Projects/Domains/ChartDomain/Sources/ResponseDTO/FetchChartRankingResponseDTO.swift
@@ -30,7 +30,7 @@ public extension SingleChartRankingResponseDTO {
public extension SingleChartRankingResponseDTO {
func toDomain(type: ChartDateType) -> ChartEntity {
return ChartEntity(
- updatedAt: Date(timeIntervalSince1970: updatedAt).changeDateFormatForChart() + " ์
๋ฐ์ดํธ",
+ updatedAt: Date(timeIntervalSince1970: updatedAt / 1000).changeDateFormatForChart() + " ์
๋ฐ์ดํธ",
songs: songs.map {
return ChartRankingEntity(
id: $0.songID,
diff --git a/Projects/Domains/CreditDomain/Interface/DataSource/RemoteCreditDataSource.swift b/Projects/Domains/CreditDomain/Interface/DataSource/RemoteCreditDataSource.swift
index 1cc4162ed..fe8e6f884 100644
--- a/Projects/Domains/CreditDomain/Interface/DataSource/RemoteCreditDataSource.swift
+++ b/Projects/Domains/CreditDomain/Interface/DataSource/RemoteCreditDataSource.swift
@@ -9,4 +9,6 @@ public protocol RemoteCreditDataSource {
page: Int,
limit: Int
) -> Single<[SongEntity]>
+
+ func fetchCreditProfile(name: String) -> Single
}
diff --git a/Projects/Domains/CreditDomain/Interface/Entity/CreditProfileEntity.swift b/Projects/Domains/CreditDomain/Interface/Entity/CreditProfileEntity.swift
new file mode 100644
index 000000000..7bfe31835
--- /dev/null
+++ b/Projects/Domains/CreditDomain/Interface/Entity/CreditProfileEntity.swift
@@ -0,0 +1,11 @@
+import Foundation
+
+public struct CreditProfileEntity {
+ public init(name: String, imageURL: String?) {
+ self.name = name
+ self.imageURL = imageURL
+ }
+
+ public let name: String
+ public let imageURL: String?
+}
diff --git a/Projects/Domains/CreditDomain/Interface/Repository/CreditRepository.swift b/Projects/Domains/CreditDomain/Interface/Repository/CreditRepository.swift
index 920465675..ef3035664 100644
--- a/Projects/Domains/CreditDomain/Interface/Repository/CreditRepository.swift
+++ b/Projects/Domains/CreditDomain/Interface/Repository/CreditRepository.swift
@@ -9,4 +9,6 @@ public protocol CreditRepository {
page: Int,
limit: Int
) -> Single<[SongEntity]>
+
+ func fetchCreditProfile(name: String) -> Single
}
diff --git a/Projects/Domains/CreditDomain/Interface/UseCase/FetchCreditProfileUseCase.swift b/Projects/Domains/CreditDomain/Interface/UseCase/FetchCreditProfileUseCase.swift
new file mode 100644
index 000000000..5bec1b3b7
--- /dev/null
+++ b/Projects/Domains/CreditDomain/Interface/UseCase/FetchCreditProfileUseCase.swift
@@ -0,0 +1,5 @@
+import RxSwift
+
+public protocol FetchCreditProfileUseCase {
+ func execute(name: String) -> Single
+}
diff --git a/Projects/Domains/CreditDomain/Sources/API/CreditAPI.swift b/Projects/Domains/CreditDomain/Sources/API/CreditAPI.swift
index 4a503e7fb..edecaba2b 100644
--- a/Projects/Domains/CreditDomain/Sources/API/CreditAPI.swift
+++ b/Projects/Domains/CreditDomain/Sources/API/CreditAPI.swift
@@ -12,6 +12,7 @@ public enum CreditAPI {
page: Int,
limit: Int
)
+ case fetchProfile(name: String)
}
extension CreditAPI: WMAPI {
@@ -23,12 +24,14 @@ extension CreditAPI: WMAPI {
switch self {
case let .fetchCreditSongList(name, _, _, _):
return "/\(name)/songs"
+ case let .fetchProfile(name):
+ return "/\(name)"
}
}
public var method: Moya.Method {
switch self {
- case .fetchCreditSongList:
+ case .fetchCreditSongList, .fetchProfile:
return .get
}
}
@@ -41,12 +44,15 @@ extension CreditAPI: WMAPI {
"page": page,
"limit": limit
], encoding: URLEncoding.queryString)
+
+ case .fetchProfile:
+ return .requestPlain
}
}
public var jwtTokenType: JwtTokenType {
switch self {
- case .fetchCreditSongList:
+ case .fetchCreditSongList, .fetchProfile:
return .none
}
}
diff --git a/Projects/Domains/CreditDomain/Sources/DataSource/RemoteCreditDataSourceImpl.swift b/Projects/Domains/CreditDomain/Sources/DataSource/RemoteCreditDataSourceImpl.swift
index 087dae52d..dbc298a38 100644
--- a/Projects/Domains/CreditDomain/Sources/DataSource/RemoteCreditDataSourceImpl.swift
+++ b/Projects/Domains/CreditDomain/Sources/DataSource/RemoteCreditDataSourceImpl.swift
@@ -15,4 +15,10 @@ public final class RemoteCreditDataSourceImpl: BaseRemoteDataSource,
.map([FetchCreditSongListResponseDTO].self)
.map { $0.toDomain() }
}
+
+ public func fetchCreditProfile(name: String) -> Single {
+ request(.fetchProfile(name: name))
+ .map(FetchCreditProfileResponseDTO.self)
+ .map { $0.toDomain() }
+ }
}
diff --git a/Projects/Domains/CreditDomain/Sources/Repository/CreditRepositoryImpl.swift b/Projects/Domains/CreditDomain/Sources/Repository/CreditRepositoryImpl.swift
index 0a6800ed7..624c1c7fe 100644
--- a/Projects/Domains/CreditDomain/Sources/Repository/CreditRepositoryImpl.swift
+++ b/Projects/Domains/CreditDomain/Sources/Repository/CreditRepositoryImpl.swift
@@ -20,4 +20,8 @@ public final class CreditRepositoryImpl: CreditRepository {
) -> Single<[SongEntity]> {
remoteCreditDataSource.fetchCreditSongList(name: name, order: order, page: page, limit: limit)
}
+
+ public func fetchCreditProfile(name: String) -> Single {
+ remoteCreditDataSource.fetchCreditProfile(name: name)
+ }
}
diff --git a/Projects/Domains/CreditDomain/Sources/ResponseDTO/FetchCreditProfileResponseDTO.swift b/Projects/Domains/CreditDomain/Sources/ResponseDTO/FetchCreditProfileResponseDTO.swift
new file mode 100644
index 000000000..5831cfbcf
--- /dev/null
+++ b/Projects/Domains/CreditDomain/Sources/ResponseDTO/FetchCreditProfileResponseDTO.swift
@@ -0,0 +1,21 @@
+import CreditDomainInterface
+import Foundation
+
+struct FetchCreditProfileResponseDTO: Decodable {
+ let name: String
+ let imageURL: String?
+
+ enum CodingKeys: String, CodingKey {
+ case name
+ case imageURL = "profileUrl"
+ }
+}
+
+extension FetchCreditProfileResponseDTO {
+ func toDomain() -> CreditProfileEntity {
+ return CreditProfileEntity(
+ name: name,
+ imageURL: imageURL
+ )
+ }
+}
diff --git a/Projects/Domains/CreditDomain/Sources/UseCase/FetchCreditProfileUseCaseImpl.swift b/Projects/Domains/CreditDomain/Sources/UseCase/FetchCreditProfileUseCaseImpl.swift
new file mode 100644
index 000000000..7a3450f49
--- /dev/null
+++ b/Projects/Domains/CreditDomain/Sources/UseCase/FetchCreditProfileUseCaseImpl.swift
@@ -0,0 +1,16 @@
+import CreditDomainInterface
+import RxSwift
+
+public final class FetchCreditProfileUseCaseImpl: FetchCreditProfileUseCase {
+ private let creditRepository: any CreditRepository
+
+ public init(
+ creditRepository: any CreditRepository
+ ) {
+ self.creditRepository = creditRepository
+ }
+
+ public func execute(name: String) -> Single {
+ creditRepository.fetchCreditProfile(name: name)
+ }
+}
diff --git a/Projects/Domains/CreditDomain/Testing/UseCase/FetchCreditProfileUseCaseSpy.swift b/Projects/Domains/CreditDomain/Testing/UseCase/FetchCreditProfileUseCaseSpy.swift
new file mode 100644
index 000000000..fd207404c
--- /dev/null
+++ b/Projects/Domains/CreditDomain/Testing/UseCase/FetchCreditProfileUseCaseSpy.swift
@@ -0,0 +1,17 @@
+import CreditDomainInterface
+import RxSwift
+import SongsDomainInterface
+
+public final class FetchCreditProfileUseCaseSpy: FetchCreditProfileUseCase {
+ public var callCount = 0
+ public var handler: (String) -> Single = { _ in fatalError() }
+
+ public init() {}
+
+ public func execute(
+ name: String
+ ) -> Single {
+ callCount += 1
+ return handler(name)
+ }
+}
diff --git a/Projects/Domains/PlaylistDomain/Interface/DataSource/RemotePlaylistDataSource.swift b/Projects/Domains/PlaylistDomain/Interface/DataSource/RemotePlaylistDataSource.swift
index e59932829..b4130c423 100644
--- a/Projects/Domains/PlaylistDomain/Interface/DataSource/RemotePlaylistDataSource.swift
+++ b/Projects/Domains/PlaylistDomain/Interface/DataSource/RemotePlaylistDataSource.swift
@@ -6,6 +6,7 @@ import SongsDomainInterface
public protocol RemotePlaylistDataSource {
func fetchRecommendPlaylist() -> Single<[RecommendPlaylistEntity]>
func fetchPlaylistDetail(id: String, type: PlaylistType) -> Single
+ func fetchWMPlaylistDetail(id: String) -> Single
func updateTitleAndPrivate(key: String, title: String?, isPrivate: Bool?) -> Completable
func createPlaylist(title: String) -> Single
func fetchPlaylistSongs(key: String) -> Single<[SongEntity]>
diff --git a/Projects/Domains/PlaylistDomain/Interface/Entity/WMPlaylistDetailEntity.swift b/Projects/Domains/PlaylistDomain/Interface/Entity/WMPlaylistDetailEntity.swift
new file mode 100644
index 000000000..e435b490a
--- /dev/null
+++ b/Projects/Domains/PlaylistDomain/Interface/Entity/WMPlaylistDetailEntity.swift
@@ -0,0 +1,21 @@
+import Foundation
+import SongsDomainInterface
+
+public struct WMPlaylistDetailEntity: Equatable {
+ public init(
+ key: String,
+ title: String,
+ songs: [SongEntity],
+ image: String,
+ playlistURL: String
+ ) {
+ self.key = key
+ self.title = title
+ self.songs = songs
+ self.image = image
+ self.playlistURL = playlistURL
+ }
+
+ public let key, title, image, playlistURL: String
+ public var songs: [SongEntity]
+}
diff --git a/Projects/Domains/PlaylistDomain/Interface/Repository/PlaylistRepository.swift b/Projects/Domains/PlaylistDomain/Interface/Repository/PlaylistRepository.swift
index c1a5cd8ee..c8d0e9eea 100644
--- a/Projects/Domains/PlaylistDomain/Interface/Repository/PlaylistRepository.swift
+++ b/Projects/Domains/PlaylistDomain/Interface/Repository/PlaylistRepository.swift
@@ -6,6 +6,7 @@ import SongsDomainInterface
public protocol PlaylistRepository {
func fetchRecommendPlaylist() -> Single<[RecommendPlaylistEntity]>
func fetchPlaylistDetail(id: String, type: PlaylistType) -> Single
+ func fetchWMPlaylistDetail(id: String) -> Single
func updateTitleAndPrivate(key: String, title: String?, isPrivate: Bool?) -> Completable
func createPlaylist(title: String) -> Single
func fetchPlaylistSongs(key: String) -> Single<[SongEntity]>
diff --git a/Projects/Domains/PlaylistDomain/Interface/UseCase/FetchWMPlaylistDetailUseCase.swift b/Projects/Domains/PlaylistDomain/Interface/UseCase/FetchWMPlaylistDetailUseCase.swift
new file mode 100644
index 000000000..4f3a3ad50
--- /dev/null
+++ b/Projects/Domains/PlaylistDomain/Interface/UseCase/FetchWMPlaylistDetailUseCase.swift
@@ -0,0 +1,6 @@
+import Foundation
+import RxSwift
+
+public protocol FetchWMPlaylistDetailUseCase {
+ func execute(id: String) -> Single
+}
diff --git a/Projects/Domains/PlaylistDomain/Sources/API/PlaylistAPI.swift b/Projects/Domains/PlaylistDomain/Sources/API/PlaylistAPI.swift
index adc218984..9262992a1 100644
--- a/Projects/Domains/PlaylistDomain/Sources/API/PlaylistAPI.swift
+++ b/Projects/Domains/PlaylistDomain/Sources/API/PlaylistAPI.swift
@@ -8,6 +8,7 @@ import PlaylistDomainInterface
public enum PlaylistAPI {
case fetchPlaylistDetail(id: String, type: PlaylistType) // ํ๋ฆฌ ์์ธ ๋ถ๋ฌ์ค๊ธฐ
+ case fetchWMPlaylistDetail(id: String) // ์๋ฎค ํ๋ฆฌ ์์ธ ๋ถ๋ฌ์ค๊ธฐ
case updateTitleAndPrivate(key: String, title: String?, isPrivate: Bool?) // title and private ์
๋ฐ์ดํธ
case createPlaylist(title: String) // ํ๋ฆฌ ์์ฑ
case fetchPlaylistSongs(key: String) // ์ ์ฒด ์ฌ์ ์ ๊ณก ๋ฐ์ดํฐ๋ง ๊ฐ์ ธ์ค๊ธฐ
@@ -33,12 +34,10 @@ extension PlaylistAPI: WMAPI {
return "/recommend/list"
case let .fetchPlaylistDetail(id: id, type: type):
- switch type {
- case .unknown, .my:
- return "/\(id)"
- case .wmRecommend:
- return "/recommend/\(id)"
- }
+ return "/\(id)"
+
+ case let .fetchWMPlaylistDetail(id: id):
+ return "/recommend/\(id)"
case let .updateTitleAndPrivate(key: key, _, _):
return "/\(key)"
@@ -65,7 +64,8 @@ extension PlaylistAPI: WMAPI {
public var method: Moya.Method {
switch self {
- case .fetchRecommendPlaylist, .fetchPlaylistDetail, .fetchPlaylistSongs, .checkSubscription,
+ case .fetchRecommendPlaylist, .fetchPlaylistDetail, .fetchWMPlaylistDetail, .fetchPlaylistSongs,
+ .checkSubscription,
.requestPlaylistOwnerID:
return .get
@@ -85,7 +85,8 @@ extension PlaylistAPI: WMAPI {
public var task: Moya.Task {
switch self {
- case .fetchRecommendPlaylist, .fetchPlaylistDetail, .fetchPlaylistSongs, .subscribePlaylist, .checkSubscription,
+ case .fetchRecommendPlaylist, .fetchPlaylistDetail, .fetchWMPlaylistDetail, .fetchPlaylistSongs,
+ .subscribePlaylist, .checkSubscription,
.requestPlaylistOwnerID:
return .requestPlain
@@ -124,7 +125,7 @@ extension PlaylistAPI: WMAPI {
public var jwtTokenType: JwtTokenType {
switch self {
- case .fetchRecommendPlaylist:
+ case .fetchRecommendPlaylist, .fetchWMPlaylistDetail:
return .none
case let .fetchPlaylistDetail(_, type):
diff --git a/Projects/Domains/PlaylistDomain/Sources/DataSource/RemotePlaylistDataSourceImpl.swift b/Projects/Domains/PlaylistDomain/Sources/DataSource/RemotePlaylistDataSourceImpl.swift
index afe05c298..b53b551a4 100644
--- a/Projects/Domains/PlaylistDomain/Sources/DataSource/RemotePlaylistDataSourceImpl.swift
+++ b/Projects/Domains/PlaylistDomain/Sources/DataSource/RemotePlaylistDataSourceImpl.swift
@@ -22,6 +22,12 @@ public final class RemotePlaylistDataSourceImpl: BaseRemoteDataSource Single {
+ request(.fetchWMPlaylistDetail(id: id))
+ .map(WMPlaylistDetailResponseDTO.self)
+ .map { $0.toDomain() }
+ }
+
public func updateTitleAndPrivate(key: String, title: String?, isPrivate: Bool?) -> Completable {
request(.updateTitleAndPrivate(key: key, title: title, isPrivate: isPrivate))
.asCompletable()
diff --git a/Projects/Domains/PlaylistDomain/Sources/Repository/PlaylistRepositoryImpl.swift b/Projects/Domains/PlaylistDomain/Sources/Repository/PlaylistRepositoryImpl.swift
index 7f19d4b69..fd21dbf66 100644
--- a/Projects/Domains/PlaylistDomain/Sources/Repository/PlaylistRepositoryImpl.swift
+++ b/Projects/Domains/PlaylistDomain/Sources/Repository/PlaylistRepositoryImpl.swift
@@ -21,6 +21,10 @@ public final class PlaylistRepositoryImpl: PlaylistRepository {
remotePlaylistDataSource.fetchPlaylistDetail(id: id, type: type)
}
+ public func fetchWMPlaylistDetail(id: String) -> Single {
+ remotePlaylistDataSource.fetchWMPlaylistDetail(id: id)
+ }
+
public func updateTitleAndPrivate(key: String, title: String?, isPrivate: Bool?) -> Completable {
remotePlaylistDataSource.updateTitleAndPrivate(key: key, title: title, isPrivate: isPrivate)
}
diff --git a/Projects/Domains/PlaylistDomain/Sources/ResponseDTO/WmPlaylistDetailResponseDTO.swift b/Projects/Domains/PlaylistDomain/Sources/ResponseDTO/WmPlaylistDetailResponseDTO.swift
new file mode 100644
index 000000000..8a81ac91b
--- /dev/null
+++ b/Projects/Domains/PlaylistDomain/Sources/ResponseDTO/WmPlaylistDetailResponseDTO.swift
@@ -0,0 +1,32 @@
+import Foundation
+import PlaylistDomainInterface
+import SongsDomain
+import SongsDomainInterface
+
+public struct WMPlaylistDetailResponseDTO: Decodable {
+ public let key: String?
+ public let title: String
+ public let songs: [SingleSongResponseDTO]?
+ public let imageURL: String
+ public let playlistURL: String
+
+ enum CodingKeys: String, CodingKey {
+ case key
+ case title
+ case songs
+ case imageURL = "imageUrl"
+ case playlistURL = "playlistUrl"
+ }
+}
+
+public extension WMPlaylistDetailResponseDTO {
+ func toDomain() -> WMPlaylistDetailEntity {
+ WMPlaylistDetailEntity(
+ key: key ?? "",
+ title: title,
+ songs: (songs ?? []).map { $0.toDomain() },
+ image: imageURL,
+ playlistURL: playlistURL
+ )
+ }
+}
diff --git a/Projects/Domains/PlaylistDomain/Sources/UseCase/FetchPlaylistDetailUseCaseImpl.swift b/Projects/Domains/PlaylistDomain/Sources/UseCase/FetchPlaylistDetailUseCaseImpl.swift
index ceb2cd73a..cc84d35e5 100644
--- a/Projects/Domains/PlaylistDomain/Sources/UseCase/FetchPlaylistDetailUseCaseImpl.swift
+++ b/Projects/Domains/PlaylistDomain/Sources/UseCase/FetchPlaylistDetailUseCaseImpl.swift
@@ -1,11 +1,3 @@
-//
-// FetchArtistListUseCaseImpl.swift
-// DataModule
-//
-// Created by KTH on 2023/02/08.
-// Copyright ยฉ 2023 yongbeomkwak. All rights reserved.
-//
-
import Foundation
import PlaylistDomainInterface
import RxSwift
diff --git a/Projects/Domains/PlaylistDomain/Sources/UseCase/FetchWMPlaylistDetailUseCaseImpl.swift b/Projects/Domains/PlaylistDomain/Sources/UseCase/FetchWMPlaylistDetailUseCaseImpl.swift
new file mode 100644
index 000000000..419decf16
--- /dev/null
+++ b/Projects/Domains/PlaylistDomain/Sources/UseCase/FetchWMPlaylistDetailUseCaseImpl.swift
@@ -0,0 +1,17 @@
+import Foundation
+import PlaylistDomainInterface
+import RxSwift
+
+public struct FetchWMPlaylistDetailUseCaseImpl: FetchWMPlaylistDetailUseCase {
+ private let playlistRepository: any PlaylistRepository
+
+ public init(
+ playlistRepository: PlaylistRepository
+ ) {
+ self.playlistRepository = playlistRepository
+ }
+
+ public func execute(id: String) -> Single {
+ playlistRepository.fetchWMPlaylistDetail(id: id)
+ }
+}
diff --git a/Projects/Domains/SearchDomain/Interface/Enum/Option.swift b/Projects/Domains/SearchDomain/Interface/Enum/Option.swift
index e6f91ad43..bee7fa6ea 100644
--- a/Projects/Domains/SearchDomain/Interface/Enum/Option.swift
+++ b/Projects/Domains/SearchDomain/Interface/Enum/Option.swift
@@ -25,7 +25,7 @@ public enum FilterType: String, Encodable, SearchOptionType {
public enum SortType: String, Encodable, SearchOptionType {
case latest
case oldest
- case alphabetical
+ case relevance
case popular
public var title: String {
@@ -34,8 +34,8 @@ public enum SortType: String, Encodable, SearchOptionType {
"์ต์ ์"
case .oldest:
"๊ณผ๊ฑฐ์"
- case .alphabetical:
- "๊ฐ๋๋ค์"
+ case .relevance:
+ "๊ด๋ จ๋์"
case .popular:
"์ธ๊ธฐ์"
}
diff --git a/Projects/Domains/UserDomain/Interface/Entity/PlaylistEntity.swift b/Projects/Domains/UserDomain/Interface/Entity/PlaylistEntity.swift
index 2468db8bf..023dcd975 100644
--- a/Projects/Domains/UserDomain/Interface/Entity/PlaylistEntity.swift
+++ b/Projects/Domains/UserDomain/Interface/Entity/PlaylistEntity.swift
@@ -8,11 +8,13 @@ public struct PlaylistEntity: Equatable {
image: String,
songCount: Int,
userId: String,
+ private: Bool,
isSelected: Bool = false
) {
self.key = key
self.title = title
self.image = image
+ self.private = `private`
self.isSelected = isSelected
self.songCount = songCount
self.userId = userId
@@ -20,5 +22,5 @@ public struct PlaylistEntity: Equatable {
public let key, title, image, userId: String
public let songCount: Int
- public var isSelected: Bool
+ public var `private`, isSelected: Bool
}
diff --git a/Projects/Domains/UserDomain/Sources/ResponseDTO/PlaylistResponseDTO.swift b/Projects/Domains/UserDomain/Sources/ResponseDTO/PlaylistResponseDTO.swift
index 249d42c2f..4250207fe 100644
--- a/Projects/Domains/UserDomain/Sources/ResponseDTO/PlaylistResponseDTO.swift
+++ b/Projects/Domains/UserDomain/Sources/ResponseDTO/PlaylistResponseDTO.swift
@@ -16,6 +16,7 @@ public struct PlaylistResponseDTO: Decodable {
public let user: PlaylistResponseDTO.User
public let imageUrl: String
public let songCount: Int
+ public let `private`: Bool
public struct User: Decodable {
public let handle: String
@@ -30,7 +31,8 @@ public extension PlaylistResponseDTO {
title: title,
image: imageUrl,
songCount: songCount,
- userId: user.handle
+ userId: user.handle,
+ private: `private`
)
}
}
diff --git a/Projects/Domains/UserDomain/Testing/FetchPlaylistUseCaseStub.swift b/Projects/Domains/UserDomain/Testing/FetchPlaylistUseCaseStub.swift
index 530fe9e9f..eb31b2137 100644
--- a/Projects/Domains/UserDomain/Testing/FetchPlaylistUseCaseStub.swift
+++ b/Projects/Domains/UserDomain/Testing/FetchPlaylistUseCaseStub.swift
@@ -4,15 +4,36 @@ import UserDomainInterface
public struct FetchPlaylistUseCaseStub: FetchPlaylistUseCase {
let items: [PlaylistEntity] = [
- .init(key: "123", title: "์ฐ์ค์ถฉํ ์ฅ๋ง์ฒ ์ฌ๋ฆ์ ๋ฃ๊ธฐ ์ข์ ์ผ๋ณธ ์ํฐํ ํ๋ ์ด๋ฆฌ์คํธ", image: "", songCount: 0, userId: ""),
- .init(key: "1234", title: "๋น๋ด๋ฆฌ๋ ๋์, ์ธ๋ จ๋ ๋ฌด๋ ๊ฐ๊ฐ์ ์ธ ํ์กโ๏ธ ๋ถ์๊ธฐ ์๋ ๋
ธ๋ ๋ชจ์", image: "", songCount: 1, userId: ""),
- .init(key: "1424", title: "[๐๐ฅ๐๐ฒ๐ฅ๐ข๐ฌ๐ญ] แแ
งแ
แ
ณแท แแ
กแท, แแ
ฌแแ
ณแซแแ
ตแฏ์ ๊ฝ๋ แแ
ณแฏแ
แ
ฆแแ
ตแ
แ
ตแแ
ณแแ
ณ๐", image: "", songCount: 200, userId: ""),
+ .init(
+ key: "123",
+ title: "์ฐ์ค์ถฉํ ์ฅ๋ง์ฒ ์ฌ๋ฆ์ ๋ฃ๊ธฐ ์ข์ ์ผ๋ณธ ์ํฐํ ํ๋ ์ด๋ฆฌ์คํธ",
+ image: "",
+ songCount: 0,
+ userId: "",
+ private: true
+ ),
+ .init(
+ key: "1234",
+ title: "๋น๋ด๋ฆฌ๋ ๋์, ์ธ๋ จ๋ ๋ฌด๋ ๊ฐ๊ฐ์ ์ธ ํ์กโ๏ธ ๋ถ์๊ธฐ ์๋ ๋
ธ๋ ๋ชจ์",
+ image: "",
+ songCount: 1,
+ userId: "",
+ private: true
+ ),
+ .init(
+ key: "1424",
+ title: "[๐๐ฅ๐๐ฒ๐ฅ๐ข๐ฌ๐ญ] แแ
งแ
แ
ณแท แแ
กแท, แแ
ฌแแ
ณแซแแ
ตแฏ์ ๊ฝ๋ แแ
ณแฏแ
แ
ฆแแ
ตแ
แ
ตแแ
ณแแ
ณ๐",
+ image: "",
+ songCount: 200,
+ userId: "",
+ private: false
+ ),
.init(
key: "1324",
title: "๐๐ฅ๐๐ฒ๐ฅ๐ข๐ฌ๐ญ ๋ฒ์จ ์ฌ๋ฆ์ด์ผ? ๋ด ๋ฐฉ์ ์ฒญ๋ํ ์บ๋ฆฌํฌ๋์ ํด๋ณ์ผ๋ก ์ ๋๋ ์ฌ๋ฆ ํ์ก ๐๐ฎ๐ฆ๐ฆ๐๐ซ ๐ข๐ฌ ๐๐จ๐ฆ๐ข๐ง๐ ๐ด",
image: "",
songCount: 1000,
- userId: ""
+ userId: "", private: true
)
]
diff --git a/Projects/Features/ArtistFeature/Resources/Artist.storyboard b/Projects/Features/ArtistFeature/Resources/Artist.storyboard
index 9f9175a06..3b918801a 100644
--- a/Projects/Features/ArtistFeature/Resources/Artist.storyboard
+++ b/Projects/Features/ArtistFeature/Resources/Artist.storyboard
@@ -138,16 +138,13 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
+
+
+
+
-
-
-
-
+
@@ -227,9 +223,10 @@
-
-
-
+
+
+
+
@@ -245,16 +242,13 @@
-
+
-
-
-
@@ -262,8 +256,6 @@
-
-
@@ -393,13 +385,17 @@
-
+