Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: #544-Add short.io as a shortening link #564

Open
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

ak7550
Copy link

@ak7550 ak7550 commented Jan 19, 2025

What kind of change does this PR introduce?

#544

Feature that integrates short.io as a shortening link.

Why was this change needed?

#544
Please link to related issues when possible, and explain WHY you changed things, not WHAT you changed.

Other information:

eg: Did you discuss this change with anybody before working on it (not required, but can be a good idea for bigger changes). Any plans for the future, etc?

Checklist:

Put a "X" in the boxes below to indicate you have followed the checklist;

  • I have read the CONTRIBUTING guide.
  • I checked that there were not similar issues or PRs already open for this.
  • This PR fixes just ONE issue (do not include multiple issues or types of change in the same PR) For example, don't try and fix a UI issue and include new dependencies in the same PR.

Summary by CodeRabbit

  • New Features

    • Added Short.io as a new short-linking provider.
    • Enhanced link management capabilities with new methods for:
      • Retrieving link statistics.
      • Converting links to short links.
      • Fetching original URLs from short links.
  • Improvements

    • Expanded short-linking service to support multiple providers.
    • Added dynamic provider selection based on environment configuration.

Copy link

vercel bot commented Jan 19, 2025

@ak7550 is attempting to deploy a commit to the Listinai Team on Vercel.

A member of the Team first needs to authorize it.

Copy link

coderabbitai bot commented Jan 19, 2025

Walkthrough

The pull request introduces a new ShortIo provider for short link management in the NestJS libraries. This implementation adds support for Short.io as an alternative short linking service, expanding the existing short linking functionality. The changes include creating a new provider class that implements the ShortLinking interface and updating the service to recognize and use this new provider when the Short.io secret key is available.

Changes

File Change Summary
libraries/nestjs-libraries/src/short-linking/providers/short.io.ts Added new ShortIo class implementing ShortLinking interface with methods for link statistics, conversion, and retrieval
libraries/nestjs-libraries/src/short-linking/short.link.service.ts Updated getProvider function to include ShortIo provider when SHORT_IO_SECRET_KEY is present

Possibly related issues

  • gitroomhq/postiz-app#544: Directly matches the implementation of adding Short.io as a shortening link provider, with the exact steps outlined in the issue description being followed in this PR.

Poem

🐰 A Rabbit's Ode to Short Links

Hop, hop, through URLs so long,
Short.io joins our linking song!
Clicks counted, links made neat,
A digital warren, oh so sweet! 🔗✨
Brevity's magic, now complete!

Finishing Touches

  • 📝 Generate Docstrings (Beta)

Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR. (Beta)
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@ak7550 ak7550 marked this pull request as draft January 19, 2025 09:46
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🧹 Nitpick comments (1)
libraries/nestjs-libraries/src/short-linking/short.link.service.ts (1)

12-15: Consider provider precedence in getProvider function

The current logic checks for DUB_TOKEN before SHORT_IO_SECRET_KEY. If both environment variables are set, the Dub provider will be selected over ShortIo. If ShortIo should take precedence, adjust the order of the checks.

Apply this diff to prioritize ShortIo:

 const getProvider = (): ShortLinking => {
+  if (process.env.SHORT_IO_SECRET_KEY) {
+    return new ShortIo();
+  }

   if (process.env.DUB_TOKEN) {
     return new Dub();
   }

-  if (process.env.SHORT_IO_SECRET_KEY) {
-    return new ShortIo();
-  }

   return new Empty();
 };

Alternatively, clearly document the intended order of provider selection if the current configuration is deliberate.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 339416f and 9c10d0f.

📒 Files selected for processing (2)
  • libraries/nestjs-libraries/src/short-linking/providers/short.io.ts (1 hunks)
  • libraries/nestjs-libraries/src/short-linking/short.link.service.ts (1 hunks)
🔇 Additional comments (1)
libraries/nestjs-libraries/src/short-linking/short.link.service.ts (1)

5-5: Verify the import path for ShortIo provider

Ensure that the import path './providers/short.io' correctly references the ShortIo provider file. Incorrect import paths can lead to module resolution errors.

If the short.io.ts file is not located directly under the providers directory or if there's a casing mismatch, update the import accordingly.

};

