Created
May 31, 2024 00:32
-
-
Save paul-brenner/2b1d0487e668f85a9fd3b2f72e490f5b to your computer and use it in GitHub Desktop.
SivRepository
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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