From f16f51a2b3789acb680aa4fe0f8b37efa7221b44 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Mon, 7 Aug 2023 14:02:36 +0200 Subject: [PATCH] Move all relevant API types to api-contract --- .../src/api-contract/ShlinkApiClient.ts | 8 ++-- .../src/api-contract/types.ts | 42 ++++++++++++------- .../src/short-urls/CreateShortUrl.tsx | 10 ++--- .../src/short-urls/EditShortUrl.tsx | 3 +- .../src/short-urls/ShortUrlForm.tsx | 23 ++++++---- .../src/short-urls/data/index.ts | 15 +------ .../src/short-urls/helpers/index.ts | 6 +-- .../short-urls/reducers/shortUrlCreation.ts | 8 +--- .../short-urls/reducers/shortUrlEdition.ts | 4 +- src/api/services/ShlinkApiClient.ts | 8 ++-- 10 files changed, 66 insertions(+), 61 deletions(-) diff --git a/shlink-web-component/src/api-contract/ShlinkApiClient.ts b/shlink-web-component/src/api-contract/ShlinkApiClient.ts index 2d8a6805..9ec85594 100644 --- a/shlink-web-component/src/api-contract/ShlinkApiClient.ts +++ b/shlink-web-component/src/api-contract/ShlinkApiClient.ts @@ -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; - createShortUrl(options: ShortUrlData): Promise; + createShortUrl(options: ShlinkCreateShortUrlData): Promise; getShortUrlVisits(shortCode: string, query?: ShlinkVisitsParams): Promise; @@ -42,7 +42,7 @@ export type ShlinkApiClient = { updateShortUrl( shortCode: string, domain: string | null | undefined, - body: ShlinkShortUrlData, + body: ShlinkEditShortUrlData, ): Promise; listTags(): Promise; diff --git a/shlink-web-component/src/api-contract/types.ts b/shlink-web-component/src/api-contract/types.ts index d889f7b9..5fb2f692 100644 --- a/shlink-web-component/src/api-contract/types.ts +++ b/shlink-web-component/src/api-contract/types.ts @@ -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 { + 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; diff --git a/shlink-web-component/src/short-urls/CreateShortUrl.tsx b/shlink-web-component/src/short-urls/CreateShortUrl.tsx index 4280f15d..80428ded 100644 --- a/shlink-web-component/src/short-urls/CreateShortUrl.tsx +++ b/shlink-web-component/src/short-urls/CreateShortUrl.tsx @@ -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; + createShortUrl: (data: ShlinkCreateShortUrlData) => Promise; 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, + ShortUrlForm: FC>, CreateShortUrlResult: FC, ) => ({ 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); }} diff --git a/shlink-web-component/src/short-urls/EditShortUrl.tsx b/shlink-web-component/src/short-urls/EditShortUrl.tsx index 1c30a077..229f224d 100644 --- a/shlink-web-component/src/short-urls/EditShortUrl.tsx +++ b/shlink-web-component/src/short-urls/EditShortUrl.tsx @@ -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) => ({ +export const EditShortUrl = (ShortUrlForm: FC>) => ({ shortUrlDetail, getShortUrlDetail, shortUrlEdition, diff --git a/shlink-web-component/src/short-urls/ShortUrlForm.tsx b/shlink-web-component/src/short-urls/ShortUrlForm.tsx index 36da839e..16e2fead 100644 --- a/shlink-web-component/src/short-urls/ShortUrlForm.tsx +++ b/shlink-web-component/src/short-urls/ShortUrlForm.tsx @@ -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 { + // 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; + initialState: T; + onSave: (shortUrlData: T) => Promise; } 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, DomainSelector: FC, -): FC => ({ mode, saving, onSave, initialState }) => { +) => function ShortUrlFormComp( + { mode, saving, onSave, initialState }: ShortUrlFormProps, +) { 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 && ( <>
@@ -216,7 +223,7 @@ export const ShortUrlForm = ( > Validate URL - {!isEdit && ( + {!isEdit && isCreation && (

{ - longUrl: string; - customSlug?: string; - shortCodeLength?: number; - domain?: string; - findIfExists?: boolean; - deviceLongUrls?: { - android?: string; - ios?: string; - desktop?: string; - } -} - export interface ShortUrlIdentifier { shortCode: string; domain?: OptionalString; diff --git a/shlink-web-component/src/short-urls/helpers/index.ts b/shlink-web-component/src/short-urls/helpers/index.ts index a5ab7fd8..f576bc5f 100644 --- a/shlink-web-component/src/short-urls/helpers/index.ts +++ b/shlink-web-component/src/short-urls/helpers/index.ts @@ -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) { diff --git a/shlink-web-component/src/short-urls/reducers/shortUrlCreation.ts b/shlink-web-component/src/short-urls/reducers/shortUrlCreation.ts index a8a7bc7e..df3efd0c 100644 --- a/shlink-web-component/src/short-urls/reducers/shortUrlCreation.ts +++ b/shlink-web-component/src/short-urls/reducers/shortUrlCreation.ts @@ -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; - 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 => apiClientFactory().createShortUrl(data), + (data: ShlinkCreateShortUrlData): Promise => apiClientFactory().createShortUrl(data), ); export const shortUrlCreationReducerCreator = (createShortUrlThunk: ReturnType) => { diff --git a/shlink-web-component/src/short-urls/reducers/shortUrlEdition.ts b/shlink-web-component/src/short-urls/reducers/shortUrlEdition.ts index 6ddc4db3..87d4bbe9 100644 --- a/shlink-web-component/src/short-urls/reducers/shortUrlEdition.ts +++ b/shlink-web-component/src/short-urls/reducers/shortUrlEdition.ts @@ -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 = { diff --git a/src/api/services/ShlinkApiClient.ts b/src/api/services/ShlinkApiClient.ts index 0f3d03c5..21e6a60d 100644 --- a/src/api/services/ShlinkApiClient.ts +++ b/src/api/services/ShlinkApiClient.ts @@ -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 => { + public readonly createShortUrl = async (options: ShlinkCreateShortUrlData): Promise => { const body = reject((value) => isEmpty(value) || isNil(value), options as any); return this.performRequest({ 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 => this.performRequest({ url: `/short-urls/${shortCode}`, method: 'PATCH', query: { domain }, body });