export class ShortIo implements ShortLinking {
shortLinkDomain = 'aniket.short.gy';
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Make shortLinkDomain configurable

The shortLinkDomain is hardcoded as 'aniket.short.gy'. To enhance flexibility and reusability, consider making it configurable via an environment variable or a constructor parameter. Hardcoding the domain limits the usability of this provider in different environments.

Apply this diff to use an environment variable:

 export class ShortIo implements ShortLinking {
-  shortLinkDomain = 'aniket.short.gy';
+  shortLinkDomain = process.env.SHORT_IO_DOMAIN || 'your-default-domain.com';

Ensure that SHORT_IO_DOMAIN is defined in your environment configuration.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
shortLinkDomain = 'aniket.short.gy';
shortLinkDomain = process.env.SHORT_IO_DOMAIN || 'your-default-domain.com';

Comment on lines +6 to +9
Authorization: `Bearer ${process.env.SHORT_IO_SECRET_KEY}`,
'Content-Type': 'application/json',
},
};
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Handle missing SHORT_IO_SECRET_KEY environment variable gracefully

If process.env.SHORT_IO_SECRET_KEY is undefined, the Authorization header will be 'Bearer undefined', which may cause authentication failures or security issues when making API calls. It's important to check if the environment variable is set and handle the absence appropriately.

Apply this diff to ensure the secret key is present and throw an error if it's missing:

 const options = {
   headers: {
-    Authorization: `Bearer ${process.env.SHORT_IO_SECRET_KEY}`,
+    Authorization: `Bearer ${process.env.SHORT_IO_SECRET_KEY || ''}`,
     'Content-Type': 'application/json',
   },
 };
+if (!process.env.SHORT_IO_SECRET_KEY) {
+  throw new Error('SHORT_IO_SECRET_KEY environment variable is not set.');
+}

Alternatively, consider initializing the options within the class constructor after verifying the presence of the secret key.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
Authorization: `Bearer ${process.env.SHORT_IO_SECRET_KEY}`,
'Content-Type': 'application/json',
},
};
const options = {
headers: {
Authorization: `Bearer ${process.env.SHORT_IO_SECRET_KEY || ''}`,
'Content-Type': 'application/json',
},
};
if (!process.env.SHORT_IO_SECRET_KEY) {
throw new Error('SHORT_IO_SECRET_KEY environment variable is not set.');
}

Comment on lines 54 to 58
`https://api.dub.co/links/info?domain=${shortLink}`,
options
)
).json()
).url;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Incorrect API endpoint in convertShortLinkToLink method

Similarly, the convertShortLinkToLink method is using https://api.dub.co/links/info, which should be a Short.io API endpoint. This inconsistency may cause the method to fail or return incorrect data.

Update the endpoint and parameters to align with Short.io's API:

             await fetch(
-              `https://api.dub.co/links/info?domain=${shortLink}`,
+              `https://api.short.io/links/expand?shortURL=${encodeURIComponent(shortLink)}`,
               options
             )

Adjust the response handling accordingly to extract the original URL:

         ).json()
-      ).url;
+      ).originalURL;

Ensure that you handle errors and check the API documentation for correct usage.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
`https://api.dub.co/links/info?domain=${shortLink}`,
options
)
).json()
).url;
`https://api.short.io/links/expand?shortURL=${encodeURIComponent(shortLink)}`,
options
)
).json()
).originalURL;

Comment on lines 38 to 48
...options,
method: 'POST',
body: JSON.stringify({
url: link,
tenantId: id,
domain: this.shortLinkDomain,
}),
})
).json()
).shortLink;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Incorrect API endpoint in convertLinkToShortLink method

The method is using the https://api.dub.co/links endpoint, which does not align with the short.io service. Since this is a ShortIo provider, the API calls should be directed to short.io endpoints.

Update the API endpoint and adjust the request parameters according to the Short.io API documentation:

           await fetch(`https://api.dub.co/links`, {
+          await fetch(`https://api.short.io/links`, {
               ...options,
               method: 'POST',
               body: JSON.stringify({
-                url: link,
-                tenantId: id,
-                domain: this.shortLinkDomain,
+                originalURL: link,
+                domain: this.shortLinkDomain,
+                campaignSource: id,
               }),
             })

Ensure that the request body matches the expected format of the Short.io API for creating short links.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
...options,
method: 'POST',
body: JSON.stringify({
url: link,
tenantId: id,
domain: this.shortLinkDomain,
}),
})
).json()
).shortLink;
}
...options,
method: 'POST',
body: JSON.stringify({
originalURL: link,
domain: this.shortLinkDomain,
campaignSource: id,
}),
})
).json()
).shortLink;
}

