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

Support API calls via backend #6

Open
yashsehgal opened this issue Sep 27, 2024 · 6 comments
Open

Support API calls via backend #6

yashsehgal opened this issue Sep 27, 2024 · 6 comments
Assignees
Labels
API docs hacktoberfest Related to Hacktoberfest

Comments

@yashsehgal
Copy link
Owner

yashsehgal commented Sep 27, 2024

Planning to work on a backend supported set of methods similar to all the ones we have inside useGitHub() hook. Keeping a hook for all the methods will be a handy thing in frontend, but for server-side modules and SSR, it will not work in such cases.

As a solution, we should have a class-based implementation supported as well. Which can have a DX something like,

const gh = utiliseGitHubRestAPI();
const userInfo = gh.getUserInfo(); // returns the user info on function call
const repos = gh.getRepositories(); // returns the user repositories on function call

These methods will perform the same actions as the functions inside useGitHub(). Also, these methods will be shared across both hooks and classes, considering the better coding patterns.

@Akshaygore1
Copy link

can we discuss this on this love to work on this issue

@yashsehgal
Copy link
Owner Author

Good to have you here @Akshaygore1 - I would like to discuss this improvement project with you. I have couple of initial questions before starting, that will be helpful for leading the discussion.

  • What are the current limitations you see in the current developer experience + the methods present inside the hook.
  • Can you please also share a POC / Pseudo-code version of how the current hook methods will have their similar getter-methods, respectively.

Thanks very much!

@techmannih
Copy link

@yashsehgal Can I work on this issue

@Akshaygore1
Copy link

Akshaygore1 commented Oct 5, 2024

Hey @yashsehgal

implemented the class like implementations for the apis

import axios from "axios";
import {
  IGitHubUserInfo,
  IGitHubRepo,
  ProgrammingLanguage,
  LanguageDistribution,
} from "./interfaces.js";

const GITHUB_REST_URL: string = "https://api.github.com" as const;
const GITHUB_GRAPHQL_URL: string = "https://api.github.com/graphql" as const;
const GITHUB_RAW_CONTENT_URL: string =
  "https://raw.githubusercontent.com" as const;

class GitHubService {
  private username: string;
  private personalAccessToken: string | null;

  constructor(username: string, personalAccessToken: string | null = null) {
    this.username = username;
    this.personalAccessToken = personalAccessToken;
  }

  // Fetch GitHub User Info
  async getUserInfo(): Promise<IGitHubUserInfo | null> {
    if (!this.username) return null;

    try {
      const response = await axios.get(
        `${GITHUB_REST_URL}/users/${this.username}`
      );
      return response.data as IGitHubUserInfo;
    } catch (error) {
      console.error("Error while fetching GitHub user info:", error);
      return null;
    }
  }

  // Fetch repositories
  async getRepositories(): Promise<IGitHubRepo[]> {
    if (!this.username) return [];

    try {
      const response = await axios.get(
        `${GITHUB_REST_URL}/users/${this.username}/repos?per_page=100&sort=updated`
      );
      return response.data.map((repo: any) => ({
        ...repo,
        language: repo.language
          ? (repo.language.toLowerCase() as ProgrammingLanguage)
          : null,
      })) as IGitHubRepo[];
    } catch (error) {
      console.error("Error while fetching repositories:", error);
      return [];
    }
  }

