Move all relevant API types to api-contract

This commit is contained in:
Alejandro Celaya 2023-08-07 14:02:36 +02:00
parent d97db9e17c
commit f16f51a2b3
10 changed files with 66 additions and 61 deletions

View file

@ -1,12 +1,12 @@
import type { ShortUrlData } from '../short-urls/data';
import type {
ShlinkCreateShortUrlData,
ShlinkDomainRedirects,
ShlinkDomainsResponse,
ShlinkEditDomainRedirects,
ShlinkEditShortUrlData,
ShlinkHealth,
ShlinkMercureInfo,
ShlinkShortUrl,
ShlinkShortUrlData,
ShlinkShortUrlsListParams,
ShlinkShortUrlsResponse,
ShlinkTags,
@ -21,7 +21,7 @@ export type ShlinkApiClient = {
listShortUrls(params?: ShlinkShortUrlsListParams): Promise<ShlinkShortUrlsResponse>;
createShortUrl(options: ShortUrlData): Promise<ShlinkShortUrl>;
createShortUrl(options: ShlinkCreateShortUrlData): Promise<ShlinkShortUrl>;
getShortUrlVisits(shortCode: string, query?: ShlinkVisitsParams): Promise<ShlinkVisits>;
@ -42,7 +42,7 @@ export type ShlinkApiClient = {
updateShortUrl(
shortCode: string,
domain: string | null | undefined,
body: ShlinkShortUrlData,
body: ShlinkEditShortUrlData,
): Promise<ShlinkShortUrl>;
listTags(): Promise<ShlinkTags>;

View file

@ -31,6 +31,34 @@ export interface ShlinkShortUrl {
forwardQuery?: boolean;
}
export interface ShlinkEditShortUrlData {
longUrl?: string;
title?: string | null;
tags?: string[];
deviceLongUrls?: ShlinkDeviceLongUrls;
crawlable?: boolean;
forwardQuery?: boolean;
validSince?: string | null;
validUntil?: string | null;
maxVisits?: number | null;
/** @deprecated */
validateUrl?: boolean;
}
export interface ShlinkCreateShortUrlData extends Omit<ShlinkEditShortUrlData, 'deviceLongUrls'> {
longUrl: string;
customSlug?: string;
shortCodeLength?: number;
domain?: string;
findIfExists?: boolean;
deviceLongUrls?: {
android?: string;
ios?: string;
desktop?: string;
}
}
export interface ShlinkShortUrlsResponse {
data: ShlinkShortUrl[];
pagination: ShlinkPaginator;
@ -106,20 +134,6 @@ export interface ShlinkVisitsParams {
excludeBots?: boolean;
}
export interface ShlinkShortUrlData {
longUrl?: string;
title?: string | null;
/** @deprecated */
validateUrl?: boolean;
tags?: string[];
deviceLongUrls?: ShlinkDeviceLongUrls;
crawlable?: boolean;
forwardQuery?: boolean;
validSince?: string | null;
validUntil?: string | null;
maxVisits?: number | null;
}
export interface ShlinkDomainRedirects {
baseUrlRedirect: string | null;
regular404Redirect: string | null;

View file

@ -1,8 +1,8 @@
import type { ShlinkCreateShortUrlData } from '@shlinkio/shlink-web-component/api-contract';
import type { FC } from 'react';
import { useMemo } from 'react';
import type { ShortUrlCreationSettings } from '../utils/settings';
import { useSetting } from '../utils/settings';
import type { ShortUrlData } from './data';
import type { CreateShortUrlResultProps } from './helpers/CreateShortUrlResult';
import type { ShortUrlCreation } from './reducers/shortUrlCreation';
import type { ShortUrlFormProps } from './ShortUrlForm';
@ -13,11 +13,11 @@ export interface CreateShortUrlProps {
interface CreateShortUrlConnectProps extends CreateShortUrlProps {
shortUrlCreation: ShortUrlCreation;
createShortUrl: (data: ShortUrlData) => Promise<void>;
createShortUrl: (data: ShlinkCreateShortUrlData) => Promise<void>;
resetCreateShortUrl: () => void;
}
const getInitialState = (settings?: ShortUrlCreationSettings): ShortUrlData => ({
const getInitialState = (settings?: ShortUrlCreationSettings): ShlinkCreateShortUrlData => ({
longUrl: '',
tags: [],
customSlug: '',
@ -33,7 +33,7 @@ const getInitialState = (settings?: ShortUrlCreationSettings): ShortUrlData => (
});
export const CreateShortUrl = (
ShortUrlForm: FC<ShortUrlFormProps>,
ShortUrlForm: FC<ShortUrlFormProps<ShlinkCreateShortUrlData>>,
CreateShortUrlResult: FC<CreateShortUrlResultProps>,
) => ({
createShortUrl,
@ -50,7 +50,7 @@ export const CreateShortUrl = (
initialState={initialState}
saving={shortUrlCreation.saving}
mode={basicMode ? 'create-basic' : 'create'}
onSave={async (data: ShortUrlData) => {
onSave={async (data) => {
resetCreateShortUrl();
return createShortUrl(data);
}}

View file

@ -1,6 +1,7 @@
import { faArrowLeft } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Message, parseQuery, Result } from '@shlinkio/shlink-frontend-kit';
import type { ShlinkEditShortUrlData } from '@shlinkio/shlink-web-component/api-contract';
import type { FC } from 'react';
import { useEffect, useMemo } from 'react';
import { ExternalLink } from 'react-external-link';
@ -22,7 +23,7 @@ interface EditShortUrlConnectProps {
editShortUrl: (editShortUrl: EditShortUrlInfo) => void;
}
export const EditShortUrl = (ShortUrlForm: FC<ShortUrlFormProps>) => ({
export const EditShortUrl = (ShortUrlForm: FC<ShortUrlFormProps<ShlinkEditShortUrlData>>) => ({
shortUrlDetail,
getShortUrlDetail,
shortUrlEdition,

View file

@ -9,7 +9,7 @@ import type { ChangeEvent, FC } from 'react';
import { useEffect, useState } from 'react';
import { Button, FormGroup, Input, Row } from 'reactstrap';
import type { InputType } from 'reactstrap/types/lib/Input';
import type { ShlinkDeviceLongUrls } from '../api-contract';
import type { ShlinkCreateShortUrlData, ShlinkDeviceLongUrls, ShlinkEditShortUrlData } from '../api-contract';
import type { DomainSelectorProps } from '../domains/DomainSelector';
import type { TagsSelectorProps } from '../tags/helpers/TagsSelector';
import { IconInput } from '../utils/components/IconInput';
@ -18,7 +18,6 @@ import { DateTimeInput } from '../utils/dates/DateTimeInput';
import { formatIsoDate } from '../utils/dates/helpers/date';
import { useFeature } from '../utils/features';
import { handleEventPreventingDefault, hasValue } from '../utils/helpers';
import type { ShortUrlData } from './data';
import { ShortUrlFormCheckboxGroup } from './helpers/ShortUrlFormCheckboxGroup';
import { UseExistingIfFoundInfoIcon } from './UseExistingIfFoundInfoIcon';
import './ShortUrlForm.scss';
@ -28,25 +27,32 @@ export type Mode = 'create' | 'create-basic' | 'edit';
type DateFields = 'validSince' | 'validUntil';
type NonDateFields = 'longUrl' | 'customSlug' | 'shortCodeLength' | 'domain' | 'maxVisits' | 'title';
export interface ShortUrlFormProps {
export interface ShortUrlFormProps<T extends ShlinkCreateShortUrlData | ShlinkEditShortUrlData> {
// FIXME Try to get rid of the mode param, and infer creation or edition from initialState if possible
mode: Mode;
saving: boolean;
initialState: ShortUrlData;
onSave: (shortUrlData: ShortUrlData) => Promise<unknown>;
initialState: T;
onSave: (shortUrlData: T) => Promise<unknown>;
}
const normalizeTag = pipe(trim, replace(/ /g, '-'));
const toDate = (date?: string | Date): Date | undefined => (typeof date === 'string' ? parseISO(date) : date);
const isCreationData = (data: ShlinkCreateShortUrlData | ShlinkEditShortUrlData): data is ShlinkCreateShortUrlData =>
'shortCodeLength' in data && 'customSlug' in data && 'domain' in data;
export const ShortUrlForm = (
TagsSelector: FC<TagsSelectorProps>,
DomainSelector: FC<DomainSelectorProps>,
): FC<ShortUrlFormProps> => ({ mode, saving, onSave, initialState }) => {
) => function ShortUrlFormComp<T extends ShlinkCreateShortUrlData | ShlinkEditShortUrlData>(
{ mode, saving, onSave, initialState }: ShortUrlFormProps<T>,
) {
const [shortUrlData, setShortUrlData] = useState(initialState);
const reset = () => setShortUrlData(initialState);
const supportsDeviceLongUrls = useFeature('deviceLongUrls');
const isEdit = mode === 'edit';
const isCreation = isCreationData(shortUrlData);
const isBasicMode = mode === 'create-basic';
const changeTags = (tags: string[]) => setShortUrlData({ ...shortUrlData, tags: tags.map(normalizeTag) });
const setResettableValue = (value: string, initialValue?: any) => {
@ -82,6 +88,7 @@ export const ShortUrlForm = (
id={id}
type={type}
placeholder={placeholder}
// @ts-expect-error FIXME Make sure id is a key from T
value={shortUrlData[id] ?? ''}
onChange={props.onChange ?? ((e) => setShortUrlData({ ...shortUrlData, [id]: e.target.value }))}
{...props}
@ -171,7 +178,7 @@ export const ShortUrlForm = (
title: setResettableValue(target.value, initialState.title),
}),
})}
{!isEdit && (
{!isEdit && isCreation && (
<>
<Row>
<div className="col-lg-6">
@ -216,7 +223,7 @@ export const ShortUrlForm = (
>
Validate URL
</ShortUrlFormCheckboxGroup>
{!isEdit && (
{!isEdit && isCreation && (
<p>
<Checkbox
inline

View file

@ -1,20 +1,7 @@
import type { Order } from '@shlinkio/shlink-frontend-kit';
import type { ShlinkShortUrl, ShlinkShortUrlData } from '../../api-contract';
import type { ShlinkShortUrl } from '../../api-contract';
import type { OptionalString } from '../../utils/helpers';
export interface ShortUrlData extends Omit<ShlinkShortUrlData, 'deviceLongUrls'> {
longUrl: string;
customSlug?: string;
shortCodeLength?: number;
domain?: string;
findIfExists?: boolean;
deviceLongUrls?: {
android?: string;
ios?: string;
desktop?: string;
}
}
export interface ShortUrlIdentifier {
shortCode: string;
domain?: OptionalString;

View file

@ -1,9 +1,8 @@
import { isNil } from 'ramda';
import type { ShlinkShortUrl } from '../../api-contract';
import type { ShlinkCreateShortUrlData, ShlinkShortUrl } from '../../api-contract';
import type { OptionalString } from '../../utils/helpers';
import type { ShortUrlCreationSettings } from '../../utils/settings';
import { DEFAULT_DOMAIN } from '../../visits/reducers/domainVisits';
import type { ShortUrlData } from '../data';
export const shortUrlMatches = (shortUrl: ShlinkShortUrl, shortCode: string, domain: OptionalString): boolean => {
if (isNil(domain)) {
@ -21,10 +20,11 @@ export const domainMatches = (shortUrl: ShlinkShortUrl, domain: string): boolean
return shortUrl.domain === domain;
};
// FIXME This should return ShlinkEditShortUrlData
export const shortUrlDataFromShortUrl = (
shortUrl?: ShlinkShortUrl,
settings?: ShortUrlCreationSettings,
): ShortUrlData => {
): ShlinkCreateShortUrlData => {
const validateUrl = settings?.validateUrls ?? false;
if (!shortUrl) {

View file

@ -1,9 +1,7 @@
import type { PayloadAction } from '@reduxjs/toolkit';
import { createSlice } from '@reduxjs/toolkit';
import type { ProblemDetailsError, ShlinkApiClient, ShlinkShortUrl } from '../../api-contract';
import type { ProblemDetailsError, ShlinkApiClient, ShlinkCreateShortUrlData, ShlinkShortUrl } from '../../api-contract';
import { parseApiError } from '../../api-contract/utils';
import { createAsyncThunk } from '../../utils/redux';
import type { ShortUrlData } from '../data';
const REDUCER_PREFIX = 'shlink/shortUrlCreation';
@ -27,8 +25,6 @@ export type ShortUrlCreation = {
error: false;
};
export type CreateShortUrlAction = PayloadAction<ShlinkShortUrl>;
const initialState: ShortUrlCreation = {
saving: false,
saved: false,
@ -37,7 +33,7 @@ const initialState: ShortUrlCreation = {
export const createShortUrl = (apiClientFactory: () => ShlinkApiClient) => createAsyncThunk(
`${REDUCER_PREFIX}/createShortUrl`,
(data: ShortUrlData): Promise<ShlinkShortUrl> => apiClientFactory().createShortUrl(data),
(data: ShlinkCreateShortUrlData): Promise<ShlinkShortUrl> => apiClientFactory().createShortUrl(data),
);
export const shortUrlCreationReducerCreator = (createShortUrlThunk: ReturnType<typeof createShortUrl>) => {

View file

@ -1,5 +1,5 @@
import { createSlice } from '@reduxjs/toolkit';
import type { ProblemDetailsError, ShlinkApiClient, ShlinkShortUrl, ShlinkShortUrlData } from '../../api-contract';
import type { ProblemDetailsError, ShlinkApiClient, ShlinkEditShortUrlData, ShlinkShortUrl } from '../../api-contract';
import { parseApiError } from '../../api-contract/utils';
import { createAsyncThunk } from '../../utils/redux';
import type { ShortUrlIdentifier } from '../data';
@ -15,7 +15,7 @@ export interface ShortUrlEdition {
}
export interface EditShortUrl extends ShortUrlIdentifier {
data: ShlinkShortUrlData;
data: ShlinkEditShortUrlData;
}
const initialState: ShortUrlEdition = {

View file

@ -2,13 +2,14 @@ import { orderToString, stringifyQuery } from '@shlinkio/shlink-frontend-kit';
import type {
RegularNotFound,
ShlinkApiClient as BaseShlinkApiClient,
ShlinkCreateShortUrlData,
ShlinkDomainRedirects,
ShlinkDomainsResponse,
ShlinkEditDomainRedirects,
ShlinkEditShortUrlData,
ShlinkHealth,
ShlinkMercureInfo,
ShlinkShortUrl,
ShlinkShortUrlData,
ShlinkShortUrlsListNormalizedParams,
ShlinkShortUrlsListParams,
ShlinkShortUrlsResponse,
@ -24,7 +25,6 @@ import {
ErrorTypeV3,
} from '@shlinkio/shlink-web-component/api-contract';
import { isEmpty, isNil, reject } from 'ramda';
import type { ShortUrlData } from '../../../shlink-web-component/src/short-urls/data';
import type { HttpClient } from '../../common/services/HttpClient';
import { replaceAuthorityFromUri } from '../../utils/helpers/uri';
import type { OptionalString } from '../../utils/utils';
@ -73,7 +73,7 @@ export class ShlinkApiClient implements BaseShlinkApiClient {
{ url: '/short-urls', query: normalizeListParams(params) },
).then(({ shortUrls }) => shortUrls);
public readonly createShortUrl = async (options: ShortUrlData): Promise<ShlinkShortUrl> => {
public readonly createShortUrl = async (options: ShlinkCreateShortUrlData): Promise<ShlinkShortUrl> => {
const body = reject((value) => isEmpty(value) || isNil(value), options as any);
return this.performRequest<ShlinkShortUrl>({ url: '/short-urls', method: 'POST', body });
};
@ -106,7 +106,7 @@ export class ShlinkApiClient implements BaseShlinkApiClient {
public readonly updateShortUrl = async (
shortCode: string,
domain: OptionalString,
body: ShlinkShortUrlData,
body: ShlinkEditShortUrlData,
): Promise<ShlinkShortUrl> =>
this.performRequest<ShlinkShortUrl>({ url: `/short-urls/${shortCode}`, method: 'PATCH', query: { domain }, body });