Comment on lines +17 to +32
const url = `https://api.short.io/links/expand?domain=${this.shortLinkDomain}&path=${link.split('/').pop()}`;
const response = await ( fetch( url, options )
.then( res => res.json() ) );

const linkStatisticsUrl = `https://statistics.short.io/statistics/link/${response.id}?period=last30&tz=UTC`;

const statResponse = await ( fetch( linkStatisticsUrl, options ).then( ( res ) => res.json() ) );

return {
short: response.shortURL,
original: response.originalURL,
clicks: statResponse.totalClicks,
};
})
);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add error handling for API requests in linksStatistics method

The linksStatistics method lacks error handling for network failures or unexpected responses, which can lead to unhandled exceptions. It's important to catch errors from fetch and handle HTTP response statuses appropriately.

Apply this diff to include error handling:

       links.map(async (link) => {
         const url = `https://api.short.io/links/expand?domain=${this.shortLinkDomain}&path=${link.split('/').pop()}`;
-        const response = await ( fetch( url, options )
-          .then( res => res.json() ) );
+        let response;
+        try {
+          const res = await fetch(url, options);
+          if (!res.ok) {
+            throw new Error(`Failed to expand link: ${res.status} ${res.statusText}`);
+          }
+          response = await res.json();
+        } catch (error) {
+          console.error('Error fetching link expansion:', error);
+          throw error;
+        }

         const linkStatisticsUrl = `https://statistics.short.io/statistics/link/${response.id}?period=last30&tz=UTC`;

-        const statResponse = await ( fetch( linkStatisticsUrl, options ).then( ( res ) => res.json() ) );
+        let statResponse;
+        try {
+          const res = await fetch(linkStatisticsUrl, options);
+          if (!res.ok) {
+            throw new Error(`Failed to get link statistics: ${res.status} ${res.statusText}`);
+          }
+          statResponse = await res.json();
+        } catch (error) {
+          console.error('Error fetching link statistics:', error);
+          throw error;
+        }

         return {
           short: response.shortURL,
           original: response.originalURL,
           clicks: statResponse.totalClicks,
         };
       })

This ensures that any errors during the fetch operations are caught and can be handled appropriately by the calling code.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const url = `https://api.short.io/links/expand?domain=${this.shortLinkDomain}&path=${link.split('/').pop()}`;
const response = await ( fetch( url, options )
.then( res => res.json() ) );
const linkStatisticsUrl = `https://statistics.short.io/statistics/link/${response.id}?period=last30&tz=UTC`;
const statResponse = await ( fetch( linkStatisticsUrl, options ).then( ( res ) => res.json() ) );
return {
short: response.shortURL,
original: response.originalURL,
clicks: statResponse.totalClicks,
};
})
);
}
const url = `https://api.short.io/links/expand?domain=${this.shortLinkDomain}&path=${link.split('/').pop()}`;
let response;
try {
const res = await fetch(url, options);
if (!res.ok) {
throw new Error(`Failed to expand link: ${res.status} ${res.statusText}`);
}
response = await res.json();
} catch (error) {
console.error('Error fetching link expansion:', error);
throw error;
}
const linkStatisticsUrl = `https://statistics.short.io/statistics/link/${response.id}?period=last30&tz=UTC`;
let statResponse;
try {
const res = await fetch(linkStatisticsUrl, options);
if (!res.ok) {
throw new Error(`Failed to get link statistics: ${res.status} ${res.statusText}`);
}
statResponse = await res.json();
} catch (error) {
console.error('Error fetching link statistics:', error);
throw error;
}
return {
short: response.shortURL,
original: response.originalURL,
clicks: statResponse.totalClicks,
};
})
);
}

Comment on lines 66 to 85
const response = await (
await fetch(
`https://api.dub.co/links?tenantId=${id}&page=${page}&pageSize=100`,
options
)
).json();

