Skip to content

Commit

Permalink
FEATURE: Person external IDs (#132)
Browse files Browse the repository at this point in the history
FEATURE: Person external ids
  • Loading branch information
adamayoung authored Jan 5, 2024
1 parent 2d9d26e commit f49d7ea
Show file tree
Hide file tree
Showing 14 changed files with 381 additions and 8 deletions.
10 changes: 5 additions & 5 deletions Sources/TMDb/Models/MovieExternalLinksCollection.swift
Original file line number Diff line number Diff line change
Expand Up @@ -73,11 +73,11 @@ public struct MovieExternalLinksCollection: Identifiable, Codable, Equatable, Ha

public static func == (lhs: MovieExternalLinksCollection, rhs: MovieExternalLinksCollection) -> Bool {
lhs.id == rhs.id
&& lhs.imdb?.id == rhs.imdb?.id
&& lhs.wikiData?.id == rhs.wikiData?.id
&& lhs.facebook?.id == rhs.facebook?.id
&& lhs.instagram?.id == rhs.instagram?.id
&& lhs.twitter?.id == rhs.twitter?.id
&& lhs.imdb == rhs.imdb
&& lhs.wikiData == rhs.wikiData
&& lhs.facebook == rhs.facebook
&& lhs.instagram == rhs.instagram
&& lhs.twitter == rhs.twitter
}

}
Expand Down
149 changes: 149 additions & 0 deletions Sources/TMDb/Models/PersonExternalLinksCollection.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
import Foundation

///
/// A model representing a collection of media databases and social IDs and links for a person.
///
public struct PersonExternalLinksCollection: Identifiable, Codable, Equatable, Hashable {

///
/// The TMDb person identifier.
///
public let id: Person.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?

///
/// TikTok llink.
///
public let tikTok: TikTokLink?

///
/// Creates an external links collection for a movie.
///
/// - Parameters:
/// - id: The TMDb person identifier.
/// - imdb: IMDb link.
/// - wikiData: WikiData link.
/// - facebook: Facebook link.
/// - instagram: Instagram link.
/// - twitter: Twitter link.
/// - tikTok: TikTok link.
///
public init(
id: Movie.ID,
imdb: IMDbLink? = nil,
wikiData: WikiDataLink? = nil,
facebook: FacebookLink? = nil,
instagram: InstagramLink? = nil,
twitter: TwitterLink? = nil,
tikTok: TikTokLink? = nil
) {
self.id = id
self.imdb = imdb
self.wikiData = wikiData
self.facebook = facebook
self.instagram = instagram
self.twitter = twitter
self.tikTok = tikTok
}

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)
hasher.combine(tikTok?.id)
}

public static func == (lhs: PersonExternalLinksCollection, rhs: PersonExternalLinksCollection) -> 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
&& lhs.tikTok == rhs.tikTok
}

}

extension PersonExternalLinksCollection {

private enum CodingKeys: String, CodingKey {
case id
case imdbID = "imdbId"
case wikiDataID = "wikidataId"
case facebookID = "facebookId"
case instagramID = "instagramId"
case twitterID = "twitterId"
case tikTokID = "tiktokId"
}

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 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 tikTokID = try container.decodeIfPresent(String.self, forKey: .tikTokID)

let imdbLink = IMDbLink(imdbNameID: imdbID)
let wikiDataLink = WikiDataLink(wikiDataID: wikiDataID)
let facebookLink = FacebookLink(facebookID: facebookID)
let instagramLink = InstagramLink(instagramID: instagramID)
let twitterLink = TwitterLink(twitterID: twitterID)
let tikTokLink = TikTokLink(tikTokID: tikTokID)

self.init(
id: id,
imdb: imdbLink,
wikiData: wikiDataLink,
facebook: facebookLink,
instagram: instagramLink,
twitter: twitterLink,
tikTok: tikTokLink
)
}

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)
try container.encodeIfPresent(tikTok?.id, forKey: .tikTokID)
}

}
53 changes: 53 additions & 0 deletions Sources/TMDb/Models/TikTokLink.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import Foundation

///
/// A TikTok external link.
///
/// e.g. to a person's TikTok profile.
///
public struct TikTokLink: ExternalLink {

///
/// TikTok profile identifier.
///
public let id: String

///
/// URL of the TikTok profile page.
///
public let url: URL

///
/// Creates a TikTok link object using a TikTok user identifier.
///
/// - Parameter tikTokID: The TikTok user identifier.
///
public init?(tikTokID: String?) {
guard
let tikTokID,
let url = Self.tikTokURL(for: tikTokID)
else {
return nil
}

self.init(id: tikTokID, url: url)
}

}

extension TikTokLink {

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

}

