diff --git a/.env.example b/.env.example index 869c0124..bbdc5e48 100644 --- a/.env.example +++ b/.env.example @@ -104,3 +104,9 @@ POSTIZ_OAUTH_CLIENT_SECRET="" # DUB_TOKEN="" # Your self-hosted Dub API token # DUB_API_ENDPOINT="https://api.dub.co" # Your self-hosted Dub API endpoint # DUB_SHORT_LINK_DOMAIN="dub.sh" # Your self-hosted Dub domain + +# SHORT_IO_SECRET_KEY="" # Your Short.io API secret key + +# KUTT_API_KEY="" # Your Kutt.it API key +# KUTT_API_ENDPOINT="https://kutt.it/api/v2" # Your self-hosted Kutt API endpoint +# KUTT_SHORT_LINK_DOMAIN="kutt.it" # Your self-hosted Kutt domain \ No newline at end of file diff --git a/libraries/nestjs-libraries/src/short-linking/providers/kutt.ts b/libraries/nestjs-libraries/src/short-linking/providers/kutt.ts new file mode 100644 index 00000000..118d37ec --- /dev/null +++ b/libraries/nestjs-libraries/src/short-linking/providers/kutt.ts @@ -0,0 +1,123 @@ +import { ShortLinking } from '@gitroom/nestjs-libraries/short-linking/short-linking.interface'; + +const KUTT_API_ENDPOINT = process.env.KUTT_API_ENDPOINT || 'https://kutt.it/api/v2'; +const KUTT_SHORT_LINK_DOMAIN = process.env.KUTT_SHORT_LINK_DOMAIN || 'kutt.it'; + +const getOptions = () => ({ + headers: { + 'X-API-Key': process.env.KUTT_API_KEY, + 'Content-Type': 'application/json', + }, +}); + +export class Kutt implements ShortLinking { + shortLinkDomain = KUTT_SHORT_LINK_DOMAIN; + + async linksStatistics(links: string[]) { + return Promise.all( + links.map(async (link) => { + const linkId = link.split('/').pop(); + + try { + const response = await fetch( + `${KUTT_API_ENDPOINT}/links/${linkId}/stats`, + getOptions() + ); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + const data = await response.json(); + + return { + short: link, + original: data.address || '', + clicks: data.lastDay?.stats?.reduce((total: number, stat: any) => total + stat, 0)?.toString() || '0', + }; + } catch (error) { + return { + short: link, + original: '', + clicks: '0', + }; + } + }) + ); + } + + async convertLinkToShortLink(id: string, link: string) { + try { + const response = await fetch(`${KUTT_API_ENDPOINT}/links`, { + ...getOptions(), + method: 'POST', + body: JSON.stringify({ + target: link, + domain: this.shortLinkDomain, + reuse: false, + }), + }); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + const data = await response.json(); + return data.link; + } catch (error) { + throw new Error(`Failed to create short link: ${error}`); + } + } + + async convertShortLinkToLink(shortLink: string) { + const linkId = shortLink.split('/').pop(); + + try { + const response = await fetch( + `${KUTT_API_ENDPOINT}/links/${linkId}/stats`, + getOptions() + ); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + const data = await response.json(); + return data.address || ''; + } catch (error) { + throw new Error(`Failed to get original link: ${error}`); + } + } + + async getAllLinksStatistics( + id: string, + page = 1 + ): Promise<{ short: string; original: string; clicks: string }[]> { + try { + const response = await fetch( + `${KUTT_API_ENDPOINT}/links?limit=100&skip=${(page - 1) * 100}`, + getOptions() + ); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + const data = await response.json(); + + const mapLinks = data.data?.map((link: any) => ({ + short: link.link, + original: link.address, + clicks: link.visit_count?.toString() || '0', + })) || []; + + if (mapLinks.length < 100) { + return mapLinks; + } + + return [...mapLinks, ...(await this.getAllLinksStatistics(id, page + 1))]; + } catch (error) { + return []; + } + } +} \ No newline at end of file diff --git a/libraries/nestjs-libraries/src/short-linking/short.link.service.ts b/libraries/nestjs-libraries/src/short-linking/short.link.service.ts index c8ed16db..bb73aa9e 100644 --- a/libraries/nestjs-libraries/src/short-linking/short.link.service.ts +++ b/libraries/nestjs-libraries/src/short-linking/short.link.service.ts @@ -3,6 +3,7 @@ import { Empty } from '@gitroom/nestjs-libraries/short-linking/providers/empty'; import { ShortLinking } from '@gitroom/nestjs-libraries/short-linking/short-linking.interface'; import { Injectable } from '@nestjs/common'; import { ShortIo } from './providers/short.io'; +import { Kutt } from './providers/kutt'; const getProvider = (): ShortLinking => { if (process.env.DUB_TOKEN) { @@ -13,6 +14,10 @@ const getProvider = (): ShortLinking => { return new ShortIo(); } + if (process.env.KUTT_API_KEY) { + return new Kutt(); + } + return new Empty(); };