Rename recommendedBy to recommendations

This commit is contained in:
Jackson Harper 2022-12-06 21:18:21 +08:00
parent ba8cd22611
commit a8df439297
21 changed files with 453 additions and 109 deletions

View file

@ -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
}

View file

@ -55,7 +55,7 @@
<attribute name="updatedAt" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
<relationship name="highlights" toMany="YES" deletionRule="Cascade" destinationEntity="Highlight" inverseName="linkedItem" inverseEntity="Highlight"/>
<relationship name="labels" toMany="YES" deletionRule="Nullify" destinationEntity="LinkedItemLabel" inverseName="linkedItems" inverseEntity="LinkedItemLabel"/>
<relationship name="recommendedBy" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="Recommendation" inverseName="linkedItem" inverseEntity="Recommendation"/>
<relationship name="recommendations" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="Recommendation" inverseName="linkedItem" inverseEntity="Recommendation"/>
<uniquenessConstraints>
<uniquenessConstraint>
<constraint value="id"/>
@ -96,7 +96,8 @@
<attribute name="id" optional="YES" attributeType="String"/>
<attribute name="name" optional="YES" attributeType="String"/>
<attribute name="recommendedAt" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
<relationship name="linkedItem" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="LinkedItem" inverseName="recommendedBy" inverseEntity="LinkedItem"/>
<relationship name="linkedItem" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="LinkedItem" inverseName="recommendations" inverseEntity="LinkedItem"/>
<relationship name="user" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="UserProfile"/>
</entity>
<entity name="RecommendationGroup" representedClassName="RecommendationGroup" syncable="YES" codeGenerationType="class">
<attribute name="createdAt" optional="YES" attributeType="Date" usesScalarValueType="NO"/>

View file

@ -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

View file

@ -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<Type>(selection: Selection<Type, [Objects.Recommendation]?>) throws -> Type {
func recommendations<Type>(selection: Selection<Type, [Objects.Recommendation]?>) 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<Type>(selection: Selection<Type, Objects.RecommendingUser?>) 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<T> = Selection<T, Objects.RecommendationGroup>
}
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<T> = Selection<T, Objects.RecommendingUser>
}
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<Type>(selection: Selection<Type, [Objects.Recommendation]?>) throws -> Type {
func recommendations<Type>(selection: Selection<Type, [Objects.Recommendation]?>) 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<String> = .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
}
}

View file

@ -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
)
}

View file

@ -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(),

View file

@ -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) ?? []
)
}

View file

@ -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: []
)

View file

@ -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
)
}

View file

@ -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

View file

@ -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()

View file

@ -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,

View file

@ -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

View file

@ -110,7 +110,7 @@ export type Article = {
readAt?: Maybe<Scalars['Date']>;
readingProgressAnchorIndex: Scalars['Int'];
readingProgressPercent: Scalars['Float'];
recommendedBy?: Maybe<Array<Recommendation>>;
recommendations?: Maybe<Array<Recommendation>>;
savedAt: Scalars['Date'];
savedByViewer?: Maybe<Scalars['Boolean']>;
shareInfo?: Maybe<LinkShareInfo>;
@ -1804,7 +1804,7 @@ export type Recommendation = {
name: Scalars['String'];
note?: Maybe<Scalars['String']>;
recommendedAt: Scalars['Date'];
user: RecommendingUser;
user?: Maybe<RecommendingUser>;
};
export type RecommendationGroup = {
@ -2057,7 +2057,7 @@ export type SearchItem = {
readAt?: Maybe<Scalars['Date']>;
readingProgressAnchorIndex: Scalars['Int'];
readingProgressPercent: Scalars['Float'];
recommendedBy?: Maybe<Array<Recommendation>>;
recommendations?: Maybe<Array<Recommendation>>;
savedAt: Scalars['Date'];
shortId?: Maybe<Scalars['String']>;
siteIcon?: Maybe<Scalars['String']>;
@ -3889,7 +3889,7 @@ export type ArticleResolvers<ContextType = ResolverContext, ParentType extends R
readAt?: Resolver<Maybe<ResolversTypes['Date']>, ParentType, ContextType>;
readingProgressAnchorIndex?: Resolver<ResolversTypes['Int'], ParentType, ContextType>;
readingProgressPercent?: Resolver<ResolversTypes['Float'], ParentType, ContextType>;
recommendedBy?: Resolver<Maybe<Array<ResolversTypes['Recommendation']>>, ParentType, ContextType>;
recommendations?: Resolver<Maybe<Array<ResolversTypes['Recommendation']>>, ParentType, ContextType>;
savedAt?: Resolver<ResolversTypes['Date'], ParentType, ContextType>;
savedByViewer?: Resolver<Maybe<ResolversTypes['Boolean']>, ParentType, ContextType>;
shareInfo?: Resolver<Maybe<ResolversTypes['LinkShareInfo']>, ParentType, ContextType>;
@ -4851,7 +4851,7 @@ export type RecommendationResolvers<ContextType = ResolverContext, ParentType ex
name?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
note?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
recommendedAt?: Resolver<ResolversTypes['Date'], ParentType, ContextType>;
user?: Resolver<ResolversTypes['RecommendingUser'], ParentType, ContextType>;
user?: Resolver<Maybe<ResolversTypes['RecommendingUser']>, ParentType, ContextType>;
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
};
@ -5016,7 +5016,7 @@ export type SearchItemResolvers<ContextType = ResolverContext, ParentType extend
readAt?: Resolver<Maybe<ResolversTypes['Date']>, ParentType, ContextType>;
readingProgressAnchorIndex?: Resolver<ResolversTypes['Int'], ParentType, ContextType>;
readingProgressPercent?: Resolver<ResolversTypes['Float'], ParentType, ContextType>;
recommendedBy?: Resolver<Maybe<Array<ResolversTypes['Recommendation']>>, ParentType, ContextType>;
recommendations?: Resolver<Maybe<Array<ResolversTypes['Recommendation']>>, ParentType, ContextType>;
savedAt?: Resolver<ResolversTypes['Date'], ParentType, ContextType>;
shortId?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
siteIcon?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;

View file

@ -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

View file

@ -1015,6 +1015,7 @@ export const updatesSinceResolver = authorized<
}
})
console.log('edges', JSON.stringify(edges))
return {
edges,
pageInfo: {

View file

@ -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,

View file

@ -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 {

View file

@ -167,7 +167,7 @@
"wordsCount": {
"type": "integer"
},
"recommendedBy": {
"recommendations": {
"type": "nested",
"properties": {
"id": {

View file

@ -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<void> => {
})
}
const reIndex = async (): Promise<void> => {
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<void> => {
// 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<void> => {
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...'))

View file

@ -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