Update getContentItems to return total count and items

The changes involved modifying the implementation of `getContentItems` across multiple files, specifically in the CMS-related codebase. This method now returns an object containing the total count of items and the items themselves. The updates also included necessary adjustments in the code where `getContentItems` is called to accommodate the new structure of the returned result.
This commit is contained in:
giancarlo
2024-04-10 16:29:19 +08:00
parent 44373c0372
commit f94557e333
8 changed files with 89 additions and 71 deletions

View File

@@ -23,7 +23,7 @@ async function BlogPage({ searchParams }: { searchParams: { page: string } }) {
const limit = 10; const limit = 10;
const offset = page * limit; const offset = page * limit;
const posts = await cms.getContentItems({ const { items: posts, total } = await cms.getContentItems({
collection: 'posts', collection: 'posts',
limit, limit,
offset, offset,

View File

@@ -5,12 +5,10 @@ import { DocsNavigation } from '~/(marketing)/docs/_components/docs-navigation';
async function DocsLayout({ children }: React.PropsWithChildren) { async function DocsLayout({ children }: React.PropsWithChildren) {
const cms = await createCmsClient(); const cms = await createCmsClient();
const pages = await cms.getContentItems({ const { items: pages } = await cms.getContentItems({
collection: 'documentation', collection: 'documentation',
}); });
console.log(pages);
return ( return (
<div className={'flex'}> <div className={'flex'}>
<DocsNavigation pages={buildDocumentationTree(pages)} /> <DocsNavigation pages={buildDocumentationTree(pages)} />

View File

@@ -18,12 +18,12 @@ async function DocsPage() {
const client = await createCmsClient(); const client = await createCmsClient();
const { t } = await createI18nServerInstance(); const { t } = await createI18nServerInstance();
const docs = await client.getContentItems({ const { items } = await client.getContentItems({
collection: 'documentation', collection: 'documentation',
}); });
// Filter out any docs that have a parentId, as these are children of other docs // Filter out any docs that have a parentId, as these are children of other docs
const cards = docs.filter((item) => !item.parentId); const cards = items.filter((item) => !item.parentId);
return ( return (
<PageBody> <PageBody>
@@ -33,7 +33,7 @@ async function DocsPage() {
subtitle={t('marketing:documentationSubtitle')} subtitle={t('marketing:documentationSubtitle')}
/> />
<div className={'flex flex-col items-center'}> <div className={'container mx-auto flex flex-col items-center'}>
<DocsCards cards={cards} /> <DocsCards cards={cards} />
</div> </div>
</div> </div>

View File

@@ -9,14 +9,14 @@ invariant(appConfig.url, 'No NEXT_PUBLIC_SITE_URL environment variable found');
export async function GET() { export async function GET() {
const urls = getSiteUrls(); const urls = getSiteUrls();
const client = await createCmsClient();
const contentItems = await client.getContentItems(); const items = await getAllItems();
return getServerSideSitemap([ return getServerSideSitemap([
...urls, ...urls,
...contentItems.map((item) => { ...items.map((path) => {
return { return {
loc: new URL(item.url, appConfig.url).href, loc: new URL(path, appConfig.url).href,
lastmod: new Date().toISOString(), lastmod: new Date().toISOString(),
}; };
}), }),
@@ -33,3 +33,23 @@ function getSiteUrls() {
}; };
}); });
} }
async function getAllItems() {
const client = await createCmsClient();
const posts = client
.getContentItems({
collection: 'posts',
})
.then((response) => response.items)
.then((posts) => posts.map((post) => `/blog/${post.slug}`));
const docs = client
.getContentItems({
collection: 'documentation',
})
.then((response) => response.items)
.then((docs) => docs.map((doc) => `/docs/${doc.slug}`));
return Promise.all([posts, docs]).then((items) => items.flat());
}

View File

@@ -59,9 +59,10 @@ export abstract class CmsClient {
* @param options - Options for filtering and pagination. * @param options - Options for filtering and pagination.
* @returns A promise that resolves to an array of content items. * @returns A promise that resolves to an array of content items.
*/ */
abstract getContentItems( abstract getContentItems(options?: Cms.GetContentItemsOptions): Promise<{
options?: Cms.GetContentItemsOptions, total: number;
): Promise<Cms.ContentItem[]>; items: Cms.ContentItem[];
}>;
/** /**
* Retrieves a content item by its ID and type. * Retrieves a content item by its ID and type.

View File

@@ -22,38 +22,40 @@ export class KeystaticClient implements CmsClient {
const startOffset = options?.offset ?? 0; const startOffset = options?.offset ?? 0;
const endOffset = startOffset + (options?.limit ?? 10); const endOffset = startOffset + (options?.limit ?? 10);
return Promise.all( const filtered = docs.filter((item) => {
docs const categoryMatch = options?.categories
.filter((item) => { ? options.categories.find((category) =>
const categoryMatch = options?.categories item.entry.categories.includes(category),
? options.categories.find((category) => )
item.entry.categories.includes(category), : true;
)
: true;
if (!categoryMatch) { if (!categoryMatch) {
return false; return false;
} }
const tagMatch = options?.tags const tagMatch = options?.tags
? options.tags.find((tag) => item.entry.tags.includes(tag)) ? options.tags.find((tag) => item.entry.tags.includes(tag))
: true; : true;
if (!tagMatch) { if (!tagMatch) {
return false; return false;
} }
return true; return true;
}) });
.slice(startOffset, endOffset)
.map(async (item) => {
const children = docs.filter(
(item) => item.entry.parent === item.slug,
);
return this.mapPost(item, children); const items = await Promise.all(
}), filtered.slice(startOffset, endOffset).map(async (item) => {
const children = docs.filter((item) => item.entry.parent === item.slug);
return this.mapPost(item, children);
}),
); );
return {
total: filtered.length,
items,
};
} }
async getContentItemBySlug(params: { slug: string; collection: string }) { async getContentItemBySlug(params: { slug: string; collection: string }) {

View File

@@ -79,12 +79,14 @@ export class WordpressClient implements CmsClient {
const url = `${this.apiUrl}${endpoint}`; const url = `${this.apiUrl}${endpoint}`;
const posts = await fetch(url).then( const response = await fetch(url);
(value) => value.json() as Promise<WP_REST_API_Post[]>, const totalHeader = response.headers.get('X-WP-Total');
);
return Promise.all( const total = totalHeader ? Number(totalHeader) : 0;
posts.map(async (item: WP_REST_API_Post) => { const results = (await response.json()) as WP_REST_API_Post[];
const posts = await Promise.all(
results.map(async (item: WP_REST_API_Post) => {
let parentId: string | undefined; let parentId: string | undefined;
if (!item) { if (!item) {
@@ -117,6 +119,11 @@ export class WordpressClient implements CmsClient {
}; };
}), }),
); );
return {
total,
items: posts,
};
} }
async getContentItemBySlug({ async getContentItemBySlug({

View File

@@ -173,6 +173,7 @@ create policy "public config can be read by authenticated users" on
for select to authenticated for select to authenticated
using (true); using (true);
-- Function to get the config settings
create or replace function public.get_config() create or replace function public.get_config()
returns json returns json
as $$ as $$
@@ -215,8 +216,7 @@ end
$$ $$
language plpgsql; language plpgsql;
-- Automatically set user tracking on tables when a row is inserted or -- Automatically set user tracking on tables when a row is inserted or updated
-- updated
create or replace function public.trigger_set_user_tracking() create or replace function public.trigger_set_user_tracking()
returns trigger returns trigger
as $$ as $$
@@ -240,6 +240,7 @@ language plpgsql;
grant execute on function public.get_config() to authenticated, service_role; grant execute on function public.get_config() to authenticated, service_role;
-- check if a field is set in the config
create or replace function public.is_set(field_name text) create or replace function public.is_set(field_name text)
returns boolean returns boolean
as $$ as $$
@@ -278,6 +279,7 @@ create table if not exists public.accounts(
created_by uuid references auth.users, created_by uuid references auth.users,
updated_by uuid references auth.users, updated_by uuid references auth.users,
picture_url varchar(1000), picture_url varchar(1000),
public_data jsonb default '{}'::jsonb not null,
primary key (id) primary key (id)
); );
@@ -1733,6 +1735,7 @@ public.is_set(
'enable_team_accounts') 'enable_team_accounts')
and public.accounts.is_personal_account = false); and public.accounts.is_personal_account = false);
-- Function: create an invitation to an account
create or replace function public.create_invitation(account_id uuid, create or replace function public.create_invitation(account_id uuid,
email text, role varchar(50)) email text, role varchar(50))
returns public.invitations returns public.invitations
@@ -1765,30 +1768,10 @@ end;
$$ $$
language plpgsql; language plpgsql;
create or replace function public.get_user_accounts() --
returns setof public.accounts -- VIEW "user_account_workspace":
as $$
begin
select
id,
name,
picture_url
from
public.accounts
join public.accounts_memberships on accounts.id =
accounts_memberships.account_id
where
accounts_memberships.user_id = auth.uid();
end;
$$
language plpgsql;
-- we create a view to load the general app data for the authenticated -- we create a view to load the general app data for the authenticated
-- user -- user which includes the user's accounts, memberships, and roles, and relative subscription status
-- which includes the user's accounts, memberships, and roles, and
-- relative subscription status
create or replace view public.user_account_workspace as create or replace view public.user_account_workspace as
select select
accounts.id as id, accounts.id as id,
@@ -1804,6 +1787,10 @@ where
grant select on public.user_account_workspace to authenticated, service_role; grant select on public.user_account_workspace to authenticated, service_role;
--
-- VIEW "user_accounts":
-- we create a view to load the user's accounts and memberships
-- useful to display the user's accounts in the app
create or replace view public.user_accounts as create or replace view public.user_accounts as
select select
accounts.id as id, accounts.id as id,
@@ -1821,6 +1808,9 @@ where
grant select on public.user_accounts to authenticated, service_role; grant select on public.user_accounts to authenticated, service_role;
--
-- Function: get the account workspace for an organization account
-- to load all the required data for the authenticated user within the account scope
create or replace function create or replace function
public.organization_account_workspace(account_slug text) public.organization_account_workspace(account_slug text)
returns table( returns table(
@@ -1872,6 +1862,7 @@ language plpgsql;
grant execute on function public.organization_account_workspace(text) grant execute on function public.organization_account_workspace(text)
to authenticated, service_role; to authenticated, service_role;
-- Functions: get account members
create or replace function public.get_account_members(account_slug text) create or replace function public.get_account_members(account_slug text)
returns table( returns table(
id uuid, id uuid,
@@ -1961,7 +1952,6 @@ create type kit.invitation as (
email text, email text,
role varchar( 50)); role varchar( 50));
-- Then, modify your function to use this type
create or replace function create or replace function
public.add_invitations_to_account(account_slug text, invitations public.add_invitations_to_account(account_slug text, invitations
kit.invitation[]) kit.invitation[])