Skip to content
This repository was archived by the owner on Nov 20, 2024. It is now read-only.

Commit da6c0fb

Browse files
authored
feat: [SPMVP -6521] use typesense to fetch article list in karbon to reduce api server load (#281)
1 parent 55fbb1e commit da6c0fb

File tree

8 files changed

+119
-167
lines changed

8 files changed

+119
-167
lines changed

packages/karbon/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,7 @@
174174
"tiny-invariant": "^1.3.1",
175175
"ts-pattern": "^5.0.5",
176176
"type-fest": "^4.3.1",
177+
"typesense": "^1.7.1",
177178
"typesense-instantsearch-adapter": "^2.7.1",
178179
"unbuild": "^2.0.0",
179180
"unenv": "^1.7.4",
@@ -229,4 +230,4 @@
229230
"access": "public"
230231
},
231232
"gitHead": "8df1f4d5837a7e2ddbff6cc79f5fec256c34a394"
232-
}
233+
}

packages/karbon/src/runtime/api/article.ts

Lines changed: 19 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -5,80 +5,18 @@ import { encrypt } from 'micro-aes-gcm'
55
// This file contains global crypto polyfill
66
import { CompactEncrypt } from '@storipress/jose-browser'
77
import { useStoripressClient } from '../composables/storipress-client'
8+
import type { TypesenseFilter } from '../composables/typesense-client'
9+
import { PER_PAGE, getSearchQuery, useTypesenseClient } from '../composables/typesense-client'
810
import { splitPaidContent } from '../lib/split-paid-content'
911
import type { NormalSegment } from '../lib/split-article'
1012
import { splitArticle } from '../lib/split-article'
1113
import { getStoripressConfig } from '../composables/storipress-base-client'
1214
import { verboseInvariant } from '../utils/verbose-invariant'
13-
import { getAllWithPagination } from './helper'
1415
import type { PaidContent, RawArticleLike, _NormalizeArticle } from './normalize-article'
1516
import { normalizeArticle } from './normalize-article'
1617

1718
export type { NormalizeArticle, PaidContent } from './normalize-article'
1819

