feat: SEO backend data (1/4)
This commit is contained in:
1 parent
cb1410f0b0
commit
0eb46b0d74
90 files changed
+6094
-350
No files matched your search
@@ -180,11 +180,41 @@ const article = (overrides: Record<string, unknown> = {}) => ({
|
||||
body_format: 'markdown',
|
||||
is_online: true,
|
||||
site_id: 1,
|
||||
slug: 'how-we-price-a-bathroom-renovation',
|
||||
excerpt: 'What drives the price of a bathroom renovation.',
|
||||
seo_title: '',
|
||||
seo_description: '',
|
||||
cover_image_id: 4,
|
||||
cover_image_alt: 'A tiled bathroom wall',
|
||||
social_title: '',
|
||||
social_description: '',
|
||||
social_image_id: null,
|
||||
canonical_url: '',
|
||||
noindex: false,
|
||||
seo_dismissed: [],
|
||||
created_at: '2026-03-12T09:14:00Z',
|
||||
updated_at: '2026-09-10T07:00:00Z',
|
||||
...overrides,
|
||||
})
|
||||
|
||||
const LIST_ITEM_OMITTED_KEYS = [
|
||||
'seo_title',
|
||||
'seo_description',
|
||||
'cover_image_id',
|
||||
'cover_image_alt',
|
||||
'social_title',
|
||||
'social_description',
|
||||
'social_image_id',
|
||||
'canonical_url',
|
||||
'noindex',
|
||||
'seo_dismissed',
|
||||
]
|
||||
|
||||
const articleListItem = (overrides: Record<string, unknown> = {}) =>
|
||||
Object.fromEntries(
|
||||
Object.entries(article(overrides)).filter(([key]) => !LIST_ITEM_OMITTED_KEYS.includes(key)),
|
||||
)
|
||||
|
||||
const mediaItem = (overrides: Record<string, unknown> = {}) => ({
|
||||
id: 4,
|
||||
filename: 'north-wall.jpg',
|
||||
@@ -211,6 +241,9 @@ const site = (overrides: Record<string, unknown> = {}) => ({
|
||||
locale: 'en',
|
||||
is_default: true,
|
||||
article_count: 30,
|
||||
site_name: 'Renew',
|
||||
title_template: '%title% | %site%',
|
||||
default_description: 'Notes from a renovation site.',
|
||||
...overrides,
|
||||
})
|
||||
|
||||
@@ -263,12 +296,14 @@ const ownerOrAdminErrors = (...own: ApiError[]) => [
|
||||
|
||||
const articlePage = () => ({
|
||||
items: [
|
||||
article(),
|
||||
article({
|
||||
articleListItem(),
|
||||
articleListItem({
|
||||
id: 13,
|
||||
title: 'Choosing tiles for a small bathroom',
|
||||
body: 'Small rooms show every grout line.',
|
||||
is_online: false,
|
||||
slug: 'choosing-tiles-for-a-small-bathroom',
|
||||
excerpt: '',
|
||||
updated_at: '2026-09-09T07:00:00Z',
|
||||
}),
|
||||
],
|
||||
@@ -417,13 +452,20 @@ export const API_GROUPS: ApiGroup[] = [
|
||||
'art-update',
|
||||
'PATCH',
|
||||
'/v1/article/{id}',
|
||||
'Update the body of an article, or move it to another site. Send body, site_id or both.',
|
||||
'Update the body of an article, move it to another site, or edit its SEO fields. Send at least one field; absent fields are untouched.',
|
||||
'Verified user',
|
||||
verified(
|
||||
articleErrors(
|
||||
e(400, BODY_MISMATCH),
|
||||
e(400, 'invalid_slug'),
|
||||
e(400, 'image_not_found'),
|
||||
...articleIdErrors,
|
||||
e(404, 'site_not_found'),
|
||||
e(
|
||||
409,
|
||||
'slug_taken (the X-Slug-Suggestion header holds the first free slug)',
|
||||
),
|
||||
e(422, 'invalid_url'),
|
||||
),
|
||||
),
|
||||
json(article({ body: 'Updated text.', is_online: false })),
|
||||
@@ -441,10 +483,88 @@ export const API_GROUPS: ApiGroup[] = [
|
||||
name: 'site_id',
|
||||
type: 'number',
|
||||
required: false,
|
||||
rule: 'The id of an existing site, 1 or more. null is refused. At least one of body and site_id is required.',
|
||||
rule: 'The id of an existing site, 1 or more. null is refused.',
|
||||
},
|
||||
{
|
||||
name: 'slug',
|
||||
type: 'string',
|
||||
required: false,
|
||||
rule: 'Lowercase letters, digits and single dashes, 1 to 100 characters, unique on the site. An empty string regenerates it from the title. null is refused.',
|
||||
},
|
||||
{
|
||||
name: 'excerpt',
|
||||
type: 'string',
|
||||
required: false,
|
||||
rule: 'Up to 300 characters. An empty string clears it; null is refused.',
|
||||
},
|
||||
{
|
||||
name: 'seo_title',
|
||||
type: 'string',
|
||||
required: false,
|
||||
rule: 'Up to 120 characters. An empty string clears it; null is refused.',
|
||||
},
|
||||
{
|
||||
name: 'seo_description',
|
||||
type: 'string',
|
||||
required: false,
|
||||
rule: 'Up to 320 characters. An empty string clears it; null is refused.',
|
||||
},
|
||||
{
|
||||
name: 'cover_image_id',
|
||||
type: 'number | null',
|
||||
required: false,
|
||||
rule: 'The id of an existing image, 1 or more. null removes the cover.',
|
||||
},
|
||||
{
|
||||
name: 'cover_image_alt',
|
||||
type: 'string',
|
||||
required: false,
|
||||
rule: 'Up to 200 characters. An empty string clears it; null is refused.',
|
||||
},
|
||||
{
|
||||
name: 'social_title',
|
||||
type: 'string',
|
||||
required: false,
|
||||
rule: 'Up to 120 characters. An empty string clears it; null is refused.',
|
||||
},
|
||||
{
|
||||
name: 'social_description',
|
||||
type: 'string',
|
||||
required: false,
|
||||
rule: 'Up to 320 characters. An empty string clears it; null is refused.',
|
||||
},
|
||||
{
|
||||
name: 'social_image_id',
|
||||
type: 'number | null',
|
||||
required: false,
|
||||
rule: 'The id of an existing image, 1 or more. null removes the social image.',
|
||||
},
|
||||
{
|
||||
name: 'canonical_url',
|
||||
type: 'string',
|
||||
required: false,
|
||||
rule: 'An absolute https address with a host, up to 2048 characters. An empty string clears it; null is refused.',
|
||||
},
|
||||
{
|
||||
name: 'noindex',
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
rule: 'Only a flag: the article stays online. null is refused.',
|
||||
},
|
||||
{
|
||||
name: 'seo_dismissed',
|
||||
type: 'string[]',
|
||||
required: false,
|
||||
rule: 'Dismissed suggestion codes: title_long, title_empty, desc_short, desc_long, cover_alt, cover_none, slug_long, no_au, no_h, body_alt, short. Duplicates are collapsed; null is refused.',
|
||||
},
|
||||
],
|
||||
bodySample: { body: 'Updated text.', site_id: 2 },
|
||||
bodySample: {
|
||||
body: 'Updated text.',
|
||||
site_id: 2,
|
||||
slug: 'bathroom-pricing',
|
||||
excerpt: 'What drives the price.',
|
||||
cover_image_id: 4,
|
||||
},
|
||||
},
|
||||
),
|
||||
endpoint(
|
||||
@@ -602,10 +722,20 @@ export const API_GROUPS: ApiGroup[] = [
|
||||
'med-use',
|
||||
'GET',
|
||||
'/v1/media/{id}/usage',
|
||||
'List the articles that use an image.',
|
||||
'List the articles, authors and sites that use an image, with the places where each one uses it.',
|
||||
'Verified user',
|
||||
verified([e(400, ID_FORMAT), e(401, UNAUTHORIZED), e(404, 'Image not found')]),
|
||||
json([{ id: 12, title: ARTICLE_TITLE, published: true }]),
|
||||
json([
|
||||
{
|
||||
type: 'article',
|
||||
id: 12,
|
||||
title: ARTICLE_TITLE,
|
||||
published: true,
|
||||
fields: ['body', 'cover'],
|
||||
},
|
||||
{ type: 'author', id: 2, title: 'Inès Duarte', fields: ['avatar'] },
|
||||
{ type: 'site', id: 1, title: 'Renew blog', fields: ['default_social_image'] },
|
||||
]),
|
||||
{ pathId: 4 },
|
||||
),
|
||||
endpoint(
|
||||
@@ -868,6 +998,9 @@ export const API_GROUPS: ApiGroup[] = [
|
||||
locale: null,
|
||||
is_default: false,
|
||||
article_count: 4,
|
||||
site_name: '',
|
||||
title_template: '%title% - %site%',
|
||||
default_description: '',
|
||||
}),
|
||||
]),
|
||||
),
|
||||
|
||||
Reference in new issue
Block a user