Add Shlink prefix to api-contract models

This commit is contained in:
Alejandro Celaya 2023-08-06 21:27:57 +02:00
parent 47dd105cd6
commit 23daa2de72
35 changed files with 160 additions and 159 deletions

View file

@ -1,4 +1,4 @@
import type { ShortUrl, ShortUrlData } from '../short-urls/data'; import type { ShlinkShortUrl, ShortUrlData } from '../short-urls/data';
import type { import type {
ShlinkDomainRedirects, ShlinkDomainRedirects,
ShlinkDomainsResponse, ShlinkDomainsResponse,
@ -20,7 +20,7 @@ export type ShlinkApiClient = {
listShortUrls(params?: ShlinkShortUrlsListParams): Promise<ShlinkShortUrlsResponse>; listShortUrls(params?: ShlinkShortUrlsListParams): Promise<ShlinkShortUrlsResponse>;
createShortUrl(options: ShortUrlData): Promise<ShortUrl>; createShortUrl(options: ShortUrlData): Promise<ShlinkShortUrl>;
getShortUrlVisits(shortCode: string, query?: ShlinkVisitsParams): Promise<ShlinkVisits>; getShortUrlVisits(shortCode: string, query?: ShlinkVisitsParams): Promise<ShlinkVisits>;
@ -34,7 +34,7 @@ export type ShlinkApiClient = {
getVisitsOverview(): Promise<ShlinkVisitsOverview>; getVisitsOverview(): Promise<ShlinkVisitsOverview>;
getShortUrl(shortCode: string, domain?: string | null): Promise<ShortUrl>; getShortUrl(shortCode: string, domain?: string | null): Promise<ShlinkShortUrl>;
deleteShortUrl(shortCode: string, domain?: string | null): Promise<void>; deleteShortUrl(shortCode: string, domain?: string | null): Promise<void>;
@ -42,7 +42,7 @@ export type ShlinkApiClient = {
shortCode: string, shortCode: string,
domain: string | null | undefined, domain: string | null | undefined,
body: ShlinkShortUrlData, body: ShlinkShortUrlData,
): Promise<ShortUrl>; ): Promise<ShlinkShortUrl>;
listTags(): Promise<ShlinkTags>; listTags(): Promise<ShlinkTags>;

View file

@ -1,9 +1,9 @@
import type { Order } from '@shlinkio/shlink-frontend-kit'; import type { Order } from '@shlinkio/shlink-frontend-kit';
import type { ShortUrl, ShortUrlMeta } from '../short-urls/data'; import type { ShlinkDeviceLongUrls, ShlinkShortUrl } from '../short-urls/data';
import type { Visit } from '../visits/types'; import type { Visit } from '../visits/types';
export interface ShlinkShortUrlsResponse { export interface ShlinkShortUrlsResponse {
data: ShortUrl[]; data: ShlinkShortUrl[];
pagination: ShlinkPaginator; pagination: ShlinkPaginator;
} }
@ -77,11 +77,18 @@ export interface ShlinkVisitsParams {
excludeBots?: boolean; excludeBots?: boolean;
} }
export interface ShlinkShortUrlData extends ShortUrlMeta { export interface ShlinkShortUrlData {
longUrl?: string; longUrl?: string;
title?: string; title?: string | null;
/** @deprecated */
validateUrl?: boolean; validateUrl?: boolean;
tags?: string[]; tags?: string[];
deviceLongUrls?: ShlinkDeviceLongUrls;
crawlable?: boolean;
forwardQuery?: boolean;
validSince?: string | null;
validUntil?: string | null;
maxVisits?: number | null;
} }
export interface ShlinkDomainRedirects { export interface ShlinkDomainRedirects {

View file

@ -17,7 +17,7 @@ import { DateTimeInput } from '../utils/dates/DateTimeInput';
import { formatIsoDate } from '../utils/dates/helpers/date'; import { formatIsoDate } from '../utils/dates/helpers/date';
import { useFeature } from '../utils/features'; import { useFeature } from '../utils/features';
import { handleEventPreventingDefault, hasValue } from '../utils/helpers'; import { handleEventPreventingDefault, hasValue } from '../utils/helpers';
import type { DeviceLongUrls, ShortUrlData } from './data'; import type { ShlinkDeviceLongUrls, ShortUrlData } from './data';
import { ShortUrlFormCheckboxGroup } from './helpers/ShortUrlFormCheckboxGroup'; import { ShortUrlFormCheckboxGroup } from './helpers/ShortUrlFormCheckboxGroup';
import { UseExistingIfFoundInfoIcon } from './UseExistingIfFoundInfoIcon'; import { UseExistingIfFoundInfoIcon } from './UseExistingIfFoundInfoIcon';
import './ShortUrlForm.scss'; import './ShortUrlForm.scss';
@ -87,7 +87,7 @@ export const ShortUrlForm = (
/> />
</FormGroup> </FormGroup>
); );
const renderDeviceLongUrlInput = (id: keyof DeviceLongUrls, placeholder: string, icon: IconProp) => ( const renderDeviceLongUrlInput = (id: keyof ShlinkDeviceLongUrls, placeholder: string, icon: IconProp) => (
<IconInput <IconInput
icon={icon} icon={icon}
id={id} id={id}

View file

@ -1,49 +1,36 @@
import type { Order } from '@shlinkio/shlink-frontend-kit'; import type { Order } from '@shlinkio/shlink-frontend-kit';
import type { ShlinkVisitsSummary } from '../../api-contract'; import type { ShlinkShortUrlData, ShlinkVisitsSummary } from '../../api-contract';
import type { Nullable, OptionalString } from '../../utils/helpers'; import type { Nullable, OptionalString } from '../../utils/helpers';
export interface DeviceLongUrls { export interface ShortUrlData extends Omit<ShlinkShortUrlData, 'deviceLongUrls'> {
android?: OptionalString;
ios?: OptionalString;
desktop?: OptionalString;
}
export interface EditShortUrlData {
longUrl?: string;
deviceLongUrls?: DeviceLongUrls;
tags?: string[];
title?: string | null;
validSince?: Date | string | null;
validUntil?: Date | string | null;
maxVisits?: number | null;
validateUrl?: boolean;
crawlable?: boolean;
forwardQuery?: boolean;
}
export interface ShortUrlData extends EditShortUrlData {
longUrl: string; longUrl: string;
customSlug?: string; customSlug?: string;
shortCodeLength?: number; shortCodeLength?: number;
domain?: string; domain?: string;
findIfExists?: boolean; findIfExists?: boolean;
deviceLongUrls?: {
android?: string;
ios?: string;
desktop?: string;
}
} }
export interface ShortUrlIdentifier { export interface ShlinkDeviceLongUrls {
shortCode: string; android?: OptionalString;
domain?: OptionalString; ios?: OptionalString;
desktop?: OptionalString;
} }
export interface ShortUrl { export interface ShlinkShortUrl {
shortCode: string; shortCode: string;
shortUrl: string; shortUrl: string;
longUrl: string; longUrl: string;
deviceLongUrls?: Required<DeviceLongUrls>, // Optional only before Shlink 3.5.0 deviceLongUrls?: Required<ShlinkDeviceLongUrls>, // Optional only before Shlink 3.5.0
dateCreated: string; dateCreated: string;
/** @deprecated */ /** @deprecated */
visitsCount: number; // Deprecated since Shlink 3.4.0 visitsCount: number; // Deprecated since Shlink 3.4.0
visitsSummary?: ShlinkVisitsSummary; // Optional only before Shlink 3.4.0 visitsSummary?: ShlinkVisitsSummary; // Optional only before Shlink 3.4.0
meta: Required<Nullable<ShortUrlMeta>>; meta: Required<Nullable<ShlinkShortUrlMeta>>;
tags: string[]; tags: string[];
domain: string | null; domain: string | null;
title?: string | null; title?: string | null;
@ -51,14 +38,19 @@ export interface ShortUrl {
forwardQuery?: boolean; forwardQuery?: boolean;
} }
export interface ShortUrlMeta { export interface ShlinkShortUrlMeta {
validSince?: string; validSince?: string;
validUntil?: string; validUntil?: string;
maxVisits?: number; maxVisits?: number;
} }
export interface ShortUrlIdentifier {
shortCode: string;
domain?: OptionalString;
}
export interface ShortUrlModalProps { export interface ShortUrlModalProps {
shortUrl: ShortUrl; shortUrl: ShlinkShortUrl;
isOpen: boolean; isOpen: boolean;
toggle: () => void; toggle: () => void;
} }

View file

@ -4,7 +4,7 @@ import { useCallback } from 'react';
import type { ShlinkApiClient } from '../../api-contract'; import type { ShlinkApiClient } from '../../api-contract';
import { ExportBtn } from '../../utils/components/ExportBtn'; import { ExportBtn } from '../../utils/components/ExportBtn';
import type { ReportExporter } from '../../utils/services/ReportExporter'; import type { ReportExporter } from '../../utils/services/ReportExporter';
import type { ShortUrl } from '../data'; import type { ShlinkShortUrl } from '../data';
import { useShortUrlsQuery } from './hooks'; import { useShortUrlsQuery } from './hooks';
export interface ExportShortUrlsBtnProps { export interface ExportShortUrlsBtnProps {
@ -21,7 +21,7 @@ export const ExportShortUrlsBtn = (
const [loading,, startLoading, stopLoading] = useToggle(); const [loading,, startLoading, stopLoading] = useToggle();
const exportAllUrls = useCallback(async () => { const exportAllUrls = useCallback(async () => {
const totalPages = amount / itemsPerPage; const totalPages = amount / itemsPerPage;
const loadAllUrls = async (page = 1): Promise<ShortUrl[]> => { const loadAllUrls = async (page = 1): Promise<ShlinkShortUrl[]> => {
const { data } = await apiClientFactory().listShortUrls( const { data } = await apiClientFactory().listShortUrls(
{ page: `${page}`, tags, searchTerm: search, startDate, endDate, orderBy, tagsMode, itemsPerPage }, { page: `${page}`, tags, searchTerm: search, startDate, endDate, orderBy, tagsMode, itemsPerPage },
); );

View file

@ -1,18 +1,18 @@
import type { FC } from 'react'; import type { FC } from 'react';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import { useRoutesPrefix } from '../../utils/routesPrefix'; import { useRoutesPrefix } from '../../utils/routesPrefix';
import type { ShortUrl } from '../data'; import type { ShlinkShortUrl } from '../data';
import { urlEncodeShortCode } from './index'; import { urlEncodeShortCode } from './index';
export type LinkSuffix = 'visits' | 'edit'; export type LinkSuffix = 'visits' | 'edit';
export interface ShortUrlDetailLinkProps { export interface ShortUrlDetailLinkProps {
shortUrl?: ShortUrl | null; shortUrl?: ShlinkShortUrl | null;
suffix: LinkSuffix; suffix: LinkSuffix;
asLink?: boolean; asLink?: boolean;
} }
const buildUrl = (routePrefix: string, { shortCode, domain }: ShortUrl, suffix: LinkSuffix) => { const buildUrl = (routePrefix: string, { shortCode, domain }: ShlinkShortUrl, suffix: LinkSuffix) => {
const query = domain ? `?domain=${domain}` : ''; const query = domain ? `?domain=${domain}` : '';
return `${routePrefix}/short-code/${urlEncodeShortCode(shortCode)}/${suffix}${query}`; return `${routePrefix}/short-code/${urlEncodeShortCode(shortCode)}/${suffix}${query}`;
}; };

View file

@ -6,10 +6,10 @@ import { isBefore } from 'date-fns';
import type { FC, ReactNode } from 'react'; import type { FC, ReactNode } from 'react';
import { UncontrolledTooltip } from 'reactstrap'; import { UncontrolledTooltip } from 'reactstrap';
import { formatHumanFriendly, now, parseISO } from '../../utils/dates/helpers/date'; import { formatHumanFriendly, now, parseISO } from '../../utils/dates/helpers/date';
import type { ShortUrl } from '../data'; import type { ShlinkShortUrl } from '../data';
interface ShortUrlStatusProps { interface ShortUrlStatusProps {
shortUrl: ShortUrl; shortUrl: ShlinkShortUrl;
} }
interface StatusResult { interface StatusResult {
@ -18,7 +18,7 @@ interface StatusResult {
description: ReactNode; description: ReactNode;
} }
const resolveShortUrlStatus = (shortUrl: ShortUrl): StatusResult => { const resolveShortUrlStatus = (shortUrl: ShlinkShortUrl): StatusResult => {
const { meta, visitsCount, visitsSummary } = shortUrl; const { meta, visitsCount, visitsSummary } = shortUrl;
const { maxVisits, validSince, validUntil } = meta; const { maxVisits, validSince, validUntil } = meta;
const totalVisits = visitsSummary?.total ?? visitsCount; const totalVisits = visitsSummary?.total ?? visitsCount;

View file

@ -5,12 +5,12 @@ import classNames from 'classnames';
import { UncontrolledTooltip } from 'reactstrap'; import { UncontrolledTooltip } from 'reactstrap';
import { formatHumanFriendly, parseISO } from '../../utils/dates/helpers/date'; import { formatHumanFriendly, parseISO } from '../../utils/dates/helpers/date';
import { prettify } from '../../utils/helpers/numbers'; import { prettify } from '../../utils/helpers/numbers';
import type { ShortUrl } from '../data'; import type { ShlinkShortUrl } from '../data';
import { ShortUrlDetailLink } from './ShortUrlDetailLink'; import { ShortUrlDetailLink } from './ShortUrlDetailLink';
import './ShortUrlVisitsCount.scss'; import './ShortUrlVisitsCount.scss';
interface ShortUrlVisitsCountProps { interface ShortUrlVisitsCountProps {
shortUrl?: ShortUrl | null; shortUrl?: ShlinkShortUrl | null;
visitsCount: number; visitsCount: number;
active?: boolean; active?: boolean;
asLink?: boolean; asLink?: boolean;

View file

@ -6,7 +6,7 @@ import { Time } from '../../utils/dates/Time';
import type { TimeoutToggle } from '../../utils/helpers/hooks'; import type { TimeoutToggle } from '../../utils/helpers/hooks';
import type { ColorGenerator } from '../../utils/services/ColorGenerator'; import type { ColorGenerator } from '../../utils/services/ColorGenerator';
import { useSetting } from '../../utils/settings'; import { useSetting } from '../../utils/settings';
import type { ShortUrl } from '../data'; import type { ShlinkShortUrl } from '../data';
import { useShortUrlsQuery } from './hooks'; import { useShortUrlsQuery } from './hooks';
import type { ShortUrlsRowMenuType } from './ShortUrlsRowMenu'; import type { ShortUrlsRowMenuType } from './ShortUrlsRowMenu';
import { ShortUrlStatus } from './ShortUrlStatus'; import { ShortUrlStatus } from './ShortUrlStatus';
@ -16,7 +16,7 @@ import './ShortUrlsRow.scss';
interface ShortUrlsRowProps { interface ShortUrlsRowProps {
onTagClick?: (tag: string) => void; onTagClick?: (tag: string) => void;
shortUrl: ShortUrl; shortUrl: ShlinkShortUrl;
} }
export type ShortUrlsRowType = FC<ShortUrlsRowProps>; export type ShortUrlsRowType = FC<ShortUrlsRowProps>;

View file

@ -8,11 +8,11 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { RowDropdownBtn, useToggle } from '@shlinkio/shlink-frontend-kit'; import { RowDropdownBtn, useToggle } from '@shlinkio/shlink-frontend-kit';
import type { FC } from 'react'; import type { FC } from 'react';
import { DropdownItem } from 'reactstrap'; import { DropdownItem } from 'reactstrap';
import type { ShortUrl, ShortUrlModalProps } from '../data'; import type { ShlinkShortUrl, ShortUrlModalProps } from '../data';
import { ShortUrlDetailLink } from './ShortUrlDetailLink'; import { ShortUrlDetailLink } from './ShortUrlDetailLink';
interface ShortUrlsRowMenuProps { interface ShortUrlsRowMenuProps {
shortUrl: ShortUrl; shortUrl: ShlinkShortUrl;
} }
type ShortUrlModal = FC<ShortUrlModalProps>; type ShortUrlModal = FC<ShortUrlModalProps>;

View file

@ -2,9 +2,9 @@ import { isNil } from 'ramda';
import type { OptionalString } from '../../utils/helpers'; import type { OptionalString } from '../../utils/helpers';
import type { ShortUrlCreationSettings } from '../../utils/settings'; import type { ShortUrlCreationSettings } from '../../utils/settings';
import { DEFAULT_DOMAIN } from '../../visits/reducers/domainVisits'; import { DEFAULT_DOMAIN } from '../../visits/reducers/domainVisits';
import type { ShortUrl, ShortUrlData } from '../data'; import type { ShlinkShortUrl, ShortUrlData } from '../data';
export const shortUrlMatches = (shortUrl: ShortUrl, shortCode: string, domain: OptionalString): boolean => { export const shortUrlMatches = (shortUrl: ShlinkShortUrl, shortCode: string, domain: OptionalString): boolean => {
if (isNil(domain)) { if (isNil(domain)) {
return shortUrl.shortCode === shortCode && !shortUrl.domain; return shortUrl.shortCode === shortCode && !shortUrl.domain;
} }
@ -12,7 +12,7 @@ export const shortUrlMatches = (shortUrl: ShortUrl, shortCode: string, domain: O
return shortUrl.shortCode === shortCode && shortUrl.domain === domain; return shortUrl.shortCode === shortCode && shortUrl.domain === domain;
}; };
export const domainMatches = (shortUrl: ShortUrl, domain: string): boolean => { export const domainMatches = (shortUrl: ShlinkShortUrl, domain: string): boolean => {
if (!shortUrl.domain && domain === DEFAULT_DOMAIN) { if (!shortUrl.domain && domain === DEFAULT_DOMAIN) {
return true; return true;
} }
@ -20,7 +20,7 @@ export const domainMatches = (shortUrl: ShortUrl, domain: string): boolean => {
return shortUrl.domain === domain; return shortUrl.domain === domain;
}; };
export const shortUrlDataFromShortUrl = (shortUrl?: ShortUrl, settings?: ShortUrlCreationSettings): ShortUrlData => { export const shortUrlDataFromShortUrl = (shortUrl?: ShlinkShortUrl, settings?: ShortUrlCreationSettings): ShortUrlData => {
const validateUrl = settings?.validateUrls ?? false; const validateUrl = settings?.validateUrls ?? false;
if (!shortUrl) { if (!shortUrl) {
@ -37,7 +37,11 @@ export const shortUrlDataFromShortUrl = (shortUrl?: ShortUrl, settings?: ShortUr
maxVisits: shortUrl.meta.maxVisits ?? undefined, maxVisits: shortUrl.meta.maxVisits ?? undefined,
crawlable: shortUrl.crawlable, crawlable: shortUrl.crawlable,
forwardQuery: shortUrl.forwardQuery, forwardQuery: shortUrl.forwardQuery,
deviceLongUrls: shortUrl.deviceLongUrls, deviceLongUrls: shortUrl.deviceLongUrls && {
android: shortUrl.deviceLongUrls.android ?? undefined,
ios: shortUrl.deviceLongUrls.ios ?? undefined,
desktop: shortUrl.deviceLongUrls.desktop ?? undefined,
},
validateUrl, validateUrl,
}; };
}; };

View file

@ -3,7 +3,7 @@ import { createSlice } from '@reduxjs/toolkit';
import type { ProblemDetailsError, ShlinkApiClient } from '../../api-contract'; import type { ProblemDetailsError, ShlinkApiClient } from '../../api-contract';
import { parseApiError } from '../../api-contract/utils'; import { parseApiError } from '../../api-contract/utils';
import { createAsyncThunk } from '../../utils/redux'; import { createAsyncThunk } from '../../utils/redux';
import type { ShortUrl, ShortUrlData } from '../data'; import type { ShlinkShortUrl, ShortUrlData } from '../data';
const REDUCER_PREFIX = 'shlink/shortUrlCreation'; const REDUCER_PREFIX = 'shlink/shortUrlCreation';
@ -21,13 +21,13 @@ export type ShortUrlCreation = {
error: true; error: true;
errorData?: ProblemDetailsError; errorData?: ProblemDetailsError;
} | { } | {
result: ShortUrl; result: ShlinkShortUrl;
saving: false; saving: false;
saved: true; saved: true;
error: false; error: false;
}; };
export type CreateShortUrlAction = PayloadAction<ShortUrl>; export type CreateShortUrlAction = PayloadAction<ShlinkShortUrl>;
const initialState: ShortUrlCreation = { const initialState: ShortUrlCreation = {
saving: false, saving: false,
@ -37,7 +37,7 @@ const initialState: ShortUrlCreation = {
export const createShortUrl = (apiClientFactory: () => ShlinkApiClient) => createAsyncThunk( export const createShortUrl = (apiClientFactory: () => ShlinkApiClient) => createAsyncThunk(
`${REDUCER_PREFIX}/createShortUrl`, `${REDUCER_PREFIX}/createShortUrl`,
(data: ShortUrlData): Promise<ShortUrl> => apiClientFactory().createShortUrl(data), (data: ShortUrlData): Promise<ShlinkShortUrl> => apiClientFactory().createShortUrl(data),
); );
export const shortUrlCreationReducerCreator = (createShortUrlThunk: ReturnType<typeof createShortUrl>) => { export const shortUrlCreationReducerCreator = (createShortUrlThunk: ReturnType<typeof createShortUrl>) => {

View file

@ -2,7 +2,7 @@ import { createAction, createSlice } from '@reduxjs/toolkit';
import type { ProblemDetailsError, ShlinkApiClient } from '../../api-contract'; import type { ProblemDetailsError, ShlinkApiClient } from '../../api-contract';
import { parseApiError } from '../../api-contract/utils'; import { parseApiError } from '../../api-contract/utils';
import { createAsyncThunk } from '../../utils/redux'; import { createAsyncThunk } from '../../utils/redux';
import type { ShortUrl, ShortUrlIdentifier } from '../data'; import type { ShlinkShortUrl, ShortUrlIdentifier } from '../data';
const REDUCER_PREFIX = 'shlink/shortUrlDeletion'; const REDUCER_PREFIX = 'shlink/shortUrlDeletion';
@ -29,7 +29,7 @@ export const deleteShortUrl = (apiClientFactory: () => ShlinkApiClient) => creat
}, },
); );
export const shortUrlDeleted = createAction<ShortUrl>(`${REDUCER_PREFIX}/shortUrlDeleted`); export const shortUrlDeleted = createAction<ShlinkShortUrl>(`${REDUCER_PREFIX}/shortUrlDeleted`);
export const shortUrlDeletionReducerCreator = (deleteShortUrlThunk: ReturnType<typeof deleteShortUrl>) => { export const shortUrlDeletionReducerCreator = (deleteShortUrlThunk: ReturnType<typeof deleteShortUrl>) => {
const { actions, reducer } = createSlice({ const { actions, reducer } = createSlice({

View file

@ -3,19 +3,19 @@ import { createSlice } from '@reduxjs/toolkit';
import type { ProblemDetailsError, ShlinkApiClient } from '../../api-contract'; import type { ProblemDetailsError, ShlinkApiClient } from '../../api-contract';
import { parseApiError } from '../../api-contract/utils'; import { parseApiError } from '../../api-contract/utils';
import { createAsyncThunk } from '../../utils/redux'; import { createAsyncThunk } from '../../utils/redux';
import type { ShortUrl, ShortUrlIdentifier } from '../data'; import type { ShlinkShortUrl, ShortUrlIdentifier } from '../data';
import { shortUrlMatches } from '../helpers'; import { shortUrlMatches } from '../helpers';
const REDUCER_PREFIX = 'shlink/shortUrlDetail'; const REDUCER_PREFIX = 'shlink/shortUrlDetail';
export interface ShortUrlDetail { export interface ShortUrlDetail {
shortUrl?: ShortUrl; shortUrl?: ShlinkShortUrl;
loading: boolean; loading: boolean;
error: boolean; error: boolean;
errorData?: ProblemDetailsError; errorData?: ProblemDetailsError;
} }
export type ShortUrlDetailAction = PayloadAction<ShortUrl>; export type ShortUrlDetailAction = PayloadAction<ShlinkShortUrl>;
const initialState: ShortUrlDetail = { const initialState: ShortUrlDetail = {
loading: false, loading: false,
@ -25,7 +25,7 @@ const initialState: ShortUrlDetail = {
export const shortUrlDetailReducerCreator = (apiClientFactory: () => ShlinkApiClient) => { export const shortUrlDetailReducerCreator = (apiClientFactory: () => ShlinkApiClient) => {
const getShortUrlDetail = createAsyncThunk( const getShortUrlDetail = createAsyncThunk(
`${REDUCER_PREFIX}/getShortUrlDetail`, `${REDUCER_PREFIX}/getShortUrlDetail`,
async ({ shortCode, domain }: ShortUrlIdentifier, { getState }): Promise<ShortUrl> => { async ({ shortCode, domain }: ShortUrlIdentifier, { getState }): Promise<ShlinkShortUrl> => {
const { shortUrlsList } = getState(); const { shortUrlsList } = getState();
const alreadyLoaded = shortUrlsList?.shortUrls?.data.find((url) => shortUrlMatches(url, shortCode, domain)); const alreadyLoaded = shortUrlsList?.shortUrls?.data.find((url) => shortUrlMatches(url, shortCode, domain));

View file

@ -1,14 +1,13 @@
import type { PayloadAction } from '@reduxjs/toolkit';
import { createSlice } from '@reduxjs/toolkit'; import { createSlice } from '@reduxjs/toolkit';
import type { ProblemDetailsError, ShlinkApiClient } from '../../api-contract'; import type { ProblemDetailsError, ShlinkApiClient, ShlinkShortUrlData } from '../../api-contract';
import { parseApiError } from '../../api-contract/utils'; import { parseApiError } from '../../api-contract/utils';
import { createAsyncThunk } from '../../utils/redux'; import { createAsyncThunk } from '../../utils/redux';
import type { EditShortUrlData, ShortUrl, ShortUrlIdentifier } from '../data'; import type { ShlinkShortUrl, ShortUrlIdentifier } from '../data';
const REDUCER_PREFIX = 'shlink/shortUrlEdition'; const REDUCER_PREFIX = 'shlink/shortUrlEdition';
export interface ShortUrlEdition { export interface ShortUrlEdition {
shortUrl?: ShortUrl; shortUrl?: ShlinkShortUrl;
saving: boolean; saving: boolean;
saved: boolean; saved: boolean;
error: boolean; error: boolean;
@ -16,11 +15,9 @@ export interface ShortUrlEdition {
} }
export interface EditShortUrl extends ShortUrlIdentifier { export interface EditShortUrl extends ShortUrlIdentifier {
data: EditShortUrlData; data: ShlinkShortUrlData;
} }
export type ShortUrlEditedAction = PayloadAction<ShortUrl>;
const initialState: ShortUrlEdition = { const initialState: ShortUrlEdition = {
saving: false, saving: false,
saved: false, saved: false,
@ -29,7 +26,7 @@ const initialState: ShortUrlEdition = {
export const editShortUrl = (apiClientFactory: () => ShlinkApiClient) => createAsyncThunk( export const editShortUrl = (apiClientFactory: () => ShlinkApiClient) => createAsyncThunk(
`${REDUCER_PREFIX}/editShortUrl`, `${REDUCER_PREFIX}/editShortUrl`,
({ shortCode, domain, data }: EditShortUrl): Promise<ShortUrl> => ({ shortCode, domain, data }: EditShortUrl): Promise<ShlinkShortUrl> =>
apiClientFactory().updateShortUrl(shortCode, domain, data as any) // TODO parse dates apiClientFactory().updateShortUrl(shortCode, domain, data as any) // TODO parse dates
, ,
); );

View file

@ -3,7 +3,7 @@ import { assocPath, last, pipe, reject } from 'ramda';
import type { ShlinkApiClient, ShlinkShortUrlsListParams, ShlinkShortUrlsResponse } from '../../api-contract'; import type { ShlinkApiClient, ShlinkShortUrlsListParams, ShlinkShortUrlsResponse } from '../../api-contract';
import { createAsyncThunk } from '../../utils/redux'; import { createAsyncThunk } from '../../utils/redux';
import { createNewVisits } from '../../visits/reducers/visitCreation'; import { createNewVisits } from '../../visits/reducers/visitCreation';
import type { ShortUrl } from '../data'; import type { ShlinkShortUrl } from '../data';
import { shortUrlMatches } from '../helpers'; import { shortUrlMatches } from '../helpers';
import type { createShortUrl } from './shortUrlCreation'; import type { createShortUrl } from './shortUrlCreation';
import { shortUrlDeleted } from './shortUrlDeletion'; import { shortUrlDeleted } from './shortUrlDeletion';
@ -82,7 +82,7 @@ export const shortUrlsListReducerCreator = (
pipe( pipe(
(state, { payload }) => (!state.shortUrls ? state : assocPath( (state, { payload }) => (!state.shortUrls ? state : assocPath(
['shortUrls', 'data'], ['shortUrls', 'data'],
reject<ShortUrl, ShortUrl[]>((shortUrl) => reject<ShlinkShortUrl, ShlinkShortUrl[]>((shortUrl) =>
shortUrlMatches(shortUrl, payload.shortCode, payload.domain), state.shortUrls.data), shortUrlMatches(shortUrl, payload.shortCode, payload.domain), state.shortUrls.data),
state, state,
)), )),

View file

@ -2,7 +2,7 @@ import { faArrowLeft } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import type { FC, PropsWithChildren, ReactNode } from 'react'; import type { FC, PropsWithChildren, ReactNode } from 'react';
import { Button, Card } from 'reactstrap'; import { Button, Card } from 'reactstrap';
import type { ShortUrl } from '../short-urls/data'; import type { ShlinkShortUrl } from '../short-urls/data';
import { ShortUrlVisitsCount } from '../short-urls/helpers/ShortUrlVisitsCount'; import { ShortUrlVisitsCount } from '../short-urls/helpers/ShortUrlVisitsCount';
import type { Visit } from './types'; import type { Visit } from './types';
@ -10,7 +10,7 @@ type VisitsHeaderProps = PropsWithChildren<{
visits: Visit[]; visits: Visit[];
goBack: () => void; goBack: () => void;
title: ReactNode; title: ReactNode;
shortUrl?: ShortUrl; shortUrl?: ShlinkShortUrl;
}>; }>;
export const VisitsHeader: FC<VisitsHeaderProps> = ({ visits, goBack, shortUrl, children, title }) => ( export const VisitsHeader: FC<VisitsHeaderProps> = ({ visits, goBack, shortUrl, children, title }) => (

View file

@ -1,4 +1,4 @@
import type { ShortUrl } from '../../short-urls/data'; import type { ShlinkShortUrl } from '../../short-urls/data';
import type { DateRange } from '../../utils/dates/helpers/dateIntervals'; import type { DateRange } from '../../utils/dates/helpers/dateIntervals';
export type OrphanVisitType = 'base_url' | 'invalid_short_url' | 'regular_404'; export type OrphanVisitType = 'base_url' | 'invalid_short_url' | 'regular_404';
@ -52,7 +52,7 @@ export interface NormalizedOrphanVisit extends NormalizedRegularVisit {
export type NormalizedVisit = NormalizedRegularVisit | NormalizedOrphanVisit; export type NormalizedVisit = NormalizedRegularVisit | NormalizedOrphanVisit;
export interface CreateVisit { export interface CreateVisit {
shortUrl?: ShortUrl; shortUrl?: ShlinkShortUrl;
visit: Visit; visit: Visit;
} }

View file

@ -2,14 +2,14 @@ import { screen, waitFor } from '@testing-library/react';
import { fromPartial } from '@total-typescript/shoehorn'; import { fromPartial } from '@total-typescript/shoehorn';
import type { InvalidShortUrlDeletion } from '../../../src/api-contract'; import type { InvalidShortUrlDeletion } from '../../../src/api-contract';
import { ErrorTypeV2, ErrorTypeV3 } from '../../../src/api-contract'; import { ErrorTypeV2, ErrorTypeV3 } from '../../../src/api-contract';
import type { ShortUrl } from '../../../src/short-urls/data'; import type { ShlinkShortUrl } from '../../../src/short-urls/data';
import { DeleteShortUrlModal } from '../../../src/short-urls/helpers/DeleteShortUrlModal'; import { DeleteShortUrlModal } from '../../../src/short-urls/helpers/DeleteShortUrlModal';
import type { ShortUrlDeletion } from '../../../src/short-urls/reducers/shortUrlDeletion'; import type { ShortUrlDeletion } from '../../../src/short-urls/reducers/shortUrlDeletion';
import { renderWithEvents } from '../../__helpers__/setUpTest'; import { renderWithEvents } from '../../__helpers__/setUpTest';
import { TestModalWrapper } from '../../__helpers__/TestModalWrapper'; import { TestModalWrapper } from '../../__helpers__/TestModalWrapper';
describe('<DeleteShortUrlModal />', () => { describe('<DeleteShortUrlModal />', () => {
const shortUrl = fromPartial<ShortUrl>({ const shortUrl = fromPartial<ShlinkShortUrl>({
tags: [], tags: [],
shortCode: 'abc123', shortCode: 'abc123',
longUrl: 'https://long-domain.com/foo/bar', longUrl: 'https://long-domain.com/foo/bar',

View file

@ -1,7 +1,7 @@
import { screen } from '@testing-library/react'; import { screen } from '@testing-library/react';
import { fromPartial } from '@total-typescript/shoehorn'; import { fromPartial } from '@total-typescript/shoehorn';
import { MemoryRouter } from 'react-router-dom'; import { MemoryRouter } from 'react-router-dom';
import type { ShortUrl } from '../../../src/short-urls/data'; import type { ShlinkShortUrl } from '../../../src/short-urls/data';
import { ExportShortUrlsBtn as createExportShortUrlsBtn } from '../../../src/short-urls/helpers/ExportShortUrlsBtn'; import { ExportShortUrlsBtn as createExportShortUrlsBtn } from '../../../src/short-urls/helpers/ExportShortUrlsBtn';
import type { ReportExporter } from '../../../src/utils/services/ReportExporter'; import type { ReportExporter } from '../../../src/utils/services/ReportExporter';
import { renderWithEvents } from '../../__helpers__/setUpTest'; import { renderWithEvents } from '../../__helpers__/setUpTest';
@ -46,7 +46,7 @@ describe('<ExportShortUrlsBtn />', () => {
it('maps short URLs for exporting', async () => { it('maps short URLs for exporting', async () => {
listShortUrls.mockResolvedValue({ listShortUrls.mockResolvedValue({
data: [fromPartial<ShortUrl>({ data: [fromPartial<ShlinkShortUrl>({
shortUrl: 'https://s.test/short-code', shortUrl: 'https://s.test/short-code',
tags: [], tags: [],
})], })],

View file

@ -1,7 +1,7 @@
import { render, screen } from '@testing-library/react'; import { render, screen } from '@testing-library/react';
import { fromPartial } from '@total-typescript/shoehorn'; import { fromPartial } from '@total-typescript/shoehorn';
import { MemoryRouter } from 'react-router-dom'; import { MemoryRouter } from 'react-router-dom';
import type { ShortUrl } from '../../../src/short-urls/data'; import type { ShlinkShortUrl } from '../../../src/short-urls/data';
import type { LinkSuffix } from '../../../src/short-urls/helpers/ShortUrlDetailLink'; import type { LinkSuffix } from '../../../src/short-urls/helpers/ShortUrlDetailLink';
import { ShortUrlDetailLink } from '../../../src/short-urls/helpers/ShortUrlDetailLink'; import { ShortUrlDetailLink } from '../../../src/short-urls/helpers/ShortUrlDetailLink';
import { RoutesPrefixProvider } from '../../../src/utils/routesPrefix'; import { RoutesPrefixProvider } from '../../../src/utils/routesPrefix';
@ -12,8 +12,8 @@ describe('<ShortUrlDetailLink />', () => {
[false, null], [false, null],
[true, null], [true, null],
[true, undefined], [true, undefined],
[false, fromPartial<ShortUrl>({})], [false, fromPartial<ShlinkShortUrl>({})],
[false, fromPartial<ShortUrl>({})], [false, fromPartial<ShlinkShortUrl>({})],
])('only renders a plain span when either server or short URL are not set', (asLink, shortUrl) => { ])('only renders a plain span when either server or short URL are not set', (asLink, shortUrl) => {
render( render(
<ShortUrlDetailLink shortUrl={shortUrl} asLink={asLink} suffix="visits"> <ShortUrlDetailLink shortUrl={shortUrl} asLink={asLink} suffix="visits">
@ -28,25 +28,25 @@ describe('<ShortUrlDetailLink />', () => {
it.each([ it.each([
[ [
'/server/1', '/server/1',
fromPartial<ShortUrl>({ shortCode: 'abc123' }), fromPartial<ShlinkShortUrl>({ shortCode: 'abc123' }),
'visits' as LinkSuffix, 'visits' as LinkSuffix,
'/server/1/short-code/abc123/visits', '/server/1/short-code/abc123/visits',
], ],
[ [
'/foobar', '/foobar',
fromPartial<ShortUrl>({ shortCode: 'def456', domain: 'example.com' }), fromPartial<ShlinkShortUrl>({ shortCode: 'def456', domain: 'example.com' }),
'visits' as LinkSuffix, 'visits' as LinkSuffix,
'/foobar/short-code/def456/visits?domain=example.com', '/foobar/short-code/def456/visits?domain=example.com',
], ],
[ [
'/server/1', '/server/1',
fromPartial<ShortUrl>({ shortCode: 'abc123' }), fromPartial<ShlinkShortUrl>({ shortCode: 'abc123' }),
'edit' as LinkSuffix, 'edit' as LinkSuffix,
'/server/1/short-code/abc123/edit', '/server/1/short-code/abc123/edit',
], ],
[ [
'/server/3', '/server/3',
fromPartial<ShortUrl>({ shortCode: 'def456', domain: 'example.com' }), fromPartial<ShlinkShortUrl>({ shortCode: 'def456', domain: 'example.com' }),
'edit' as LinkSuffix, 'edit' as LinkSuffix,
'/server/3/short-code/def456/edit?domain=example.com', '/server/3/short-code/def456/edit?domain=example.com',
], ],

View file

@ -2,41 +2,41 @@ import { render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event'; import userEvent from '@testing-library/user-event';
import { fromPartial } from '@total-typescript/shoehorn'; import { fromPartial } from '@total-typescript/shoehorn';
import type { ShlinkVisitsSummary } from '../../../src/api-contract'; import type { ShlinkVisitsSummary } from '../../../src/api-contract';
import type { ShortUrl, ShortUrlMeta } from '../../../src/short-urls/data'; import type { ShlinkShortUrlMeta, ShlinkShortUrl } from '../../../src/short-urls/data';
import { ShortUrlStatus } from '../../../src/short-urls/helpers/ShortUrlStatus'; import { ShortUrlStatus } from '../../../src/short-urls/helpers/ShortUrlStatus';
describe('<ShortUrlStatus />', () => { describe('<ShortUrlStatus />', () => {
const setUp = (shortUrl: ShortUrl) => ({ const setUp = (shortUrl: ShlinkShortUrl) => ({
user: userEvent.setup(), user: userEvent.setup(),
...render(<ShortUrlStatus shortUrl={shortUrl} />), ...render(<ShortUrlStatus shortUrl={shortUrl} />),
}); });
it.each([ it.each([
[ [
fromPartial<ShortUrlMeta>({ validSince: '2099-01-01T10:30:15' }), fromPartial<ShlinkShortUrlMeta>({ validSince: '2099-01-01T10:30:15' }),
{}, {},
'This short URL will start working on 2099-01-01 10:30.', 'This short URL will start working on 2099-01-01 10:30.',
], ],
[ [
fromPartial<ShortUrlMeta>({ validUntil: '2020-01-01T10:30:15' }), fromPartial<ShlinkShortUrlMeta>({ validUntil: '2020-01-01T10:30:15' }),
{}, {},
'This short URL cannot be visited since 2020-01-01 10:30.', 'This short URL cannot be visited since 2020-01-01 10:30.',
], ],
[ [
fromPartial<ShortUrlMeta>({ maxVisits: 10 }), fromPartial<ShlinkShortUrlMeta>({ maxVisits: 10 }),
fromPartial<ShlinkVisitsSummary>({ total: 10 }), fromPartial<ShlinkVisitsSummary>({ total: 10 }),
'This short URL cannot be currently visited because it has reached the maximum amount of 10 visits.', 'This short URL cannot be currently visited because it has reached the maximum amount of 10 visits.',
], ],
[ [
fromPartial<ShortUrlMeta>({ maxVisits: 1 }), fromPartial<ShlinkShortUrlMeta>({ maxVisits: 1 }),
fromPartial<ShlinkVisitsSummary>({ total: 1 }), fromPartial<ShlinkVisitsSummary>({ total: 1 }),
'This short URL cannot be currently visited because it has reached the maximum amount of 1 visit.', 'This short URL cannot be currently visited because it has reached the maximum amount of 1 visit.',
], ],
[{}, {}, 'This short URL can be visited normally.'], [{}, {}, 'This short URL can be visited normally.'],
[fromPartial<ShortUrlMeta>({ validUntil: '2099-01-01T10:30:15' }), {}, 'This short URL can be visited normally.'], [fromPartial<ShlinkShortUrlMeta>({ validUntil: '2099-01-01T10:30:15' }), {}, 'This short URL can be visited normally.'],
[fromPartial<ShortUrlMeta>({ validSince: '2020-01-01T10:30:15' }), {}, 'This short URL can be visited normally.'], [fromPartial<ShlinkShortUrlMeta>({ validSince: '2020-01-01T10:30:15' }), {}, 'This short URL can be visited normally.'],
[ [
fromPartial<ShortUrlMeta>({ maxVisits: 10 }), fromPartial<ShlinkShortUrlMeta>({ maxVisits: 10 }),
fromPartial<ShlinkVisitsSummary>({ total: 1 }), fromPartial<ShlinkVisitsSummary>({ total: 1 }),
'This short URL can be visited normally.', 'This short URL can be visited normally.',
], ],

View file

@ -1,11 +1,11 @@
import { render, screen, waitFor } from '@testing-library/react'; import { render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event'; import userEvent from '@testing-library/user-event';
import { fromPartial } from '@total-typescript/shoehorn'; import { fromPartial } from '@total-typescript/shoehorn';
import type { ShortUrl } from '../../../src/short-urls/data'; import type { ShlinkShortUrl } from '../../../src/short-urls/data';
import { ShortUrlVisitsCount } from '../../../src/short-urls/helpers/ShortUrlVisitsCount'; import { ShortUrlVisitsCount } from '../../../src/short-urls/helpers/ShortUrlVisitsCount';
describe('<ShortUrlVisitsCount />', () => { describe('<ShortUrlVisitsCount />', () => {
const setUp = (visitsCount: number, shortUrl: ShortUrl) => ({ const setUp = (visitsCount: number, shortUrl: ShlinkShortUrl) => ({
user: userEvent.setup(), user: userEvent.setup(),
...render( ...render(
<ShortUrlVisitsCount visitsCount={visitsCount} shortUrl={shortUrl} />, <ShortUrlVisitsCount visitsCount={visitsCount} shortUrl={shortUrl} />,

View file

@ -4,7 +4,7 @@ import { addDays, formatISO, subDays } from 'date-fns';
import { last } from 'ramda'; import { last } from 'ramda';
import { MemoryRouter, useLocation } from 'react-router-dom'; import { MemoryRouter, useLocation } from 'react-router-dom';
import type { Settings } from '../../../src'; import type { Settings } from '../../../src';
import type { ShortUrl, ShortUrlMeta } from '../../../src/short-urls/data'; import type { ShlinkShortUrlMeta, ShlinkShortUrl } from '../../../src/short-urls/data';
import { ShortUrlsRow as createShortUrlsRow } from '../../../src/short-urls/helpers/ShortUrlsRow'; import { ShortUrlsRow as createShortUrlsRow } from '../../../src/short-urls/helpers/ShortUrlsRow';
import { now, parseDate } from '../../../src/utils/dates/helpers/date'; import { now, parseDate } from '../../../src/utils/dates/helpers/date';
import type { TimeoutToggle } from '../../../src/utils/helpers/hooks'; import type { TimeoutToggle } from '../../../src/utils/helpers/hooks';
@ -15,7 +15,7 @@ import { colorGeneratorMock } from '../../utils/services/__mocks__/ColorGenerato
interface SetUpOptions { interface SetUpOptions {
title?: string | null; title?: string | null;
tags?: string[]; tags?: string[];
meta?: ShortUrlMeta; meta?: ShlinkShortUrlMeta;
settings?: Partial<Settings>; settings?: Partial<Settings>;
} }
@ -27,7 +27,7 @@ vi.mock('react-router-dom', async () => ({
describe('<ShortUrlsRow />', () => { describe('<ShortUrlsRow />', () => {
const timeoutToggle = vi.fn(() => true); const timeoutToggle = vi.fn(() => true);
const useTimeoutToggle = vi.fn(() => [false, timeoutToggle]) as TimeoutToggle; const useTimeoutToggle = vi.fn(() => [false, timeoutToggle]) as TimeoutToggle;
const shortUrl: ShortUrl = { const shortUrl: ShlinkShortUrl = {
shortCode: 'abc123', shortCode: 'abc123',
shortUrl: 'https://s.test/abc123', shortUrl: 'https://s.test/abc123',
longUrl: 'https://foo.com/bar', longUrl: 'https://foo.com/bar',

View file

@ -1,13 +1,13 @@
import { screen } from '@testing-library/react'; import { screen } from '@testing-library/react';
import { fromPartial } from '@total-typescript/shoehorn'; import { fromPartial } from '@total-typescript/shoehorn';
import { MemoryRouter } from 'react-router-dom'; import { MemoryRouter } from 'react-router-dom';
import type { ShortUrl } from '../../../src/short-urls/data'; import type { ShlinkShortUrl } from '../../../src/short-urls/data';
import { ShortUrlsRowMenu as createShortUrlsRowMenu } from '../../../src/short-urls/helpers/ShortUrlsRowMenu'; import { ShortUrlsRowMenu as createShortUrlsRowMenu } from '../../../src/short-urls/helpers/ShortUrlsRowMenu';
import { renderWithEvents } from '../../__helpers__/setUpTest'; import { renderWithEvents } from '../../__helpers__/setUpTest';
describe('<ShortUrlsRowMenu />', () => { describe('<ShortUrlsRowMenu />', () => {
const ShortUrlsRowMenu = createShortUrlsRowMenu(() => <i>DeleteShortUrlModal</i>, () => <i>QrCodeModal</i>); const ShortUrlsRowMenu = createShortUrlsRowMenu(() => <i>DeleteShortUrlModal</i>, () => <i>QrCodeModal</i>);
const shortUrl = fromPartial<ShortUrl>({ const shortUrl = fromPartial<ShlinkShortUrl>({
shortCode: 'abc123', shortCode: 'abc123',
shortUrl: 'https://s.test/abc123', shortUrl: 'https://s.test/abc123',
}); });

View file

@ -1,5 +1,5 @@
import { fromPartial } from '@total-typescript/shoehorn'; import { fromPartial } from '@total-typescript/shoehorn';
import type { ShortUrl } from '../../../src/short-urls/data'; import type { ShlinkShortUrl } from '../../../src/short-urls/data';
import { shortUrlDataFromShortUrl, urlDecodeShortCode, urlEncodeShortCode } from '../../../src/short-urls/helpers'; import { shortUrlDataFromShortUrl, urlDecodeShortCode, urlEncodeShortCode } from '../../../src/short-urls/helpers';
describe('helpers', () => { describe('helpers', () => {
@ -8,7 +8,7 @@ describe('helpers', () => {
[undefined, { validateUrls: true }, { longUrl: '', validateUrl: true }], [undefined, { validateUrls: true }, { longUrl: '', validateUrl: true }],
[undefined, undefined, { longUrl: '', validateUrl: false }], [undefined, undefined, { longUrl: '', validateUrl: false }],
[ [
fromPartial<ShortUrl>({ meta: {} }), fromPartial<ShlinkShortUrl>({ meta: {} }),
{ validateUrls: false }, { validateUrls: false },
{ {
longUrl: undefined, longUrl: undefined,

View file

@ -1,13 +1,13 @@
import { fromPartial } from '@total-typescript/shoehorn'; import { fromPartial } from '@total-typescript/shoehorn';
import type { ShlinkApiClient } from '../../../src/api-contract'; import type { ShlinkApiClient } from '../../../src/api-contract';
import type { ShortUrl } from '../../../src/short-urls/data'; import type { ShlinkShortUrl } from '../../../src/short-urls/data';
import { import {
createShortUrl as createShortUrlCreator, createShortUrl as createShortUrlCreator,
shortUrlCreationReducerCreator, shortUrlCreationReducerCreator,
} from '../../../src/short-urls/reducers/shortUrlCreation'; } from '../../../src/short-urls/reducers/shortUrlCreation';
describe('shortUrlCreationReducer', () => { describe('shortUrlCreationReducer', () => {
const shortUrl = fromPartial<ShortUrl>({}); const shortUrl = fromPartial<ShlinkShortUrl>({});
const createShortUrlCall = vi.fn(); const createShortUrlCall = vi.fn();
const buildShlinkApiClient = () => fromPartial<ShlinkApiClient>({ createShortUrl: createShortUrlCall }); const buildShlinkApiClient = () => fromPartial<ShlinkApiClient>({ createShortUrl: createShortUrlCall });
const createShortUrl = createShortUrlCreator(buildShlinkApiClient); const createShortUrl = createShortUrlCreator(buildShlinkApiClient);

View file

@ -1,7 +1,7 @@
import { fromPartial } from '@total-typescript/shoehorn'; import { fromPartial } from '@total-typescript/shoehorn';
import type { ShlinkApiClient } from '../../../src/api-contract'; import type { ShlinkApiClient } from '../../../src/api-contract';
import type { RootState } from '../../../src/container/store'; import type { RootState } from '../../../src/container/store';
import type { ShortUrl } from '../../../src/short-urls/data'; import type { ShlinkShortUrl } from '../../../src/short-urls/data';
import { shortUrlDetailReducerCreator } from '../../../src/short-urls/reducers/shortUrlDetail'; import { shortUrlDetailReducerCreator } from '../../../src/short-urls/reducers/shortUrlDetail';
import type { ShortUrlsList } from '../../../src/short-urls/reducers/shortUrlsList'; import type { ShortUrlsList } from '../../../src/short-urls/reducers/shortUrlsList';
@ -25,7 +25,7 @@ describe('shortUrlDetailReducer', () => {
}); });
it('return short URL on GET_SHORT_URL_DETAIL', () => { it('return short URL on GET_SHORT_URL_DETAIL', () => {
const actionShortUrl = fromPartial<ShortUrl>({ longUrl: 'foo', shortCode: 'bar' }); const actionShortUrl = fromPartial<ShlinkShortUrl>({ longUrl: 'foo', shortCode: 'bar' });
const state = reducer( const state = reducer(
{ loading: true, error: false }, { loading: true, error: false },
getShortUrlDetail.fulfilled(actionShortUrl, '', { shortCode: '' }), getShortUrlDetail.fulfilled(actionShortUrl, '', { shortCode: '' }),
@ -58,7 +58,7 @@ describe('shortUrlDetailReducer', () => {
}), }),
], ],
])('performs API call when short URL is not found in local state', async (shortUrlsList?: ShortUrlsList) => { ])('performs API call when short URL is not found in local state', async (shortUrlsList?: ShortUrlsList) => {
const resolvedShortUrl = fromPartial<ShortUrl>({ longUrl: 'foo', shortCode: 'abc123' }); const resolvedShortUrl = fromPartial<ShlinkShortUrl>({ longUrl: 'foo', shortCode: 'abc123' });
getShortUrlCall.mockResolvedValue(resolvedShortUrl); getShortUrlCall.mockResolvedValue(resolvedShortUrl);
await getShortUrlDetail({ shortCode: 'abc123', domain: '' })(dispatchMock, buildGetState(shortUrlsList), {}); await getShortUrlDetail({ shortCode: 'abc123', domain: '' })(dispatchMock, buildGetState(shortUrlsList), {});
@ -69,8 +69,8 @@ describe('shortUrlDetailReducer', () => {
}); });
it('avoids API calls when short URL is found in local state', async () => { it('avoids API calls when short URL is found in local state', async () => {
const foundShortUrl = fromPartial<ShortUrl>({ longUrl: 'foo', shortCode: 'abc123' }); const foundShortUrl = fromPartial<ShlinkShortUrl>({ longUrl: 'foo', shortCode: 'abc123' });
getShortUrlCall.mockResolvedValue(fromPartial<ShortUrl>({})); getShortUrlCall.mockResolvedValue(fromPartial<ShlinkShortUrl>({}));
await getShortUrlDetail(foundShortUrl)( await getShortUrlDetail(foundShortUrl)(
dispatchMock, dispatchMock,

View file

@ -1,5 +1,5 @@
import { fromPartial } from '@total-typescript/shoehorn'; import { fromPartial } from '@total-typescript/shoehorn';
import type { ShortUrl } from '../../../src/short-urls/data'; import type { ShlinkShortUrl } from '../../../src/short-urls/data';
import { import {
editShortUrl as editShortUrlCreator, editShortUrl as editShortUrlCreator,
shortUrlEditionReducerCreator, shortUrlEditionReducerCreator,
@ -8,7 +8,7 @@ import {
describe('shortUrlEditionReducer', () => { describe('shortUrlEditionReducer', () => {
const longUrl = 'https://shlink.io'; const longUrl = 'https://shlink.io';
const shortCode = 'abc123'; const shortCode = 'abc123';
const shortUrl = fromPartial<ShortUrl>({ longUrl, shortCode }); const shortUrl = fromPartial<ShlinkShortUrl>({ longUrl, shortCode });
const updateShortUrl = vi.fn().mockResolvedValue(shortUrl); const updateShortUrl = vi.fn().mockResolvedValue(shortUrl);
const buildShlinkApiClient = vi.fn().mockReturnValue({ updateShortUrl }); const buildShlinkApiClient = vi.fn().mockReturnValue({ updateShortUrl });
const editShortUrl = editShortUrlCreator(buildShlinkApiClient); const editShortUrl = editShortUrlCreator(buildShlinkApiClient);

View file

@ -1,6 +1,6 @@
import { fromPartial } from '@total-typescript/shoehorn'; import { fromPartial } from '@total-typescript/shoehorn';
import type { ShlinkApiClient, ShlinkShortUrlsResponse } from '../../../src/api-contract'; import type { ShlinkApiClient, ShlinkShortUrlsResponse } from '../../../src/api-contract';
import type { ShortUrl } from '../../../src/short-urls/data'; import type { ShlinkShortUrl } from '../../../src/short-urls/data';
import { createShortUrl as createShortUrlCreator } from '../../../src/short-urls/reducers/shortUrlCreation'; import { createShortUrl as createShortUrlCreator } from '../../../src/short-urls/reducers/shortUrlCreation';
import { shortUrlDeleted } from '../../../src/short-urls/reducers/shortUrlDeletion'; import { shortUrlDeleted } from '../../../src/short-urls/reducers/shortUrlDeletion';
import { editShortUrl as editShortUrlCreator } from '../../../src/short-urls/reducers/shortUrlEdition'; import { editShortUrl as editShortUrlCreator } from '../../../src/short-urls/reducers/shortUrlEdition';
@ -102,36 +102,36 @@ describe('shortUrlsListReducer', () => {
it.each([ it.each([
[ [
[ [
fromPartial<ShortUrl>({ shortCode }), fromPartial<ShlinkShortUrl>({ shortCode }),
fromPartial<ShortUrl>({ shortCode, domain: 'example.com' }), fromPartial<ShlinkShortUrl>({ shortCode, domain: 'example.com' }),
fromPartial<ShortUrl>({ shortCode: 'foo' }), fromPartial<ShlinkShortUrl>({ shortCode: 'foo' }),
], ],
[{ shortCode: 'newOne' }, { shortCode }, { shortCode, domain: 'example.com' }, { shortCode: 'foo' }], [{ shortCode: 'newOne' }, { shortCode }, { shortCode, domain: 'example.com' }, { shortCode: 'foo' }],
], ],
[ [
[ [
fromPartial<ShortUrl>({ shortCode }), fromPartial<ShlinkShortUrl>({ shortCode }),
fromPartial<ShortUrl>({ shortCode: 'code' }), fromPartial<ShlinkShortUrl>({ shortCode: 'code' }),
fromPartial<ShortUrl>({ shortCode: 'foo' }), fromPartial<ShlinkShortUrl>({ shortCode: 'foo' }),
fromPartial<ShortUrl>({ shortCode: 'bar' }), fromPartial<ShlinkShortUrl>({ shortCode: 'bar' }),
fromPartial<ShortUrl>({ shortCode: 'baz' }), fromPartial<ShlinkShortUrl>({ shortCode: 'baz' }),
], ],
[{ shortCode: 'newOne' }, { shortCode }, { shortCode: 'code' }, { shortCode: 'foo' }, { shortCode: 'bar' }], [{ shortCode: 'newOne' }, { shortCode }, { shortCode: 'code' }, { shortCode: 'foo' }, { shortCode: 'bar' }],
], ],
[ [
[ [
fromPartial<ShortUrl>({ shortCode }), fromPartial<ShlinkShortUrl>({ shortCode }),
fromPartial<ShortUrl>({ shortCode: 'code' }), fromPartial<ShlinkShortUrl>({ shortCode: 'code' }),
fromPartial<ShortUrl>({ shortCode: 'foo' }), fromPartial<ShlinkShortUrl>({ shortCode: 'foo' }),
fromPartial<ShortUrl>({ shortCode: 'bar' }), fromPartial<ShlinkShortUrl>({ shortCode: 'bar' }),
fromPartial<ShortUrl>({ shortCode: 'baz1' }), fromPartial<ShlinkShortUrl>({ shortCode: 'baz1' }),
fromPartial<ShortUrl>({ shortCode: 'baz2' }), fromPartial<ShlinkShortUrl>({ shortCode: 'baz2' }),
fromPartial<ShortUrl>({ shortCode: 'baz3' }), fromPartial<ShlinkShortUrl>({ shortCode: 'baz3' }),
], ],
[{ shortCode: 'newOne' }, { shortCode }, { shortCode: 'code' }, { shortCode: 'foo' }, { shortCode: 'bar' }], [{ shortCode: 'newOne' }, { shortCode }, { shortCode: 'code' }, { shortCode: 'foo' }, { shortCode: 'bar' }],
], ],
])('prepends new short URL and increases total on CREATE_SHORT_URL', (data, expectedData) => { ])('prepends new short URL and increases total on CREATE_SHORT_URL', (data, expectedData) => {
const newShortUrl = fromPartial<ShortUrl>({ shortCode: 'newOne' }); const newShortUrl = fromPartial<ShlinkShortUrl>({ shortCode: 'newOne' });
const state = { const state = {
shortUrls: fromPartial<ShlinkShortUrlsResponse>({ shortUrls: fromPartial<ShlinkShortUrlsResponse>({
data, data,
@ -152,15 +152,15 @@ describe('shortUrlsListReducer', () => {
}); });
it.each([ it.each([
((): [ShortUrl, ShortUrl[], ShortUrl[]] => { ((): [ShlinkShortUrl, ShlinkShortUrl[], ShlinkShortUrl[]] => {
const editedShortUrl = fromPartial<ShortUrl>({ shortCode: 'notMatching' }); const editedShortUrl = fromPartial<ShlinkShortUrl>({ shortCode: 'notMatching' });
const list: ShortUrl[] = [fromPartial({ shortCode: 'foo' }), fromPartial({ shortCode: 'bar' })]; const list: ShlinkShortUrl[] = [fromPartial({ shortCode: 'foo' }), fromPartial({ shortCode: 'bar' })];
return [editedShortUrl, list, list]; return [editedShortUrl, list, list];
})(), })(),
((): [ShortUrl, ShortUrl[], ShortUrl[]] => { ((): [ShlinkShortUrl, ShlinkShortUrl[], ShlinkShortUrl[]] => {
const editedShortUrl = fromPartial<ShortUrl>({ shortCode: 'matching', longUrl: 'new_one' }); const editedShortUrl = fromPartial<ShlinkShortUrl>({ shortCode: 'matching', longUrl: 'new_one' });
const list: ShortUrl[] = [ const list: ShlinkShortUrl[] = [
fromPartial({ shortCode: 'matching', longUrl: 'old_one' }), fromPartial({ shortCode: 'matching', longUrl: 'old_one' }),
fromPartial({ shortCode: 'bar' }), fromPartial({ shortCode: 'bar' }),
]; ];

View file

@ -1,6 +1,6 @@
import { fromPartial } from '@total-typescript/shoehorn'; import { fromPartial } from '@total-typescript/shoehorn';
import type { RootState } from '../../../src/container/store'; import type { RootState } from '../../../src/container/store';
import type { ShortUrl } from '../../../src/short-urls/data'; import type { ShlinkShortUrl } from '../../../src/short-urls/data';
import { createShortUrl as createShortUrlCreator } from '../../../src/short-urls/reducers/shortUrlCreation'; import { createShortUrl as createShortUrlCreator } from '../../../src/short-urls/reducers/shortUrlCreation';
import { tagDeleted } from '../../../src/tags/reducers/tagDelete'; import { tagDeleted } from '../../../src/tags/reducers/tagDelete';
import { tagEdited } from '../../../src/tags/reducers/tagEdit'; import { tagEdited } from '../../../src/tags/reducers/tagEdit';
@ -112,7 +112,7 @@ describe('tagsListReducer', () => {
[['new', 'tag'], ['foo', 'bar', 'baz', 'foo2', 'fo', 'new', 'tag']], [['new', 'tag'], ['foo', 'bar', 'baz', 'foo2', 'fo', 'new', 'tag']],
])('appends new short URL\'s tags to the list of tags on CREATE_SHORT_URL', (shortUrlTags, expectedTags) => { ])('appends new short URL\'s tags to the list of tags on CREATE_SHORT_URL', (shortUrlTags, expectedTags) => {
const tags = ['foo', 'bar', 'baz', 'foo2', 'fo']; const tags = ['foo', 'bar', 'baz', 'foo2', 'fo'];
const payload = fromPartial<ShortUrl>({ tags: shortUrlTags }); const payload = fromPartial<ShlinkShortUrl>({ tags: shortUrlTags });
expect(reducer(state({ tags }), createShortUrl.fulfilled(payload, '', fromPartial({})))).toEqual({ expect(reducer(state({ tags }), createShortUrl.fulfilled(payload, '', fromPartial({})))).toEqual({
tags: expectedTags, tags: expectedTags,

View file

@ -2,7 +2,7 @@ import { fromPartial } from '@total-typescript/shoehorn';
import { addDays, formatISO, subDays } from 'date-fns'; import { addDays, formatISO, subDays } from 'date-fns';
import type { ShlinkApiClient, ShlinkVisits } from '../../../src/api-contract'; import type { ShlinkApiClient, ShlinkVisits } from '../../../src/api-contract';
import type { RootState } from '../../../src/container/store'; import type { RootState } from '../../../src/container/store';
import type { ShortUrl } from '../../../src/short-urls/data'; import type { ShlinkShortUrl } from '../../../src/short-urls/data';
import { formatIsoDate } from '../../../src/utils/dates/helpers/date'; import { formatIsoDate } from '../../../src/utils/dates/helpers/date';
import type { DateInterval } from '../../../src/utils/dates/helpers/dateIntervals'; import type { DateInterval } from '../../../src/utils/dates/helpers/dateIntervals';
import { rangeOf } from '../../../src/utils/helpers'; import { rangeOf } from '../../../src/utils/helpers';
@ -124,7 +124,7 @@ describe('domainVisitsReducer', () => {
visitsMocks.length, visitsMocks.length,
], ],
])('prepends new visits on CREATE_VISIT', (state, shortUrlDomain, expectedVisits) => { ])('prepends new visits on CREATE_VISIT', (state, shortUrlDomain, expectedVisits) => {
const shortUrl = fromPartial<ShortUrl>({ domain: shortUrlDomain }); const shortUrl = fromPartial<ShlinkShortUrl>({ domain: shortUrlDomain });
const { visits } = reducer(buildState({ ...state, visits: visitsMocks }), createNewVisits([ const { visits } = reducer(buildState({ ...state, visits: visitsMocks }), createNewVisits([
fromPartial({ shortUrl, visit: { date: formatIsoDate(now) ?? undefined } }), fromPartial({ shortUrl, visit: { date: formatIsoDate(now) ?? undefined } }),
])); ]));

View file

@ -1,11 +1,11 @@
import { fromPartial } from '@total-typescript/shoehorn'; import { fromPartial } from '@total-typescript/shoehorn';
import type { ShortUrl } from '../../../src/short-urls/data'; import type { ShlinkShortUrl } from '../../../src/short-urls/data';
import { createNewVisits } from '../../../src/visits/reducers/visitCreation'; import { createNewVisits } from '../../../src/visits/reducers/visitCreation';
import type { Visit } from '../../../src/visits/types'; import type { Visit } from '../../../src/visits/types';
describe('visitCreationReducer', () => { describe('visitCreationReducer', () => {
describe('createNewVisits', () => { describe('createNewVisits', () => {
const shortUrl = fromPartial<ShortUrl>({}); const shortUrl = fromPartial<ShlinkShortUrl>({});
const visit = fromPartial<Visit>({}); const visit = fromPartial<Visit>({});
it('just returns the action with proper type', () => { it('just returns the action with proper type', () => {

View file

@ -16,13 +16,14 @@ import type {
ShlinkTagsStatsResponse, ShlinkTagsStatsResponse,
ShlinkVisits, ShlinkVisits,
ShlinkVisitsOverview, ShlinkVisitsOverview,
ShlinkVisitsParams } from '@shlinkio/shlink-web-component/api-contract'; ShlinkVisitsParams,
} from '@shlinkio/shlink-web-component/api-contract';
import { import {
ErrorTypeV2, ErrorTypeV2,
ErrorTypeV3, ErrorTypeV3,
} from '@shlinkio/shlink-web-component/api-contract'; } from '@shlinkio/shlink-web-component/api-contract';
import { isEmpty, isNil, reject } from 'ramda'; import { isEmpty, isNil, reject } from 'ramda';
import type { ShortUrl, ShortUrlData } from '../../../shlink-web-component/src/short-urls/data'; import type { ShlinkShortUrl, ShortUrlData } from '../../../shlink-web-component/src/short-urls/data';
import type { HttpClient } from '../../common/services/HttpClient'; import type { HttpClient } from '../../common/services/HttpClient';
import { replaceAuthorityFromUri } from '../../utils/helpers/uri'; import { replaceAuthorityFromUri } from '../../utils/helpers/uri';
import type { OptionalString } from '../../utils/utils'; import type { OptionalString } from '../../utils/utils';
@ -71,9 +72,9 @@ export class ShlinkApiClient implements BaseShlinkApiClient {
{ url: '/short-urls', query: normalizeListParams(params) }, { url: '/short-urls', query: normalizeListParams(params) },
).then(({ shortUrls }) => shortUrls); ).then(({ shortUrls }) => shortUrls);
public readonly createShortUrl = async (options: ShortUrlData): Promise<ShortUrl> => { public readonly createShortUrl = async (options: ShortUrlData): Promise<ShlinkShortUrl> => {
const body = reject((value) => isEmpty(value) || isNil(value), options as any); const body = reject((value) => isEmpty(value) || isNil(value), options as any);
return this.performRequest<ShortUrl>({ url: '/short-urls', method: 'POST', body }); return this.performRequest<ShlinkShortUrl>({ url: '/short-urls', method: 'POST', body });
}; };
public readonly getShortUrlVisits = async (shortCode: string, query?: ShlinkVisitsParams): Promise<ShlinkVisits> => public readonly getShortUrlVisits = async (shortCode: string, query?: ShlinkVisitsParams): Promise<ShlinkVisits> =>
@ -95,8 +96,8 @@ export class ShlinkApiClient implements BaseShlinkApiClient {
public readonly getVisitsOverview = async (): Promise<ShlinkVisitsOverview> => public readonly getVisitsOverview = async (): Promise<ShlinkVisitsOverview> =>
this.performRequest<{ visits: ShlinkVisitsOverview }>({ url: '/visits' }).then(({ visits }) => visits); this.performRequest<{ visits: ShlinkVisitsOverview }>({ url: '/visits' }).then(({ visits }) => visits);
public readonly getShortUrl = async (shortCode: string, domain?: OptionalString): Promise<ShortUrl> => public readonly getShortUrl = async (shortCode: string, domain?: OptionalString): Promise<ShlinkShortUrl> =>
this.performRequest<ShortUrl>({ url: `/short-urls/${shortCode}`, query: { domain } }); this.performRequest<ShlinkShortUrl>({ url: `/short-urls/${shortCode}`, query: { domain } });
public readonly deleteShortUrl = async (shortCode: string, domain?: OptionalString): Promise<void> => public readonly deleteShortUrl = async (shortCode: string, domain?: OptionalString): Promise<void> =>
this.performEmptyRequest({ url: `/short-urls/${shortCode}`, method: 'DELETE', query: { domain } }); this.performEmptyRequest({ url: `/short-urls/${shortCode}`, method: 'DELETE', query: { domain } });
@ -105,8 +106,8 @@ export class ShlinkApiClient implements BaseShlinkApiClient {
shortCode: string, shortCode: string,
domain: OptionalString, domain: OptionalString,
body: ShlinkShortUrlData, body: ShlinkShortUrlData,
): Promise<ShortUrl> => ): Promise<ShlinkShortUrl> =>
this.performRequest<ShortUrl>({ url: `/short-urls/${shortCode}`, method: 'PATCH', query: { domain }, body }); this.performRequest<ShlinkShortUrl>({ url: `/short-urls/${shortCode}`, method: 'PATCH', query: { domain }, body });
public readonly listTags = async (): Promise<ShlinkTags> => public readonly listTags = async (): Promise<ShlinkTags> =>
this.performRequest<{ tags: ShlinkTagsResponse }>({ url: '/tags', query: { withStats: 'true' } }) this.performRequest<{ tags: ShlinkTagsResponse }>({ url: '/tags', query: { withStats: 'true' } })

View file

@ -1,7 +1,7 @@
import type { ShlinkDomain, ShlinkVisits, ShlinkVisitsOverview } from '@shlinkio/shlink-web-component/api-contract'; import type { ShlinkDomain, ShlinkVisits, ShlinkVisitsOverview } from '@shlinkio/shlink-web-component/api-contract';
import { ErrorTypeV2, ErrorTypeV3 } from '@shlinkio/shlink-web-component/api-contract'; import { ErrorTypeV2, ErrorTypeV3 } from '@shlinkio/shlink-web-component/api-contract';
import { fromPartial } from '@total-typescript/shoehorn'; import { fromPartial } from '@total-typescript/shoehorn';
import type { ShortUrl, ShortUrlsOrder } from '../../../shlink-web-component/src/short-urls/data'; import type { ShlinkShortUrl, ShortUrlsOrder } from '../../../shlink-web-component/src/short-urls/data';
import { ShlinkApiClient } from '../../../src/api/services/ShlinkApiClient'; import { ShlinkApiClient } from '../../../src/api/services/ShlinkApiClient';
import type { HttpClient } from '../../../src/common/services/HttpClient'; import type { HttpClient } from '../../../src/common/services/HttpClient';
import type { OptionalString } from '../../../src/utils/utils'; import type { OptionalString } from '../../../src/utils/utils';
@ -175,7 +175,7 @@ describe('ShlinkApiClient', () => {
maxVisits: 50, maxVisits: 50,
validSince: '2025-01-01T10:00:00+01:00', validSince: '2025-01-01T10:00:00+01:00',
}; };
const expectedResp = fromPartial<ShortUrl>({}); const expectedResp = fromPartial<ShlinkShortUrl>({});
fetchJson.mockResolvedValue(expectedResp); fetchJson.mockResolvedValue(expectedResp);
const { updateShortUrl } = buildApiClient(); const { updateShortUrl } = buildApiClient();
const expectedQuery = domain ? `?domain=${domain}` : ''; const expectedQuery = domain ? `?domain=${domain}` : '';