Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion biome.json
Original file line number Diff line number Diff line change
@@ -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/**"]
Expand Down
168 changes: 123 additions & 45 deletions src/endpoints/Newsrooms/Client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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, Abstract> = Concrete &
Record<Exclude<keyof Concrete, keyof Abstract>, never>;

type InferExtraFields<T> =
T extends Required<IncludeOptions<infer I>> ? Pick<Newsroom.ExtraFields, I> : unknown;

export type Client = ReturnType<typeof createClient>;

export function createClient(api: DeferredJobsApiClient) {
async function list({
limit,
offset,
search,
sortOrder,
}: ListOptions = {}): Promise<ListResponse> {
return api.get<ListResponse>(routing.newsroomsUrl, {
async function list<Options extends ListOptions>(options?: Exactly<Options, ListOptions>) {
const { search, limit, offset, sortOrder, include } = options ?? {};

return api.get<ListResponse<Newsroom & InferExtraFields<Options>>>(routing.newsroomsUrl, {
query: {
limit,
offset,
search,
sort: SortOrder.stringify(sortOrder),
include: include?.join(','),
},
});
}

async function search(options: SearchOptions = {}): Promise<ListResponse> {
const { query, limit, offset, search, sortOrder } = options;
async function search<Options extends SearchOptions>(
options?: Exactly<Options, SearchOptions>,
) {
const { query, search, limit, offset, sortOrder, include } = options ?? {};

// TODO: Introduce dedicated Search POST API
return api.get<ListResponse>(routing.newsroomsUrl, {
return api.get<ListResponse<Newsroom & InferExtraFields<Options>>>(routing.newsroomsUrl, {
query: {
query: Query.stringify(query),
limit,
offset,
search,
sort: SortOrder.stringify(sortOrder),
include: include?.join(','),
},
});
}

async function get(id: NewsroomId): Promise<Newsroom> {
const { newsroom } = await api.get<{ newsroom: Newsroom }>(`${routing.newsroomsUrl}/${id}`);
async function get<Options extends IncludeOptions>(
id: Newsroom['uuid'] | Newsroom['id'],
options?: Exactly<Options, IncludeOptions>,
) {
const { include } = options ?? {};

const { newsroom } = await api.get<{
newsroom: Newsroom & InferExtraFields<Options>;
}>(`${routing.newsroomsUrl}/${id}`, {
query: {
include: include?.join(','),
},
});
return newsroom;
}

async function create(payload: CreateRequest): Promise<Newsroom> {
const { newsroom } = await api.post<{ newsroom: Newsroom }>(routing.newsroomsUrl, {
async function create<Options extends IncludeOptions>(
payload: CreateRequest,
options?: Exactly<Options, IncludeOptions>,
) {
const { include } = options ?? {};
const { newsroom } = await api.post<{
newsroom: Newsroom & InferExtraFields<Options>;
}>(routing.newsroomsUrl, {
payload,
query: {
include: include?.join(','),
},
});
return newsroom;
}

async function update(id: NewsroomId, payload: UpdateRequest): Promise<Newsroom> {
const { newsroom } = await api.patch<{ newsroom: Newsroom }>(
`${routing.newsroomsUrl}/${id}`,
{
payload,
async function update<Options extends IncludeOptions>(
id: Newsroom['uuid'] | Newsroom['id'],
payload: UpdateRequest,
options?: Exactly<Options, IncludeOptions>,
) {
const { include } = options ?? {};
const { newsroom } = await api.patch<{
newsroom: Newsroom & InferExtraFields<Options>;
}>(`${routing.newsroomsUrl}/${id}`, {
payload,
query: {
include: include?.join(','),
},
);
});
return newsroom;
}

async function archive(id: NewsroomId): Promise<Newsroom> {
const { newsroom } = await api.post<{ newsroom: Newsroom }>(
`${routing.newsroomsUrl}/${id}/archive`,
);
async function archive<Options extends IncludeOptions>(
id: Newsroom['uuid'] | Newsroom['id'],
options?: Exactly<Options, IncludeOptions>,
) {
const { include } = options ?? {};
const { newsroom } = await api.post<{
newsroom: Newsroom & InferExtraFields<Options>;
}>(`${routing.newsroomsUrl}/${id}/archive`, {
query: {
include: include?.join(','),
},
});
return newsroom;
}

async function unarchive(id: NewsroomId): Promise<Newsroom> {
const { newsroom } = await api.post<{ newsroom: Newsroom }>(
`${routing.newsroomsUrl}/${id}/unarchive`,
);
async function unarchive<Options extends IncludeOptions>(
id: Newsroom['uuid'] | Newsroom['id'],
options?: Exactly<Options, IncludeOptions>,
) {
const { include } = options ?? {};
const { newsroom } = await api.post<{
newsroom: Newsroom & InferExtraFields<Options>;
}>(`${routing.newsroomsUrl}/${id}/unarchive`, {
query: {
include: include?.join(','),
},
});
return newsroom;
}

async function doDelete(id: NewsroomId): Promise<void> {
return api.delete(`${routing.newsroomsUrl}/${id}`);
async function takeOffline<Options extends IncludeOptions>(
id: Newsroom['uuid'] | Newsroom['id'],
options?: Exactly<Options, IncludeOptions>,
) {
const { include } = options ?? {};
const { newsroom } = await api.post<{
newsroom: Newsroom & InferExtraFields<Options>;
}>(`${routing.newsroomsUrl}/${id}/offline`, {
query: {
include: include?.join(','),
},
});
return newsroom;
}

async function takeOffline(id: NewsroomId): Promise<Newsroom> {
const { newsroom } = await api.post<{ newsroom: Newsroom }>(
`${routing.newsroomsUrl}/${id}/offline`,
);
async function takeOnline<Options extends IncludeOptions>(
id: Newsroom['uuid'] | Newsroom['id'],
options?: Exactly<Options, IncludeOptions>,
) {
const { include } = options ?? {};
const { newsroom } = await api.post<{
newsroom: Newsroom & InferExtraFields<Options>;
}>(`${routing.newsroomsUrl}/${id}/online`, {
query: {
include: include?.join(','),
},
});
return newsroom;
}

async function takeOnline(id: NewsroomId): Promise<Newsroom> {
const { newsroom } = await api.post<{ newsroom: Newsroom }>(
`${routing.newsroomsUrl}/${id}/online`,
);
async function convertToHub<Options extends IncludeOptions>(
id: Newsroom['uuid'] | Newsroom['id'],
options?: Exactly<Options, IncludeOptions>,
) {
const { include } = options ?? {};
const { newsroom } = await api.post<{
newsroom: Newsroom & InferExtraFields<Options>;
}>(`${routing.newsroomsUrl}/${id}/convert`, {
query: {
include: include?.join(','),
},
});
return newsroom;
}

async function convertToHub(id: NewsroomId): Promise<Newsroom> {
const { newsroom } = await api.post<{ newsroom: Newsroom }>(
`${routing.newsroomsUrl}/${id}/convert`,
);
return newsroom;
async function deleteNewsroom(id: Newsroom['uuid'] | Newsroom['id']): Promise<void> {
return api.delete(`${routing.newsroomsUrl}/${id}`);
}

return {
Expand All @@ -115,7 +193,7 @@ export function createClient(api: DeferredJobsApiClient) {
update,
archive,
unarchive,
delete: doDelete,
delete: deleteNewsroom,
takeOnline,
takeOffline,
convertToHub,
Expand Down
19 changes: 15 additions & 4 deletions src/endpoints/Newsrooms/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
/**
Expand All @@ -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<Include> {
/**
* Filter query using Prezly JSON Query Language
*/
query?: Query;
}

export interface ListResponse {
newsrooms: Omit<Newsroom, 'policies'>[];
export interface ListResponse<T extends Newsroom = Newsroom> {
newsrooms: T[];
pagination: Pagination;
sort: string;
}
Expand Down
5 changes: 2 additions & 3 deletions src/endpoints/Stories/Client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,8 @@ type Formats = Story.FormatVersion[];
type Exactly<Concrete, Abstract> = Concrete &
Record<Exclude<keyof Concrete, keyof Abstract>, never>;

type InferExtraFields<T> = T extends Required<IncludeOptions<infer I>>
? Pick<Story.ExtraFields, I>
: unknown;
type InferExtraFields<T> =
T extends Required<IncludeOptions<infer I>> ? Pick<Story.ExtraFields, I> : unknown;

type MaybeArray<T> = T | T[];

Expand Down
48 changes: 29 additions & 19 deletions src/types/Newsroom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}
}
Loading