Skip to content

Instantly share code, notes, and snippets.

@paul-brenner
Created May 31, 2024 00:32
Show Gist options
  • Save paul-brenner/2b1d0487e668f85a9fd3b2f72e490f5b to your computer and use it in GitHub Desktop.
Save paul-brenner/2b1d0487e668f85a9fd3b2f72e490f5b to your computer and use it in GitHub Desktop.
SivRepository
import CryptoKit
import Foundation
import MusicKit
import OSLog
import SendbirdChatSDK
import Supabase
import SwiftUI
let logger = Logger.make(category: "SivRepository")
protocol SivRepository: Sendable {
// func createSiv(_ siv: InsertSivDto) async throws -> UUID
func getSivShareList(userID: UserID) async throws -> [SivShare]
func getSivElement(sivElementUUID: UUID) async throws -> SivElement
func searchSivElements(searchString: String) async throws -> [SivElement]
func deleteSivElement(sivElementUUID: UUID) async throws -> PostgrestResponse<Void>
func deleteSiv(sivElementUUID: UUID) async throws
func getSivShares(userID: UserID, sivElementUUID: UUID) async throws -> [SivShare]
func getSivDetailShares(userID: UserID, sivElementUUID: UUID) async throws -> [SivDetailShare]
func getSivCollection(sivElementUUID: UUID, userUUID: UUID) async throws -> SivCollection
func updateSivCollection(sivElementUUID: UUID, userUUID: UUID, collection: SivCollectionType) async throws
func updateSiv(sivElementDTO: SivElementDTO) async throws
func createSivShares(createSivShareParams: [CreateSivShareParams]) async throws
func getSivElements(sourceTable: String) async throws -> [SivElement]
func getCategoryDetails() async throws -> [CategoryDetails]
func getMusicResults(category: String, artist: String?, album: String?, song: String?) async throws -> [MusicResult]
func getMediaResults(category: String, query: String) async throws -> [MediaResult]
func getTMDBWatchProviders(category: String, tmdbID: Int) async throws -> (TMDBWatchProviderResponse, String)
// func getGenericShare(shareId: UUID) async -> GenericBasicShare?
}
struct SivRepositoryImpl: SivRepository {
let supabase: SupabaseClient
let queryBasicElement = "siv_element_uuid,created_at,category,primary_name,secondary_name,location,url,additional_metadata"
let queryBasicCollection = "siv_element_uuid, user_uuid, collection"
let ChatRepository = ChatRepositoryImpl()
func createSiv(siv: CreateSivParams, sourceUserUUID: UUID, notes: [UserElementNotes]? = nil) async throws -> UUID {
var resultingSivElementUUID: UUID
// this checks first to make sure a duplicate siv_element is not created
var sivElement: [SivElement]? = nil
print("inside create siv")
if let id = siv.externalId {
sivElement = try await supabase.from("siv_elements")
.select()
.eq("category", value: siv.category)
.eq("external_id", value: id)
.execute()
.value
} else if let placeID = siv.additionalMetadata?.placeID {
sivElement = try await supabase.from("siv_elements")
.select()
.eq("additional_metadata->placeID", value: placeID)
.execute()
.value
} else {
sivElement = try await supabase.from("siv_elements")
.select()
.eq("category", value: siv.category)
.eq("primary_name", value: siv.primaryName)
.eq("secondary_name", value: siv.secondaryName)
.execute()
.value
}
if let sivElement, !sivElement.isEmpty {
// this if unwraps sivElement if it exists - but I need to actually check if its empty
// when creating a siv that aleady exists, the user gets a siv_share added with no targetUserUUID to give them their own record for mysivs
logger.info("siv_elements exists")
try await supabase.from("siv_shares").insert(CreateSivShareParams(sivElementUUID: sivElement[0].sivElementUUID, sourceUserUUID: sourceUserUUID, targetUserUUID: nil, messageChannelUrl: nil, notes: notes)).execute()
resultingSivElementUUID = sivElement[0].sivElementUUID
} else {
logger.info("create new siv element")
let newSivElement: SivElement = try await supabase.from("siv_elements")
.insert(siv, returning: .representation)
.single()
.execute()
.value
try await supabase.from("siv_shares").insert(CreateSivShareParams(sivElementUUID: newSivElement.sivElementUUID, sourceUserUUID: sourceUserUUID, targetUserUUID: nil, messageChannelUrl: nil, notes: notes)).execute()
resultingSivElementUUID = newSivElement.sivElementUUID
}
// should really check in both cases if user has already added this and if so its a duplicate for sivShares
return resultingSivElementUUID
}
func getSivShareList(userID: UserID) async throws -> [SivShare] {
try await supabase
.from("siv_shares")
.select("siv_share_uuid, siv_element_uuid, source_user_uuid, target_user_uuid,created_at, siv_elements(siv_element_uuid,created_at,category,primary_name,secondary_name,location,url),source_user:profiles!siv_shares_source_user_uuid_fkey!left(avatar_url),target_user:profiles!siv_shares_target_user_uuid_fkey!left(avatar_url)")
.or("source_user_uuid.eq.\(userID), target_user_uuid.eq.\(userID)")
.order("created_at", ascending: false).execute().value
}
func getSivElements(sourceTable: String) async throws -> [SivElement] {
if sourceTable == "siv_elements_requiring_edits" {
try await supabase
.from("siv_elements_requiring_edits")
.select("*")
.order("created_at", ascending: false).execute().value
} else { // assume sourceTable == "siv_elements" as is the default
try await supabase
.from("siv_elements")
.select("*")
.order("created_at", ascending: false).execute().value
}
}
func getCategoryDetails() -> [CategoryDetails] {
CategoryManager.shared.getCategoryDetails()
}
func getCategoryImage(category: String? = nil) -> Image {
guard let category else {
return Image(systemName: "circle.dotted.circle")
}
var categoryImage = Image("lucide_\(category)")
let categoryDetails = getCategoryDetails()
let symbolName = categoryDetails.first(where: { $0.category == category })?.icon ?? "circle.dotted.circle"
if symbolName.hasSuffix("-symbol") || symbolName.hasPrefix("lucide_") || symbolName.hasPrefix("tabler_") {
categoryImage = Image(symbolName)
} else {
categoryImage = Image(systemName: symbolName)
}
return categoryImage
}
func getSivElement(sivElementUUID: UUID) async -> SivElement {
do {
return try await supabase
.from("siv_elements").select("\(queryBasicElement), music:music_table(artist,song,album,apple_music_url,artwork_url,song_link_response)")
.eq("siv_element_uuid", value: sivElementUUID).single()
.execute().value
} catch {
logger.error("Error in getSivElement: \(error)")
}
return SivElement(sivElementUUID: sivElementUUID, category: nil, createdAt: Date(), primaryName: "Not Found", secondaryName: nil, location: nil, address: nil, url: nil, additionalMetadata: nil)
}
func searchSivElements(searchString: String) async -> [SivElement] {
do {
let modifiedSearchString = searchString.split(separator: " ").joined(separator: ":*&") + ":*"
let response: [SivElement] = try await supabase.rpc(
"search_siv_elements",
params: ["search_string": modifiedSearchString]
)
.execute()
.value
return response
} catch {
logger.error("Error in searchSivElement: \(error)")
}
return []
}
func getMusicTableResult(sivElementUUID: UUID) async throws -> MusicDetailsDTO {
try await supabase
.from("music_table")
.select("siv_element_uuid, created_at, updated_at, artist, song, album, duration_s, apple_music_url, release_date, genre_names, song_link_response, artwork_url, artwork_max_width")
.eq("siv_element_uuid", value: sivElementUUID).single()
.execute()
.value
}
func getMusicTableResult(sivElementUUIDs: [UUID]) async throws -> [MusicDetailsDTO] {
let sivElementUUIDsString = sivElementUUIDs.map { $0.uuidString.lowercased() }.joined(separator: ",")
let sivElementUUIDsList = "(\(sivElementUUIDsString))"
return try await supabase
.from("music_table")
.select("siv_element_uuid, created_at, updated_at, artist, song, album, duration_s, apple_music_url, release_date, genre_names, song_link_response, artwork_url, artwork_max_width")
.filter("siv_element_uuid", operator: "in", value: sivElementUUIDsList)
.execute()
.value
}
func getSivCollection(sivElementUUID: UUID, userUUID: UUID) async throws -> SivCollection {
try await supabase
.from("user_siv_element_collection").select("\(queryBasicCollection)")
.eq("siv_element_uuid", value: sivElementUUID)
.eq("user_uuid", value: userUUID)
.single()
.execute().value
}
func updateSivCollection(sivElementUUID: UUID, userUUID: UUID, collection: SivCollectionType) async throws {
try await supabase
.from("user_siv_element_collection")
.update(["collection": collection.rawValue])
.eq("siv_element_uuid", value: sivElementUUID)
.eq("user_uuid", value: userUUID)
.single()
.execute().value
}
func deleteSivElement(sivElementUUID: UUID) async throws -> PostgrestResponse<Void> {
try await supabase
.from("siv_elements")
.delete()
.eq("siv_element_uuid", value: sivElementUUID)
.execute()
}
func deleteSiv(sivElementUUID: UUID) async throws {
try await supabase.from("user_submissions").delete().eq("id", value: sivElementUUID).execute()
.value
}
func updateSiv(sivElementDTO: SivElementDTO) async throws {
if let sivElementUUID = sivElementDTO.sivElementUUID {
try await supabase.from("siv_elements")
.update(sivElementDTO)
.eq("siv_element_uuid", value: sivElementUUID)
.execute()
}
}
func getSivShares(userID: UserID, sivElementUUID: UUID) async throws -> [SivShare] {
try await supabase
.from("siv_shares")
.select("siv_share_uuid, siv_element_uuid, source_user_uuid, target_user_uuid, created_at")
.or("source_user_uuid.eq.\(userID), siv_element_uuid.eq.\(sivElementUUID)")
.order("created_at", ascending: false).execute().value
}
func getSivDetailShares(userID: UserID, sivElementUUID: UUID) async throws -> [SivDetailShare] {
try await supabase
.from("siv_shares")
.select("siv_share_uuid, siv_element_uuid, source_user_uuid, target_user_uuid, created_at, source_user:profiles!siv_shares_source_user_uuid_fkey!left(id, full_name, avatar_url), target_user:profiles!siv_shares_target_user_uuid_fkey!left(id, full_name, avatar_url)")
.eq("siv_element_uuid", value: sivElementUUID)
.or("source_user_uuid.eq.\(userID), target_user_uuid.eq.\(userID)")
.execute().value
}
func createSivShares(createSivShareParams: [CreateSivShareParams]) async {
logger.log("enter createSivShares")
do {
try await supabase.from("siv_shares").insert(createSivShareParams).execute()
} catch {
logger.error("Error in createSivShares: \(error)")
}
}
func createGuestSivShares(createSivShareParams: [CreateSivShareParams]) async {
do {
logger.log("create guest sivshare")
try await supabase.from("siv_guest_shares").insert(createSivShareParams).execute()
} catch {
logger.error("Error in createGuestSivShares: \(error)")
}
}
// to delete once new version is out
struct SivGroupShareCreateObject: Encodable {
let sivElementUUID: UUID?
let sourceUserUUID: UUID?
let groupUUID: UUID?
let messageChannelUrl: String?
let notes: [UserElementNotes]?
let containsGuest: Bool?
enum CodingKeys: String, CodingKey {
case sivElementUUID = "siv_element_uuid"
case sourceUserUUID = "source_user_uuid"
case groupUUID = "group_uuid"
case messageChannelUrl = "message_channel_url"
case notes
case containsGuest = "contains_guest"
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encodeIfPresent(sivElementUUID, forKey: .sivElementUUID)
try container.encodeIfPresent(sourceUserUUID, forKey: .sourceUserUUID)
try container.encodeIfPresent(groupUUID, forKey: .groupUUID)
try container.encodeIfPresent(messageChannelUrl, forKey: .messageChannelUrl)
try container.encodeIfPresent(notes, forKey: .notes)
try container.encodeIfPresent(containsGuest, forKey: .containsGuest)
}
}
func createSivShareReturn(createSivShareParams: CreateSivShareParams) async -> SivShareTableEntry? {
do {
let share: SivShareTableEntry = try await supabase.from("siv_shares").insert(createSivShareParams, returning: .representation).single().execute().value
if let targetGroupUUID = createSivShareParams.targetGroupUUID {
try await supabase.from("siv_group_shares").insert(SivGroupShareCreateObject(sivElementUUID: createSivShareParams.sivElementUUID, sourceUserUUID: createSivShareParams.sourceUserUUID, groupUUID: targetGroupUUID, messageChannelUrl: createSivShareParams.messageChannelUrl, notes: createSivShareParams.notes, containsGuest: createSivShareParams.containsGuest)).execute()
}
return share
} catch {
logger.error("Error in createSivShareReturn: \(error)")
return nil
}
}
func updateSivShares(sivShareUUID: UUID, createSivShareParams: CreateSivShareParams) async throws {
try await supabase.from("siv_shares")
.update(createSivShareParams)
.eq("siv_share_uuid", value: sivShareUUID)
.execute()
}
func createSivShareWithNotification(sourceUser: SessionUserPreferences, sivElementUUID: UUID, profilesOrGroupsSelectedForShare: ShareStatusForProfileOrGroup, newSivParams: CreateSivParams, initialMessage: String?, notes: [UserElementNotes]? = nil, categoryIcon: String) async {
let (channelName, userIds, targetUUIDString, targetType) = ChatRepository.getChannelMetadataSD(sourceUser: sourceUser, profilesOrGroupsSelectedForShare: profilesOrGroupsSelectedForShare, sivElementPrimaryName: newSivParams.primaryName)
let
metadata: [String: String] = [
"sourceUserUUID": sourceUser.id.lowercased(),
"targetUUID": targetUUIDString,
"sivElementUUID": sivElementUUID.uuidString.lowercased(),
"targetType": targetType
]
ChatRepository.createChatChannel(channelName: channelName, userIds: userIds, metadata: metadata) { result in
switch result {
case .success(let channel):
Task.init {
var shareUUID = UUID()
if let targetUserProfile = profilesOrGroupsSelectedForShare.userProfile {
let createSivShareParams = CreateSivShareParams(sivElementUUID: sivElementUUID, sourceUserUUID: sourceUser.userUUID, targetUserUUID: targetUserProfile.id, targetGroupUUID: nil, messageChannelUrl: channel?.channelURL, notes: notes)
let shares = await createSivShareReturn(createSivShareParams: createSivShareParams)
if let sivShareUUID = shares?.sivShareUUID {
shareUUID = sivShareUUID
}
} else if let groupProfiles = profilesOrGroupsSelectedForShare.userGroup {
let createSivShareParams = CreateSivShareParams(sivElementUUID: sivElementUUID, sourceUserUUID: sourceUser.userUUID, targetGroupUUID: groupProfiles.id, containsGuest: false, messageChannelUrl: channel?.channelURL, notes: notes)
let shares = await createSivShareReturn(createSivShareParams: createSivShareParams)
if let sivShareUUID = shares?.sivShareUUID {
shareUUID = sivShareUUID
}
}
let newMetadata = [
"sourceUserUUID": sourceUser.id.lowercased(),
"targetUUID": targetUUIDString,
"sivElementUUID": sivElementUUID.uuidString.lowercased(),
"sivShareUUID": shareUUID.uuidString.lowercased(),
"targetType": targetType
]
func getImageUrl(from newSivParams: CreateSivParams) -> String {
if let poster_path = newSivParams.additionalMetadata?.poster_path {
if newSivParams.category == "Book" || newSivParams.category == "AudioBook" {
return poster_path.replacingOccurrences(of: "&edge=curl", with: "") + "&fife=w800"
} else if newSivParams.category == "Documentary" || newSivParams.category == "Movie" || newSivParams.category == "TV Show" {
return "https://image.tmdb.org/t/p/{w}\(poster_path)".replaceBracketVariables("original")
}
}
if let artworkObject = newSivParams.additionalMetadata?.artworkObject {
return artworkObject.url(width: 190, height: 190)?.absoluteString ?? ""
}
return "https://aerbkcqzortdqhxcppiw.supabase.co/storage/v1/object/public/avatars/og_category_images/\(categoryIcon).png"
}
let imageUrl = getImageUrl(from: newSivParams)
let messageMetadata = MessageMetadata(
sourceUserUUID: sourceUser.id.lowercased(),
targetUUID: targetUUIDString, sivElementUUID: sivElementUUID.uuidString.lowercased(),
sivShareUUID: shareUUID.uuidString.lowercased(),
targetType: targetType,
category: newSivParams.category,
primaryName: newSivParams.primaryName,
secondaryName: newSivParams.secondaryName,
imageUrl: imageUrl,
url: newSivParams.url,
notesArray: notes?.map(\.note)
)
logger.siv(imageUrl)
ChatRepository.sendSivShareMessageToChannel(channel: channel, sivElementUrl: Deeplink.element(sivElementUUID: sivElementUUID.uuidString).url?.absoluteString, initialMessage: initialMessage, metadata: messageMetadata)
ChatRepository.updateChatChannelMetaData(metadata: newMetadata, channel: channel)
DispatchQueue.main.async {
AppState.shared.pendingTaskCount -= 1
}
}
case .failure(let error):
logger.error("Error createSivShareWithNotification: \(error)")
DispatchQueue.main.async {
AppState.shared.pendingTaskCount -= 1
}
}
}
}
func getMusicResults(category: String, artist: String?, album: String?, song: String?) async throws -> [MusicResult] {
let url = URL(string: "https://aerbkcqzortdqhxcppiw.supabase.co/functions/v1/get-music-results")!
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.setValue("Bearer " + Config.SUPABASE_ANON_KEY, forHTTPHeaderField: "Authorization")
let parameters = [
"category": category,
"song": song ?? "",
"artist": artist ?? "",
"album": album ?? ""
]
request.httpBody = try JSONSerialization.data(withJSONObject: parameters, options: [])
let (data, _) = try await URLSession.shared.data(for: request)
let musicResults = try JSONDecoder().decode([MusicResult].self, from: data)
return musicResults
}
func convertToMovieTVResult(media: Media) -> MediaResult {
switch media {
case .movie(let movie):
MediaResult(id: movie.id, title: movie.title ?? "", releaseDate: movie.releaseDate ?? "", posterPath: movie.posterPath)
case .tv(let tv):
MediaResult(id: tv.id, title: tv.name ?? "", releaseDate: tv.firstAirDate ?? "", posterPath: tv.posterPath)
}
}
func getMediaResults(category: String, query: String) async throws -> [MediaResult] {
let lowercasedCategory = if category == "Movie" {
"movie"
} else if category == "TV Show" {
"tv"
} else {
// Handle other cases or throw an error
"movie"
}
let headers = [
"accept": "application/json",
"Authorization": "Bearer eyJhbGciOiJIUzI1NiJ9.eyJhdWQiOiJiZjJkYjIzN2EwZTc3ZTBmYjgwNGIwYTFkNTgyMDhjOCIsInN1YiI6IjY1YWEzOTg4ZTI2N2RlMDEzMWEzMDQ1ZiIsInNjb3BlcyI6WyJhcGlfcmVhZCJdLCJ2ZXJzaW9uIjoxfQ.lMlUzR_9PnqexjDvTiFbXIYVW8YSHWM6g1udF6QIDpo"
]
let formattedQuery = query.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? ""
let urlString = "https://api.themoviedb.org/3/search/\(lowercasedCategory)?query=\(formattedQuery)&include_adult=false&append_to_response=images"
let request = NSMutableURLRequest(url: NSURL(string: urlString)! as URL,
cachePolicy: .useProtocolCachePolicy,
timeoutInterval: 10.0)
request.httpMethod = "GET"
request.allHTTPHeaderFields = headers
do {
let (data, _) = try await URLSession.shared.data(for: request as URLRequest)
let decoder = JSONDecoder()
if lowercasedCategory == "movie" {
let response = try decoder.decode(TMDBResultsResponse<MovieResult>.self, from: data)
return response.results.map { convertToMovieTVResult(media: .movie($0)) }
} else if lowercasedCategory == "tv" {
let response = try decoder.decode(TMDBResultsResponse<TVResult>.self, from: data)
return response.results.map { convertToMovieTVResult(media: .tv($0)) }
} else {
throw NSError(domain: "", code: -1, userInfo: [NSLocalizedDescriptionKey: "Invalid category"])
}
} catch {
logger.error("\(error)")
throw error
}
}
struct TMDBResultsResponse<T: Decodable>: Decodable {
let results: [T]
}
func getTMDBWatchProviders(category: String, tmdbID: Int) async throws -> (TMDBWatchProviderResponse, String) {
let lowercasedCategory = if category == "Movie" {
"movie"
} else if category == "TV Show" {
"tv"
} else {
// Handle other cases or throw an error
"movie"
}
let headers = [
"accept": "application/json",
"Authorization": "Bearer eyJhbGciOiJIUzI1NiJ9.eyJhdWQiOiJiZjJkYjIzN2EwZTc3ZTBmYjgwNGIwYTFkNTgyMDhjOCIsInN1YiI6IjY1YWEzOTg4ZTI2N2RlMDEzMWEzMDQ1ZiIsInNjb3BlcyI6WyJhcGlfcmVhZCJdLCJ2ZXJzaW9uIjoxfQ.lMlUzR_9PnqexjDvTiFbXIYVW8YSHWM6g1udF6QIDpo"
]
let urlString = "https://api.themoviedb.org/3/\(lowercasedCategory)/\(tmdbID)/watch/providers"
let request = NSMutableURLRequest(url: NSURL(string: urlString)! as URL,
cachePolicy: .useProtocolCachePolicy,
timeoutInterval: 10.0)
request.httpMethod = "GET"
request.allHTTPHeaderFields = headers
do {
let (data, _) = try await URLSession.shared.data(for: request as URLRequest)
let decoder = JSONDecoder()
let response = try decoder.decode(TMDBWatchProviderResponse.self, from: data)
let jsonString = String(data: data, encoding: .utf8) ?? ""
return (response, jsonString)
} catch {
logger.error("\(error)")
throw error
}
}
struct GoogleBooksResponse: Decodable {
let items: [BookResult]
}
func getBookResults(category: String, query: String) async throws -> [BookResult] {
let formattedQuery = query.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? ""
let urlString = "https://www.googleapis.com/books/v1/volumes?q=\(formattedQuery)&key=\(Config.GOOGLE_BOOKS_API_KEY)&fields=items(volumeInfo.title,volumeInfo.authors,volumeInfo.publishedDate,volumeInfo.imageLinks.smallThumbnail,volumeInfo.imageLinks.thumbnail)"
let request = NSMutableURLRequest(url: NSURL(string: urlString)! as URL,
cachePolicy: .useProtocolCachePolicy,
timeoutInterval: 10.0)
request.httpMethod = "GET"
do {
let (data, _) = try await URLSession.shared.data(for: request as URLRequest)
let decoder = JSONDecoder()
let response = try decoder.decode(GoogleBooksResponse.self, from: data)
return response.items
} catch {
logger.error("\(error)")
throw error
}
}
func getPodcastResults(category: String, query: String, max: Int? = nil) async throws -> [Podcast] {
let formattedQuery = query.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? ""
let timeInSeconds: TimeInterval = Date().timeIntervalSince1970
let apiHeaderTime = Int(timeInSeconds)
let data4Hash = Config.PODCASTINDEX_KEY + Config.PODCASTINDEX_SECRET + "\(apiHeaderTime)"
let inputData = Data(data4Hash.utf8)
let hashed = Insecure.SHA1.hash(data: inputData)
let hashString = hashed.compactMap { String(format: "%02x", $0) }.joined()
var urlComponents = URLComponents(string: "https://api.podcastindex.org/api/1.0/search/byterm")!
urlComponents.queryItems = [
URLQueryItem(name: "q", value: formattedQuery)
]
if let max {
urlComponents.queryItems?.append(URLQueryItem(name: "max", value: "\(max)"))
}
let urlString = urlComponents.url!.absoluteString
let request = NSMutableURLRequest(url: NSURL(string: urlString)! as URL,
cachePolicy: .useProtocolCachePolicy,
timeoutInterval: 10.0)
request.httpMethod = "GET"
request.addValue("\(apiHeaderTime)", forHTTPHeaderField: "X-Auth-Date")
request.addValue(Config.PODCASTINDEX_KEY, forHTTPHeaderField: "X-Auth-Key")
request.addValue(hashString, forHTTPHeaderField: "Authorization")
request.addValue(Config.USER_AGENT, forHTTPHeaderField: "User-Agent")
do {
let (data, _) = try await URLSession.shared.data(for: request as URLRequest)
let decoder = JSONDecoder()
let response = try decoder.decode(PodcastArrayResponse.self, from: data)
guard let feeds = response.feeds else {
// Define your custom error
struct ResponseError: Error {
var message: String
}
throw ResponseError(message: "Podcast search response is nil")
}
return feeds
} catch {
logger.error("\(error)")
throw error
}
}
func getPodcastEpisodeResults(id: String) async throws -> [PodcastEpisode] {
let formattedQuery = id.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? ""
let timeInSeconds: TimeInterval = Date().timeIntervalSince1970
let apiHeaderTime = Int(timeInSeconds)
let data4Hash = Config.PODCASTINDEX_KEY + Config.PODCASTINDEX_SECRET + "\(apiHeaderTime)"
let inputData = Data(data4Hash.utf8)
let hashed = Insecure.SHA1.hash(data: inputData)
let hashString = hashed.compactMap { String(format: "%02x", $0) }.joined()
let urlString = "https://api.podcastindex.org/api/1.0/episodes/byfeedid?id=" + formattedQuery
let request = NSMutableURLRequest(url: NSURL(string: urlString)! as URL,
cachePolicy: .useProtocolCachePolicy,
timeoutInterval: 10.0)
request.httpMethod = "GET"
request.addValue("\(apiHeaderTime)", forHTTPHeaderField: "X-Auth-Date")
request.addValue(Config.PODCASTINDEX_KEY, forHTTPHeaderField: "X-Auth-Key")
request.addValue(hashString, forHTTPHeaderField: "Authorization")
request.addValue(Config.USER_AGENT, forHTTPHeaderField: "User-Agent")
do {
let (data, _) = try await URLSession.shared.data(for: request as URLRequest)
let decoder = JSONDecoder()
let response = try decoder.decode(PodcastEpisodeArrayResponse.self, from: data)
guard let episodes = response.items else {
// Define your custom error
struct ResponseError: Error {
var message: String
}
throw ResponseError(message: "Podcast episode search response is nil")
}
return episodes
} catch {
logger.error("\(error)")
throw error
}
}
func getSivByCollection(userID: UserID, collections: [SivCollectionType]) async throws -> [SivCollection] {
let query = collections.map { collection in
"collection.eq.\(collection.rawValue)"
}.joined(separator: ", ")
return try await supabase
.from("user_siv_element_collection")
.select("\(queryBasicCollection), element:siv_elements!public_user_siv_element_collection_siv_element_uuid_fkey(\(queryBasicElement))")
.eq("user_uuid", value: userID)
.or(query)
.order("created_at", ascending: false)
.execute()
.value
}
// This appears to be deprecated
// func getSivCollectionElements(userID: UserID) async throws -> [SivCollectionElement] {
// try await supabase
// .from("user_collection_view")
// .select("siv_element_uuid,user_uuid,collection,category,primary_name,secondary_name,location,address,url,alternate_search_terms,additional_metadata,siv_shares,siv_group_shares")
// .eq("user_uuid", value: userID)
// .execute()
// .value
// }
func getUserCollectionSD(userID: UserID) async throws -> [UserCollectionDTO] {
try await supabase
.from("user_collection_view_temp")
.select("user_uuid,siv_element_uuid,collection,collection_entry_created_at,collection_entry_updated_at,category,primary_name,secondary_name,location,address,url,alternate_search_terms,additional_metadata,siv_shares")
.eq("user_uuid", value: userID)
.execute()
.value
}
func getSingleUserCollectionSD(userID: UserID, sivElementUUID: UUID) async throws -> [UserCollectionDTO] {
try await supabase
.from("user_collection_view_temp")
.select("user_uuid,siv_element_uuid,collection,collection_entry_created_at,collection_entry_updated_at,category,primary_name,secondary_name,location,address,url,alternate_search_terms,additional_metadata,siv_shares")
.eq("user_uuid", value: userID)
.eq("siv_element_uuid", value: sivElementUUID)
.execute()
.value
}
func getCategoryDetailsSD() async throws -> [CategoryDetailsDTO] {
try await supabase
.from("category_details")
.select("*")
.execute()
.value
}
func getSharesToFromUser(userID: UserID) async throws -> [SivShareBasic] {
try await supabase
.from("siv_shares")
.select("siv_share_uuid, siv_element_uuid, source_user_uuid, target_user_uuid, created_at,message_channel_url, source_user:profiles!siv_shares_source_user_uuid_fkey!left(id, full_name, avatar_url), target_user:profiles!siv_shares_target_user_uuid_fkey!left(id, full_name, avatar_url)")
.or("source_user_uuid.eq.\(userID), target_user_uuid.eq.\(userID)")
.execute()
.value
}
}
class CategoryManager {
static let shared = CategoryManager()
private var categoryDetails = [CategoryDetails]()
func getCategoryDetails() -> [CategoryDetails] {
guard categoryDetails.isEmpty else {
return categoryDetails
}
if let url = Bundle.main.url(forResource: "category_taxonomy_table_data", withExtension: "json") {
do {
let data = try Data(contentsOf: url)
let decoder = JSONDecoder()
let records = try decoder.decode([AirtableTaxonomyRecord].self, from: data)
categoryDetails = records.map(\.fields)
return categoryDetails
} catch {
logger.error("Error: getCategoryDetails \(error)")
}
}
logger.error("Failed to load category details json")
return []
}
}
struct MessageMetaData: Codable {}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment