diff --git a/apple/OmnivoreKit/Sources/App/Views/WebReader/RecommendToView.swift b/apple/OmnivoreKit/Sources/App/Views/WebReader/RecommendToView.swift index 24b624a91..20d737d03 100644 --- a/apple/OmnivoreKit/Sources/App/Views/WebReader/RecommendToView.swift +++ b/apple/OmnivoreKit/Sources/App/Views/WebReader/RecommendToView.swift @@ -10,6 +10,7 @@ import Views @Published var selectedGroups = [String]() @Published var isRunning = false @Published var showError = false + @Published var note: String? let pageID: String @@ -34,7 +35,7 @@ import Views isRunning = true do { - try await dataService.recommendPage(pageID: pageID, groupIDs: selectedGroups) + try await dataService.recommendPage(pageID: pageID, groupIDs: selectedGroups, note: note) } catch { showError = true } diff --git a/apple/OmnivoreKit/Sources/Models/CoreData/CoreDataModel.xcdatamodeld/CoreDataModel.xcdatamodel/contents b/apple/OmnivoreKit/Sources/Models/CoreData/CoreDataModel.xcdatamodeld/CoreDataModel.xcdatamodel/contents index 9dbaf53aa..deb3afd4f 100644 --- a/apple/OmnivoreKit/Sources/Models/CoreData/CoreDataModel.xcdatamodeld/CoreDataModel.xcdatamodel/contents +++ b/apple/OmnivoreKit/Sources/Models/CoreData/CoreDataModel.xcdatamodeld/CoreDataModel.xcdatamodel/contents @@ -55,7 +55,7 @@ - + @@ -96,7 +96,8 @@ - + + diff --git a/apple/OmnivoreKit/Sources/Models/CoreData/StorageProvider.swift b/apple/OmnivoreKit/Sources/Models/CoreData/StorageProvider.swift index 6198ec852..12a31526a 100644 --- a/apple/OmnivoreKit/Sources/Models/CoreData/StorageProvider.swift +++ b/apple/OmnivoreKit/Sources/Models/CoreData/StorageProvider.swift @@ -21,6 +21,7 @@ public class PersistentContainer: NSPersistentContainer { #endif let appGroupContainer = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroupID) let appGroupContainerURL = appGroupContainer?.appendingPathComponent("store.sqlite") + print("starting with sqlite container", appGroupContainerURL) container.persistentStoreDescriptions.first!.url = appGroupContainerURL diff --git a/apple/OmnivoreKit/Sources/Services/DataService/GQLSchema.swift b/apple/OmnivoreKit/Sources/Services/DataService/GQLSchema.swift index 33c18d55a..22963144d 100644 --- a/apple/OmnivoreKit/Sources/Services/DataService/GQLSchema.swift +++ b/apple/OmnivoreKit/Sources/Services/DataService/GQLSchema.swift @@ -684,7 +684,7 @@ extension Objects { let readAt: [String: DateTime] let readingProgressAnchorIndex: [String: Int] let readingProgressPercent: [String: Double] - let recommendedBy: [String: [Objects.Recommendation]] + let recommendations: [String: [Objects.Recommendation]] let savedAt: [String: DateTime] let savedByViewer: [String: Bool] let shareInfo: [String: Objects.LinkShareInfo] @@ -807,7 +807,7 @@ extension Objects.Article: Decodable { if let value = try container.decode(Double?.self, forKey: codingKey) { map.set(key: field, hash: alias, value: value as Any) } - case "recommendedBy": + case "recommendations": if let value = try container.decode([Objects.Recommendation]?.self, forKey: codingKey) { map.set(key: field, hash: alias, value: value as Any) } @@ -903,7 +903,7 @@ extension Objects.Article: Decodable { readAt = map["readAt"] readingProgressAnchorIndex = map["readingProgressAnchorIndex"] readingProgressPercent = map["readingProgressPercent"] - recommendedBy = map["recommendedBy"] + recommendations = map["recommendations"] savedAt = map["savedAt"] savedByViewer = map["savedByViewer"] shareInfo = map["shareInfo"] @@ -1282,9 +1282,9 @@ extension Fields where TypeLock == Objects.Article { } } - func recommendedBy(selection: Selection) throws -> Type { + func recommendations(selection: Selection) throws -> Type { let field = GraphQLField.composite( - name: "recommendedBy", + name: "recommendations", arguments: [], selection: selection.selection ) @@ -1292,7 +1292,7 @@ extension Fields where TypeLock == Objects.Article { switch response { case let .decoding(data): - return try selection.decode(data: data.recommendedBy[field.alias!]) + return try selection.decode(data: data.recommendations[field.alias!]) case .mocking: return selection.mock() } @@ -13733,7 +13733,9 @@ extension Objects { let __typename: TypeName = .recommendation let id: [String: String] let name: [String: String] + let note: [String: String] let recommendedAt: [String: DateTime] + let user: [String: Objects.RecommendingUser] enum TypeName: String, Codable { case recommendation = "Recommendation" @@ -13761,10 +13763,18 @@ extension Objects.Recommendation: Decodable { if let value = try container.decode(String?.self, forKey: codingKey) { map.set(key: field, hash: alias, value: value as Any) } + case "note": + if let value = try container.decode(String?.self, forKey: codingKey) { + map.set(key: field, hash: alias, value: value as Any) + } case "recommendedAt": if let value = try container.decode(DateTime?.self, forKey: codingKey) { map.set(key: field, hash: alias, value: value as Any) } + case "user": + if let value = try container.decode(Objects.RecommendingUser?.self, forKey: codingKey) { + map.set(key: field, hash: alias, value: value as Any) + } default: throw DecodingError.dataCorrupted( DecodingError.Context( @@ -13777,7 +13787,9 @@ extension Objects.Recommendation: Decodable { id = map["id"] name = map["name"] + note = map["note"] recommendedAt = map["recommendedAt"] + user = map["user"] } } @@ -13818,6 +13830,21 @@ extension Fields where TypeLock == Objects.Recommendation { } } + func note() throws -> String? { + let field = GraphQLField.leaf( + name: "note", + arguments: [] + ) + select(field) + + switch response { + case let .decoding(data): + return data.note[field.alias!] + case .mocking: + return nil + } + } + func recommendedAt() throws -> DateTime { let field = GraphQLField.leaf( name: "recommendedAt", @@ -13835,6 +13862,22 @@ extension Fields where TypeLock == Objects.Recommendation { return DateTime.mockValue } } + + func user(selection: Selection) throws -> Type { + let field = GraphQLField.composite( + name: "user", + arguments: [], + selection: selection.selection + ) + select(field) + + switch response { + case let .decoding(data): + return try selection.decode(data: data.user[field.alias!]) + case .mocking: + return selection.mock() + } + } } extension Selection where TypeLock == Never, Type == Never { @@ -14052,6 +14095,140 @@ extension Selection where TypeLock == Never, Type == Never { typealias RecommendationGroup = Selection } +extension Objects { + struct RecommendingUser { + let __typename: TypeName = .recommendingUser + let name: [String: String] + let profileImageUrl: [String: String] + let userId: [String: String] + let username: [String: String] + + enum TypeName: String, Codable { + case recommendingUser = "RecommendingUser" + } + } +} + +extension Objects.RecommendingUser: 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 "name": + if let value = try container.decode(String?.self, forKey: codingKey) { + map.set(key: field, hash: alias, value: value as Any) + } + case "profileImageUrl": + if let value = try container.decode(String?.self, forKey: codingKey) { + map.set(key: field, hash: alias, value: value as Any) + } + case "userId": + if let value = try container.decode(String?.self, forKey: codingKey) { + map.set(key: field, hash: alias, value: value as Any) + } + case "username": + 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)." + ) + ) + } + } + + name = map["name"] + profileImageUrl = map["profileImageUrl"] + userId = map["userId"] + username = map["username"] + } +} + +extension Fields where TypeLock == Objects.RecommendingUser { + func name() throws -> String { + let field = GraphQLField.leaf( + name: "name", + arguments: [] + ) + select(field) + + switch response { + case let .decoding(data): + if let data = data.name[field.alias!] { + return data + } + throw HttpError.badpayload + case .mocking: + return String.mockValue + } + } + + func profileImageUrl() throws -> String? { + let field = GraphQLField.leaf( + name: "profileImageURL", + arguments: [] + ) + select(field) + + switch response { + case let .decoding(data): + return data.profileImageUrl[field.alias!] + case .mocking: + return nil + } + } + + func userId() throws -> String { + let field = GraphQLField.leaf( + name: "userId", + arguments: [] + ) + select(field) + + switch response { + case let .decoding(data): + if let data = data.userId[field.alias!] { + return data + } + throw HttpError.badpayload + case .mocking: + return String.mockValue + } + } + + func username() throws -> String { + let field = GraphQLField.leaf( + name: "username", + arguments: [] + ) + select(field) + + switch response { + case let .decoding(data): + if let data = data.username[field.alias!] { + return data + } + throw HttpError.badpayload + case .mocking: + return String.mockValue + } + } +} + +extension Selection where TypeLock == Never, Type == Never { + typealias RecommendingUser = Selection +} + extension Objects { struct Reminder { let __typename: TypeName = .reminder @@ -15471,7 +15648,7 @@ extension Objects { let readAt: [String: DateTime] let readingProgressAnchorIndex: [String: Int] let readingProgressPercent: [String: Double] - let recommendedBy: [String: [Objects.Recommendation]] + let recommendations: [String: [Objects.Recommendation]] let savedAt: [String: DateTime] let shortId: [String: String] let siteIcon: [String: String] @@ -15584,7 +15761,7 @@ extension Objects.SearchItem: Decodable { if let value = try container.decode(Double?.self, forKey: codingKey) { map.set(key: field, hash: alias, value: value as Any) } - case "recommendedBy": + case "recommendations": if let value = try container.decode([Objects.Recommendation]?.self, forKey: codingKey) { map.set(key: field, hash: alias, value: value as Any) } @@ -15670,7 +15847,7 @@ extension Objects.SearchItem: Decodable { readAt = map["readAt"] readingProgressAnchorIndex = map["readingProgressAnchorIndex"] readingProgressPercent = map["readingProgressPercent"] - recommendedBy = map["recommendedBy"] + recommendations = map["recommendations"] savedAt = map["savedAt"] shortId = map["shortId"] siteIcon = map["siteIcon"] @@ -16011,9 +16188,9 @@ extension Fields where TypeLock == Objects.SearchItem { } } - func recommendedBy(selection: Selection) throws -> Type { + func recommendations(selection: Selection) throws -> Type { let field = GraphQLField.composite( - name: "recommendedBy", + name: "recommendations", arguments: [], selection: selection.selection ) @@ -16021,7 +16198,7 @@ extension Fields where TypeLock == Objects.SearchItem { switch response { case let .decoding(data): - return try selection.decode(data: data.recommendedBy[field.alias!]) + return try selection.decode(data: data.recommendations[field.alias!]) case .mocking: return selection.mock() } @@ -30128,16 +30305,20 @@ extension InputObjects { struct RecommendInput: Encodable, Hashable { var groupIds: [String] + var note: OptionalArgument = .absent() + var pageId: String func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(groupIds, forKey: .groupIds) + if note.hasValue { try container.encode(note, forKey: .note) } try container.encode(pageId, forKey: .pageId) } enum CodingKeys: String, CodingKey { case groupIds + case note case pageId } } diff --git a/apple/OmnivoreKit/Sources/Services/DataService/Mutations/RecommendPage.swift b/apple/OmnivoreKit/Sources/Services/DataService/Mutations/RecommendPage.swift index 6716460db..68f604817 100644 --- a/apple/OmnivoreKit/Sources/Services/DataService/Mutations/RecommendPage.swift +++ b/apple/OmnivoreKit/Sources/Services/DataService/Mutations/RecommendPage.swift @@ -4,7 +4,7 @@ import Models import SwiftGraphQL public extension DataService { - func recommendPage(pageID: String, groupIDs: [String]) async throws { + func recommendPage(pageID: String, groupIDs: [String], note: String?) async throws { enum MutationResult { case saved(taskNames: [String]) case error(errorMessage: String) @@ -21,7 +21,7 @@ public extension DataService { let mutation = Selection.Mutation { try $0.recommend( - input: .init(groupIds: groupIDs, pageId: pageID), + input: .init(groupIds: groupIDs, note: OptionalArgument(note), pageId: pageID), selection: selection ) } diff --git a/apple/OmnivoreKit/Sources/Services/DataService/Queries/ArticleContentQuery.swift b/apple/OmnivoreKit/Sources/Services/DataService/Queries/ArticleContentQuery.swift index bf7f5f639..a9170e38e 100644 --- a/apple/OmnivoreKit/Sources/Services/DataService/Queries/ArticleContentQuery.swift +++ b/apple/OmnivoreKit/Sources/Services/DataService/Queries/ArticleContentQuery.swift @@ -44,7 +44,7 @@ extension DataService { contentReader: try $0.contentReader().rawValue, originalHtml: nil, language: try $0.language(), - recommendedBy: try $0.recommendedBy(selection: recommendationSelection.list.nullable) ?? [], + recommendations: try $0.recommendations(selection: recommendationSelection.list.nullable) ?? [], labels: try $0.labels(selection: feedItemLabelSelection.list.nullable) ?? [] ), htmlContent: try $0.content(), diff --git a/apple/OmnivoreKit/Sources/Services/DataService/Queries/LinkedItemNetworkQuery.swift b/apple/OmnivoreKit/Sources/Services/DataService/Queries/LinkedItemNetworkQuery.swift index 6bb543079..51ded19f7 100644 --- a/apple/OmnivoreKit/Sources/Services/DataService/Queries/LinkedItemNetworkQuery.swift +++ b/apple/OmnivoreKit/Sources/Services/DataService/Queries/LinkedItemNetworkQuery.swift @@ -224,12 +224,38 @@ extension DataService { } } +let recommendingUserSelection = Selection.RecommendingUser { + do { + return InternalUserProfile( + userID: try $0.userId(), + name: try $0.name(), + username: try $0.username(), + profileImageURL: nil // try $0.profileImageUrl() ?? nil + ) + } catch { + print("ERROR WITH recommendingUserSelection", error) + throw error + } +} + let recommendationSelection = Selection.Recommendation { - InternalRecommendation( - id: try $0.id(), - name: try $0.name(), - recommendedAt: try $0.recommendedAt().value ?? Date() - ) + do { + let result = InternalRecommendation( + id: try $0.id(), + name: try $0.name(), + user: try $0.user(selection: recommendingUserSelection.nullable), + recommendedAt: try $0.recommendedAt().value ?? Date() + ) + return result + } catch { + print("ERROR WITH recommendationSelection", error) + throw error + } +} + +private func emptyrecommended() -> [InternalRecommendation] { + print("got the empty InternalRecommendation") + return [] } private let libraryArticleSelection = Selection.Article { @@ -257,7 +283,7 @@ private let libraryArticleSelection = Selection.Article { contentReader: try $0.contentReader().rawValue, originalHtml: nil, language: try $0.language(), - recommendedBy: try $0.recommendedBy(selection: recommendationSelection.list.nullable) ?? [], + recommendations: try $0.recommendations(selection: recommendationSelection.list.nullable) ?? emptyrecommended(), labels: try $0.labels(selection: feedItemLabelSelection.list.nullable) ?? [] ) } @@ -295,7 +321,7 @@ private let searchItemSelection = Selection.SearchItem { contentReader: try $0.contentReader().rawValue, originalHtml: nil, language: try $0.language(), - recommendedBy: try $0.recommendedBy(selection: recommendationSelection.list.nullable) ?? [], + recommendations: try $0.recommendations(selection: recommendationSelection.list.nullable) ?? [], labels: try $0.labels(selection: feedItemLabelSelection.list.nullable) ?? [] ) } diff --git a/apple/OmnivoreKit/Sources/Services/InternalModels/InternalLinkedItem.swift b/apple/OmnivoreKit/Sources/Services/InternalModels/InternalLinkedItem.swift index 77ee4ae91..46827adf8 100644 --- a/apple/OmnivoreKit/Sources/Services/InternalModels/InternalLinkedItem.swift +++ b/apple/OmnivoreKit/Sources/Services/InternalModels/InternalLinkedItem.swift @@ -26,7 +26,7 @@ struct InternalLinkedItem { let contentReader: String? let originalHtml: String? let language: String? - let recommendedBy: [InternalRecommendation] + let recommendations: [InternalRecommendation] var labels: [InternalLinkedItemLabel] var isPDF: Bool { @@ -73,12 +73,12 @@ struct InternalLinkedItem { linkedItem.addToLabels(label.asManagedObject(inContext: context)) } - if let existingRecommendation = linkedItem.recommendedBy { - linkedItem.removeFromRecommendedBy(existingRecommendation) + if let existingRecommendation = linkedItem.recommendations { + linkedItem.removeFromRecommendations(existingRecommendation) } - for recommendation in recommendedBy { - linkedItem.addToRecommendedBy(recommendation.asManagedObject(inContext: context)) + for recommendation in recommendations { + linkedItem.addToRecommendations(recommendation.asManagedObject(inContext: context)) } return linkedItem @@ -142,7 +142,7 @@ extension JSONArticle { contentReader: contentReader, originalHtml: nil, language: language, - recommendedBy: [], // TODO: + recommendations: [], // TODO: labels: [] ) diff --git a/apple/OmnivoreKit/Sources/Services/InternalModels/InternalRecommendation.swift b/apple/OmnivoreKit/Sources/Services/InternalModels/InternalRecommendation.swift index c706d7014..80a33b0cc 100644 --- a/apple/OmnivoreKit/Sources/Services/InternalModels/InternalRecommendation.swift +++ b/apple/OmnivoreKit/Sources/Services/InternalModels/InternalRecommendation.swift @@ -2,10 +2,11 @@ import CoreData import Foundation import Models -public struct InternalRecommendation: Encodable { +public struct InternalRecommendation { let id: String let name: String - let recommendedAt: Date? + let user: InternalUserProfile? + let recommendedAt: Date func asManagedObject(inContext context: NSManagedObjectContext) -> Recommendation { let existing = Recommendation.lookup(byID: id, inContext: context) @@ -13,6 +14,7 @@ public struct InternalRecommendation: Encodable { recommendation.id = id recommendation.name = name recommendation.recommendedAt = recommendedAt + recommendation.user = user?.asManagedObject(inContext: context) return recommendation } @@ -27,6 +29,7 @@ public struct InternalRecommendation: Encodable { return InternalRecommendation( id: id, name: name, + user: InternalUserProfile.makeSingle(recommendation.user), recommendedAt: recommendedAt ) } diff --git a/apple/OmnivoreKit/Sources/Services/InternalModels/InternalUserProfile.swift b/apple/OmnivoreKit/Sources/Services/InternalModels/InternalUserProfile.swift index 75a2924a8..53b656890 100644 --- a/apple/OmnivoreKit/Sources/Services/InternalModels/InternalUserProfile.swift +++ b/apple/OmnivoreKit/Sources/Services/InternalModels/InternalUserProfile.swift @@ -42,6 +42,22 @@ public struct InternalUserProfile: Identifiable { return userProfile } + public static func makeSingle(_ user: UserProfile?) -> InternalUserProfile? { + if let user = user, + let userID = user.userID, + let name = user.name, + let username = user.username + { + return InternalUserProfile( + userID: userID, + name: name, + username: username, + profileImageURL: user.profileImageURL + ) + } + return nil + } + public static func make(_ users: NSSet?) -> [InternalUserProfile] { users? .compactMap { user in diff --git a/apple/OmnivoreKit/Sources/Views/FeedItem/HomeFeedCardView.swift b/apple/OmnivoreKit/Sources/Views/FeedItem/HomeFeedCardView.swift index 1a7d04bd6..027bc4427 100644 --- a/apple/OmnivoreKit/Sources/Views/FeedItem/HomeFeedCardView.swift +++ b/apple/OmnivoreKit/Sources/Views/FeedItem/HomeFeedCardView.swift @@ -87,15 +87,21 @@ public struct FeedCard: View { #endif } - if let recommendedBy = item.recommendedBy, recommendedBy.count > 0 { - let str = recommendedBy.reduce("") { str, item in + if let recommendations = item.recommendations, recommendations.count > 0 { + let byStr = recommendations.reduce("") { str, item in + if let item = item as? Recommendation, let name = item.user?.name { + return str + name + } + return str + } + let inStr = recommendations.reduce("") { str, item in if let item = item as? Recommendation, let name = item.name { return str + name } return str } HStack { - Text("Recommended in \(str)") + Text("Recommended by \(byStr) in \(inStr)") .font(.appCaption) .frame(alignment: .leading) Spacer() diff --git a/packages/api/src/elastic/recommendation.ts b/packages/api/src/elastic/recommendation.ts index 511b3fae1..924f444fe 100644 --- a/packages/api/src/elastic/recommendation.ts +++ b/packages/api/src/elastic/recommendation.ts @@ -13,19 +13,19 @@ export const addRecommendation = async ( url: page.url, }) if (existingPage) { - if (existingPage.recommendedBy?.includes(recommendation)) { + if (existingPage.recommendations?.includes(recommendation)) { return existingPage._id } // update recommendedBy in the existing page - const recommendedBy = (existingPage.recommendedBy || []).concat( + const recommendations = (existingPage.recommendations || []).concat( recommendation ) await updatePage( existingPage.id, { - recommendedBy, + recommendations, }, ctx ) @@ -36,7 +36,7 @@ export const addRecommendation = async ( const newPage: Page = { ...page, id: '', - recommendedBy: [recommendation], + recommendations: [recommendation], userId: ctx.uid, readingProgressPercent: 0, readingProgressAnchorIndex: 0, diff --git a/packages/api/src/elastic/types.ts b/packages/api/src/elastic/types.ts index ade5f5f1f..e4164b9c3 100644 --- a/packages/api/src/elastic/types.ts +++ b/packages/api/src/elastic/types.ts @@ -249,7 +249,7 @@ export interface Page { readAt?: Date listenedAt?: Date wordsCount?: number - recommendedBy?: Recommendation[] + recommendations?: Recommendation[] } export interface SearchItem { @@ -282,7 +282,7 @@ export interface SearchItem { wordsCount?: number siteName?: string siteIcon?: string - recommendedBy?: Recommendation[] + recommendations?: Recommendation[] } const keys = ['_id', 'url', 'slug', 'userId', 'uploadFileId', 'state'] as const diff --git a/packages/api/src/generated/graphql.ts b/packages/api/src/generated/graphql.ts index c68407531..8728bb795 100644 --- a/packages/api/src/generated/graphql.ts +++ b/packages/api/src/generated/graphql.ts @@ -110,7 +110,7 @@ export type Article = { readAt?: Maybe; readingProgressAnchorIndex: Scalars['Int']; readingProgressPercent: Scalars['Float']; - recommendedBy?: Maybe>; + recommendations?: Maybe>; savedAt: Scalars['Date']; savedByViewer?: Maybe; shareInfo?: Maybe; @@ -1804,7 +1804,7 @@ export type Recommendation = { name: Scalars['String']; note?: Maybe; recommendedAt: Scalars['Date']; - user: RecommendingUser; + user?: Maybe; }; export type RecommendationGroup = { @@ -2057,7 +2057,7 @@ export type SearchItem = { readAt?: Maybe; readingProgressAnchorIndex: Scalars['Int']; readingProgressPercent: Scalars['Float']; - recommendedBy?: Maybe>; + recommendations?: Maybe>; savedAt: Scalars['Date']; shortId?: Maybe; siteIcon?: Maybe; @@ -3889,7 +3889,7 @@ export type ArticleResolvers, ParentType, ContextType>; readingProgressAnchorIndex?: Resolver; readingProgressPercent?: Resolver; - recommendedBy?: Resolver>, ParentType, ContextType>; + recommendations?: Resolver>, ParentType, ContextType>; savedAt?: Resolver; savedByViewer?: Resolver, ParentType, ContextType>; shareInfo?: Resolver, ParentType, ContextType>; @@ -4851,7 +4851,7 @@ export type RecommendationResolvers; note?: Resolver, ParentType, ContextType>; recommendedAt?: Resolver; - user?: Resolver; + user?: Resolver, ParentType, ContextType>; __isTypeOf?: IsTypeOfResolverFn; }; @@ -5016,7 +5016,7 @@ export type SearchItemResolvers, ParentType, ContextType>; readingProgressAnchorIndex?: Resolver; readingProgressPercent?: Resolver; - recommendedBy?: Resolver>, ParentType, ContextType>; + recommendations?: Resolver>, ParentType, ContextType>; savedAt?: Resolver; shortId?: Resolver, ParentType, ContextType>; siteIcon?: Resolver, ParentType, ContextType>; diff --git a/packages/api/src/generated/schema.graphql b/packages/api/src/generated/schema.graphql index 7178aa080..89f6cbf6b 100644 --- a/packages/api/src/generated/schema.graphql +++ b/packages/api/src/generated/schema.graphql @@ -86,7 +86,7 @@ type Article { readAt: Date readingProgressAnchorIndex: Int! readingProgressPercent: Float! - recommendedBy: [Recommendation!] + recommendations: [Recommendation!] savedAt: Date! savedByViewer: Boolean shareInfo: LinkShareInfo @@ -1265,7 +1265,7 @@ type Recommendation { name: String! note: String recommendedAt: Date! - user: RecommendingUser! + user: RecommendingUser } type RecommendationGroup { @@ -1498,7 +1498,7 @@ type SearchItem { readAt: Date readingProgressAnchorIndex: Int! readingProgressPercent: Float! - recommendedBy: [Recommendation!] + recommendations: [Recommendation!] savedAt: Date! shortId: String siteIcon: String diff --git a/packages/api/src/resolvers/article/index.ts b/packages/api/src/resolvers/article/index.ts index 695e47664..6333aba8a 100644 --- a/packages/api/src/resolvers/article/index.ts +++ b/packages/api/src/resolvers/article/index.ts @@ -1015,6 +1015,7 @@ export const updatesSinceResolver = authorized< } }) + console.log('edges', JSON.stringify(edges)) return { edges, pageInfo: { diff --git a/packages/api/src/resolvers/recommendations/index.ts b/packages/api/src/resolvers/recommendations/index.ts index d86d45bde..d3e90df89 100644 --- a/packages/api/src/resolvers/recommendations/index.ts +++ b/packages/api/src/resolvers/recommendations/index.ts @@ -185,7 +185,8 @@ export const recommendResolver = authorized< member.user.id, page.id, { - ...group, + id: group.id, + name: group.name, note: input.note ?? null, user: { userId: user.id, diff --git a/packages/api/src/schema.ts b/packages/api/src/schema.ts index a281a70de..de1a182e3 100755 --- a/packages/api/src/schema.ts +++ b/packages/api/src/schema.ts @@ -384,7 +384,7 @@ const schema = gql` state: ArticleSavingRequestStatus language: String readAt: Date - recommendedBy: [Recommendation!] + recommendations: [Recommendation!] } # Query: article @@ -1531,7 +1531,7 @@ const schema = gql` savedAt: Date! highlights: [Highlight!] siteIcon: String - recommendedBy: [Recommendation!] + recommendations: [Recommendation!] } type SearchItemEdge { diff --git a/packages/db/elastic_migrations/index_settings.json b/packages/db/elastic_migrations/index_settings.json index ec53bce1a..cebf4c58b 100644 --- a/packages/db/elastic_migrations/index_settings.json +++ b/packages/db/elastic_migrations/index_settings.json @@ -167,7 +167,7 @@ "wordsCount": { "type": "integer" }, - "recommendedBy": { + "recommendations": { "type": "nested", "properties": { "id": { diff --git a/packages/db/migrate.ts b/packages/db/migrate.ts index 01b6ef065..767ac5efc 100755 --- a/packages/db/migrate.ts +++ b/packages/db/migrate.ts @@ -5,6 +5,7 @@ import chalk from 'chalk' import { Client } from '@elastic/elasticsearch' import { readFileSync } from 'fs' import { join } from 'path' +import { print } from 'graphql' dotenv.config() @@ -109,6 +110,86 @@ const createIndex = async (): Promise => { }) } +const reIndex = async (): Promise => { + const newPages = INDEX_NAME + '-002' + // // create index + // try { + // await esClient.indices.delete({ + // index: newPages, + // }) + // } catch (e) { + // console.log('001: ', e) + // } + + // try { + // await esClient.indices.delete({ + // index: 'pages-temp', + // }) + // } catch (e) { + // console.log('002: ', e) + // } + + // try { + // await esClient.indices.delete({ + // index: 'pages-temp-001', + // }) + // } catch (e) { + // console.log('003: ', e) + // } + + // try { + // await esClient.indices.delete({ + // index: 'pages-temp', + // }) + // } catch (e) { + // console.log('002: ', e) + // } + + try { + log('getting pages alias:') + const result = await esClient.indices.getAlias({ + index: 'pages_alias', + }) + log('result ' + JSON.stringify(result.body)) + } catch (err) { + log('fetch error: ' + err) + } + + log('creating newPages') + await esClient.indices.create({ + index: newPages, + body: JSON.parse(indexSettings), + }) + + log('reindexing') + await esClient.reindex({ + body: { + source: { index: INDEX_NAME }, + dest: { index: newPages }, + }, + wait_for_completion: true, + }) + + console.log('updateAliases') + + await esClient.indices.updateAliases({ + body: { + actions: [ + { + add: { index: newPages, alias: 'pages_alias', is_write_index: true }, + }, + ], + }, + }) + + console.log('updateAliases 002') + esClient.indices.updateAliases({ + body: { + actions: [{ remove: { index: INDEX_NAME, alias: 'pages_alias' } }], + }, + }) +} + const updateMappings = async (): Promise => { // update mappings await esClient.indices.putMapping({ @@ -131,59 +212,85 @@ const postgresMigration = postgrator process.exit(1) }) +// console.log('reindexing') +// reIndex() +// .then(() => console.log('reindexed elastic')) +// .catch((err) => console.log('error reindexing', err)) + // elastic migration log('Creating elastic index...') -const elasticMigration = esClient.indices - .exists({ index: INDEX_ALIAS }) - .then(({ body: exists }) => { - if (!exists) { - return createIndex().then(() => log('Elastic index created.')) - } else { - log('Elastic index already exists.') - } - }) - .then(() => { - log('Updating elastic index mappings...') - return updateMappings().then(() => { - log('Elastic index mappings updated.') +const indexTest = async (): Promise => { + log('running indexTest') + try { + const result = await esClient.indices.getAlias({ + index: 'pages_alias', }) - }) - .then(() => { - log('Adding default state to pages in elastic...') - return esClient - .update_by_query({ - index: INDEX_ALIAS, - requests_per_second: 250, - scroll_size: 500, - timeout: '30m', - body: { - script: { - source: 'ctx._source.state = params.state', - lang: 'painless', - params: { - state: 'SUCCEEDED', - }, - }, - query: { - bool: { - must_not: [ - { - exists: { - field: 'state', - }, - }, - ], - }, - }, - }, - }) - .then(() => log('Default state added.')) - }) - .catch((error) => { - log(`${chalk.red('Elastic migration failed: ')}${error.message}`, chalk.red) - const { appliedMigrations } = error - logAppliedMigrations(appliedMigrations) - process.exit(1) - }) + log('result ' + JSON.stringify(result.body)) + } catch (err) { + log('fetch error: ' + err) + } +} -Promise.all([postgresMigration, elasticMigration]).then(() => log('Exiting...')) +reIndex().then(() => console.log('ran reindex')) +// indexTest().then(() => console.log('ran index test')) + +// const elasticMigration = esClient.indices +// .exists({ index: INDEX_ALIAS }) +// .then(({ body: exists }) => { +// if (!exists) { +// return createIndex().then(() => log('Elastic index created.')) +// } else { +// log('Elastic index already exists.') +// } +// }) +// .then(() => { +// log('Updating elastic index mappings...') +// return updateMappings().then(() => { +// log('Elastic index mappings updated.') +// }) +// }) +// .then(() => { +// log('Adding default state to pages in elastic...') +// return esClient +// .update_by_query({ +// index: INDEX_ALIAS, +// requests_per_second: 250, +// scroll_size: 500, +// timeout: '30m', +// body: { +// script: { +// source: 'ctx._source.state = params.state', +// lang: 'painless', +// params: { +// state: 'SUCCEEDED', +// }, +// }, +// query: { +// bool: { +// must_not: [ +// { +// exists: { +// field: 'state', +// }, +// }, +// ], +// }, +// }, +// }, +// }) +// .then(() => log('Default state added.')) +// }) +// .catch((error) => { +// console.log('error', JSON.stringify(error)) +// log(`${chalk.red('Elastic migration failed: ')}${error.message}`, chalk.red) +// const { appliedMigrations } = error +// logAppliedMigrations(appliedMigrations) +// process.exit(1) +// }) + +Promise.all([ + // postgresMigration, + // indexTest, + // reIndex, + /* elasticMigration */ +]).then(() => log('Exiting...')) diff --git a/packages/web/lib/networking/queries/useGetLibraryItemsQuery.tsx b/packages/web/lib/networking/queries/useGetLibraryItemsQuery.tsx index 287ee1a43..be7893b41 100644 --- a/packages/web/lib/networking/queries/useGetLibraryItemsQuery.tsx +++ b/packages/web/lib/networking/queries/useGetLibraryItemsQuery.tsx @@ -82,7 +82,7 @@ export type LibraryItemNode = { siteName?: string subscription?: string readAt?: string - recommendedBy?: Recommendation[] + recommendations?: Recommendation[] } export type Recommendation = { @@ -151,7 +151,7 @@ export function useGetLibraryItemsQuery({ siteName subscription readAt - recommendedBy { + recommendations { id name note