diff --git a/apple/OmnivoreKit/Sources/Services/DataService/GQLSchema.swift b/apple/OmnivoreKit/Sources/Services/DataService/GQLSchema.swift index d06651a19..5cde5af97 100644 --- a/apple/OmnivoreKit/Sources/Services/DataService/GQLSchema.swift +++ b/apple/OmnivoreKit/Sources/Services/DataService/GQLSchema.swift @@ -7525,7 +7525,6 @@ extension Objects { let googleLogin: [String: Unions.LoginResult] let googleSignup: [String: Unions.GoogleSignupResult] let logOut: [String: Unions.LogOutResult] - let login: [String: Unions.LoginResult] let mergeHighlight: [String: Unions.MergeHighlightResult] let reportItem: [String: Objects.ReportItemResult] let revokeApiKey: [String: Unions.RevokeApiKeyResult] @@ -7543,7 +7542,6 @@ extension Objects { let setShareHighlight: [String: Unions.SetShareHighlightResult] let setUserPersonalization: [String: Unions.SetUserPersonalizationResult] let setWebhook: [String: Unions.SetWebhookResult] - let signup: [String: Unions.SignupResult] let subscribe: [String: Unions.SubscribeResult] let unsubscribe: [String: Unions.UnsubscribeResult] let updateHighlight: [String: Unions.UpdateHighlightResult] @@ -7659,10 +7657,6 @@ extension Objects.Mutation: Decodable { if let value = try container.decode(Unions.LogOutResult?.self, forKey: codingKey) { map.set(key: field, hash: alias, value: value as Any) } - case "login": - if let value = try container.decode(Unions.LoginResult?.self, forKey: codingKey) { - map.set(key: field, hash: alias, value: value as Any) - } case "mergeHighlight": if let value = try container.decode(Unions.MergeHighlightResult?.self, forKey: codingKey) { map.set(key: field, hash: alias, value: value as Any) @@ -7731,10 +7725,6 @@ extension Objects.Mutation: Decodable { if let value = try container.decode(Unions.SetWebhookResult?.self, forKey: codingKey) { map.set(key: field, hash: alias, value: value as Any) } - case "signup": - if let value = try container.decode(Unions.SignupResult?.self, forKey: codingKey) { - map.set(key: field, hash: alias, value: value as Any) - } case "subscribe": if let value = try container.decode(Unions.SubscribeResult?.self, forKey: codingKey) { map.set(key: field, hash: alias, value: value as Any) @@ -7814,7 +7804,6 @@ extension Objects.Mutation: Decodable { googleLogin = map["googleLogin"] googleSignup = map["googleSignup"] logOut = map["logOut"] - login = map["login"] mergeHighlight = map["mergeHighlight"] reportItem = map["reportItem"] revokeApiKey = map["revokeApiKey"] @@ -7832,7 +7821,6 @@ extension Objects.Mutation: Decodable { setShareHighlight = map["setShareHighlight"] setUserPersonalization = map["setUserPersonalization"] setWebhook = map["setWebhook"] - signup = map["signup"] subscribe = map["subscribe"] unsubscribe = map["unsubscribe"] updateHighlight = map["updateHighlight"] @@ -8248,25 +8236,6 @@ extension Fields where TypeLock == Objects.Mutation { } } - func login(input: InputObjects.LoginInput, selection: Selection) throws -> Type { - let field = GraphQLField.composite( - name: "login", - arguments: [Argument(name: "input", type: "LoginInput!", value: input)], - selection: selection.selection - ) - select(field) - - switch response { - case let .decoding(data): - if let data = data.login[field.alias!] { - return try selection.decode(data: data) - } - throw HttpError.badpayload - case .mocking: - return selection.mock() - } - } - func mergeHighlight(input: InputObjects.MergeHighlightInput, selection: Selection) throws -> Type { let field = GraphQLField.composite( name: "mergeHighlight", @@ -8590,25 +8559,6 @@ extension Fields where TypeLock == Objects.Mutation { } } - func signup(input: InputObjects.SignupInput, selection: Selection) throws -> Type { - let field = GraphQLField.composite( - name: "signup", - arguments: [Argument(name: "input", type: "SignupInput!", value: input)], - selection: selection.selection - ) - select(field) - - switch response { - case let .decoding(data): - if let data = data.signup[field.alias!] { - return try selection.decode(data: data) - } - throw HttpError.badpayload - case .mocking: - return selection.mock() - } - } - func subscribe(name: String, selection: Selection) throws -> Type { let field = GraphQLField.composite( name: "subscribe", @@ -9778,6 +9728,7 @@ extension Objects { let sendInstallInstructions: [String: Unions.SendInstallInstructionsResult] let sharedArticle: [String: Unions.SharedArticleResult] let subscriptions: [String: Unions.SubscriptionsResult] + let typeaheadSearch: [String: Unions.TypeaheadSearchResult] let user: [String: Unions.UserResult] let users: [String: Unions.UsersResult] let validateUsername: [String: Bool] @@ -9870,6 +9821,10 @@ extension Objects.Query: Decodable { if let value = try container.decode(Unions.SubscriptionsResult?.self, forKey: codingKey) { map.set(key: field, hash: alias, value: value as Any) } + case "typeaheadSearch": + if let value = try container.decode(Unions.TypeaheadSearchResult?.self, forKey: codingKey) { + map.set(key: field, hash: alias, value: value as Any) + } case "user": if let value = try container.decode(Unions.UserResult?.self, forKey: codingKey) { map.set(key: field, hash: alias, value: value as Any) @@ -9917,6 +9872,7 @@ extension Objects.Query: Decodable { sendInstallInstructions = map["sendInstallInstructions"] sharedArticle = map["sharedArticle"] subscriptions = map["subscriptions"] + typeaheadSearch = map["typeaheadSearch"] user = map["user"] users = map["users"] validateUsername = map["validateUsername"] @@ -10242,6 +10198,25 @@ extension Fields where TypeLock == Objects.Query { } } + func typeaheadSearch(first: OptionalArgument = .absent(), query: String, selection: Selection) throws -> Type { + let field = GraphQLField.composite( + name: "typeaheadSearch", + arguments: [Argument(name: "first", type: "Int", value: first), Argument(name: "query", type: "String!", value: query)], + selection: selection.selection + ) + select(field) + + switch response { + case let .decoding(data): + if let data = data.typeaheadSearch[field.alias!] { + return try selection.decode(data: data) + } + throw HttpError.badpayload + case .mocking: + return selection.mock() + } + } + func user(userId: OptionalArgument = .absent(), username: OptionalArgument = .absent(), selection: Selection) throws -> Type { let field = GraphQLField.composite( name: "user", @@ -11474,6 +11449,7 @@ extension Objects { let contentReader: [String: Enums.ContentReader] let createdAt: [String: DateTime] let description: [String: String] + let highlights: [String: [Objects.Highlight]] let id: [String: String] let image: [String: String] let isArchived: [String: Bool] @@ -11539,6 +11515,10 @@ extension Objects.SearchItem: Decodable { if let value = try container.decode(String?.self, forKey: codingKey) { map.set(key: field, hash: alias, value: value as Any) } + case "highlights": + if let value = try container.decode([Objects.Highlight]?.self, forKey: codingKey) { + map.set(key: field, hash: alias, value: value as Any) + } case "id": if let value = try container.decode(String?.self, forKey: codingKey) { map.set(key: field, hash: alias, value: value as Any) @@ -11658,6 +11638,7 @@ extension Objects.SearchItem: Decodable { contentReader = map["contentReader"] createdAt = map["createdAt"] description = map["description"] + highlights = map["highlights"] id = map["id"] image = map["image"] isArchived = map["isArchived"] @@ -11769,6 +11750,22 @@ extension Fields where TypeLock == Objects.SearchItem { } } + func highlights(selection: Selection) throws -> Type { + let field = GraphQLField.composite( + name: "highlights", + arguments: [], + selection: selection.selection + ) + select(field) + + switch response { + case let .decoding(data): + return try selection.decode(data: data.highlights[field.alias!]) + case .mocking: + return selection.mock() + } + } + func id() throws -> String { let field = GraphQLField.leaf( name: "id", @@ -13838,137 +13835,6 @@ extension Selection where TypeLock == Never, Type == Never { typealias SharedArticleSuccess = Selection } -extension Objects { - struct SignupError { - let __typename: TypeName = .signupError - let errorCodes: [String: [Enums.SignupErrorCode?]] - - enum TypeName: String, Codable { - case signupError = "SignupError" - } - } -} - -extension Objects.SignupError: Decodable { - init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: DynamicCodingKeys.self) - - var map = HashMap() - for codingKey in container.allKeys { - if codingKey.isTypenameKey { continue } - - let alias = codingKey.stringValue - let field = GraphQLField.getFieldNameFromAlias(alias) - - switch field { - case "errorCodes": - if let value = try container.decode([Enums.SignupErrorCode?]?.self, forKey: codingKey) { - map.set(key: field, hash: alias, value: value as Any) - } - default: - throw DecodingError.dataCorrupted( - DecodingError.Context( - codingPath: decoder.codingPath, - debugDescription: "Unknown key \(field)." - ) - ) - } - } - - errorCodes = map["errorCodes"] - } -} - -extension Fields where TypeLock == Objects.SignupError { - func errorCodes() throws -> [Enums.SignupErrorCode?] { - let field = GraphQLField.leaf( - name: "errorCodes", - arguments: [] - ) - select(field) - - switch response { - case let .decoding(data): - if let data = data.errorCodes[field.alias!] { - return data - } - throw HttpError.badpayload - case .mocking: - return [] - } - } -} - -extension Selection where TypeLock == Never, Type == Never { - typealias SignupError = Selection -} - -extension Objects { - struct SignupSuccess { - let __typename: TypeName = .signupSuccess - let me: [String: Objects.User] - - enum TypeName: String, Codable { - case signupSuccess = "SignupSuccess" - } - } -} - -extension Objects.SignupSuccess: Decodable { - init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: DynamicCodingKeys.self) - - var map = HashMap() - for codingKey in container.allKeys { - if codingKey.isTypenameKey { continue } - - let alias = codingKey.stringValue - let field = GraphQLField.getFieldNameFromAlias(alias) - - switch field { - case "me": - if let value = try container.decode(Objects.User?.self, forKey: codingKey) { - map.set(key: field, hash: alias, value: value as Any) - } - default: - throw DecodingError.dataCorrupted( - DecodingError.Context( - codingPath: decoder.codingPath, - debugDescription: "Unknown key \(field)." - ) - ) - } - } - - me = map["me"] - } -} - -extension Fields where TypeLock == Objects.SignupSuccess { - func me(selection: Selection) throws -> Type { - let field = GraphQLField.composite( - name: "me", - arguments: [], - selection: selection.selection - ) - select(field) - - switch response { - case let .decoding(data): - if let data = data.me[field.alias!] { - return try selection.decode(data: data) - } - throw HttpError.badpayload - case .mocking: - return selection.mock() - } - } -} - -extension Selection where TypeLock == Never, Type == Never { - typealias SignupSuccess = Selection -} - extension Objects { struct SubscribeError { let __typename: TypeName = .subscribeError @@ -14500,6 +14366,271 @@ extension Selection where TypeLock == Never, Type == Never { typealias SubscriptionsSuccess = Selection } +extension Objects { + struct TypeaheadSearchError { + let __typename: TypeName = .typeaheadSearchError + let errorCodes: [String: [Enums.TypeaheadSearchErrorCode]] + + enum TypeName: String, Codable { + case typeaheadSearchError = "TypeaheadSearchError" + } + } +} + +extension Objects.TypeaheadSearchError: Decodable { + init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: DynamicCodingKeys.self) + + var map = HashMap() + for codingKey in container.allKeys { + if codingKey.isTypenameKey { continue } + + let alias = codingKey.stringValue + let field = GraphQLField.getFieldNameFromAlias(alias) + + switch field { + case "errorCodes": + if let value = try container.decode([Enums.TypeaheadSearchErrorCode]?.self, forKey: codingKey) { + map.set(key: field, hash: alias, value: value as Any) + } + default: + throw DecodingError.dataCorrupted( + DecodingError.Context( + codingPath: decoder.codingPath, + debugDescription: "Unknown key \(field)." + ) + ) + } + } + + errorCodes = map["errorCodes"] + } +} + +extension Fields where TypeLock == Objects.TypeaheadSearchError { + func errorCodes() throws -> [Enums.TypeaheadSearchErrorCode] { + let field = GraphQLField.leaf( + name: "errorCodes", + arguments: [] + ) + select(field) + + switch response { + case let .decoding(data): + if let data = data.errorCodes[field.alias!] { + return data + } + throw HttpError.badpayload + case .mocking: + return [] + } + } +} + +extension Selection where TypeLock == Never, Type == Never { + typealias TypeaheadSearchError = Selection +} + +extension Objects { + struct TypeaheadSearchItem { + let __typename: TypeName = .typeaheadSearchItem + let id: [String: String] + let siteName: [String: String] + let slug: [String: String] + let title: [String: String] + + enum TypeName: String, Codable { + case typeaheadSearchItem = "TypeaheadSearchItem" + } + } +} + +extension Objects.TypeaheadSearchItem: Decodable { + init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: DynamicCodingKeys.self) + + var map = HashMap() + for codingKey in container.allKeys { + if codingKey.isTypenameKey { continue } + + let alias = codingKey.stringValue + let field = GraphQLField.getFieldNameFromAlias(alias) + + switch field { + case "id": + if let value = try container.decode(String?.self, forKey: codingKey) { + map.set(key: field, hash: alias, value: value as Any) + } + case "siteName": + if let value = try container.decode(String?.self, forKey: codingKey) { + map.set(key: field, hash: alias, value: value as Any) + } + case "slug": + if let value = try container.decode(String?.self, forKey: codingKey) { + map.set(key: field, hash: alias, value: value as Any) + } + case "title": + if let value = try container.decode(String?.self, forKey: codingKey) { + map.set(key: field, hash: alias, value: value as Any) + } + default: + throw DecodingError.dataCorrupted( + DecodingError.Context( + codingPath: decoder.codingPath, + debugDescription: "Unknown key \(field)." + ) + ) + } + } + + id = map["id"] + siteName = map["siteName"] + slug = map["slug"] + title = map["title"] + } +} + +extension Fields where TypeLock == Objects.TypeaheadSearchItem { + func id() throws -> String { + let field = GraphQLField.leaf( + name: "id", + arguments: [] + ) + select(field) + + switch response { + case let .decoding(data): + if let data = data.id[field.alias!] { + return data + } + throw HttpError.badpayload + case .mocking: + return String.mockValue + } + } + + func siteName() throws -> String? { + let field = GraphQLField.leaf( + name: "siteName", + arguments: [] + ) + select(field) + + switch response { + case let .decoding(data): + return data.siteName[field.alias!] + case .mocking: + return nil + } + } + + func slug() throws -> String { + let field = GraphQLField.leaf( + name: "slug", + arguments: [] + ) + select(field) + + switch response { + case let .decoding(data): + if let data = data.slug[field.alias!] { + return data + } + throw HttpError.badpayload + case .mocking: + return String.mockValue + } + } + + func title() throws -> String { + let field = GraphQLField.leaf( + name: "title", + arguments: [] + ) + select(field) + + switch response { + case let .decoding(data): + if let data = data.title[field.alias!] { + return data + } + throw HttpError.badpayload + case .mocking: + return String.mockValue + } + } +} + +extension Selection where TypeLock == Never, Type == Never { + typealias TypeaheadSearchItem = Selection +} + +extension Objects { + struct TypeaheadSearchSuccess { + let __typename: TypeName = .typeaheadSearchSuccess + let items: [String: [Objects.TypeaheadSearchItem]] + + enum TypeName: String, Codable { + case typeaheadSearchSuccess = "TypeaheadSearchSuccess" + } + } +} + +extension Objects.TypeaheadSearchSuccess: Decodable { + init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: DynamicCodingKeys.self) + + var map = HashMap() + for codingKey in container.allKeys { + if codingKey.isTypenameKey { continue } + + let alias = codingKey.stringValue + let field = GraphQLField.getFieldNameFromAlias(alias) + + switch field { + case "items": + if let value = try container.decode([Objects.TypeaheadSearchItem]?.self, forKey: codingKey) { + map.set(key: field, hash: alias, value: value as Any) + } + default: + throw DecodingError.dataCorrupted( + DecodingError.Context( + codingPath: decoder.codingPath, + debugDescription: "Unknown key \(field)." + ) + ) + } + } + + items = map["items"] + } +} + +extension Fields where TypeLock == Objects.TypeaheadSearchSuccess { + func items(selection: Selection) throws -> Type { + let field = GraphQLField.composite( + name: "items", + arguments: [], + selection: selection.selection + ) + select(field) + + switch response { + case let .decoding(data): + if let data = data.items[field.alias!] { + return try selection.decode(data: data) + } + throw HttpError.badpayload + case .mocking: + return selection.mock() + } + } +} + +extension Selection where TypeLock == Never, Type == Never { + typealias TypeaheadSearchSuccess = Selection +} + extension Objects { struct UnsubscribeError { let __typename: TypeName = .unsubscribeError @@ -20924,80 +21055,6 @@ extension Selection where TypeLock == Never, Type == Never { typealias SharedArticleResult = Selection } -extension Unions { - struct SignupResult { - let __typename: TypeName - let errorCodes: [String: [Enums.SignupErrorCode?]] - let me: [String: Objects.User] - - enum TypeName: String, Codable { - case signupError = "SignupError" - case signupSuccess = "SignupSuccess" - } - } -} - -extension Unions.SignupResult: Decodable { - init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: DynamicCodingKeys.self) - - var map = HashMap() - for codingKey in container.allKeys { - if codingKey.isTypenameKey { continue } - - let alias = codingKey.stringValue - let field = GraphQLField.getFieldNameFromAlias(alias) - - switch field { - case "errorCodes": - if let value = try container.decode([Enums.SignupErrorCode?]?.self, forKey: codingKey) { - map.set(key: field, hash: alias, value: value as Any) - } - case "me": - if let value = try container.decode(Objects.User?.self, forKey: codingKey) { - map.set(key: field, hash: alias, value: value as Any) - } - default: - throw DecodingError.dataCorrupted( - DecodingError.Context( - codingPath: decoder.codingPath, - debugDescription: "Unknown key \(field)." - ) - ) - } - } - - __typename = try container.decode(TypeName.self, forKey: DynamicCodingKeys(stringValue: "__typename")!) - - errorCodes = map["errorCodes"] - me = map["me"] - } -} - -extension Fields where TypeLock == Unions.SignupResult { - func on(signupError: Selection, signupSuccess: Selection) throws -> Type { - select([GraphQLField.fragment(type: "SignupError", selection: signupError.selection), GraphQLField.fragment(type: "SignupSuccess", selection: signupSuccess.selection)]) - - switch response { - case let .decoding(data): - switch data.__typename { - case .signupError: - let data = Objects.SignupError(errorCodes: data.errorCodes) - return try signupError.decode(data: data) - case .signupSuccess: - let data = Objects.SignupSuccess(me: data.me) - return try signupSuccess.decode(data: data) - } - case .mocking: - return signupError.mock() - } - } -} - -extension Selection where TypeLock == Never, Type == Never { - typealias SignupResult = Selection -} - extension Unions { struct SubscribeResult { let __typename: TypeName @@ -21146,6 +21203,80 @@ extension Selection where TypeLock == Never, Type == Never { typealias SubscriptionsResult = Selection } +extension Unions { + struct TypeaheadSearchResult { + let __typename: TypeName + let errorCodes: [String: [Enums.TypeaheadSearchErrorCode]] + let items: [String: [Objects.TypeaheadSearchItem]] + + enum TypeName: String, Codable { + case typeaheadSearchError = "TypeaheadSearchError" + case typeaheadSearchSuccess = "TypeaheadSearchSuccess" + } + } +} + +extension Unions.TypeaheadSearchResult: Decodable { + init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: DynamicCodingKeys.self) + + var map = HashMap() + for codingKey in container.allKeys { + if codingKey.isTypenameKey { continue } + + let alias = codingKey.stringValue + let field = GraphQLField.getFieldNameFromAlias(alias) + + switch field { + case "errorCodes": + if let value = try container.decode([Enums.TypeaheadSearchErrorCode]?.self, forKey: codingKey) { + map.set(key: field, hash: alias, value: value as Any) + } + case "items": + if let value = try container.decode([Objects.TypeaheadSearchItem]?.self, forKey: codingKey) { + map.set(key: field, hash: alias, value: value as Any) + } + default: + throw DecodingError.dataCorrupted( + DecodingError.Context( + codingPath: decoder.codingPath, + debugDescription: "Unknown key \(field)." + ) + ) + } + } + + __typename = try container.decode(TypeName.self, forKey: DynamicCodingKeys(stringValue: "__typename")!) + + errorCodes = map["errorCodes"] + items = map["items"] + } +} + +extension Fields where TypeLock == Unions.TypeaheadSearchResult { + func on(typeaheadSearchError: Selection, typeaheadSearchSuccess: Selection) throws -> Type { + select([GraphQLField.fragment(type: "TypeaheadSearchError", selection: typeaheadSearchError.selection), GraphQLField.fragment(type: "TypeaheadSearchSuccess", selection: typeaheadSearchSuccess.selection)]) + + switch response { + case let .decoding(data): + switch data.__typename { + case .typeaheadSearchError: + let data = Objects.TypeaheadSearchError(errorCodes: data.errorCodes) + return try typeaheadSearchError.decode(data: data) + case .typeaheadSearchSuccess: + let data = Objects.TypeaheadSearchSuccess(items: data.items) + return try typeaheadSearchSuccess.decode(data: data) + } + case .mocking: + return typeaheadSearchError.mock() + } + } +} + +extension Selection where TypeLock == Never, Type == Never { + typealias TypeaheadSearchResult = Selection +} + extension Unions { struct UnsubscribeResult { let __typename: TypeName @@ -22856,6 +22987,8 @@ extension Enums { case googleAuthError = "GOOGLE_AUTH_ERROR" + case invalidEmail = "INVALID_EMAIL" + case invalidPassword = "INVALID_PASSWORD" case invalidUsername = "INVALID_USERNAME" @@ -22921,6 +23054,13 @@ extension Enums { } } +extension Enums { + /// TypeaheadSearchErrorCode + enum TypeaheadSearchErrorCode: String, CaseIterable, Codable { + case unauthorized = "UNAUTHORIZED" + } +} + extension Enums { /// UnsubscribeErrorCode enum UnsubscribeErrorCode: String, CaseIterable, Codable { @@ -23437,25 +23577,6 @@ extension InputObjects { } } -extension InputObjects { - struct LoginInput: Encodable, Hashable { - var email: String - - var password: String - - func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(email, forKey: .email) - try container.encode(password, forKey: .password) - } - - enum CodingKeys: String, CodingKey { - case email - case password - } - } -} - extension InputObjects { struct MergeHighlightInput: Encodable, Hashable { var annotation: OptionalArgument = .absent() @@ -23907,41 +24028,6 @@ extension InputObjects { } } -extension InputObjects { - struct SignupInput: Encodable, Hashable { - var bio: OptionalArgument = .absent() - - var email: String - - var name: String - - var password: String - - var pictureUrl: OptionalArgument = .absent() - - var username: String - - func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - if bio.hasValue { try container.encode(bio, forKey: .bio) } - try container.encode(email, forKey: .email) - try container.encode(name, forKey: .name) - try container.encode(password, forKey: .password) - if pictureUrl.hasValue { try container.encode(pictureUrl, forKey: .pictureUrl) } - try container.encode(username, forKey: .username) - } - - enum CodingKeys: String, CodingKey { - case bio - case email - case name - case password - case pictureUrl - case username - } - } -} - extension InputObjects { struct SortParams: Encodable, Hashable { var by: Enums.SortBy diff --git a/apple/OmnivoreKit/Sources/Services/InternalModels/InternalLinkedItemLabel.swift b/apple/OmnivoreKit/Sources/Services/InternalModels/InternalLinkedItemLabel.swift index f54cf6f8b..d6707c7b1 100644 --- a/apple/OmnivoreKit/Sources/Services/InternalModels/InternalLinkedItemLabel.swift +++ b/apple/OmnivoreKit/Sources/Services/InternalModels/InternalLinkedItemLabel.swift @@ -110,7 +110,21 @@ extension Sequence where Element == InternalLinkedItemLabel { var result: [NSManagedObjectID]? context.performAndWait { + // Get currently stored label ids so we can later delete the old ones + let labelsFetchRequest: NSFetchRequest = LinkedItemLabel.fetchRequest() + let existingLabels = (try? labelsFetchRequest.execute()) ?? [] + + let validLabelIDs = map(\.id) + let invalidLinkedItemLabels = existingLabels.filter { !validLabelIDs.contains($0.unwrappedID) } + + // Delete all existing labels that aren't part of the newly updated list + // received from the server + for linkedItem in invalidLinkedItemLabels { + context.delete(linkedItem) + } + let labels = map { $0.asManagedObject(inContext: context) } + do { try context.save() logger.debug("labels saved succesfully")