diff --git a/apps/web/app/(marketing)/blog/page.tsx b/apps/web/app/(marketing)/blog/page.tsx
index 0b1becd36..89be0c7c1 100644
--- a/apps/web/app/(marketing)/blog/page.tsx
+++ b/apps/web/app/(marketing)/blog/page.tsx
@@ -23,7 +23,7 @@ async function BlogPage({ searchParams }: { searchParams: { page: string } }) {
const limit = 10;
const offset = page * limit;
- const posts = await cms.getContentItems({
+ const { items: posts, total } = await cms.getContentItems({
collection: 'posts',
limit,
offset,
diff --git a/apps/web/app/(marketing)/docs/layout.tsx b/apps/web/app/(marketing)/docs/layout.tsx
index fdc54c06e..0b6c81515 100644
--- a/apps/web/app/(marketing)/docs/layout.tsx
+++ b/apps/web/app/(marketing)/docs/layout.tsx
@@ -5,12 +5,10 @@ import { DocsNavigation } from '~/(marketing)/docs/_components/docs-navigation';
async function DocsLayout({ children }: React.PropsWithChildren) {
const cms = await createCmsClient();
- const pages = await cms.getContentItems({
+ const { items: pages } = await cms.getContentItems({
collection: 'documentation',
});
- console.log(pages);
-
return (
diff --git a/apps/web/app/(marketing)/docs/page.tsx b/apps/web/app/(marketing)/docs/page.tsx
index 68c2e5aee..f4d02cbbf 100644
--- a/apps/web/app/(marketing)/docs/page.tsx
+++ b/apps/web/app/(marketing)/docs/page.tsx
@@ -18,12 +18,12 @@ async function DocsPage() {
const client = await createCmsClient();
const { t } = await createI18nServerInstance();
- const docs = await client.getContentItems({
+ const { items } = await client.getContentItems({
collection: 'documentation',
});
// 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 (
@@ -33,7 +33,7 @@ async function DocsPage() {
subtitle={t('marketing:documentationSubtitle')}
/>
-
diff --git a/apps/web/app/server-sitemap.xml/route.ts b/apps/web/app/server-sitemap.xml/route.ts
index bce59c57e..ed6756b41 100644
--- a/apps/web/app/server-sitemap.xml/route.ts
+++ b/apps/web/app/server-sitemap.xml/route.ts
@@ -9,14 +9,14 @@ invariant(appConfig.url, 'No NEXT_PUBLIC_SITE_URL environment variable found');
export async function GET() {
const urls = getSiteUrls();
- const client = await createCmsClient();
- const contentItems = await client.getContentItems();
+
+ const items = await getAllItems();
return getServerSideSitemap([
...urls,
- ...contentItems.map((item) => {
+ ...items.map((path) => {
return {
- loc: new URL(item.url, appConfig.url).href,
+ loc: new URL(path, appConfig.url).href,
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());
+}
diff --git a/packages/cms/core/src/cms-client.ts b/packages/cms/core/src/cms-client.ts
index 691a92058..0fd56bcea 100644
--- a/packages/cms/core/src/cms-client.ts
+++ b/packages/cms/core/src/cms-client.ts
@@ -59,9 +59,10 @@ export abstract class CmsClient {
* @param options - Options for filtering and pagination.
* @returns A promise that resolves to an array of content items.
*/
- abstract getContentItems(
- options?: Cms.GetContentItemsOptions,
- ): Promise;
+ abstract getContentItems(options?: Cms.GetContentItemsOptions): Promise<{
+ total: number;
+ items: Cms.ContentItem[];
+ }>;
/**
* Retrieves a content item by its ID and type.
diff --git a/packages/cms/keystatic/src/client.ts b/packages/cms/keystatic/src/client.ts
index d5125ea3f..f134e9718 100644
--- a/packages/cms/keystatic/src/client.ts
+++ b/packages/cms/keystatic/src/client.ts
@@ -22,38 +22,40 @@ export class KeystaticClient implements CmsClient {
const startOffset = options?.offset ?? 0;
const endOffset = startOffset + (options?.limit ?? 10);
- return Promise.all(
- docs
- .filter((item) => {
- const categoryMatch = options?.categories
- ? options.categories.find((category) =>
- item.entry.categories.includes(category),
- )
- : true;
+ const filtered = docs.filter((item) => {
+ const categoryMatch = options?.categories
+ ? options.categories.find((category) =>
+ item.entry.categories.includes(category),
+ )
+ : true;
- if (!categoryMatch) {
- return false;
- }
+ if (!categoryMatch) {
+ return false;
+ }
- const tagMatch = options?.tags
- ? options.tags.find((tag) => item.entry.tags.includes(tag))
- : true;
+ const tagMatch = options?.tags
+ ? options.tags.find((tag) => item.entry.tags.includes(tag))
+ : true;
- if (!tagMatch) {
- return false;
- }
+ if (!tagMatch) {
+ return false;
+ }
- return true;
- })
- .slice(startOffset, endOffset)
- .map(async (item) => {
- const children = docs.filter(
- (item) => item.entry.parent === item.slug,
- );
+ return true;
+ });
- 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 }) {
diff --git a/packages/cms/wordpress/src/wp-client.ts b/packages/cms/wordpress/src/wp-client.ts
index f5a41de52..1428ef965 100644
--- a/packages/cms/wordpress/src/wp-client.ts
+++ b/packages/cms/wordpress/src/wp-client.ts
@@ -79,12 +79,14 @@ export class WordpressClient implements CmsClient {
const url = `${this.apiUrl}${endpoint}`;
- const posts = await fetch(url).then(
- (value) => value.json() as Promise,
- );
+ const response = await fetch(url);
+ const totalHeader = response.headers.get('X-WP-Total');
- return Promise.all(
- posts.map(async (item: WP_REST_API_Post) => {
+ const total = totalHeader ? Number(totalHeader) : 0;
+ 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;
if (!item) {
@@ -117,6 +119,11 @@ export class WordpressClient implements CmsClient {
};
}),
);
+
+ return {
+ total,
+ items: posts,
+ };
}
async getContentItemBySlug({
diff --git a/supabase/migrations/20221215192558_schema.sql b/supabase/migrations/20221215192558_schema.sql
index 344e99857..bcb273a97 100644
--- a/supabase/migrations/20221215192558_schema.sql
+++ b/supabase/migrations/20221215192558_schema.sql
@@ -173,6 +173,7 @@ create policy "public config can be read by authenticated users" on
for select to authenticated
using (true);
+-- Function to get the config settings
create or replace function public.get_config()
returns json
as $$
@@ -215,8 +216,7 @@ end
$$
language plpgsql;
--- Automatically set user tracking on tables when a row is inserted or
--- updated
+-- Automatically set user tracking on tables when a row is inserted or updated
create or replace function public.trigger_set_user_tracking()
returns trigger
as $$
@@ -240,6 +240,7 @@ language plpgsql;
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)
returns boolean
as $$
@@ -278,6 +279,7 @@ create table if not exists public.accounts(
created_by uuid references auth.users,
updated_by uuid references auth.users,
picture_url varchar(1000),
+ public_data jsonb default '{}'::jsonb not null,
primary key (id)
);
@@ -1733,6 +1735,7 @@ public.is_set(
'enable_team_accounts')
and public.accounts.is_personal_account = false);
+-- Function: create an invitation to an account
create or replace function public.create_invitation(account_id uuid,
email text, role varchar(50))
returns public.invitations
@@ -1765,30 +1768,10 @@ end;
$$
language plpgsql;
-create or replace function public.get_user_accounts()
- returns setof public.accounts
- 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;
-
+--
+-- VIEW "user_account_workspace":
-- we create a view to load the general app data for the authenticated
--- user
--- which includes the user's accounts, memberships, and roles, and
--- relative subscription status
+-- user which includes the user's accounts, memberships, and roles, and relative subscription status
create or replace view public.user_account_workspace as
select
accounts.id as id,
@@ -1804,6 +1787,10 @@ where
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
select
accounts.id as id,
@@ -1821,6 +1808,9 @@ where
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
public.organization_account_workspace(account_slug text)
returns table(
@@ -1872,6 +1862,7 @@ language plpgsql;
grant execute on function public.organization_account_workspace(text)
to authenticated, service_role;
+-- Functions: get account members
create or replace function public.get_account_members(account_slug text)
returns table(
id uuid,
@@ -1961,7 +1952,6 @@ create type kit.invitation as (
email text,
role varchar( 50));
--- Then, modify your function to use this type
create or replace function
public.add_invitations_to_account(account_slug text, invitations
kit.invitation[])