feat: updated integration page with missing API routes
This commit is contained in:
1 parent
25d9c30b8a
commit
7c0ee21906
25 files changed
+2402
-350
No files matched your search
@@ -22,12 +22,15 @@ import SessionOnlyGroup from '@/components/SessionOnlyGroup.vue'
|
||||
import {
|
||||
API_GROUPS,
|
||||
ERROR_ROWS,
|
||||
FIELD_TYPES_ANCHOR,
|
||||
ERROR_SAMPLE,
|
||||
PUBLIC_AUTH_ROWS,
|
||||
SESSION_GROUPS,
|
||||
authExamples,
|
||||
type ApiGroup,
|
||||
type ErrorRow,
|
||||
} from '@/data/apiCatalogue'
|
||||
import { SECTION_TITLE, endpointAnchor } from '@/lib/apiReference'
|
||||
import { SECTION_TITLE, endpointAnchor, groupsAroundImages } from '@/lib/apiReference'
|
||||
import { buildSearchIndex, type SearchEntry } from '@/lib/apiSearch'
|
||||
|
||||
const SPY_OFFSET_PX = 120
|
||||
@@ -45,13 +48,17 @@ const errorSampleTabs = computed(() => [
|
||||
|
||||
const sessionRouteCount = SESSION_GROUPS.reduce((total, group) => total + group.rows.length, 0)
|
||||
|
||||
const { before: groupsBeforeImages, after: groupsAfterImages } = groupsAroundImages(API_GROUPS)
|
||||
|
||||
function groupSection(group: ApiGroup) {
|
||||
return { id: group.key, label: translate(group.title) }
|
||||
}
|
||||
|
||||
const sections = computed(() => [
|
||||
{ id: 'auth', label: t('Authentication') },
|
||||
...API_GROUPS.map((group) => ({
|
||||
id: group.key,
|
||||
label: translate(group.title),
|
||||
})),
|
||||
...groupsBeforeImages.map(groupSection),
|
||||
{ id: 'images', label: t('Images and variants') },
|
||||
...groupsAfterImages.map(groupSection),
|
||||
{ id: 'session', label: t('Session only') },
|
||||
{ id: 'errors', label: t('Errors') },
|
||||
{ id: 'notes', label: t('Notes') },
|
||||
@@ -67,7 +74,7 @@ const authItems = computed(() => {
|
||||
|
||||
const authNotes = computed(() => [
|
||||
t(
|
||||
'A key can\'t create, list or revoke API keys, verify or cancel a registration, or call anything under `/v1/system`. Those routes answer 403 "This action needs a real session." An invalid, unknown, expired or revoked key answers 401, even on public routes, and so does a key whose user was deleted or unverified.',
|
||||
'A key can\'t create, list or revoke API keys, verify or cancel a registration, or call anything under `/v1/system`. Those routes answer 403 "This action needs a real session." An invalid, unknown, expired or revoked key answers 401, even on public routes, and so does a key whose user was deleted or unverified. Owner and admin routes answer 403 to a key whose creator is neither.',
|
||||
),
|
||||
t(
|
||||
'Each key can make 120 requests per minute. Beyond that the API answers 429 with a `Retry-After` header.',
|
||||
@@ -110,8 +117,64 @@ const errorColumns = computed(() => [
|
||||
},
|
||||
])
|
||||
|
||||
function quoteMessages(messages: string[]) {
|
||||
return messages.map((message) => `"${message}"`).join(' / ')
|
||||
interface MessagePart {
|
||||
text: string
|
||||
code: boolean
|
||||
}
|
||||
|
||||
function messageParts(row: ErrorRow) {
|
||||
const parts: MessagePart[] = []
|
||||
const push = (text: string, code: boolean) => {
|
||||
if (parts.length > 0) {
|
||||
parts.push({ text: ' / ', code: false })
|
||||
}
|
||||
parts.push({ text, code })
|
||||
}
|
||||
row.messages.forEach((message) => push(`"${message}"`, false))
|
||||
row.codes?.forEach((code) => push(code, true))
|
||||
return parts
|
||||
}
|
||||
|
||||
const fieldTypeColumns = computed(() => [
|
||||
{
|
||||
accessorKey: 'type',
|
||||
get header() {
|
||||
return t('Type')
|
||||
},
|
||||
meta: { class: { th: 'w-22', td: 'w-22' } },
|
||||
},
|
||||
{
|
||||
accessorKey: 'value',
|
||||
get header() {
|
||||
return t('Value in an entry')
|
||||
},
|
||||
meta: { class: { th: 'lg:w-1/2', td: 'lg:w-1/2' } },
|
||||
},
|
||||
{
|
||||
accessorKey: 'rules',
|
||||
get header() {
|
||||
return t('Rules')
|
||||
},
|
||||
meta: { class: { th: 'max-lg:hidden', td: 'max-lg:hidden' } },
|
||||
},
|
||||
])
|
||||
|
||||
const pageBlocks = [
|
||||
...groupsBeforeImages.map((group) => ({ kind: 'group' as const, key: group.key, group })),
|
||||
{ kind: 'images' as const, key: 'images', group: null },
|
||||
...groupsAfterImages.map((group) => ({ kind: 'group' as const, key: group.key, group })),
|
||||
]
|
||||
|
||||
function snippetTabs(group: ApiGroup) {
|
||||
return group.snippet
|
||||
? [
|
||||
{
|
||||
k: group.snippet.key,
|
||||
label: translate(group.snippet.label),
|
||||
code: group.snippet.code,
|
||||
},
|
||||
]
|
||||
: []
|
||||
}
|
||||
|
||||
function routeCount(count: number) {
|
||||
@@ -371,7 +434,7 @@ const CARD_TITLE = 'text-md/6 font-semibold text-highlighted'
|
||||
<p class="max-w-180 text-base text-pretty text-toned">
|
||||
{{
|
||||
t(
|
||||
'RenewCMS is headless: it stores your content and your own code displays it. There are two ways in. Public read-only access lets a website read online articles. API keys let a script or service manage articles, media and users without the back-office.',
|
||||
'RenewCMS is headless: it stores your content and your own code displays it. There are two ways in. Public read-only access lets a website read online articles. API keys let a script or service manage articles, media and users, and manage content types and entries, without the back-office.',
|
||||
)
|
||||
}}
|
||||
</p>
|
||||
@@ -458,33 +521,88 @@ const CARD_TITLE = 'text-md/6 font-semibold text-highlighted'
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section
|
||||
v-for="group in API_GROUPS"
|
||||
:key="group.key"
|
||||
:data-anchor="group.key"
|
||||
:aria-labelledby="`h-${group.key}`"
|
||||
:class="SECTION_CLASS"
|
||||
>
|
||||
<div class="flex flex-wrap items-baseline gap-2.5">
|
||||
<h2 :id="`h-${group.key}`" :class="SECTION_TITLE">
|
||||
{{ translate(group.title) }}
|
||||
</h2>
|
||||
<span class="text-muted">{{ routeCount(group.endpoints.length) }}</span>
|
||||
</div>
|
||||
<span class="text-pretty text-toned">{{ translate(group.description) }}</span>
|
||||
<div class="border border-t-0 border-default bg-default">
|
||||
<EndpointCard
|
||||
v-for="endpoint in group.endpoints"
|
||||
:key="endpoint.id"
|
||||
:ref="(card) => setCard(endpoint.id, card)"
|
||||
:endpoint="endpoint"
|
||||
:origin="origin"
|
||||
:flash="flashedCard === endpoint.id ? flashMode : null"
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<IntegrationImages :origin="origin" />
|
||||
<template v-for="block in pageBlocks" :key="block.key">
|
||||
<IntegrationImages v-if="block.kind === 'images'" :origin="origin" />
|
||||
<section
|
||||
v-else
|
||||
:data-anchor="block.group.key"
|
||||
:aria-labelledby="`h-${block.group.key}`"
|
||||
:class="SECTION_CLASS"
|
||||
>
|
||||
<div class="flex flex-wrap items-baseline gap-2.5">
|
||||
<h2 :id="`h-${block.group.key}`" :class="SECTION_TITLE">
|
||||
{{ translate(block.group.title) }}
|
||||
</h2>
|
||||
<span class="text-muted">{{
|
||||
routeCount(block.group.endpoints.length)
|
||||
}}</span>
|
||||
</div>
|
||||
<span class="text-pretty text-toned">{{
|
||||
translate(block.group.description)
|
||||
}}</span>
|
||||
<div v-if="block.group.note" class="flex gap-2.5 text-base text-toned">
|
||||
<UIcon name="i-lucide-info" :class="NOTE_ICON" />
|
||||
<span class="text-pretty"
|
||||
><CodeText :text="translate(block.group.note)"
|
||||
/></span>
|
||||
</div>
|
||||
<div
|
||||
v-if="block.group.fieldTypes"
|
||||
:data-anchor="FIELD_TYPES_ANCHOR"
|
||||
class="flex scroll-mt-16 flex-col gap-1.5 lg:scroll-mt-8"
|
||||
>
|
||||
<span class="font-semibold text-highlighted">{{
|
||||
t('Field types')
|
||||
}}</span>
|
||||
<UTable
|
||||
:data="block.group.fieldTypes"
|
||||
:columns="fieldTypeColumns"
|
||||
:ui="{
|
||||
root: 'border border-default bg-default',
|
||||
base: 'table-fixed',
|
||||
thead: 'bg-elevated [&>tr]:border-t-0',
|
||||
separator: 'hidden',
|
||||
th: 'px-2.5 py-1.5 text-sm font-medium text-muted',
|
||||
td: 'px-2.5 py-2 align-top text-sm whitespace-normal text-toned',
|
||||
tbody: 'divide-y-0',
|
||||
tr: 'border-t border-default',
|
||||
}"
|
||||
>
|
||||
<template #type-cell="{ row }">
|
||||
<span class="font-mono text-xs font-medium text-highlighted">{{
|
||||
row.original.type
|
||||
}}</span>
|
||||
</template>
|
||||
<template #value-cell="{ row }">
|
||||
<div class="flex flex-col gap-0.5">
|
||||
<span class="text-pretty"
|
||||
><CodeText :text="translate(row.original.value)"
|
||||
/></span>
|
||||
<span class="text-pretty lg:hidden"
|
||||
><CodeText :text="translate(row.original.rules)"
|
||||
/></span>
|
||||
</div>
|
||||
</template>
|
||||
<template #rules-cell="{ row }">
|
||||
<span class="text-pretty"
|
||||
><CodeText :text="translate(row.original.rules)"
|
||||
/></span>
|
||||
</template>
|
||||
</UTable>
|
||||
</div>
|
||||
<CodeSnippet v-if="block.group.snippet" :tabs="snippetTabs(block.group)" />
|
||||
<div class="border border-t-0 border-default bg-default">
|
||||
<EndpointCard
|
||||
v-for="endpoint in block.group.endpoints"
|
||||
:key="endpoint.id"
|
||||
:ref="(card) => setCard(endpoint.id, card)"
|
||||
:endpoint="endpoint"
|
||||
:origin="origin"
|
||||
:flash="flashedCard === endpoint.id ? flashMode : null"
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<section data-anchor="session" aria-labelledby="h-session" :class="SECTION_CLASS">
|
||||
<div class="flex flex-wrap items-baseline gap-2.5">
|
||||
@@ -492,7 +610,7 @@ const CARD_TITLE = 'text-md/6 font-semibold text-highlighted'
|
||||
{{ t('Session only') }}
|
||||
</h2>
|
||||
<span class="text-muted">{{
|
||||
t('{0} routes, made for a browser session', [sessionRouteCount])
|
||||
t('{0} routes, not available to API keys', [sessionRouteCount])
|
||||
}}</span>
|
||||
</div>
|
||||
<SessionOnlyGroup :groups="SESSION_GROUPS" :public-rows="PUBLIC_AUTH_ROWS" />
|
||||
@@ -532,29 +650,45 @@ const CARD_TITLE = 'text-md/6 font-semibold text-highlighted'
|
||||
translate(row.original.meaning)
|
||||
}}</span>
|
||||
<div class="flex flex-col gap-0.5 lg:hidden">
|
||||
<span class="text-pretty text-toned">{{
|
||||
quoteMessages(row.original.messages)
|
||||
}}</span>
|
||||
<span class="text-pretty text-toned"
|
||||
><template
|
||||
v-for="(part, index) in messageParts(row.original)"
|
||||
:key="index"
|
||||
><code
|
||||
v-if="part.code"
|
||||
class="font-mono text-xs text-highlighted"
|
||||
>{{ part.text }}</code
|
||||
><template v-else>{{ part.text }}</template></template
|
||||
></span
|
||||
>
|
||||
<span
|
||||
v-if="row.original.note"
|
||||
class="text-pretty text-muted"
|
||||
data-testid="error-note"
|
||||
>{{ translate(row.original.note) }}</span
|
||||
>
|
||||
><CodeText :text="translate(row.original.note)"
|
||||
/></span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #messages-cell="{ row }">
|
||||
<div class="flex flex-col gap-0.5">
|
||||
<span class="text-pretty">{{
|
||||
quoteMessages(row.original.messages)
|
||||
}}</span>
|
||||
<span class="text-pretty"
|
||||
><template
|
||||
v-for="(part, index) in messageParts(row.original)"
|
||||
:key="index"
|
||||
><code
|
||||
v-if="part.code"
|
||||
class="font-mono text-xs text-highlighted"
|
||||
>{{ part.text }}</code
|
||||
><template v-else>{{ part.text }}</template></template
|
||||
></span
|
||||
>
|
||||
<span
|
||||
v-if="row.original.note"
|
||||
class="text-pretty text-muted"
|
||||
data-testid="error-note"
|
||||
>{{ translate(row.original.note) }}</span
|
||||
>
|
||||
><CodeText :text="translate(row.original.note)"
|
||||
/></span>
|
||||
</div>
|
||||
</template>
|
||||
</UTable>
|
||||
|
||||
Reference in new issue
Block a user