diff --git a/biome.json b/biome.json index 5822fc71..ffa7ae02 100644 --- a/biome.json +++ b/biome.json @@ -1,5 +1,5 @@ { - "$schema": "https://biomejs.dev/schemas/2.2.4/schema.json", + "$schema": "https://biomejs.dev/schemas/2.3.11/schema.json", "extends": ["@prezly/biome-config"], "files": { "includes": ["src/**"] diff --git a/src/endpoints/Newsrooms/Client.ts b/src/endpoints/Newsrooms/Client.ts index 33682f1e..e9bf5e56 100644 --- a/src/endpoints/Newsrooms/Client.ts +++ b/src/endpoints/Newsrooms/Client.ts @@ -5,106 +5,184 @@ import { Query, SortOrder } from '../../types'; import type { CreateRequest, + IncludeOptions, ListOptions, ListResponse, SearchOptions, UpdateRequest, } from './types'; -type NewsroomId = Newsroom['uuid'] | Newsroom['id']; +/** + * Utility type to forbid arbitrary ad-hoc extensions of generic parameters. + * @see https://stackoverflow.com/a/69666350 + */ +type Exactly = Concrete & + Record, never>; + +type InferExtraFields = + T extends Required> ? Pick : unknown; export type Client = ReturnType; export function createClient(api: DeferredJobsApiClient) { - async function list({ - limit, - offset, - search, - sortOrder, - }: ListOptions = {}): Promise { - return api.get(routing.newsroomsUrl, { + async function list(options?: Exactly) { + const { search, limit, offset, sortOrder, include } = options ?? {}; + + return api.get>>(routing.newsroomsUrl, { query: { limit, offset, search, sort: SortOrder.stringify(sortOrder), + include: include?.join(','), }, }); } - async function search(options: SearchOptions = {}): Promise { - const { query, limit, offset, search, sortOrder } = options; + async function search( + options?: Exactly, + ) { + const { query, search, limit, offset, sortOrder, include } = options ?? {}; + // TODO: Introduce dedicated Search POST API - return api.get(routing.newsroomsUrl, { + return api.get>>(routing.newsroomsUrl, { query: { query: Query.stringify(query), limit, offset, search, sort: SortOrder.stringify(sortOrder), + include: include?.join(','), }, }); } - async function get(id: NewsroomId): Promise { - const { newsroom } = await api.get<{ newsroom: Newsroom }>(`${routing.newsroomsUrl}/${id}`); + async function get( + id: Newsroom['uuid'] | Newsroom['id'], + options?: Exactly, + ) { + const { include } = options ?? {}; + + const { newsroom } = await api.get<{ + newsroom: Newsroom & InferExtraFields; + }>(`${routing.newsroomsUrl}/${id}`, { + query: { + include: include?.join(','), + }, + }); return newsroom; } - async function create(payload: CreateRequest): Promise { - const { newsroom } = await api.post<{ newsroom: Newsroom }>(routing.newsroomsUrl, { + async function create( + payload: CreateRequest, + options?: Exactly, + ) { + const { include } = options ?? {}; + const { newsroom } = await api.post<{ + newsroom: Newsroom & InferExtraFields; + }>(routing.newsroomsUrl, { payload, + query: { + include: include?.join(','), + }, }); return newsroom; } - async function update(id: NewsroomId, payload: UpdateRequest): Promise { - const { newsroom } = await api.patch<{ newsroom: Newsroom }>( - `${routing.newsroomsUrl}/${id}`, - { - payload, + async function update( + id: Newsroom['uuid'] | Newsroom['id'], + payload: UpdateRequest, + options?: Exactly, + ) { + const { include } = options ?? {}; + const { newsroom } = await api.patch<{ + newsroom: Newsroom & InferExtraFields; + }>(`${routing.newsroomsUrl}/${id}`, { + payload, + query: { + include: include?.join(','), }, - ); + }); return newsroom; } - async function archive(id: NewsroomId): Promise { - const { newsroom } = await api.post<{ newsroom: Newsroom }>( - `${routing.newsroomsUrl}/${id}/archive`, - ); + async function archive( + id: Newsroom['uuid'] | Newsroom['id'], + options?: Exactly, + ) { + const { include } = options ?? {}; + const { newsroom } = await api.post<{ + newsroom: Newsroom & InferExtraFields; + }>(`${routing.newsroomsUrl}/${id}/archive`, { + query: { + include: include?.join(','), + }, + }); return newsroom; } - async function unarchive(id: NewsroomId): Promise { - const { newsroom } = await api.post<{ newsroom: Newsroom }>( - `${routing.newsroomsUrl}/${id}/unarchive`, - ); + async function unarchive( + id: Newsroom['uuid'] | Newsroom['id'], + options?: Exactly, + ) { + const { include } = options ?? {}; + const { newsroom } = await api.post<{ + newsroom: Newsroom & InferExtraFields; + }>(`${routing.newsroomsUrl}/${id}/unarchive`, { + query: { + include: include?.join(','), + }, + }); return newsroom; } - async function doDelete(id: NewsroomId): Promise { - return api.delete(`${routing.newsroomsUrl}/${id}`); + async function takeOffline( + id: Newsroom['uuid'] | Newsroom['id'], + options?: Exactly, + ) { + const { include } = options ?? {}; + const { newsroom } = await api.post<{ + newsroom: Newsroom & InferExtraFields; + }>(`${routing.newsroomsUrl}/${id}/offline`, { + query: { + include: include?.join(','), + }, + }); + return newsroom; } - async function takeOffline(id: NewsroomId): Promise { - const { newsroom } = await api.post<{ newsroom: Newsroom }>( - `${routing.newsroomsUrl}/${id}/offline`, - ); + async function takeOnline( + id: Newsroom['uuid'] | Newsroom['id'], + options?: Exactly, + ) { + const { include } = options ?? {}; + const { newsroom } = await api.post<{ + newsroom: Newsroom & InferExtraFields; + }>(`${routing.newsroomsUrl}/${id}/online`, { + query: { + include: include?.join(','), + }, + }); return newsroom; } - async function takeOnline(id: NewsroomId): Promise { - const { newsroom } = await api.post<{ newsroom: Newsroom }>( - `${routing.newsroomsUrl}/${id}/online`, - ); + async function convertToHub( + id: Newsroom['uuid'] | Newsroom['id'], + options?: Exactly, + ) { + const { include } = options ?? {}; + const { newsroom } = await api.post<{ + newsroom: Newsroom & InferExtraFields; + }>(`${routing.newsroomsUrl}/${id}/convert`, { + query: { + include: include?.join(','), + }, + }); return newsroom; } - async function convertToHub(id: NewsroomId): Promise { - const { newsroom } = await api.post<{ newsroom: Newsroom }>( - `${routing.newsroomsUrl}/${id}/convert`, - ); - return newsroom; + async function deleteNewsroom(id: Newsroom['uuid'] | Newsroom['id']): Promise { + return api.delete(`${routing.newsroomsUrl}/${id}`); } return { @@ -115,7 +193,7 @@ export function createClient(api: DeferredJobsApiClient) { update, archive, unarchive, - delete: doDelete, + delete: deleteNewsroom, takeOnline, takeOffline, convertToHub, diff --git a/src/endpoints/Newsrooms/types.ts b/src/endpoints/Newsrooms/types.ts index 1f70a55b..d1f7a869 100644 --- a/src/endpoints/Newsrooms/types.ts +++ b/src/endpoints/Newsrooms/types.ts @@ -2,7 +2,16 @@ import type { UploadedImage } from '@prezly/uploads'; import type { CultureRef, Newsroom, Pagination, Query, SortOrder } from '../../types'; -export interface ListOptions { +export interface IncludeOptions< + Include extends keyof Newsroom.ExtraFields = keyof Newsroom.ExtraFields, +> { + include?: Include[]; +} + +export interface ListOptions< + Include extends keyof Newsroom.ExtraFields = keyof Newsroom.ExtraFields, +> { + include?: Include[]; limit?: number; offset?: number; /** @@ -12,15 +21,17 @@ export interface ListOptions { sortOrder?: SortOrder | string; } -export interface SearchOptions extends ListOptions { +export interface SearchOptions< + Include extends keyof Newsroom.ExtraFields = keyof Newsroom.ExtraFields, +> extends ListOptions { /** * Filter query using Prezly JSON Query Language */ query?: Query; } -export interface ListResponse { - newsrooms: Omit[]; +export interface ListResponse { + newsrooms: T[]; pagination: Pagination; sort: string; } diff --git a/src/endpoints/Stories/Client.ts b/src/endpoints/Stories/Client.ts index d3a32d8d..afc56692 100644 --- a/src/endpoints/Stories/Client.ts +++ b/src/endpoints/Stories/Client.ts @@ -42,9 +42,8 @@ type Formats = Story.FormatVersion[]; type Exactly = Concrete & Record, never>; -type InferExtraFields = T extends Required> - ? Pick - : unknown; +type InferExtraFields = + T extends Required> ? Pick : unknown; type MaybeArray = T | T[]; diff --git a/src/types/Newsroom.ts b/src/types/Newsroom.ts index 61a35314..0cab21e0 100644 --- a/src/types/Newsroom.ts +++ b/src/types/Newsroom.ts @@ -105,25 +105,6 @@ export interface Newsroom extends NewsroomRef { custom_privacy_policy_link: string | null; custom_data_request_link: string | null; - policies: { - privacy_policy: - | { - link: string; - } - | { - content: string; - origin: 'default' | 'custom'; - }; - cookie_policy: - | { - link: string; - } - | { - content: string; - origin: 'default' | 'custom'; - }; - }; - tracking_policy: Newsroom.TrackingPolicy; onetrust_cookie_consent: { is_enabled: boolean; @@ -233,4 +214,33 @@ export namespace Newsroom { } return arg === Status.ARCHIVED; } + + export interface ExtraFields { + policies: { + privacy_policy: + | { + link: string; + } + | { + content: string; + origin: 'default' | 'custom'; + }; + cookie_policy: + | { + link: string; + } + | { + content: string; + origin: 'default' | 'custom'; + }; + }; + draft_stories_number: number; + published_stories_number: number; + draft_campaigns_number: number; + scheduled_campaigns_number: number; + sent_campaigns_number: number; + draft_pitches_number: number; + sent_pitches_number: number; + online_coverage_number: number; + } }