fix(S29): achados da review — snapshot pós-rebase, manifest e2e e a11y do anúncio

- snapshot drizzle da migration regenerado pós-rebase (inclui as tabelas do
  S27; sem ele o próximo migrate:create recriaria content_piece)
- manifest e2e: CampaignFooter/jingleReads/campaignTime acordam o
  frontendShareLink; dynamicParams inerte removido da rota .ics
- anúncio: dl válido (Local sem dd), aria-live só no status/chip, sem
  hasImage/cast; hook direct deixa o erro de URL falar antes (+ teste int)
- débitos triados no impl plan (folding .ics -> #1281; deferidos com gatilho)

Related #1268
This commit is contained in:
Francisco Solla 2026-09-23 11:52:32 -03:00
parent b0bac78090
commit 2a0dca6243
8 changed files with 907 additions and 30 deletions

View file

@ -100,7 +100,7 @@ Não se aplica — a intenção não apresenta dado agregado (sem contagem/analy
- [ ] Aceite de produto da intenção ainda coberto (modo por link; pool com rótulo; direct sem no-ar recusado; anúncio com data/local; Entrar desativado→ativo ~1 min; agenda Google+.ics com data e sem data esconde o botão; compartilha o link curto; despublicado 404; nada hardcoded para 03/10)
- [ ] Invariantes AGENTS/engineering-standards (URL pública intacta; S19 sem mudança de comportamento; fail-closed/noindex; sem Consent/PII; schema só por migration `push:false`; `overrideAccess` documentado; lib→utilities→components→app; pt-BR em copy, inglês em identificador)
- [ ] Testes de domínio previstos (unit/int) onde access/write paths mudam — obrigatórios: unit `shareLink` (modo/live/`shareLinkIcsPath`), unit `calendarEvent` (Google/ICS/2h), unit `shareLinkAnnouncement` (view + ilha de ativação jsdom), unit `contentShare` (`event`); int `shareLink` (hook cross-field com mensagens, loader publicado, live target fail-closed). Discricionários: admin e2e do checkbox; ETag do `.ics`. E2E obrigatório estendendo `frontendShareLink`: página de anúncio 200/noindex/sem refresh, `.ics` 200/404, redirect pós-live, e a regressão S19 (redirect/meta/no-JS/404/republicação) — o spec já é do conjunto curado.
- [ ] Testes de domínio previstos (unit/int) onde access/write paths mudam — obrigatórios: unit `shareLink` (modo/live/`shareLinkIcsPath`), unit `calendarEvent` (Google/ICS/2h), unit `shareLinkAnnouncement` (view model), unit `contentShare` (`event`); int `shareLink` (hook cross-field com mensagens, loader publicado, live target fail-closed). Discricionários: admin e2e do checkbox; ETag do `.ics`. E2E obrigatório estendendo `frontendShareLink`: página de anúncio 200/noindex/sem refresh, `.ics` 200/404, redirect pós-live, e a regressão S19 (redirect/meta/no-JS/404/republicação) — o spec já é do conjunto curado. A **ilha de ativação** é coberta por e2e (client real, prod e dev) em vez de unit jsdom.
## Self-score (decision-quality)
@ -111,3 +111,10 @@ Não se aplica — a intenção não apresenta dado agregado (sem contagem/analy
5. **Intenção satisfeita:** 5 — todos os bullets do aceite mapeados em componentes/fases/testes, incluindo a questão em aberto aprovada (B: link volta a levar direto).
Média 4.8/5 — plano aprovado no modo `--auto`.
## Débitos triados (review do PR #1280)
- **Folding RFC 5545 (75 octets) no `.ics`** — registrado como **#1281** (`S29-FOLLOWUP`, kind chore, P3): owner `src/lib/ical.ts`, 2 call sites (`lib/calendarEvent.ts`, `utilities/calendarFeed.ts`).
- **`importMap.js` tracked apesar do OPS99** — descartado: dívida pré-existente do `main`, classe já rastreada em #772/#791 + postmortem 2026-09-12; reavaliar `git rm --cached` quando o guard do #772 fechar.
- **Rate limiting da rota `live`** — deferido: gatilho = pico anômalo de requisições na rota (mitigação barata: cache in-memory de 5–10s no loader).
- **Settle dev-only de 6s no e2e `frontendShareLink`** — deferido: gatilho = primeira flake local do spec (trocar por condição observável); o SQL cru é deliberado e documentado (isola a race de revalidação/HMR).

View file

@ -118,6 +118,12 @@ export const E2E_AFFECTED_MANIFEST = [
prefixes: ['src/lib/contentShare.ts'],
specs: ['frontend', 'frontendShareLink'],
},
{
// S29 — the announcement page and its spec derive the Bahia event label
// here (the spec imports the formatter), so a diff must wake it.
prefixes: ['src/lib/campaignTime.ts'],
specs: ['frontendShareLink'],
},
{
// S21 — the public jingles page: ordered published cards, the lazy
// in-page player (one at a time), the slug-based download name and the
@ -134,7 +140,9 @@ export const E2E_AFFECTED_MANIFEST = [
'src/collections/Jingle.ts',
'src/components/jingles',
],
specs: ['frontendJingles'],
// S29 — `hasPublishedJingles` also decides the footer of the announcement
// page, so the share-link spec wakes on the same owners.
specs: ['frontendJingles', 'frontendShareLink'],
},
{
// S27 — the public Central de Conteúdos: the facet/term catalogue, the
@ -157,10 +165,11 @@ export const E2E_AFFECTED_MANIFEST = [
},
{
// S21 — the campaign footer owns the conditional "Jingles" discovery link
// (it renders on the home, the cards page and `/jingles`).
// (it renders on the home, the cards page, `/jingles` and the S29
// announcement page).
// S27 — and the conditional "Conteúdos" link.
prefixes: ['src/components/CampaignFooter.tsx'],
specs: ['frontend', 'frontendJingles', 'frontendConteudos'],
specs: ['frontend', 'frontendJingles', 'frontendConteudos', 'frontendShareLink'],
},
{
// S13/S30 — the personalized-cards studio lives in shared cards components

View file

@ -2,8 +2,6 @@ import { buildCalendarEventIcs } from '@/lib/calendarEvent'
import { shareLinkIcsUid } from '@/lib/shareLink'
import { getCachedPublishedShareLinkBySlug } from '@/utilities/shareLinkReads'
export const dynamicParams = true
type RouteParams = { type: string }
/**

View file

@ -12,7 +12,6 @@ import {
isReservedShareLinkSlug,
isValidShareLinkDestination,
isValidShareLinkSlug,
resolveLiveShareLinkDestination,
resolveShareLinkMode,
type ShareLinkDestinationLike,
} from '@/lib/shareLink'
@ -79,14 +78,15 @@ const validateShareLinkConfiguration: CollectionBeforeValidateHook = ({
? (nextData.destinations as ShareLinkDestinationLike[])
: []
if (destinations.filter((destination) => destination?.live).length > 1) {
const liveCount = destinations.filter((destination) => destination?.live).length
if (liveCount > 1) {
throw new APIError(SHARE_LINK_LIVE_DUPLICATE_MESSAGE, 400)
}
if (
resolveShareLinkMode(nextData.mode) === 'direct' &&
!resolveLiveShareLinkDestination(destinations)
) {
// Only the absence of a flagged row is the `direct` rule's business: a live
// row with a malformed URL must surface the field's own URL message (the
// runtime read still fails closed on it).
if (resolveShareLinkMode(nextData.mode) === 'direct' && liveCount === 0) {
throw new APIError(SHARE_LINK_DIRECT_WITHOUT_LIVE_MESSAGE, 400)
}

View file

@ -44,7 +44,6 @@ export const ShareLinkAnnouncement = ({
}) => {
const [live, setLive] = useState<ShareLinkLiveTarget | null>(null)
const isLive = live !== null
const hasImage = Boolean(view.imageUrl)
const hasEventDetails = Boolean(view.eventLabel || view.location)
useEffect(() => {
@ -100,13 +99,13 @@ export const ShareLinkAnnouncement = ({
className={cn(
'relative z-1 mx-auto grid max-w-6xl overflow-hidden rounded-[18px] border border-(--campaign-line) bg-white',
'shadow-[0_18px_50px_rgb(71_19_14/10%)]',
hasImage && 'lg:grid-cols-[0.95fr_1.05fr]',
view.imageUrl && 'lg:grid-cols-[0.95fr_1.05fr]',
)}
>
{hasImage ? (
{view.imageUrl ? (
<div className="relative aspect-[1.91/1] w-full overflow-hidden bg-[#184e92] lg:aspect-auto lg:min-h-[535px]">
<Image
src={view.imageUrl as string}
src={view.imageUrl}
alt={view.imageAlt}
fill
priority
@ -142,9 +141,9 @@ export const ShareLinkAnnouncement = ({
)}
{!isLive && hasEventDetails ? (
<dl className="mt-4 grid gap-3 border-y border-black/10 py-4 text-sm sm:mt-7 sm:gap-4 sm:py-5">
<div className="mt-4 grid gap-3 border-y border-black/10 py-4 text-sm sm:mt-7 sm:gap-4 sm:py-5">
{view.eventLabel ? (
<div className="flex items-start gap-3">
<dl className="m-0 flex items-start gap-3">
<CalendarIcon
className="mt-0.5 size-5 flex-none text-(--pt-red)"
strokeWidth={2}
@ -156,7 +155,7 @@ export const ShareLinkAnnouncement = ({
Horário da Bahia
</dd>
</div>
</div>
</dl>
) : null}
{view.location ? (
<div className="flex items-center gap-3">
@ -165,15 +164,18 @@ export const ShareLinkAnnouncement = ({
strokeWidth={2}
aria-hidden="true"
/>
<dt className="font-bold text-black">{view.location}</dt>
<span className="font-bold text-black">{view.location}</span>
</div>
) : null}
</dl>
</div>
) : null}
<div className="mt-auto pt-5 sm:pt-7" aria-live="polite">
<div className="mt-auto pt-5 sm:pt-7">
{isLive && live ? (
<div className="mb-3 flex flex-wrap items-center justify-center gap-2 text-xs font-bold text-black/55">
<div
aria-live="polite"
className="mb-3 flex flex-wrap items-center justify-center gap-2 text-xs font-bold text-black/55"
>
<span>Destino no ar</span>
<span className="rounded-full bg-(--campaign-band) px-2.5 py-1 text-black">
{live.label || 'Destino'}
@ -204,7 +206,10 @@ export const ShareLinkAnnouncement = ({
)}
{!isLive ? (
<p className="mt-3 text-center text-xs font-medium text-black/55 sm:text-sm">
<p
aria-live="polite"
className="mt-3 text-center text-xs font-medium text-black/55 sm:text-sm"
>
A transmissão ainda não começou.
</p>
) : null}

View file

@ -16,7 +16,7 @@ export type CalendarEventInput = {
endsAt?: string | null
}
const formatICSInstant = (date: Date): string => formatICalDate(date.toISOString())
const formatInstant = (date: Date): string => formatICalDate(date.toISOString())
/**
* The event window: no `startsAt` (or an invalid one) means "no event" — the
@ -49,7 +49,7 @@ export const buildGoogleCalendarEventUrl = (event: CalendarEventInput): string |
const params = new URLSearchParams({
action: 'TEMPLATE',
text: event.title,
dates: `${formatICSInstant(window.start)}/${formatICSInstant(window.end)}`,
dates: `${formatInstant(window.start)}/${formatInstant(window.end)}`,
})
if (event.description) params.set('details', event.description)
if (event.location) params.set('location', event.location)
@ -80,9 +80,9 @@ export const buildCalendarEventIcs = ({
'METHOD:PUBLISH',
'BEGIN:VEVENT',
`UID:${escapeICalText(uid)}`,
`DTSTAMP:${formatICSInstant(dtstamp)}`,
`DTSTART:${formatICSInstant(window.start)}`,
`DTEND:${formatICSInstant(window.end)}`,
`DTSTAMP:${formatInstant(dtstamp)}`,
`DTSTART:${formatInstant(window.start)}`,
`DTEND:${formatInstant(window.end)}`,
`SUMMARY:${escapeICalText(event.title)}`,
]

View file

@ -8362,6 +8362,699 @@
"checkConstraints": {},
"isRLSEnabled": false
},
"public.content_piece_topics": {
"name": "content_piece_topics",
"schema": "",
"columns": {
"order": {
"name": "order",
"type": "integer",
"primaryKey": false,
"notNull": true
},
"parent_id": {
"name": "parent_id",
"type": "integer",
"primaryKey": false,
"notNull": true
},
"value": {
"name": "value",
"type": "enum_content_piece_topics",
"typeSchema": "public",
"primaryKey": false,
"notNull": false
},
"id": {
"name": "id",
"type": "serial",
"primaryKey": true,
"notNull": true
}
},
"indexes": {
"content_piece_topics_order_idx": {
"name": "content_piece_topics_order_idx",
"columns": [
{
"expression": "order",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": false,
"concurrently": false,
"method": "btree",
"with": {}
},
"content_piece_topics_parent_idx": {
"name": "content_piece_topics_parent_idx",
"columns": [
{
"expression": "parent_id",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": false,
"concurrently": false,
"method": "btree",
"with": {}
},
"content_piece_topics_value_idx": {
"name": "content_piece_topics_value_idx",
"columns": [
{
"expression": "value",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": false,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {
"content_piece_topics_parent_fk": {
"name": "content_piece_topics_parent_fk",
"tableFrom": "content_piece_topics",
"tableTo": "content_piece",
"columnsFrom": [
"parent_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.content_piece_curated_fields": {
"name": "content_piece_curated_fields",
"schema": "",
"columns": {
"order": {
"name": "order",
"type": "integer",
"primaryKey": false,
"notNull": true
},
"parent_id": {
"name": "parent_id",
"type": "integer",
"primaryKey": false,
"notNull": true
},
"value": {
"name": "value",
"type": "enum_content_piece_curated_fields",
"typeSchema": "public",
"primaryKey": false,
"notNull": false
},
"id": {
"name": "id",
"type": "serial",
"primaryKey": true,
"notNull": true
}
},
"indexes": {
"content_piece_curated_fields_order_idx": {
"name": "content_piece_curated_fields_order_idx",
"columns": [
{
"expression": "order",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": false,
"concurrently": false,
"method": "btree",
"with": {}
},
"content_piece_curated_fields_parent_idx": {
"name": "content_piece_curated_fields_parent_idx",
"columns": [
{
"expression": "parent_id",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": false,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {
"content_piece_curated_fields_parent_fk": {
"name": "content_piece_curated_fields_parent_fk",
"tableFrom": "content_piece_curated_fields",
"tableTo": "content_piece",
"columnsFrom": [
"parent_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.content_piece": {
"name": "content_piece",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "serial",
"primaryKey": true,
"notNull": true
},
"title": {
"name": "title",
"type": "varchar",
"primaryKey": false,
"notNull": true
},
"slug": {
"name": "slug",
"type": "varchar",
"primaryKey": false,
"notNull": false
},
"type": {
"name": "type",
"type": "enum_content_piece_type",
"typeSchema": "public",
"primaryKey": false,
"notNull": true
},
"description": {
"name": "description",
"type": "varchar",
"primaryKey": false,
"notNull": false
},
"municipality_id": {
"name": "municipality_id",
"type": "integer",
"primaryKey": false,
"notNull": false
},
"city_label": {
"name": "city_label",
"type": "varchar",
"primaryKey": false,
"notNull": false
},
"region": {
"name": "region",
"type": "varchar",
"primaryKey": false,
"notNull": false
},
"institution": {
"name": "institution",
"type": "varchar",
"primaryKey": false,
"notNull": false
},
"piece_date": {
"name": "piece_date",
"type": "timestamp(3) with time zone",
"primaryKey": false,
"notNull": false
},
"duration_seconds": {
"name": "duration_seconds",
"type": "numeric",
"primaryKey": false,
"notNull": false
},
"transcript": {
"name": "transcript",
"type": "varchar",
"primaryKey": false,
"notNull": false
},
"media_id": {
"name": "media_id",
"type": "integer",
"primaryKey": false,
"notNull": false
},
"source_url": {
"name": "source_url",
"type": "varchar",
"primaryKey": false,
"notNull": false
},
"origin": {
"name": "origin",
"type": "enum_content_piece_origin",
"typeSchema": "public",
"primaryKey": false,
"notNull": true,
"default": "'arquivo'"
},
"status": {
"name": "status",
"type": "enum_content_piece_status",
"typeSchema": "public",
"primaryKey": false,
"notNull": true,
"default": "'rascunho'"
},
"published_at": {
"name": "published_at",
"type": "timestamp(3) with time zone",
"primaryKey": false,
"notNull": false
},
"processing_status": {
"name": "processing_status",
"type": "enum_content_piece_processing_status",
"typeSchema": "public",
"primaryKey": false,
"notNull": true,
"default": "'pronto'"
},
"step": {
"name": "step",
"type": "enum_content_piece_step",
"typeSchema": "public",
"primaryKey": false,
"notNull": false
},
"error": {
"name": "error",
"type": "varchar",
"primaryKey": false,
"notNull": false
},
"search_text": {
"name": "search_text",
"type": "varchar",
"primaryKey": false,
"notNull": false
},
"created_by_id": {
"name": "created_by_id",
"type": "integer",
"primaryKey": false,
"notNull": false
},
"updated_at": {
"name": "updated_at",
"type": "timestamp(3) with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"created_at": {
"name": "created_at",
"type": "timestamp(3) with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {
"content_piece_slug_idx": {
"name": "content_piece_slug_idx",
"columns": [
{
"expression": "slug",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": true,
"concurrently": false,
"method": "btree",
"with": {}
},
"content_piece_type_idx": {
"name": "content_piece_type_idx",
"columns": [
{
"expression": "type",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": false,
"concurrently": false,
"method": "btree",
"with": {}
},
"content_piece_municipality_idx": {
"name": "content_piece_municipality_idx",
"columns": [
{
"expression": "municipality_id",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": false,
"concurrently": false,
"method": "btree",
"with": {}
},
"content_piece_piece_date_idx": {
"name": "content_piece_piece_date_idx",
"columns": [
{
"expression": "piece_date",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": false,
"concurrently": false,
"method": "btree",
"with": {}
},
"content_piece_media_idx": {
"name": "content_piece_media_idx",
"columns": [
{
"expression": "media_id",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": false,
"concurrently": false,
"method": "btree",
"with": {}
},
"content_piece_source_url_idx": {
"name": "content_piece_source_url_idx",
"columns": [
{
"expression": "source_url",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": true,
"concurrently": false,
"method": "btree",
"with": {}
},
"content_piece_status_idx": {
"name": "content_piece_status_idx",
"columns": [
{
"expression": "status",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": false,
"concurrently": false,
"method": "btree",
"with": {}
},
"content_piece_processing_status_idx": {
"name": "content_piece_processing_status_idx",
"columns": [
{
"expression": "processing_status",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": false,
"concurrently": false,
"method": "btree",
"with": {}
},
"content_piece_created_by_idx": {
"name": "content_piece_created_by_idx",
"columns": [
{
"expression": "created_by_id",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": false,
"concurrently": false,
"method": "btree",
"with": {}
},
"content_piece_updated_at_idx": {
"name": "content_piece_updated_at_idx",
"columns": [
{
"expression": "updated_at",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": false,
"concurrently": false,
"method": "btree",
"with": {}
},
"content_piece_created_at_idx": {
"name": "content_piece_created_at_idx",
"columns": [
{
"expression": "created_at",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": false,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {
"content_piece_municipality_id_municipality_id_fk": {
"name": "content_piece_municipality_id_municipality_id_fk",
"tableFrom": "content_piece",
"tableTo": "municipality",
"columnsFrom": [
"municipality_id"
],
"columnsTo": [
"id"
],
"onDelete": "set null",
"onUpdate": "no action"
},
"content_piece_media_id_content_media_id_fk": {
"name": "content_piece_media_id_content_media_id_fk",
"tableFrom": "content_piece",
"tableTo": "content_media",
"columnsFrom": [
"media_id"
],
"columnsTo": [
"id"
],
"onDelete": "set null",
"onUpdate": "no action"
},
"content_piece_created_by_id_campaign_user_id_fk": {
"name": "content_piece_created_by_id_campaign_user_id_fk",
"tableFrom": "content_piece",
"tableTo": "campaign_user",
"columnsFrom": [
"created_by_id"
],
"columnsTo": [
"id"
],
"onDelete": "set null",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.content_media": {
"name": "content_media",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "serial",
"primaryKey": true,
"notNull": true
},
"alt": {
"name": "alt",
"type": "varchar",
"primaryKey": false,
"notNull": true
},
"updated_at": {
"name": "updated_at",
"type": "timestamp(3) with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"created_at": {
"name": "created_at",
"type": "timestamp(3) with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"url": {
"name": "url",
"type": "varchar",
"primaryKey": false,
"notNull": false
},
"thumbnail_u_r_l": {
"name": "thumbnail_u_r_l",
"type": "varchar",
"primaryKey": false,
"notNull": false
},
"filename": {
"name": "filename",
"type": "varchar",
"primaryKey": false,
"notNull": false
},
"mime_type": {
"name": "mime_type",
"type": "varchar",
"primaryKey": false,
"notNull": false
},
"filesize": {
"name": "filesize",
"type": "numeric",
"primaryKey": false,
"notNull": false
},
"width": {
"name": "width",
"type": "numeric",
"primaryKey": false,
"notNull": false
},
"height": {
"name": "height",
"type": "numeric",
"primaryKey": false,
"notNull": false
},
"focal_x": {
"name": "focal_x",
"type": "numeric",
"primaryKey": false,
"notNull": false
},
"focal_y": {
"name": "focal_y",
"type": "numeric",
"primaryKey": false,
"notNull": false
}
},
"indexes": {
"content_media_updated_at_idx": {
"name": "content_media_updated_at_idx",
"columns": [
{
"expression": "updated_at",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": false,
"concurrently": false,
"method": "btree",
"with": {}
},
"content_media_created_at_idx": {
"name": "content_media_created_at_idx",
"columns": [
{
"expression": "created_at",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": false,
"concurrently": false,
"method": "btree",
"with": {}
},
"content_media_filename_idx": {
"name": "content_media_filename_idx",
"columns": [
{
"expression": "filename",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": true,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.recording_speaker_labels": {
"name": "recording_speaker_labels",
"schema": "",
@ -13993,6 +14686,18 @@
"primaryKey": false,
"notNull": false
},
"content_piece_id": {
"name": "content_piece_id",
"type": "integer",
"primaryKey": false,
"notNull": false
},
"content_media_id": {
"name": "content_media_id",
"type": "integer",
"primaryKey": false,
"notNull": false
},
"recording_id": {
"name": "recording_id",
"type": "integer",
@ -14493,6 +15198,36 @@
"method": "btree",
"with": {}
},
"payload_locked_documents_rels_content_piece_id_idx": {
"name": "payload_locked_documents_rels_content_piece_id_idx",
"columns": [
{
"expression": "content_piece_id",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": false,
"concurrently": false,
"method": "btree",
"with": {}
},
"payload_locked_documents_rels_content_media_id_idx": {
"name": "payload_locked_documents_rels_content_media_id_idx",
"columns": [
{
"expression": "content_media_id",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": false,
"concurrently": false,
"method": "btree",
"with": {}
},
"payload_locked_documents_rels_recording_id_idx": {
"name": "payload_locked_documents_rels_recording_id_idx",
"columns": [
@ -15077,6 +15812,32 @@
"onDelete": "cascade",
"onUpdate": "no action"
},
"payload_locked_documents_rels_content_piece_fk": {
"name": "payload_locked_documents_rels_content_piece_fk",
"tableFrom": "payload_locked_documents_rels",
"tableTo": "content_piece",
"columnsFrom": [
"content_piece_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
},
"payload_locked_documents_rels_content_media_fk": {
"name": "payload_locked_documents_rels_content_media_fk",
"tableFrom": "payload_locked_documents_rels",
"tableTo": "content_media",
"columnsFrom": [
"content_media_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
},
"payload_locked_documents_rels_recording_fk": {
"name": "payload_locked_documents_rels_recording_fk",
"tableFrom": "payload_locked_documents_rels",
@ -16928,6 +17689,91 @@
"unpublished"
]
},
"public.enum_content_piece_topics": {
"name": "enum_content_piece_topics",
"schema": "public",
"values": [
"saude",
"educacao",
"cultura",
"esporte",
"seguranca-publica",
"meio-ambiente",
"economia-trabalho",
"direitos-humanos",
"infraestrutura",
"ciencia-tecnologia",
"politica-instituicoes",
"agricultura",
"habitacao-cidades",
"comunicacao-midia",
"igualdade-racial",
"mulheres-genero",
"juventude",
"pessoa-deficiencia"
]
},
"public.enum_content_piece_curated_fields": {
"name": "enum_content_piece_curated_fields",
"schema": "public",
"values": [
"title",
"description",
"topics",
"municipality",
"institution",
"pieceDate",
"transcript",
"type"
]
},
"public.enum_content_piece_type": {
"name": "enum_content_piece_type",
"schema": "public",
"values": [
"video",
"foto",
"texto",
"audio",
"card"
]
},
"public.enum_content_piece_origin": {
"name": "enum_content_piece_origin",
"schema": "public",
"values": [
"arquivo",
"instagram",
"youtube"
]
},
"public.enum_content_piece_status": {
"name": "enum_content_piece_status",
"schema": "public",
"values": [
"rascunho",
"publicado"
]
},
"public.enum_content_piece_processing_status": {
"name": "enum_content_piece_processing_status",
"schema": "public",
"values": [
"processando",
"pronto",
"falhou"
]
},
"public.enum_content_piece_step": {
"name": "enum_content_piece_step",
"schema": "public",
"values": [
"extraindo",
"transcrevendo",
"catalogando",
"salvando"
]
},
"public.enum_recording_status": {
"name": "enum_recording_status",
"schema": "public",
@ -17264,6 +18110,6 @@
"tables": {},
"columns": {}
},
"id": "267190ce-de8f-4bbf-8b43-de400507dc4d",
"id": "a043d1e9-f128-4742-90d5-2deb25a9b7d4",
"prevId": "00000000-0000-0000-0000-000000000000"
}

View file

@ -251,6 +251,18 @@ describe('shareLink', () => {
},
)
it('lets the URL field speak when a live row has a malformed URL', async () => {
const messages = await createErrorMessages({
title: 'URL malformada no ar',
slug: 'url-malformada-no-ar-s29',
description: 'x',
mode: 'direct',
destinations: [{ label: 'Meet', url: 'meet.google.com/abc', live: true }],
})
expect(messages).toContain(SHARE_LINK_DESTINATION_INVALID_MESSAGE)
})
it('rejects more than one destination on air', async () => {
await expect(
createShareLink({