  // Fetch pinned repositories via GitHub GraphQL API
  async getPinnedRepositories(): Promise<IGitHubRepo[]> {
    if (!this.username || !this.personalAccessToken) return [];

    const query = `
      query {
        user(login: "${this.username}") {
          pinnedItems(first: 6, types: REPOSITORY) {
            nodes {
              ... on Repository {
                id
                name
                description
                url
                stargazerCount
                forkCount
                primaryLanguage {
                  name
                }
              }
            }
          }
        }
      }
    `;

    try {
      const response = await axios.post(
        GITHUB_GRAPHQL_URL,
        { query },
        {
          headers: {
            Authorization: `Bearer ${this.personalAccessToken}`,
          },
        }
      );

      return response.data.data.user.pinnedItems.nodes.map((repo: any) => ({
        id: repo.id,
        name: repo.name,
        description: repo.description,
        html_url: repo.url,
        stargazers_count: repo.stargazerCount,
        forks_count: repo.forkCount,
        language: repo.primaryLanguage
          ? (repo.primaryLanguage.name.toLowerCase() as ProgrammingLanguage)
          : null,
      })) as IGitHubRepo[];
    } catch (error) {
      console.error("Error while fetching pinned repositories:", error);
      return [];
    }
  }

  // Fetch followers
  async getFollowers(): Promise<IGitHubUserInfo[]> {
    if (!this.username) return [];

    try {
      const response = await axios.get(
        `${GITHUB_REST_URL}/users/${this.username}/followers?per_page=100`
      );
      const followerPromises = response.data.map((follower: { url: string }) =>
        axios.get(follower.url)
      );
      const followerResponses = await Promise.all(followerPromises);
      return followerResponses.map(
        (response) => response.data as IGitHubUserInfo
      );
    } catch (error) {
      console.error("Error while fetching followers:", error);
      return [];
    }
  }

  // Fetch followings
  async getFollowings(): Promise<IGitHubUserInfo[]> {
    if (!this.username) return [];

    try {
      const response = await axios.get(
        `${GITHUB_REST_URL}/users/${this.username}/following?per_page=100`
      );
      const followingPromises = response.data.map(
        (following: { url: string }) => axios.get(following.url)
      );
      const followingResponses = await Promise.all(followingPromises);
      return followingResponses.map(
        (response) => response.data as IGitHubUserInfo
      );
    } catch (error) {
      console.error("Error while fetching followings:", error);
      return [];
    }
  }

  // Fetch README content from profile
  async getProfileReadme(): Promise<string | null> {
    if (!this.username) return null;

    try {
      const response = await axios.get(
        `${GITHUB_RAW_CONTENT_URL}/${this.username}/${this.username}/main/README.md`
      );
      return response.data;
    } catch (error) {
      console.error("Error while fetching profile README:", error);
      return null;
    }
  }

  // Calculate language distribution
  calculateLanguageDistribution(repos: IGitHubRepo[]): LanguageDistribution[] {
    const languageCounts: { [key in ProgrammingLanguage]?: number } = {};
    let totalCount = 0;

    repos.forEach((repo) => {
      if (repo.language) {
        languageCounts[repo.language] =
          (languageCounts[repo.language] || 0) + 1;
        totalCount++;
      }
    });

    return Object.entries(languageCounts).map(([language, count]) => ({
      language: language as ProgrammingLanguage,
      percentage: count! / totalCount,
    }));
  }
}

export default GitHubService;

working fine for now

@yashsehgal
Copy link
Owner Author

Hi @Akshaygore1 - Great efforts, thanks for spending time on this one. This class-based implementation looks promising and I think we can start adding these updates and you can create a PR for the same.

Also, before you create the PR, we can discuss how we are going expose and utilise these methods.

For eg., I was thinking of exposing the class itself with name GitHubService or GitHubRestAPI (we can discuss on the class name later for sure) and let the user call the individual methods by their respective getter-functions.

Also, would you like to work on it's documentation as well. In #2 there's work going on related to docs Migration, we can discuss about the docs part there, if you're willing to take up that part.

Thanks you so much! Excited for the PR 🎉

@Akshaygore1
Copy link

Akshaygore1 commented Oct 6, 2024

Hey @yashsehgal ,

  1. Where should I implement this? Should I create a folder for the module, and how will we expose these getter functions?
  2. I'll also work on the documentation.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
API docs hacktoberfest Related to Hacktoberfest
Projects
None yet
Development

No branches or pull requests

3 participants