const mapLinks = response.links.map((link: any) => ({
short: link,
original: response.url,
clicks: response.clicks,
}));

if (mapLinks.length < 100) {
return mapLinks;
}

return [...mapLinks, ...(await this.getAllLinksStatistics(id, page + 1))];
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix incorrect response mapping and endpoint in getAllLinksStatistics method

  • The method is using https://api.dub.co/links, which should be https://api.short.io/links.
  • The mapping of the response may not align with the actual data returned by the Short.io API.
  • The termination condition for recursion might not be accurate.

Apply this diff to correct the method:

       await fetch(
-        `https://api.dub.co/links?tenantId=${id}&page=${page}&pageSize=100`,
+        `https://api.short.io/links?domain_id=${this.shortLinkDomain}&limit=100&offset=${(page - 1) * 100}`,
         options
       )
     ).json();

-    const mapLinks = response.links.map((link: any) => ({
-      short: link,
-      original: response.url,
-      clicks: response.clicks,
+    const mapLinks = response.data.map((link: any) => ({
+      short: link.shortURL,
+      original: link.originalURL,
+      clicks: link.clicks,
     }));

-    if (mapLinks.length < 100) {
+    if (response.data.length < 100) {
       return mapLinks;
     }

     return [...mapLinks, ...(await this.getAllLinksStatistics(id, page + 1))];

Ensure that the response fields data, shortURL, originalURL, and clicks match the actual response from the Short.io API. Adjust the termination condition based on the returned data.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const response = await (
await fetch(
`https://api.dub.co/links?tenantId=${id}&page=${page}&pageSize=100`,
options
)
).json();
const mapLinks = response.links.map((link: any) => ({
short: link,
original: response.url,
clicks: response.clicks,
}));
if (mapLinks.length < 100) {
return mapLinks;
}
return [...mapLinks, ...(await this.getAllLinksStatistics(id, page + 1))];
}
}
const response = await (
await fetch(
`https://api.short.io/links?domain_id=${this.shortLinkDomain}&limit=100&offset=${(page - 1) * 100}`,
options
)
).json();
const mapLinks = response.data.map((link: any) => ({
short: link.shortURL,
original: link.originalURL,
clicks: link.clicks,
}));
if (response.data.length < 100) {
return mapLinks;
}
return [...mapLinks, ...(await this.getAllLinksStatistics(id, page + 1))];
}
}

@ak7550 ak7550 marked this pull request as ready for review January 19, 2025 12:43
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (4)
libraries/nestjs-libraries/src/short-linking/providers/short.io.ts (4)

4-9: ⚠️ Potential issue

Move options object inside the class and handle API key securely

The options object should be initialized within the class to ensure proper encapsulation and secure handling of the API key.

Apply this diff:

