Setting the file. One moment.
Posts · Wix Headless Templates · wix/skills · Skills Docs
ContentsBack to the top of the page blog/rest/ posts.ts
TypeScript · 123 lines · 6 KB
11 import { fetchAuthorsByIds } from "./authors.js" ;
12 import {
13 BLOG_APP_ID,
14 CARD_FIELDSETS,
15 DETAIL_FIELDSETS,
16 RELATED_LIMIT,
17 byIdsQuery,
18 categoryPath,
19 feedQuery,
20 formatCount,
21 isNotFound,
22 pickCurated,
23 postMetaLine,
24 postPath,
25 recentRelatedQuery,
26 relativeDateLabel,
27 tagPath,
28 toDetail,
29 toMetrics,
30 toSummary,
31 withAuthors,
32 type FetchPostsOptions,
33 type Raw,
34 } from "../app/wix/blog/posts-core.js" ;
35 import type { PostDetail, PostMetrics, PostPage, PostSummary } from "../app/wix/blog/types.js" ;
36
37 export { BLOG_APP_ID, categoryPath, formatCount, postMetaLine, postPath, relativeDateLabel, tagPath, type FetchPostsOptions };
38
39 const POSTS_QUERY = "/blog/v3/posts/query" ;
40
41 // The authors of one page in one batched read, joined onto the DTOs; a failed read leaves "" (no byline).
42 async function withPageAuthors < T extends PostSummary >( items : T []) : Promise < T []> {
43 return withAuthors (items, await fetchAuthorsByIds (items. map (( p ) => p.authorId)));
44 }
45
46 /**
47 * One page of published posts, pinned first then newest first; `nextCursor` continues the same
48 * query — send it alone, the filters ride inside it. Cards carry METRICS (counts).
49 * POST /blog/v3/posts/query { fieldsets: ["METRICS"], query: { filter?, sort, cursorPaging: { limit } } } — first page
50 * POST /blog/v3/posts/query { fieldsets: ["METRICS"], query: { cursorPaging: { limit, cursor } } } — later pages
51 */
52 export async function fetchPosts ( o : FetchPostsOptions = {}) : Promise < PostPage > {
53 const res = await wixRequest < Raw >( POSTS_QUERY , { body: { fieldsets: CARD_FIELDSETS , query: feedQuery (o) } });
54 const paging : Raw = res?.pagingMetadata ?? {};
55 return {
56 posts: await withPageAuthors (((res?.posts ?? []) as Raw []). map (( p ) => toSummary (p, imgSrc))),
57 nextCursor: paging.hasNext ? (paging.cursors?.next ?? null ) : null ,
58 };
59 }
60
61 /**
62 * One post by URL slug with the full body and SEO/comments fields. Null when the slug resolves to
63 * nothing (a 404 from this read) — a real 404 for the page, never a fallback to another post; any
64 * other failure throws.
65 * GET /blog/v3/posts/slugs/{slug}?fieldsets=RICH_CONTENT&fieldsets=CONTENT_TEXT&fieldsets=SEO&fieldsets=REFERENCE_ID&fieldsets=INTERNAL_ID
66 * (INTERNAL_ID is the fieldset that carries referenceId; REFERENCE_ID alone returns the post without it)
67 */
68 export async function fetchPostBySlug ( slug : string ) : Promise < PostDetail | null > {
69 let raw : Raw | undefined ;
70 try {
71 raw = ( await wixRequest < Raw >( `/blog/v3/posts/slugs/${ encodeURIComponent ( slug ) }` , { method: "GET" , query: { fieldsets: DETAIL_FIELDSETS } }))?.post;
72 } catch (e) {
73 if ( isNotFound (e)) return null ;
74 throw e;
75 }
76 if ( ! raw) return null ;
77 const [ detail ] = await withPageAuthors ([ toDetail (raw, imgSrc)]);
78 return detail;
79 }
80
81 /**
82 * Posts by id, as cards (order is the API's — callers restore their own). Non-fatal: [] on failure.
83 * POST /blog/v3/posts/query { fieldsets: ["METRICS"], query: { filter: { id: { $in } }, cursorPaging: { limit } } }
84 */
85 export async function fetchPostsByIds ( ids : string []) : Promise < PostSummary []> {
86 if ( ! ids. length ) return [];
87 try {
88 const res = await wixRequest < Raw >( POSTS_QUERY , { body: { fieldsets: CARD_FIELDSETS , query: byIdsQuery (ids) } });
89 return withPageAuthors (((res?.posts ?? []) as Raw []). map (( p ) => toSummary (p, imgSrc)));
90 } catch {
91 return [];
92 }
93 }
94
95 /**
96 * The post page's related strip: curated `relatedPostIds` (writer order) when at least one still
97 * resolves, else the newest posts sharing a category, the current post excluded. Never topped up.
98 * POST /blog/v3/posts/query { fieldsets: ["METRICS"], query: { filter: { id: { $ne }, categoryIds?: { $hasSome } }, sort, cursorPaging: { limit: 3 } } }
99 */
100 export async function fetchRelatedPosts ( post : Pick < PostDetail , "id" | "categoryIds" | "relatedPostIds" >, limit = RELATED_LIMIT ) : Promise < PostSummary []> {
101 try {
102 if (post.relatedPostIds. length ) {
103 const curated = pickCurated (post.relatedPostIds, await fetchPostsByIds (post.relatedPostIds), post.id, limit);
104 if (curated. length ) return curated;
105 }
106 const res = await wixRequest < Raw >( POSTS_QUERY , { body: { fieldsets: CARD_FIELDSETS , query: recentRelatedQuery (post, limit) } });
107 return withPageAuthors (((res?.posts ?? []) as Raw []). map (( p ) => toSummary (p, imgSrc)));
108 } catch {
109 return [];
110 }
111 }
112
113 /**
114 * The post's fresh counters. Null on failure: keep the count you had.
115 * GET /blog/v3/posts/{postId}/metrics → { metrics: { views, likes, comments } }
116 */
117 export async function fetchPostMetrics ( postId : string ) : Promise < PostMetrics | null > {
118 try {
119 return toMetrics (( await wixRequest < Raw >( `/blog/v3/posts/${ encodeURIComponent ( postId ) }/metrics` , { method: "GET" }))?.metrics);
120 } catch {
121 return null ;
122 }
123 }