Added support for short URLs with multi-segment slugs

This commit is contained in:
Alejandro Celaya 2022-08-07 18:19:53 +02:00
parent b1749ee2ef
commit 0c17818a24
5 changed files with 35 additions and 7 deletions

View file

@ -16,7 +16,7 @@ import { ShortUrlFormProps } from './ShortUrlForm';
import { ShortUrlDetail } from './reducers/shortUrlDetail';
import { EditShortUrlData } from './data';
import { ShortUrlEdition } from './reducers/shortUrlEdition';
import { shortUrlDataFromShortUrl } from './helpers';
import { shortUrlDataFromShortUrl, urlDecodeShortCode } from './helpers';
interface EditShortUrlConnectProps {
settings: Settings;
@ -48,7 +48,7 @@ export const EditShortUrl = (ShortUrlForm: FC<ShortUrlFormProps>) => ({
const [savingSucceeded,, isSuccessful, isNotSuccessful] = useToggle();
useEffect(() => {
params.shortCode && getShortUrlDetail(params.shortCode, domain);
params.shortCode && getShortUrlDetail(urlDecodeShortCode(params.shortCode), domain);
}, []);
if (loading) {

View file

@ -2,6 +2,7 @@ import { FC } from 'react';
import { Link } from 'react-router-dom';
import { isServerWithId, SelectedServer, ServerWithId } from '../../servers/data';
import { ShortUrl } from '../data';
import { urlEncodeShortCode } from './index';
export type LinkSuffix = 'visits' | 'edit';
@ -13,7 +14,7 @@ export interface ShortUrlDetailLinkProps {
const buildUrl = ({ id }: ServerWithId, { shortCode, domain }: ShortUrl, suffix: LinkSuffix) => {
const query = domain ? `?domain=${domain}` : '';
return `/server/${id}/short-code/${shortCode}/${suffix}${query}`;
return `/server/${id}/short-code/${urlEncodeShortCode(shortCode)}/${suffix}${query}`;
};
export const ShortUrlDetailLink: FC<ShortUrlDetailLinkProps & Record<string | number, any>> = (

View file

@ -40,3 +40,9 @@ export const shortUrlDataFromShortUrl = (shortUrl?: ShortUrl, settings?: ShortUr
validateUrl,
};
};
const MULTI_SEGMENT_SEPARATOR = '__';
export const urlEncodeShortCode = (shortCode: string): string => shortCode.replaceAll('/', MULTI_SEGMENT_SEPARATOR);
export const urlDecodeShortCode = (shortCode: string): string => shortCode.replaceAll(MULTI_SEGMENT_SEPARATOR, '/');

View file

@ -13,6 +13,7 @@ import { VisitsStats } from './VisitsStats';
import { NormalizedVisit, VisitsParams } from './types';
import { CommonVisitsProps } from './types/CommonVisitsProps';
import { toApiParams } from './types/helpers';
import { urlDecodeShortCode } from '../short-urls/helpers';
export interface ShortUrlVisitsProps extends CommonVisitsProps {
getShortUrlVisits: (shortCode: string, query?: ShlinkVisitsParams, doIntervalFallback?: boolean) => void;
@ -36,14 +37,14 @@ export const ShortUrlVisits = ({ exportVisits }: ReportExporter) => boundToMercu
const goBack = useGoBack();
const { domain } = parseQuery<{ domain?: string }>(search);
const loadVisits = (params: VisitsParams, doIntervalFallback?: boolean) =>
getShortUrlVisits(shortCode, { ...toApiParams(params), domain }, doIntervalFallback);
getShortUrlVisits(urlDecodeShortCode(shortCode), { ...toApiParams(params), domain }, doIntervalFallback);
const exportCsv = (visits: NormalizedVisit[]) => exportVisits(
`short-url_${shortUrlDetail.shortUrl?.shortUrl.replace(/https?:\/\//g, '')}_visits.csv`,
visits,
);
useEffect(() => {
getShortUrlDetail(shortCode, domain);
getShortUrlDetail(urlDecodeShortCode(shortCode), domain);
}, []);
return (
@ -59,4 +60,4 @@ export const ShortUrlVisits = ({ exportVisits }: ReportExporter) => boundToMercu
<ShortUrlVisitsHeader shortUrlDetail={shortUrlDetail} shortUrlVisits={shortUrlVisits} goBack={goBack} />
</VisitsStats>
);
}, (_, params) => [Topics.shortUrlVisits(params.shortCode)]);
}, (_, params) => (params.shortCode ? [Topics.shortUrlVisits(urlDecodeShortCode(params.shortCode))] : []));

View file

@ -1,6 +1,6 @@
import { Mock } from 'ts-mockery';
import { ShortUrl } from '../../../src/short-urls/data';
import { shortUrlDataFromShortUrl } from '../../../src/short-urls/helpers';
import { shortUrlDataFromShortUrl, urlDecodeShortCode, urlEncodeShortCode } from '../../../src/short-urls/helpers';
describe('helpers', () => {
describe('shortUrlDataFromShortUrl', () => {
@ -25,4 +25,24 @@ describe('helpers', () => {
expect(shortUrlDataFromShortUrl(shortUrl, settings)).toEqual(expectedInitialState);
});
});
describe('urlEncodeShortCode', () => {
it.each([
['foo', 'foo'],
['foo/bar', 'foo__bar'],
['foo/bar/baz', 'foo__bar__baz'],
])('parses shortCode as expected', (shortCode, result) => {
expect(urlEncodeShortCode(shortCode)).toEqual(result);
});
});
describe('urlDecodeShortCode', () => {
it.each([
['foo', 'foo'],
['foo__bar', 'foo/bar'],
['foo__bar__baz', 'foo/bar/baz'],
])('parses shortCode as expected', (shortCode, result) => {
expect(urlDecodeShortCode(shortCode)).toEqual(result);
});
});
});