-const options = {
-  headers: {
-    Authorization: `Bearer ${process.env.SHORT_IO_SECRET_KEY}`,
-    'Content-Type': 'application/json',
-  },
-};

 export class ShortIo implements ShortLinking {
+  private readonly options: RequestInit;
+
+  constructor() {
+    if (!process.env.SHORT_IO_SECRET_KEY) {
+      throw new Error('SHORT_IO_SECRET_KEY environment variable is not set');
+    }
+    this.options = {
+      headers: {
+        Authorization: `Bearer ${process.env.SHORT_IO_SECRET_KEY}`,
+        'Content-Type': 'application/json',
+      },
+    };
+  }

12-12: 🛠️ Refactor suggestion

Make shortLinkDomain configurable via constructor

The domain should be configurable to support different environments and use cases.

Apply this diff:

-  shortLinkDomain = 'short.io';
+  private readonly shortLinkDomain: string;
+
+  constructor(domain?: string) {
+    if (!process.env.SHORT_IO_SECRET_KEY) {
+      throw new Error('SHORT_IO_SECRET_KEY environment variable is not set');
+    }
+    this.shortLinkDomain = domain || process.env.SHORT_IO_DOMAIN || 'short.io';
+    this.options = {
+      headers: {
+        Authorization: `Bearer ${process.env.SHORT_IO_SECRET_KEY}`,
+        'Content-Type': 'application/json',
+      },
+    };
+  }

14-32: ⚠️ Potential issue

Add error handling and input validation to linksStatistics method

The method needs proper error handling and input validation.

Apply this diff:

-  async linksStatistics(links: string[]) {
+  async linksStatistics(links: string[]): Promise<Array<{ short: string; original: string; clicks: number }>> {
+    if (!Array.isArray(links) || links.length === 0) {
+      throw new Error('Links must be a non-empty array');
+    }
+
     return Promise.all(
       links.map(async (link) => {
         const url = `https://api.short.io/links/expand?domain=${this.shortLinkDomain}&path=${link.split('/').pop()}`;
-        const response = await (fetch(url, options)
-          .then(res => res.json()));
+        let response;
+        try {
+          const res = await fetch(url, this.options);
+          if (!res.ok) {
+            throw new Error(`Failed to expand link: ${res.status} ${res.statusText}`);
+          }
+          response = await res.json();
+        } catch (error) {
+          console.error(`Error processing link ${link}:`, error);
+          throw error;
+        }

         const linkStatisticsUrl = `https://statistics.short.io/statistics/link/${response.id}?period=last30&tz=UTC`;

-        const statResponse = await (fetch(linkStatisticsUrl, options).then((res) => res.json()));
+        let statResponse;
+        try {
+          const res = await fetch(linkStatisticsUrl, this.options);
+          if (!res.ok) {
+            throw new Error(`Failed to get statistics: ${res.status} ${res.statusText}`);
+          }
+          statResponse = await res.json();
+        } catch (error) {
+          console.error(`Error fetching statistics for ${link}:`, error);
+          throw error;
+        }

         return {
           short: response.shortURL,
           original: response.originalURL,
           clicks: statResponse.totalClicks,
         };
       })
     );
   }

61-90: ⚠️ Potential issue

Fix pagination, response mapping, and add error handling in getAllLinksStatistics

The method has several critical issues including incorrect pagination and response mapping.

Apply this diff:

   async getAllLinksStatistics(
     id: string,
     page = 1
   ): Promise<{ short: string; original: string; clicks: string }[]> {
+    if (!id) {
+      throw new Error('Domain ID is required');
+    }
+
+    const limit = 100;
+    try {
       const response = await (
         await fetch(
-          `https://api.short.io/api/links?domain_id=${id}&limit=150`,
+          `https://api.short.io/api/links?domain_id=${id}&limit=${limit}&offset=${(page - 1) * limit}`,
           options
         )
       ).json();

-      const mapLinks = response.links.map(async (link: any) => {
+      if (!response.data || !Array.isArray(response.data)) {
+        throw new Error('Invalid response format from API');
+      }
+
+      const mapLinks = await Promise.all(response.data.map(async (link: any) => {
         const linkStatisticsUrl = `https://statistics.short.io/statistics/link/${response.id}?period=last30&tz=UTC`;

-        const statResponse = await(fetch(linkStatisticsUrl, options).then((res) => res.json()));
+        try {
+          const statResponse = await fetch(linkStatisticsUrl, this.options);
+          if (!statResponse.ok) {
+            throw new Error(`Failed to get statistics: ${statResponse.status}`);
+          }
+          const stats = await statResponse.json();
+
+          return {
-            short: link,
-            original: response.url,
-            clicks: statResponse.totalClicks,
+            short: link.shortURL,
+            original: link.originalURL,
+            clicks: stats.totalClicks,
           };
-      });
+        } catch (error) {
+          console.error(`Error fetching statistics for link ${link.id}:`, error);
+          throw error;
+        }
+      }));

-      if (mapLinks.length < 100) {
+      if (response.data.length < limit) {
         return mapLinks;
       }

       return [...mapLinks, ...(await this.getAllLinksStatistics(id, page + 1))];
+    } catch (error) {
+      console.error('Error fetching links:', error);
+      throw error;
+    }
   }
🧹 Nitpick comments (1)
libraries/nestjs-libraries/src/short-linking/providers/short.io.ts (1)

11-91: Consider implementing rate limiting

The Short.io API likely has rate limits. Consider implementing a rate limiter to prevent hitting API limits, especially in the recursive getAllLinksStatistics method and when processing multiple links in linksStatistics.

Consider using a rate limiting library like bottleneck:

import Bottleneck from 'bottleneck';

private readonly limiter = new Bottleneck({
  minTime: 100, // Minimum time between requests
  maxConcurrent: 5 // Maximum concurrent requests
});

// Then wrap API calls:
const response = await this.limiter.schedule(() => fetch(url, this.options));
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9c10d0f and 4d9ef69.

📒 Files selected for processing (1)
  • libraries/nestjs-libraries/src/short-linking/providers/short.io.ts (1 hunks)

Comment on lines +50 to +59
async convertShortLinkToLink(shortLink: string) {
return await(
await(
await fetch(
`https://api.short.io/links/expand?domain=${this.shortLinkDomain}&path=${shortLink.split('/').pop()}`,
options
)
).json()
).originalURL;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Simplify promise handling and add error handling in convertShortLinkToLink

The method has unnecessary nested awaits and lacks error handling.

Apply this diff:

-  async convertShortLinkToLink(shortLink: string) {
-    return await(
-      await(
-        await fetch(
-          `https://api.short.io/links/expand?domain=${this.shortLinkDomain}&path=${shortLink.split('/').pop()}`,
-          options
-        )
-      ).json()
-    ).originalURL;
+  async convertShortLinkToLink(shortLink: string): Promise<string> {
+    if (!shortLink) {
+      throw new Error('Short link is required');
+    }
+
+    try {
+      const path = shortLink.split('/').pop();
+      if (!path) {
+        throw new Error('Invalid short link format');
+      }
+
+      const response = await fetch(
+        `https://api.short.io/links/expand?domain=${this.shortLinkDomain}&path=${path}`,
+        this.options
+      );
+
+      if (!response.ok) {
+        throw new Error(`Failed to expand short link: ${response.status} ${response.statusText}`);
+      }
+
+      const data = await response.json();
+      return data.originalURL;
+    } catch (error) {
+      console.error('Error expanding short link:', error);
+      throw error;
+    }
   }

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +34 to +48
async convertLinkToShortLink ( id: string, link: string ) {
const response = await fetch( `https://api.short.io/links`, {
...options,
method: 'POST',
body: JSON.stringify( {
url: link,
tenantId: id,
domain: this.shortLinkDomain,
originalURL: link

} ),
} ).then( ( res ) => res.json() );

return response.shortURL;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix API parameters and add error handling in convertLinkToShortLink

The method has duplicate parameters and lacks error handling.

Apply this diff:

-  async convertLinkToShortLink(id: string, link: string) {
+  async convertLinkToShortLink(id: string, link: string): Promise<string> {
+    if (!link) {
+      throw new Error('Link is required');
+    }
+
+    try {
       const response = await fetch(`https://api.short.io/links`, {
         ...options,
         method: 'POST',
         body: JSON.stringify({
-          url: link,
-          tenantId: id,
+          originalURL: link,
+          domain_id: id,
           domain: this.shortLinkDomain,
-          originalURL: link
-
         }),
-      }).then((res) => res.json());
+      });
+
+      if (!response.ok) {
+        throw new Error(`Failed to create short link: ${response.status} ${response.statusText}`);
+      }
+
+      const data = await response.json();
+      return data.shortURL;
+    } catch (error) {
+      console.error('Error creating short link:', error);
+      throw error;
+    }
-    return response.shortURL;
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
async convertLinkToShortLink ( id: string, link: string ) {
const response = await fetch( `https://api.short.io/links`, {
...options,
method: 'POST',
body: JSON.stringify( {
url: link,
tenantId: id,
domain: this.shortLinkDomain,
originalURL: link
} ),
} ).then( ( res ) => res.json() );
return response.shortURL;
}
async convertLinkToShortLink(id: string, link: string): Promise<string> {
if (!link) {
throw new Error('Link is required');
}
try {
const response = await fetch(`https://api.short.io/links`, {
...options,
method: 'POST',
body: JSON.stringify({
originalURL: link,
domain_id: id,
domain: this.shortLinkDomain,
}),
});
if (!response.ok) {
throw new Error(`Failed to create short link: ${response.status} ${response.statusText}`);
}
const data = await response.json();
return data.shortURL;
} catch (error) {
console.error('Error creating short link:', error);
throw error;
}
}

@ak7550 ak7550 changed the title short.io integration is in progress. feat: #544-Add short.io as a shortening link Jan 19, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant