From 152e852b0140441115414bf6afd59951397a77dd Mon Sep 17 00:00:00 2001 From: Adam Young Date: Fri, 5 Jan 2024 17:02:09 +0000 Subject: [PATCH 1/2] FEATURE: TV series external ids --- README.md | 2 +- Sources/TMDb/Models/IMDbLink.swift | 4 +- .../PersonExternalLinksCollection.swift | 2 +- .../TVSeriesExternalLinksCollection.swift | 134 ++++++++++++++++++ Sources/TMDb/Movies/MovieService.swift | 6 +- Sources/TMDb/People/PersonService.swift | 2 + Sources/TMDb/TMDb.docc/TMDb.md | 5 +- .../TVSeries/Endpoints/TVSeriesEndpoint.swift | 6 + Sources/TMDb/TVSeries/TVSeriesService.swift | 24 +++- .../TVSeriesServiceTests.swift | 13 ++ ...VSeriesExternalLinksCollection+Mocks.swift | 35 +++++ ...TVSeriesExternalLinksCollectionTests.swift | 41 ++++++ .../json/tv-series-external-ids.json | 12 ++ .../Endpoints/TVSeriesEndpointTests.swift | 8 ++ .../TVSeries/TVSeriesServiceTests.swift | 11 ++ 15 files changed, 297 insertions(+), 8 deletions(-) create mode 100644 Sources/TMDb/Models/TVSeriesExternalLinksCollection.swift create mode 100644 Tests/TMDbTests/Mocks/Models/TVSeriesExternalLinksCollection+Mocks.swift create mode 100644 Tests/TMDbTests/Models/TVSeriesExternalLinksCollectionTests.swift create mode 100644 Tests/TMDbTests/Resources/json/tv-series-external-ids.json diff --git a/README.md b/README.md index 1a837cf6..3a62b1e9 100644 --- a/README.md +++ b/README.md @@ -59,7 +59,7 @@ Documentation and examples of usage can be found at ## References * [https://www.themoviedb.org](https://www.themoviedb.org) -* [https://developers.themoviedb.org](https://developers.themoviedb.org) +* [https://developer.themoviedb.org](https://developer.themoviedb.org) ## License diff --git a/Sources/TMDb/Models/IMDbLink.swift b/Sources/TMDb/Models/IMDbLink.swift index f5e79844..18e4b85e 100644 --- a/Sources/TMDb/Models/IMDbLink.swift +++ b/Sources/TMDb/Models/IMDbLink.swift @@ -20,9 +20,9 @@ public struct IMDbLink: ExternalLink { /// /// Creates an IMDb link object using an IMDb title identifier. /// - /// e.g. for a movie or TV show. + /// e.g. for a movie or TV series. /// - /// - Parameter imdbTitleID: The IMDb movie or TV show identifier. + /// - Parameter imdbTitleID: The IMDb movie or TV series identifier. /// public init?(imdbTitleID: String?) { guard diff --git a/Sources/TMDb/Models/PersonExternalLinksCollection.swift b/Sources/TMDb/Models/PersonExternalLinksCollection.swift index df656e32..9a05c6be 100644 --- a/Sources/TMDb/Models/PersonExternalLinksCollection.swift +++ b/Sources/TMDb/Models/PersonExternalLinksCollection.swift @@ -107,7 +107,7 @@ extension PersonExternalLinksCollection { public init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) - let id = try container.decode(Movie.ID.self, forKey: .id) + let id = try container.decode(Person.ID.self, forKey: .id) let imdbID = try container.decodeIfPresent(String.self, forKey: .imdbID) let wikiDataID = try container.decodeIfPresent(String.self, forKey: .wikiDataID) diff --git a/Sources/TMDb/Models/TVSeriesExternalLinksCollection.swift b/Sources/TMDb/Models/TVSeriesExternalLinksCollection.swift new file mode 100644 index 00000000..8c4c6740 --- /dev/null +++ b/Sources/TMDb/Models/TVSeriesExternalLinksCollection.swift @@ -0,0 +1,134 @@ +import Foundation + +/// +/// A model representing a collection of media databases and social IDs and links for a TV series. +/// +public struct TVSeriesExternalLinksCollection: Identifiable, Codable, Equatable, Hashable { + + /// + /// The TMDb TV series identifier. + /// + public let id: TVSeries.ID + + /// + /// IMDb link. + /// + public let imdb: IMDbLink? + + /// + /// WikiData link. + /// + public let wikiData: WikiDataLink? + + /// + /// Facebook link. + /// + public let facebook: FacebookLink? + + /// + /// Instagram link. + /// + public let instagram: InstagramLink? + + /// + /// Twitter link. + /// + public let twitter: TwitterLink? + + /// + /// Creates an external links collection for a movie. + /// + /// - Parameters: + /// - id: The TMDb TV series identifier. + /// - imdb: IMDb link. + /// - wikiData: WikiData link. + /// - facebook: Facebook link. + /// - instagram: Instagram link. + /// - twitter: Twitter link. + /// + public init( + id: TVSeries.ID, + imdb: IMDbLink? = nil, + wikiData: WikiDataLink? = nil, + facebook: FacebookLink? = nil, + instagram: InstagramLink? = nil, + twitter: TwitterLink? = nil + ) { + self.id = id + self.imdb = imdb + self.wikiData = wikiData + self.facebook = facebook + self.instagram = instagram + self.twitter = twitter + } + + public func hash(into hasher: inout Hasher) { + hasher.combine(id) + hasher.combine(imdb?.id) + hasher.combine(wikiData?.id) + hasher.combine(facebook?.id) + hasher.combine(instagram?.id) + hasher.combine(twitter?.id) + } + + public static func == (lhs: TVSeriesExternalLinksCollection, rhs: TVSeriesExternalLinksCollection) -> Bool { + lhs.id == rhs.id + && lhs.imdb == rhs.imdb + && lhs.wikiData == rhs.wikiData + && lhs.facebook == rhs.facebook + && lhs.instagram == rhs.instagram + && lhs.twitter == rhs.twitter + } + +} + +extension TVSeriesExternalLinksCollection { + + private enum CodingKeys: String, CodingKey { + case id + case imdbID = "imdbId" + case wikiDataID = "wikidataId" + case facebookID = "facebookId" + case instagramID = "instagramId" + case twitterID = "twitterId" + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + + let id = try container.decode(TVSeries.ID.self, forKey: .id) + + let imdbID = try container.decodeIfPresent(String.self, forKey: .imdbID) + let wikiDataID = try container.decodeIfPresent(String.self, forKey: .wikiDataID) + let facebookID = try container.decodeIfPresent(String.self, forKey: .facebookID) + let instagramID = try container.decodeIfPresent(String.self, forKey: .instagramID) + let twitterID = try container.decodeIfPresent(String.self, forKey: .twitterID) + + let imdbLink = IMDbLink(imdbTitleID: imdbID) + let wikiDataLink = WikiDataLink(wikiDataID: wikiDataID) + let facebookLink = FacebookLink(facebookID: facebookID) + let instagramLink = InstagramLink(instagramID: instagramID) + let twitterLink = TwitterLink(twitterID: twitterID) + + self.init( + id: id, + imdb: imdbLink, + wikiData: wikiDataLink, + facebook: facebookLink, + instagram: instagramLink, + twitter: twitterLink + ) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + + try container.encode(id, forKey: .id) + try container.encodeIfPresent(imdb?.id, forKey: .imdbID) + try container.encodeIfPresent(wikiData?.id, forKey: .wikiDataID) + try container.encodeIfPresent(facebook?.id, forKey: .facebookID) + try container.encodeIfPresent(instagram?.id, forKey: .instagramID) + try container.encodeIfPresent(twitter?.id, forKey: .twitterID) + } + +} diff --git a/Sources/TMDb/Movies/MovieService.swift b/Sources/TMDb/Movies/MovieService.swift index 1403d57a..37a91bdc 100644 --- a/Sources/TMDb/Movies/MovieService.swift +++ b/Sources/TMDb/Movies/MovieService.swift @@ -305,7 +305,7 @@ public final class MovieService { /// /// Returns watch providers for a movie /// - /// [TMDb API - Movie: Watch providers](https://developers.themoviedb.org/3/movies/get-movie-watch-providers) + /// [TMDb API - Movie: Watch providers](https://developer.themoviedb.org/reference/movie-watch-providers) /// - Parameters: /// - id: The identifier of the movie. /// @@ -329,7 +329,9 @@ public final class MovieService { /// /// Returns a collection of media databases and social links for a movie. - /// + /// + /// [TMDb API - Movie: External IDs](https://developer.themoviedb.org/reference/movie-external-ids) + /// /// - Parameters: /// - movieID: The identifier of the movie. /// diff --git a/Sources/TMDb/People/PersonService.swift b/Sources/TMDb/People/PersonService.swift index c1658526..3a06ab8f 100644 --- a/Sources/TMDb/People/PersonService.swift +++ b/Sources/TMDb/People/PersonService.swift @@ -192,6 +192,8 @@ public final class PersonService { /// /// Returns a collection of media databases and social links for a person. /// + /// [TMDb API - People: External IDs](https://developer.themoviedb.org/reference/person-external-ids) + /// /// - Parameters: /// - personID: The identifier of the person. /// diff --git a/Sources/TMDb/TMDb.docc/TMDb.md b/Sources/TMDb/TMDb.docc/TMDb.md index ad20217a..b3dc74f2 100644 --- a/Sources/TMDb/TMDb.docc/TMDb.md +++ b/Sources/TMDb/TMDb.docc/TMDb.md @@ -60,6 +60,7 @@ For the TMDb API documentation, see - ``Review`` - ``ImageCollection`` - ``VideoCollection`` +- ``MovieExternalLinksCollection`` - ``MoviePageableList`` ### TV Series @@ -71,7 +72,8 @@ For the TMDb API documentation, see - ``Review`` - ``ImageCollection`` - ``VideoCollection`` -- ``TVSeriesPageableList`` +- ``TVSeriesExnte`` +- ``TVSeriesExternalLinksCollection`` ### TV Seasons @@ -96,6 +98,7 @@ For the TMDb API documentation, see - ``PersonTVSeriesCredits`` - ``PersonImageCollection`` - ``Show`` +- ``PersonExternalLinksCollection`` - ``PersonPageableList`` ### Discover diff --git a/Sources/TMDb/TVSeries/Endpoints/TVSeriesEndpoint.swift b/Sources/TMDb/TVSeries/Endpoints/TVSeriesEndpoint.swift index 20efa862..6eda6fb2 100644 --- a/Sources/TMDb/TVSeries/Endpoints/TVSeriesEndpoint.swift +++ b/Sources/TMDb/TVSeries/Endpoints/TVSeriesEndpoint.swift @@ -11,6 +11,7 @@ enum TVSeriesEndpoint { case similar(tvSeriesID: TVSeries.ID, page: Int? = nil) case popular(page: Int? = nil) case watch(tvSeriesID: TVSeries.ID) + case externalIDs(tvSeriesID: TVSeries.ID) } @@ -68,6 +69,11 @@ extension TVSeriesEndpoint: Endpoint { return Self.basePath .appendingPathComponent(tvSeriesID) .appendingPathComponent("watch/providers") + + case .externalIDs(let tvSeriesID): + return Self.basePath + .appendingPathComponent(tvSeriesID) + .appendingPathComponent("external_ids") } } diff --git a/Sources/TMDb/TVSeries/TVSeriesService.swift b/Sources/TMDb/TVSeries/TVSeriesService.swift index d93ea0c0..dde68525 100644 --- a/Sources/TMDb/TVSeries/TVSeriesService.swift +++ b/Sources/TMDb/TVSeries/TVSeriesService.swift @@ -235,7 +235,8 @@ public final class TVSeriesService { /// /// Returns watch providers for a TV series /// - /// [TMDb API - TVSeries: Watch providers](https://developers.themoviedb.org/3/tv/get-tv-watch-providers) + /// [TMDb API - TVSeries: Watch providers](https://developer.themoviedb.org/reference/tv-series-watch-providers) + /// /// - Parameters: /// - id: The identifier of the TV series. /// @@ -257,4 +258,25 @@ public final class TVSeriesService { return result.results[regionCode] } + /// + /// Returns a collection of media databases and social links for a TV series. + /// + /// [TMDb API - TVSeries: External IDs](https://developer.themoviedb.org/reference/tv-series-external-ids) + /// + /// - Parameters: + /// - tvSeriesID: The identifier of the TV series. + /// + /// - Returns: A collection of external links for the specificed TV series. + /// + public func externalLinks(forTVSeries tvSeriesID: TVSeries.ID) async throws -> TVSeriesExternalLinksCollection { + let linksCollection: TVSeriesExternalLinksCollection + do { + linksCollection = try await apiClient.get(endpoint: TVSeriesEndpoint.externalIDs(tvSeriesID: tvSeriesID)) + } catch let error { + throw TMDbError(error: error) + } + + return linksCollection + } + } diff --git a/Tests/TMDbIntegrationTests/TVSeriesServiceTests.swift b/Tests/TMDbIntegrationTests/TVSeriesServiceTests.swift index 73c79dfb..d06619e6 100644 --- a/Tests/TMDbIntegrationTests/TVSeriesServiceTests.swift +++ b/Tests/TMDbIntegrationTests/TVSeriesServiceTests.swift @@ -84,4 +84,17 @@ final class TVSeriesServiceTests: XCTestCase { XCTAssertFalse(tvSeriesList.results.isEmpty) } + func testExternalLinks() async throws { + let tvSeriesID = 86423 + + let linksCollection = try await tvSeriesService.externalLinks(forTVSeries: tvSeriesID) + + XCTAssertEqual(linksCollection.id, tvSeriesID) + XCTAssertNotNil(linksCollection.imdb) + XCTAssertNil(linksCollection.wikiData) + XCTAssertNotNil(linksCollection.facebook) + XCTAssertNotNil(linksCollection.instagram) + XCTAssertNotNil(linksCollection.twitter) + } + } diff --git a/Tests/TMDbTests/Mocks/Models/TVSeriesExternalLinksCollection+Mocks.swift b/Tests/TMDbTests/Mocks/Models/TVSeriesExternalLinksCollection+Mocks.swift new file mode 100644 index 00000000..382491c5 --- /dev/null +++ b/Tests/TMDbTests/Mocks/Models/TVSeriesExternalLinksCollection+Mocks.swift @@ -0,0 +1,35 @@ +import Foundation +@testable import TMDb + +extension TVSeriesExternalLinksCollection { + + static func mock( + id: TVSeries.ID, + imdb: IMDbLink? = nil, + wikiData: WikiDataLink? = nil, + facebook: FacebookLink? = nil, + instagram: InstagramLink? = nil, + twitter: TwitterLink? = nil + ) -> TVSeriesExternalLinksCollection { + TVSeriesExternalLinksCollection( + id: id, + imdb: imdb, + wikiData: wikiData, + facebook: facebook, + instagram: instagram, + twitter: twitter + ) + } + + static var lockeAndKey: TVSeriesExternalLinksCollection { + .mock( + id: 86423, + imdb: IMDbLink(imdbTitleID: "tt3007572"), + wikiData: nil, + facebook: FacebookLink(facebookID: "lockeandkeynetflix"), + instagram: InstagramLink(instagramID: "lockeandkeynetflix"), + twitter: TwitterLink(twitterID: "lockekeynetflix") + ) + } + +} diff --git a/Tests/TMDbTests/Models/TVSeriesExternalLinksCollectionTests.swift b/Tests/TMDbTests/Models/TVSeriesExternalLinksCollectionTests.swift new file mode 100644 index 00000000..97dd140e --- /dev/null +++ b/Tests/TMDbTests/Models/TVSeriesExternalLinksCollectionTests.swift @@ -0,0 +1,41 @@ +@testable import TMDb +import XCTest + +final class TVSeriesExternalLinksCollectionTests: XCTestCase { + + func testDecodeReturnsMovieExternalLinksCollection() throws { + let expectedResult = TVSeriesExternalLinksCollection( + id: 86423, + imdb: IMDbLink(imdbTitleID: "tt3007572"), + wikiData: nil, + facebook: FacebookLink(facebookID: "lockeandkeynetflix"), + instagram: InstagramLink(instagramID: "lockeandkeynetflix"), + twitter: TwitterLink(twitterID: "lockekeynetflix") + ) + + let result = try JSONDecoder.theMovieDatabase.decode( + TVSeriesExternalLinksCollection.self, + fromResource: "tv-series-external-ids" + ) + + XCTAssertEqual(result, expectedResult) + } + + func testEncodeAndDecodeReturnsMovieExternalLinksCollection() throws { + let linksCollection = TVSeriesExternalLinksCollection( + id: 86423, + imdb: IMDbLink(imdbTitleID: "tt3007572"), + wikiData: nil, + facebook: FacebookLink(facebookID: "lockeandkeynetflix"), + instagram: InstagramLink(instagramID: "lockeandkeynetflix"), + twitter: TwitterLink(twitterID: "lockekeynetflix") + ) + + let data = try JSONEncoder.theMovieDatabase.encode(linksCollection) + + let result = try JSONDecoder.theMovieDatabase.decode(TVSeriesExternalLinksCollection.self, from: data) + + XCTAssertEqual(result, linksCollection) + } + +} diff --git a/Tests/TMDbTests/Resources/json/tv-series-external-ids.json b/Tests/TMDbTests/Resources/json/tv-series-external-ids.json new file mode 100644 index 00000000..5a695352 --- /dev/null +++ b/Tests/TMDbTests/Resources/json/tv-series-external-ids.json @@ -0,0 +1,12 @@ +{ + "id": 86423, + "imdb_id": "tt3007572", + "freebase_mid": null, + "freebase_id": null, + "tvdb_id": 361594, + "tvrage_id": null, + "wikidata_id": null, + "facebook_id": "lockeandkeynetflix", + "instagram_id": "lockeandkeynetflix", + "twitter_id": "lockekeynetflix" +} diff --git a/Tests/TMDbTests/TVSeries/Endpoints/TVSeriesEndpointTests.swift b/Tests/TMDbTests/TVSeries/Endpoints/TVSeriesEndpointTests.swift index c4ae28a7..10213051 100644 --- a/Tests/TMDbTests/TVSeries/Endpoints/TVSeriesEndpointTests.swift +++ b/Tests/TMDbTests/TVSeries/Endpoints/TVSeriesEndpointTests.swift @@ -111,4 +111,12 @@ final class TVSeriesEndpointTests: XCTestCase { XCTAssertEqual(url, expectedURL) } + func testMovieExternalIDsEndpointReturnsURL() throws { + let expectedURL = try XCTUnwrap(URL(string: "/tv/1/external_ids")) + + let url = TVSeriesEndpoint.externalIDs(tvSeriesID: 1).path + + XCTAssertEqual(url, expectedURL) + } + } diff --git a/Tests/TMDbTests/TVSeries/TVSeriesServiceTests.swift b/Tests/TMDbTests/TVSeries/TVSeriesServiceTests.swift index 6eca0af8..e66a4f83 100644 --- a/Tests/TMDbTests/TVSeries/TVSeriesServiceTests.swift +++ b/Tests/TMDbTests/TVSeries/TVSeriesServiceTests.swift @@ -216,4 +216,15 @@ final class TVSeriesServiceTests: XCTestCase { XCTAssertEqual(apiClient.lastPath, TVSeriesEndpoint.watch(tvSeriesID: tvSeriesID).path) } + func testExternalLinksReturnsExternalLinks() async throws { + let expectedResult = TVSeriesExternalLinksCollection.lockeAndKey + let tvSeriesID = 86423 + apiClient.result = .success(expectedResult) + + let result = try await service.externalLinks(forTVSeries: tvSeriesID) + + XCTAssertEqual(result, expectedResult) + XCTAssertEqual(apiClient.lastPath, TVSeriesEndpoint.externalIDs(tvSeriesID: tvSeriesID).path) + } + } From 5bbb2249cfb133e62922917928b0e86362fa3d06 Mon Sep 17 00:00:00 2001 From: Adam Young Date: Fri, 5 Jan 2024 17:03:50 +0000 Subject: [PATCH 2/2] Update documentation --- Sources/TMDb/TMDb.docc/TMDb.md | 1 - 1 file changed, 1 deletion(-) diff --git a/Sources/TMDb/TMDb.docc/TMDb.md b/Sources/TMDb/TMDb.docc/TMDb.md index b3dc74f2..76f69703 100644 --- a/Sources/TMDb/TMDb.docc/TMDb.md +++ b/Sources/TMDb/TMDb.docc/TMDb.md @@ -72,7 +72,6 @@ For the TMDb API documentation, see - ``Review`` - ``ImageCollection`` - ``VideoCollection`` -- ``TVSeriesExnte`` - ``TVSeriesExternalLinksCollection`` ### TV Seasons