extension TikTokLink {

private static func tikTokURL(for tikTokID: String) -> URL? {
URL(string: "https://www.tiktok.com/@\(tikTokID)")
}

}
6 changes: 3 additions & 3 deletions Sources/TMDb/Movies/MovieService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -331,14 +331,14 @@ public final class MovieService {
/// Returns a collection of media databases and social links for a movie.
///
/// - Parameters:
/// - id: The identifier of the movie.
/// - movieID: The identifier of the movie.
///
/// - Returns: A collection of external links for the specificed movie.
///
public func externalLinks(forMovie id: Movie.ID) async throws -> MovieExternalLinksCollection {
public func externalLinks(forMovie movieID: Movie.ID) async throws -> MovieExternalLinksCollection {
let linksCollection: MovieExternalLinksCollection
do {
linksCollection = try await apiClient.get(endpoint: MoviesEndpoint.externalIDs(movieID: id))
linksCollection = try await apiClient.get(endpoint: MoviesEndpoint.externalIDs(movieID: movieID))
} catch let error {
throw TMDbError(error: error)
}
Expand Down
5 changes: 5 additions & 0 deletions Sources/TMDb/People/Endpoints/PeopleEndpoint.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ enum PeopleEndpoint {
case tvSeriesCredits(personID: Person.ID)
case images(personID: Person.ID)
case popular(page: Int? = nil)
case externalIDs(personID: Person.ID)

}

Expand Down Expand Up @@ -46,6 +47,10 @@ extension PeopleEndpoint: Endpoint {
.appendingPathComponent("popular")
.appendingPage(page)

case .externalIDs(let personID):
return Self.basePath
.appendingPathComponent(personID)
.appendingPathComponent("external_ids")
}
}

Expand Down
19 changes: 19 additions & 0 deletions Sources/TMDb/People/PersonService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -189,4 +189,23 @@ public final class PersonService {
return personList
}

///
/// Returns a collection of media databases and social links for a person.
///
/// - Parameters:
/// - personID: The identifier of the person.
///
/// - Returns: A collection of external links for the specificed person.
///
public func externalLinks(forPerson personID: Person.ID) async throws -> PersonExternalLinksCollection {
let linksCollection: PersonExternalLinksCollection
do {
linksCollection = try await apiClient.get(endpoint: PeopleEndpoint.externalIDs(personID: personID))
} catch let error {
throw TMDbError(error: error)
}

return linksCollection
}

}
1 change: 1 addition & 0 deletions Sources/TMDb/TMDb.docc/Extensions/PersonService.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,4 @@

- ``knownFor(forPerson:)``
- ``popular(page:)``
- ``externalLinks(forPerson:)``
14 changes: 14 additions & 0 deletions Tests/TMDbIntegrationTests/PersonIntegrationTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -78,4 +78,18 @@ final class PersonIntegrationTests: XCTestCase {
XCTAssertFalse(personList.results.isEmpty)
}

func testExternalLinks() async throws {
let personID = 115440

let linksCollection = try await personService.externalLinks(forPerson: personID)

XCTAssertEqual(linksCollection.id, personID)
XCTAssertNotNil(linksCollection.imdb)
XCTAssertNotNil(linksCollection.wikiData)
XCTAssertNil(linksCollection.facebook)
XCTAssertNotNil(linksCollection.instagram)
XCTAssertNotNil(linksCollection.twitter)
XCTAssertNotNil(linksCollection.tikTok)
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import Foundation
@testable import TMDb

extension PersonExternalLinksCollection {

static func mock(
id: Person.ID,
imdb: IMDbLink? = nil,
wikiData: WikiDataLink? = nil,
facebook: FacebookLink? = nil,
instagram: InstagramLink? = nil,
twitter: TwitterLink? = nil,
tikTok: TikTokLink? = nil
) -> PersonExternalLinksCollection {
PersonExternalLinksCollection(
id: id,
imdb: imdb,
wikiData: wikiData,
facebook: facebook,
instagram: instagram,
twitter: twitter,
tikTok: tikTok
)
}

static var sydneySweeney: PersonExternalLinksCollection {
.mock(
id: 346698,
imdb: IMDbLink(imdbNameID: "nm2858875"),
wikiData: WikiDataLink(wikiDataID: "Q49561909"),
facebook: FacebookLink(facebookID: "sydney_sweeney"),
instagram: InstagramLink(instagramID: "sydney_sweeney"),
twitter: TwitterLink(twitterID: "sydney_sweeney"),
tikTok: TikTokLink(tikTokID: "syds_garage")
)
}

}
43 changes: 43 additions & 0 deletions Tests/TMDbTests/Models/PersonExternalLinksCollectionTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
@testable import TMDb
import XCTest

final class PersonExternalLinksCollectionTests: XCTestCase {

func testDecodeReturnsPersonExternalLinksCollection() throws {
let expectedResult = PersonExternalLinksCollection(
id: 115440,
imdb: IMDbLink(imdbNameID: "nm2858875"),
wikiData: WikiDataLink(wikiDataID: "Q49561909"),
facebook: FacebookLink(facebookID: "sydney_sweeney"),
instagram: InstagramLink(instagramID: "sydney_sweeney"),
twitter: TwitterLink(twitterID: "sydney_sweeney"),
tikTok: TikTokLink(tikTokID: "syds_garage")
)

let result = try JSONDecoder.theMovieDatabase.decode(
PersonExternalLinksCollection.self,
fromResource: "person-external-ids"
)

XCTAssertEqual(result, expectedResult)
}

func testEncodeAndDecodeReturnsMovieExternalLinksCollection() throws {
let linksCollection = PersonExternalLinksCollection(
id: 115440,
imdb: IMDbLink(imdbNameID: "nm2858875"),
wikiData: WikiDataLink(wikiDataID: "Q49561909"),
facebook: FacebookLink(facebookID: "sydney_sweeney"),
instagram: InstagramLink(instagramID: "sydney_sweeney"),
twitter: TwitterLink(twitterID: "sydney_sweeney"),
tikTok: TikTokLink(tikTokID: "syds_garage")
)

let data = try JSONEncoder.theMovieDatabase.encode(linksCollection)

let result = try JSONDecoder.theMovieDatabase.decode(PersonExternalLinksCollection.self, from: data)

XCTAssertEqual(result, linksCollection)
}

}
Loading

0 comments on commit f49d7ea

Please sign in to comment.