19-
export const ListArticles = gql`
20-
query ListArticles($page: Int!) {
21-
articles(page: $page, sortBy: [{ column: PUBLISHED_AT, order: DESC }], published: true) {
22-
paginatorInfo {
23-
lastPage
24-
hasMorePages
25-
count
26-
}
27-
data {
28-
id
29-
title
30-
blurb
31-
slug
32-
sid
33-
published_at
34-
updated_at
35-
featured
36-
plan
37-
cover
38-
seo
39-
layout {
40-
id
41-
name
42-
}
43-
desk {
44-
id
45-
name
46-
slug
47-
layout {
48-
id
49-
name
50-
}
51-
desk {
52-
id
53-
name
54-
slug
55-
layout {
56-
id
57-
name
58-
}
59-
}
60-
}
61-
tags {
62-
id
63-
slug
64-
name
65-
}
66-
authors {
67-
id
68-
slug
69-
bio
70-
socials
71-
avatar
72-
email
73-
location
74-
first_name
75-
last_name
76-
full_name
77-
}
78-
}
79-
}
80-
}
81-
`
8220
const GetArticle = gql`
8321
query GetArticle($id: ID!) {
8422
article(id: $id) {
@@ -315,14 +253,23 @@ const GetArticle = gql`
315253
}
316254
`
317255

318-
export async function listArticles(filter?: { desk: string; tag: string; author: string }) {
319-
return getAllWithPagination(ListArticles, filter, ({ articles: { paginatorInfo, data } }) => {
320-
const res = data.map((data: RawArticleLike) => normalizeArticle(data))
321-
return {
322-
paginatorInfo,
323-
data: res,
324-
}
325-
})
256+
export async function listArticles(filter?: TypesenseFilter) {
257+
const typesenseClient = useTypesenseClient()
258+
const documents = typesenseClient?.collections('articles').documents()
259+
260+
const articles = []
261+
let hasMore = true
262+
let page = 1
263+
while (hasMore) {
264+
const searchResult = await documents?.search(getSearchQuery(page, filter), {})
265+
const currentPageArticles =
266+
searchResult?.hits?.map(({ document }) => normalizeArticle(document as RawArticleLike)) ?? []
267+
articles.push(...currentPageArticles)
268+
269+
hasMore = searchResult.found > searchResult.page * PER_PAGE
270+
page = searchResult.page + 1
271+
}
272+
return articles
326273
}
327274

328275
export async function getArticle(id: string) {

packages/karbon/src/runtime/api/feed.ts

Lines changed: 3 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,5 @@
11
import { gql } from '@apollo/client/core/index.js'
22
import { useStoripressClient } from '../composables/storipress-client'
3-
import { getAllWithPagination } from './helper'
4-
import type { RawArticleLike } from './normalize-article'
5-
import { normalizeArticle } from './normalize-article'
6-
7-
const ListArticles = gql`
8-
query ListArticles($page: Int!, $desk: ID, $desk_ids: [ID!]) {
9-
articles(
10-
page: $page
11-
desk: $desk
12-
desk_ids: $desk_ids
13-
sortBy: [{ column: UPDATED_AT, order: DESC }]
14-
published: true
15-
) {
16-
paginatorInfo {
17-
count
18-
lastPage
19-
hasMorePages
20-
}
21-
data {
22-
id
23-
title
24-
slug
25-
sid
26-
published_at
27-
html
28-
plaintext
29-
}
30-
}
31-
}
32-
`
333

344
const GetDesk = gql`
355
query GetDesk($slug: String) {
@@ -51,14 +21,9 @@ const GetDesk = gql`
5121
}
5222
`
5323

54-
export function listFeedArticles(filter?: { desk: string; tag: string; author: string; desk_ids: string }) {
55-
return getAllWithPagination(ListArticles, filter, ({ articles: { paginatorInfo, data } }) => {
56-
const res = data.map((data: RawArticleLike) => normalizeArticle(data))
57-
return {
58-
paginatorInfo,
59-
data: res,
60-
}
61-
})
24+
export async function listFeedArticles() {
25+
const allArticles = (await $fetch('/_storipress/posts/__all.json')) ?? []
26+
return allArticles
6227
}
6328

6429
export async function getDeskWithSlug(slug: string) {

packages/karbon/src/runtime/api/normalize-article.ts

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { destr } from 'destr'
22
import truncate from 'lodash.truncate'
33
import type { Segment } from '../lib/split-article'
4-
import type { ArticlePlan } from '../types'
4+
import type { ArticleDesk, ArticlePlan, ArticleTag } from '../types'
55
import { useArticleFilter } from '#imports'
66

77
export interface RawUserLike {
@@ -24,6 +24,9 @@ export interface RawArticleLike {
2424
plaintext: string
2525
plan: ArticlePlan
2626
authors: RawUserLike[]
27+
tags: ArticleTag[]
28+
desk: ArticleDesk
29+
published_at: string
2730
}
2831

2932
export interface PaidContent {
@@ -53,9 +56,12 @@ export function normalizeArticle({
5356
html,
5457
id,
5558
authors,
59+
desk,
60+
tags,
5661
...rest
5762
}: RawArticleLike) {
5863
const articleFilter = useArticleFilter()
64+
const rootDesk = desk.desk ? { desk: { ...desk.desk, id: String(desk.desk.id) } } : {}
5965

6066
return {
6167
...rest,
@@ -74,11 +80,21 @@ export function normalizeArticle({
7480
separator: /,? +/,
7581
}),
7682
cover: destr(cover),
77-
authors: authors?.map(({ socials, ...rest }) => ({
83+
authors: authors?.map(({ socials, id, ...rest }) => ({
7884
...rest,
85+
id: String(id),
7986
socials: destr(socials),
8087
name: rest.full_name,
8188
})),
89+
desk: {
90+
...desk,
91+
...rootDesk,
92+
id: String(desk.id),
93+
},
94+
tags: tags?.map(({ id, ...rest }) => ({
95+
...rest,
96+
id: String(id),
97+
})),
8298
}
8399
}
84100

packages/karbon/src/runtime/api/sitemap.ts

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import { identity } from 'remeda'
33
import { createStoripressClient } from '../composables/storipress-client'
44
import { storipressConfigCtx } from '../composables/storipress-base-client'
55
import type { ModuleRuntimeConfig } from '../types'
6-
import { getAllWithPaginationViaGetPage } from './helper'
76

87
const ListArticles = gql`
98
query ListArticles($page: Int!) {
@@ -136,11 +135,7 @@ export async function getResources(runtimeConfig?: ModuleRuntimeConfig['storipre
136135
let resources: any = []
137136
switch (payloadScope) {
138137
case 'posts': {
139-
const getPage = async (page: number) => {
140-
const { data } = await client.query({ query, variables: { page } })
141-
return data.articles
142-
}
143-
resources = await getAllWithPaginationViaGetPage(getPage)
138+
resources = (await $fetch('/_storipress/posts/__all.json')) ?? []
144139
break
145140
}
146141
default: {
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import { SearchClient } from 'typesense'
2+
import { getStoripressConfig } from './storipress-base-client'
3+
4+
let typesenseClient: SearchClient
5+
6+
export function useTypesenseClient() {
7+
if (typesenseClient) return typesenseClient
8+
9+
const storipress = getStoripressConfig()
10+
typesenseClient = new SearchClient({
11+
nodes: [
12+
{
13+
host: storipress.searchDomain ?? '',
14+
port: 443,
15+
protocol: 'https',
16+
},
17+
],
18+
apiKey: storipress.searchKey,
19+
connectionTimeoutSeconds: 5,
20+
})
21+
return typesenseClient
22+
}
23+
24+
export interface TypesenseFilter {
25+
desk_ids?: string[]
26+
author_ids?: string[]
27+
author_names?: string[]
28+
tag_ids?: string[]
29+
tag_names?: string[]
30+
}
31+
32+
export const PER_PAGE = 100
33+
34+
export function getSearchQuery(page = 1, filter: TypesenseFilter = {}) {
35+
const { desk_ids, author_ids, author_names, tag_ids, tag_names } = filter
36+
let filterBy = 'published:=true'
37+
if (desk_ids?.length) filterBy += ` && desk_id:=[${desk_ids.join()}]`
38+
if (author_ids?.length) filterBy += ` && author_ids:=[${author_ids.join()}]`
39+
if (author_names?.length) filterBy += ` && author_names:=[${author_names.join()}]`
40+
if (tag_ids?.length) filterBy += ` && tag_ids:=[${tag_ids.join()}]`
41+
if (tag_names?.length) filterBy += ` && tag_names:=[${tag_names.join()}]`
42+
43+
return {
44+
q: '*',
45+
sort_by: 'published_at:desc,order:asc',
46+
filter_by: filterBy,
47+
per_page: PER_PAGE,
48+
page,
49+
query_by: 'title',
50+
}
51+
}

0 commit comments

Comments
 (0)