Skip to content

Commit

Permalink
Merge pull request #29 from acelaya-forks/feature/delete-visits
Browse files Browse the repository at this point in the history
Add support for delete visits endpoints
  • Loading branch information
acelaya authored Oct 27, 2023
2 parents 0e457cb + d200d92 commit c1ddf9b
Show file tree
Hide file tree
Showing 6 changed files with 66 additions and 4 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@ All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org).

## [Unreleased]
## [0.2.0] - 2023-10-27
### Added
* [#2](https://github.com/shlinkio/shlink-js-sdk/issues/2) Implement node.js `HttpClient`.
* Add support for orphan and short URL visits deletion methods.

### Changed
* *Nothing*
Expand Down
12 changes: 9 additions & 3 deletions dev/index.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,18 @@ import { ShlinkApiClient } from '../dist/index.js';
import { NodeHttpClient } from '../dist/node.js';

(async function () {
const [,, baseUrl, apiKey] = process.argv;
if (!baseUrl || !apiKey) {
console.error('Base URL or API key not provided.');
return;
}

try {
const apiClient = new ShlinkApiClient(new NodeHttpClient(), {
baseUrl: process.argv[2],
apiKey: process.argv[3],
baseUrl,
apiKey,
});
console.log('Success:', await apiClient.listShortUrls());
console.log('Success:', await apiClient.deleteShortUrlVisits('nieRB'));
} catch (e) {
console.error('Error:', e);
}
Expand Down
5 changes: 5 additions & 0 deletions src/api-contract/ShlinkApiClient.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type {
ShlinkCreateShortUrlData,
ShlinkDeleteVisitsResponse,
ShlinkDomainRedirects,
ShlinkDomainsResponse,
ShlinkEditDomainRedirects,
Expand All @@ -22,12 +23,16 @@ export type ShlinkApiClient = {

getShortUrlVisits(shortCode: string, query?: ShlinkVisitsParams): Promise<ShlinkVisits>;

deleteShortUrlVisits(shortCode: string, domain?: string | null): Promise<ShlinkDeleteVisitsResponse>;

getTagVisits(tag: string, query?: Omit<ShlinkVisitsParams, 'domain'>): Promise<ShlinkVisits>;

getDomainVisits(domain: string, query?: Omit<ShlinkVisitsParams, 'domain'>): Promise<ShlinkVisits>;

getOrphanVisits(query?: Omit<ShlinkVisitsParams, 'domain'>): Promise<ShlinkVisits>;

deleteOrphanVisits(): Promise<ShlinkDeleteVisitsResponse>;

getNonOrphanVisits(query?: Omit<ShlinkVisitsParams, 'domain'>): Promise<ShlinkVisits>;

getVisitsOverview(): Promise<ShlinkVisitsOverview>;
Expand Down
4 changes: 4 additions & 0 deletions src/api-contract/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,10 @@ export interface ShlinkVisits {
pagination: ShlinkPaginator;
}

export type ShlinkDeleteVisitsResponse = {
deletedVisits: number;
};

export interface ShlinkVisitsOverview {
/** Optional only before Shlink 3.5.0 */
nonOrphanVisits?: ShlinkVisitsSummary;
Expand Down
12 changes: 12 additions & 0 deletions src/api/ShlinkApiClient.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type {
ShlinkApiClient as BaseShlinkApiClient,
ShlinkCreateShortUrlData,
ShlinkDeleteVisitsResponse,
ShlinkDomainRedirects,
ShlinkDomainsResponse,
ShlinkEditDomainRedirects,
Expand Down Expand Up @@ -73,6 +74,13 @@ export class ShlinkApiClient implements BaseShlinkApiClient {
.then(({ visits }) => visits);
}

public async deleteShortUrlVisits(shortCode: string, domain?: string | null): Promise<ShlinkDeleteVisitsResponse> {
const query = domain ? { domain } : undefined;
return this.performRequest<ShlinkDeleteVisitsResponse>(
{ method: 'DELETE', url: `/short-urls/${shortCode}/visits`, query },
);
}

public async getTagVisits(tag: string, query?: Omit<ShlinkVisitsParams, 'domain'>): Promise<ShlinkVisits> {
return this.performRequest<{ visits: ShlinkVisits }>({ url: `/tags/${tag}/visits`, query })
.then(({ visits }) => visits);
Expand All @@ -87,6 +95,10 @@ export class ShlinkApiClient implements BaseShlinkApiClient {
return this.performRequest<{ visits: ShlinkVisits }>({ url: '/visits/orphan', query }).then(({ visits }) => visits);
}

public async deleteOrphanVisits(): Promise<ShlinkDeleteVisitsResponse> {
return this.performRequest<ShlinkDeleteVisitsResponse>({ method: 'DELETE', url: '/visits/orphan' });
}

public async getNonOrphanVisits(query?: Omit<ShlinkVisitsParams, 'domain'>): Promise<ShlinkVisits> {
return this.performRequest<{ visits: ShlinkVisits }>({ url: '/visits/non-orphan', query })
.then(({ visits }) => visits);
Expand Down
34 changes: 34 additions & 0 deletions test/api/ShlinkApiClient.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,25 @@ describe('ShlinkApiClient', () => {
});
});

describe('deleteShortUrlVisits', () => {
it.each([
['the_domain', '?domain=the_domain'],
[null, ''],
[undefined, ''],
])('deletes visits with params', async (domain, expectedQuery) => {
const response = { deletedVisits: 10 };
jsonRequest.mockResolvedValue(response);

const actualVisits = await apiClient.deleteShortUrlVisits('abc123', domain);

expect(actualVisits).toEqual(response);
expect(jsonRequest).toHaveBeenCalledWith(
expect.stringContaining(`/short-urls/abc123/visits${expectedQuery}`),
expect.objectContaining({ method: 'DELETE' }),
);
});
});

describe('getTagVisits', () => {
it('properly returns tag visits', async () => {
const expectedVisits = ['foo', 'bar'];
Expand Down Expand Up @@ -344,6 +363,21 @@ describe('ShlinkApiClient', () => {
});
});

describe('deleteOrphanVisits', () => {
it('deletes visits with params', async () => {
const response = { deletedVisits: 10 };
jsonRequest.mockResolvedValue(response);

const actualVisits = await apiClient.deleteOrphanVisits();

expect(actualVisits).toEqual(response);
expect(jsonRequest).toHaveBeenCalledWith(
expect.stringContaining('/visits/orphan'),
expect.objectContaining({ method: 'DELETE' }),
);
});
});

describe('getNonOrphanVisits', () => {
it('returns non-orphan visits', async () => {
jsonRequest.mockResolvedValue({ visits: fromPartial<ShlinkVisits>({ data: [] }) });
Expand Down

0 comments on commit c1ddf9b

Please sign in to comment.