2022-09-22 09:37:49 +00:00
|
|
|
import { atom, computed, map, ReadableAtom } from 'nanostores'
|
2022-09-13 09:59:04 +00:00
|
|
|
import type { Author, Shout, Topic } from '../../graphql/types.gen'
|
2022-09-09 11:53:35 +00:00
|
|
|
import type { WritableAtom } from 'nanostores'
|
|
|
|
import { useStore } from '@nanostores/solid'
|
|
|
|
import { apiClient } from '../../utils/apiClient'
|
2022-09-13 09:59:04 +00:00
|
|
|
import { addAuthorsByTopic } from './authors'
|
|
|
|
import { addTopicsByAuthor } from './topics'
|
|
|
|
import { byStat } from '../../utils/sortby'
|
2022-09-09 11:53:35 +00:00
|
|
|
|
2022-09-22 09:37:49 +00:00
|
|
|
import { getLogger } from '../../utils/logger'
|
|
|
|
import { createSignal } from 'solid-js'
|
|
|
|
|
|
|
|
const log = getLogger('articles store')
|
|
|
|
|
2022-09-13 09:59:04 +00:00
|
|
|
let articleEntitiesStore: WritableAtom<{ [articleSlug: string]: Shout }>
|
|
|
|
let articlesByAuthorsStore: ReadableAtom<{ [authorSlug: string]: Shout[] }>
|
2022-09-22 09:37:49 +00:00
|
|
|
let articlesByLayoutStore: ReadableAtom<{ [layout: string]: Shout[] }>
|
2022-09-13 09:59:04 +00:00
|
|
|
let articlesByTopicsStore: ReadableAtom<{ [topicSlug: string]: Shout[] }>
|
|
|
|
let topViewedArticlesStore: ReadableAtom<Shout[]>
|
|
|
|
let topCommentedArticlesStore: ReadableAtom<Shout[]>
|
2022-09-09 11:53:35 +00:00
|
|
|
|
2022-09-22 09:37:49 +00:00
|
|
|
const [getSortedArticles, setSortedArticles] = createSignal<Shout[]>([])
|
|
|
|
|
|
|
|
const topArticlesStore = atom<Shout[]>()
|
|
|
|
const topMonthArticlesStore = atom<Shout[]>()
|
|
|
|
|
2022-09-09 11:53:35 +00:00
|
|
|
const initStore = (initial?: Record<string, Shout>) => {
|
2022-09-22 09:37:49 +00:00
|
|
|
log.debug('initStore')
|
2022-09-09 11:53:35 +00:00
|
|
|
if (articleEntitiesStore) {
|
2022-09-22 09:37:49 +00:00
|
|
|
throw new Error('articles store already initialized')
|
2022-09-09 11:53:35 +00:00
|
|
|
}
|
|
|
|
|
2022-09-22 09:37:49 +00:00
|
|
|
articleEntitiesStore = map(initial)
|
2022-09-13 09:59:04 +00:00
|
|
|
|
|
|
|
articlesByAuthorsStore = computed(articleEntitiesStore, (articleEntities) => {
|
|
|
|
return Object.values(articleEntities).reduce((acc, article) => {
|
|
|
|
article.authors.forEach((author) => {
|
|
|
|
if (!acc[author.slug]) {
|
|
|
|
acc[author.slug] = []
|
|
|
|
}
|
|
|
|
acc[author.slug].push(article)
|
|
|
|
})
|
|
|
|
|
|
|
|
return acc
|
|
|
|
}, {} as { [authorSlug: string]: Shout[] })
|
|
|
|
})
|
|
|
|
|
|
|
|
articlesByTopicsStore = computed(articleEntitiesStore, (articleEntities) => {
|
|
|
|
return Object.values(articleEntities).reduce((acc, article) => {
|
|
|
|
article.topics.forEach((topic) => {
|
|
|
|
if (!acc[topic.slug]) {
|
|
|
|
acc[topic.slug] = []
|
|
|
|
}
|
|
|
|
acc[topic.slug].push(article)
|
|
|
|
})
|
|
|
|
|
|
|
|
return acc
|
|
|
|
}, {} as { [authorSlug: string]: Shout[] })
|
|
|
|
})
|
|
|
|
|
2022-09-22 09:37:49 +00:00
|
|
|
articlesByLayoutStore = computed(articleEntitiesStore, (articleEntities) => {
|
|
|
|
return Object.values(articleEntities).reduce((acc, article) => {
|
|
|
|
if (!acc[article.layout]) {
|
|
|
|
acc[article.layout] = []
|
|
|
|
}
|
|
|
|
|
|
|
|
acc[article.layout].push(article)
|
|
|
|
|
|
|
|
return acc
|
|
|
|
}, {} as { [layout: string]: Shout[] })
|
|
|
|
})
|
|
|
|
|
2022-09-13 09:59:04 +00:00
|
|
|
topViewedArticlesStore = computed(articleEntitiesStore, (articleEntities) => {
|
|
|
|
const sortedArticles = Object.values(articleEntities)
|
|
|
|
sortedArticles.sort(byStat('viewed'))
|
|
|
|
return sortedArticles
|
|
|
|
})
|
|
|
|
|
|
|
|
topCommentedArticlesStore = computed(articleEntitiesStore, (articleEntities) => {
|
|
|
|
const sortedArticles = Object.values(articleEntities)
|
|
|
|
sortedArticles.sort(byStat('commented'))
|
|
|
|
return sortedArticles
|
|
|
|
})
|
2022-09-09 11:53:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// eslint-disable-next-line sonarjs/cognitive-complexity
|
2022-09-13 09:59:04 +00:00
|
|
|
const addArticles = (...args: Shout[][]) => {
|
|
|
|
const allArticles = args.flatMap((articles) => articles || [])
|
|
|
|
|
|
|
|
const newArticleEntities = allArticles.reduce((acc, article) => {
|
2022-09-09 11:53:35 +00:00
|
|
|
acc[article.slug] = article
|
|
|
|
return acc
|
2022-09-22 09:37:49 +00:00
|
|
|
}, {} as { [articleSLug: string]: Shout })
|
2022-09-09 11:53:35 +00:00
|
|
|
|
|
|
|
if (!articleEntitiesStore) {
|
|
|
|
initStore(newArticleEntities)
|
|
|
|
} else {
|
|
|
|
articleEntitiesStore.set({
|
|
|
|
...articleEntitiesStore.get(),
|
|
|
|
...newArticleEntities
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2022-09-13 09:59:04 +00:00
|
|
|
const authorsByTopic = allArticles.reduce((acc, article) => {
|
|
|
|
const { authors, topics } = article
|
|
|
|
|
|
|
|
topics.forEach((topic) => {
|
|
|
|
if (!acc[topic.slug]) {
|
|
|
|
acc[topic.slug] = []
|
|
|
|
}
|
2022-09-09 11:53:35 +00:00
|
|
|
|
2022-09-13 09:59:04 +00:00
|
|
|
authors.forEach((author) => {
|
|
|
|
if (!acc[topic.slug].some((a) => a.slug === author.slug)) {
|
|
|
|
acc[topic.slug].push(author)
|
|
|
|
}
|
2022-09-09 11:53:35 +00:00
|
|
|
})
|
2022-09-13 09:59:04 +00:00
|
|
|
})
|
|
|
|
|
|
|
|
return acc
|
|
|
|
}, {} as { [topicSlug: string]: Author[] })
|
|
|
|
|
|
|
|
addAuthorsByTopic(authorsByTopic)
|
|
|
|
|
|
|
|
const topicsByAuthor = allArticles.reduce((acc, article) => {
|
|
|
|
const { authors, topics } = article
|
|
|
|
|
|
|
|
authors.forEach((author) => {
|
|
|
|
if (!acc[author.slug]) {
|
|
|
|
acc[author.slug] = []
|
|
|
|
}
|
|
|
|
|
|
|
|
topics.forEach((topic) => {
|
|
|
|
if (!acc[author.slug].some((t) => t.slug === topic.slug)) {
|
|
|
|
acc[author.slug].push(topic)
|
|
|
|
}
|
2022-09-09 11:53:35 +00:00
|
|
|
})
|
|
|
|
})
|
|
|
|
|
2022-09-13 09:59:04 +00:00
|
|
|
return acc
|
|
|
|
}, {} as { [authorSlug: string]: Topic[] })
|
|
|
|
|
|
|
|
addTopicsByAuthor(topicsByAuthor)
|
|
|
|
}
|
|
|
|
|
|
|
|
const addSortedArticles = (articles: Shout[]) => {
|
2022-09-22 09:37:49 +00:00
|
|
|
setSortedArticles((prevSortedArticles) => [...prevSortedArticles, ...articles])
|
2022-09-09 11:53:35 +00:00
|
|
|
}
|
|
|
|
|
2022-09-14 11:27:10 +00:00
|
|
|
export const loadRecentArticles = async ({
|
|
|
|
limit,
|
|
|
|
offset
|
|
|
|
}: {
|
|
|
|
limit?: number
|
|
|
|
offset?: number
|
|
|
|
}): Promise<void> => {
|
|
|
|
const newArticles = await apiClient.getRecentArticles({ limit, offset })
|
2022-09-09 12:18:09 +00:00
|
|
|
addArticles(newArticles)
|
2022-09-13 09:59:04 +00:00
|
|
|
addSortedArticles(newArticles)
|
2022-09-09 12:18:09 +00:00
|
|
|
}
|
|
|
|
|
2022-09-14 11:27:10 +00:00
|
|
|
export const loadPublishedArticles = async ({
|
|
|
|
limit,
|
|
|
|
offset
|
|
|
|
}: {
|
|
|
|
limit?: number
|
|
|
|
offset?: number
|
|
|
|
}): Promise<void> => {
|
|
|
|
const newArticles = await apiClient.getPublishedArticles({ limit, offset })
|
2022-09-09 11:53:35 +00:00
|
|
|
addArticles(newArticles)
|
2022-09-13 09:59:04 +00:00
|
|
|
addSortedArticles(newArticles)
|
|
|
|
}
|
|
|
|
|
2022-09-22 09:37:49 +00:00
|
|
|
export const loadTopMonthArticles = async (): Promise<void> => {
|
|
|
|
const articles = await apiClient.getTopMonthArticles()
|
|
|
|
addArticles(articles)
|
|
|
|
topMonthArticlesStore.set(articles)
|
|
|
|
}
|
|
|
|
|
|
|
|
export const loadTopArticles = async (): Promise<void> => {
|
|
|
|
const articles = await apiClient.getTopArticles()
|
|
|
|
addArticles(articles)
|
|
|
|
topArticlesStore.set(articles)
|
|
|
|
}
|
|
|
|
|
2022-09-14 11:27:10 +00:00
|
|
|
export const loadSearchResults = async ({
|
|
|
|
query,
|
|
|
|
limit,
|
|
|
|
offset
|
|
|
|
}: {
|
|
|
|
query: string
|
|
|
|
limit?: number
|
|
|
|
offset?: number
|
|
|
|
}): Promise<void> => {
|
|
|
|
const newArticles = await apiClient.getSearchResults({ query, limit, offset })
|
2022-09-13 09:59:04 +00:00
|
|
|
addArticles(newArticles)
|
|
|
|
addSortedArticles(newArticles)
|
2022-09-09 11:53:35 +00:00
|
|
|
}
|
|
|
|
|
2022-09-14 11:28:43 +00:00
|
|
|
export const incrementView = async ({ articleSlug }: { articleSlug: string }): Promise<void> => {
|
|
|
|
await apiClient.incrementView({ articleSlug })
|
|
|
|
}
|
|
|
|
|
2022-09-22 09:37:49 +00:00
|
|
|
export const loadArticle = async ({ slug }: { slug: string }): Promise<void> => {
|
|
|
|
const article = await apiClient.getArticle({ slug })
|
|
|
|
|
|
|
|
if (!article) {
|
|
|
|
throw new Error(`Can't load article, slug: "${slug}"`)
|
|
|
|
}
|
|
|
|
|
|
|
|
addArticles([article])
|
2022-09-14 11:28:43 +00:00
|
|
|
}
|
|
|
|
|
2022-09-09 11:53:35 +00:00
|
|
|
type InitialState = {
|
|
|
|
sortedArticles?: Shout[]
|
2022-09-13 09:59:04 +00:00
|
|
|
topRatedArticles?: Shout[]
|
|
|
|
topRatedMonthArticles?: Shout[]
|
2022-09-09 11:53:35 +00:00
|
|
|
}
|
|
|
|
|
2022-09-22 09:37:49 +00:00
|
|
|
export const useArticlesStore = ({ sortedArticles }: InitialState = {}) => {
|
|
|
|
addArticles(sortedArticles)
|
2022-09-09 12:18:09 +00:00
|
|
|
|
2022-09-22 09:37:49 +00:00
|
|
|
if (sortedArticles) {
|
|
|
|
addSortedArticles(sortedArticles)
|
2022-09-13 09:59:04 +00:00
|
|
|
}
|
2022-09-09 12:18:09 +00:00
|
|
|
|
2022-09-13 09:59:04 +00:00
|
|
|
const getArticleEntities = useStore(articleEntitiesStore)
|
2022-09-22 09:37:49 +00:00
|
|
|
const getTopArticles = useStore(topArticlesStore)
|
|
|
|
const getTopMonthArticles = useStore(topMonthArticlesStore)
|
2022-09-13 09:59:04 +00:00
|
|
|
const getArticlesByAuthor = useStore(articlesByAuthorsStore)
|
|
|
|
const getArticlesByTopic = useStore(articlesByTopicsStore)
|
2022-09-22 09:37:49 +00:00
|
|
|
const getArticlesByLayout = useStore(articlesByLayoutStore)
|
2022-09-13 09:59:04 +00:00
|
|
|
// TODO: get from server
|
|
|
|
const getTopViewedArticles = useStore(topViewedArticlesStore)
|
|
|
|
// TODO: get from server
|
|
|
|
const getTopCommentedArticles = useStore(topCommentedArticlesStore)
|
|
|
|
|
|
|
|
return {
|
|
|
|
getArticleEntities,
|
|
|
|
getSortedArticles,
|
|
|
|
getArticlesByTopic,
|
|
|
|
getArticlesByAuthor,
|
2022-09-22 09:37:49 +00:00
|
|
|
getTopArticles,
|
|
|
|
getTopMonthArticles,
|
2022-09-13 09:59:04 +00:00
|
|
|
getTopViewedArticles,
|
|
|
|
getTopCommentedArticles,
|
2022-09-22 09:37:49 +00:00
|
|
|
getArticlesByLayout
|
2022-09-13 09:59:04 +00:00
|
|
|
}
|
2022-09-09 12:18:09 +00:00
|
|
|
}
|