Skip to content

Commit

Permalink
feat: vote repos (#70)
Browse files Browse the repository at this point in the history
  • Loading branch information
diivi authored May 12, 2023
1 parent 7eb4c76 commit ff2c8f4
Show file tree
Hide file tree
Showing 9 changed files with 189 additions and 11 deletions.
8 changes: 0 additions & 8 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,14 +43,6 @@ module.exports = {
rules: {
// eslint:recommended
"arrow-body-style": ["error", "as-needed"],
"capitalized-comments": [
"error",
"never",
{
ignorePattern: "pragma|ignored",
ignoreInlineComments: true,
},
],
curly: ["error", "all"],
"dot-notation": "error",
eqeqeq: ["error", "always"],
Expand Down
9 changes: 8 additions & 1 deletion src/constants.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
// OpenSauced constants
export const SUPABASE_LOGIN_URL = "https://ibcwmlhcimymasokhgvn.supabase.co/auth/v1/authorize?provider=github&redirect_to=https://insights.opensauced.pizza/";
export const SUPABASE_AUTH_COOKIE_NAME = "supabase-auth-token";
export const OPEN_SAUCED_AUTH_TOKEN_KEY = "os-access-token";
export const OPEN_SAUCED_INSIGHTS_DOMAIN = "insights.opensauced.pizza";

// API endpoints
export const OPEN_SAUCED_USERS_ENDPOINT = "https://api.opensauced.pizza/v1/users";
export const OPEN_SAUCED_REPOS_ENDPOINT = "https://api.opensauced.pizza/v1/repos";
export const OPEN_SAUCED_SESSION_ENDPOINT = "https://api.opensauced.pizza/v1/auth/session";
export const OPEN_SAUCED_INSIGHTS_DOMAIN = "insights.opensauced.pizza";

// GitHub constants/selectors
export const GITHUB_PROFILE_MENU_SELECTOR = ".p-nickname.vcard-username.d-block";
export const GITHUB_PROFILE_EDIT_MENU_SELECTOR = "button.js-profile-editable-edit-button";
export const GITHUB_PR_AUTHOR_USERNAME_SELECTOR = "author Link--primary text-bold css-overflow-wrap-anywhere";
export const GITHUB_LOGGED_IN_USER_USERNAME_SELECTOR = "meta[name=\"user-login\"]";
export const GITHUB_PR_COMMENT_HEADER_SELECTOR = "timeline-comment-header clearfix d-flex";
export const GITHUB_REPO_ACTIONS_SELECTOR = ".pagehead-actions";
36 changes: 36 additions & 0 deletions src/content-scripts/components/RepoVoting/RepoUnvoteButton.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import "../../content-scripts.css";
import { createHtmlElement } from "../../../utils/createHtmlElement";
import { getAuthToken } from "../../../utils/checkAuthentication";
import { VoteRepoButton } from "./RepoVoteButton";
import { voteOrUnvoteRepo } from "../../../utils/fetchOpenSaucedApiData";

export const RepoUnvoteButton = (ownerName: string, repoName: string) => {
const repoUnvoteButton = createHtmlElement("li", {
className:
"text-white text-center hover:shadow-button bg-gradient-to-r from-[#e67e22] to-[#d35400] btn btn-sm",
innerHTML: `
<span>Unvote</span>
<svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 24 24" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg" class="align-middle"><g><path fill="none" d="M0 0h24v24H0z"></path><path d="M12 22C6.477 22 2 17.523 2 12S6.477 2 12 2s10 4.477 10 10-4.477 10-10 10zm-.997-6l7.07-7.071-1.414-1.414-5.656 5.657-2.829-2.829-1.414 1.414L11.003 16z"></path></g></svg>
`,
});

repoUnvoteButton.addEventListener("click", async () => {
repoUnvoteButton.innerHTML = `
<span>Loading...</span>
`;
const userToken = await getAuthToken();

const unvoted = await voteOrUnvoteRepo(userToken, ownerName, repoName, false);

if (unvoted) {
const voteRepoButton = VoteRepoButton(ownerName, repoName);

repoUnvoteButton.replaceWith(voteRepoButton);
} else {
console.log("Something went wrong");
}
});


return repoUnvoteButton;
};
35 changes: 35 additions & 0 deletions src/content-scripts/components/RepoVoting/RepoVoteButton.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import "../../content-scripts.css";
import { createHtmlElement } from "../../../utils/createHtmlElement";
import { getAuthToken } from "../../../utils/checkAuthentication";
import { RepoUnvoteButton } from "./RepoUnvoteButton";
import { voteOrUnvoteRepo } from "../../../utils/fetchOpenSaucedApiData";

export const VoteRepoButton = (ownerName: string, repoName: string) => {
const voteRepoButton = createHtmlElement("li", {
className:
"text-white text-center hover:shadow-button bg-gradient-to-r from-[#e67e22] to-[#d35400] btn btn-sm",
innerHTML: `
<span>Upvote</span>
<svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 24 24" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg" class="align-middle"><g><path fill="none" d="M0 0h24v24H0z"></path><path d="M12 22C6.477 22 2 17.523 2 12S6.477 2 12 2s10 4.477 10 10-4.477 10-10 10zm-.997-6l7.07-7.071-1.414-1.414-5.656 5.657-2.829-2.829-1.414 1.414L11.003 16z"></path></g></svg>
`,
});

voteRepoButton.addEventListener("click", async () => {
voteRepoButton.innerHTML = `
<span>Loading...</span>
`;
const userToken = await getAuthToken();

const voted = await voteOrUnvoteRepo(userToken, ownerName, repoName, false);

if (voted) {
const unvoteRepoButton = RepoUnvoteButton(ownerName, repoName);

voteRepoButton.replaceWith(unvoteRepoButton);
} else {
console.log("Something went wrong");
}
});

return voteRepoButton;
};
9 changes: 8 additions & 1 deletion src/content-scripts/github.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@ import {
getGithubUsername,
isGithubProfilePage,
isGithubPullRequestPage,
isGithubRepoPage,
} from "../utils/urlMatchers";
import { isOpenSaucedUser } from "../utils/fetchOpenSaucedApiData";
import injectViewOnOpenSauced from "../utils/dom-utils/viewOnOpenSauced";
import injectInviteToOpenSauced from "../utils/dom-utils/inviteToOpenSauced";
import { prefersDarkMode } from "../utils/colorPreference";
import injectAddPRToHighlightsButton from "../utils/dom-utils/addPRToHighlights";
import injectRepoVotingButtons from "../utils/dom-utils/repoVotingButtons";
import domUpdateWatch from "../utils/dom-utils/domUpdateWatcher";

const processGithubPage = async () => {
Expand All @@ -16,7 +18,7 @@ const processGithubPage = async () => {
}

if (isGithubPullRequestPage(window.location.href)) {
injectAddPRToHighlightsButton();
await injectAddPRToHighlightsButton();
} else if (isGithubProfilePage(window.location.href)) {
const username = getGithubUsername(window.location.href);

Expand All @@ -28,6 +30,11 @@ const processGithubPage = async () => {
} else {
injectInviteToOpenSauced(username);
}
} else if (isGithubRepoPage(window.location.href)) {
const ownerName = getGithubUsername(window.location.href) ?? "";
const repoName = window.location.href.split("/").pop() ?? "";

await injectRepoVotingButtons(ownerName, repoName);
}

domUpdateWatch(processGithubPage, 25);
Expand Down
3 changes: 3 additions & 0 deletions src/utils/checkAuthentication.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,6 @@ export const isLoggedIn = async () =>
// only a valid auth token can exist in the storage due to the check in line 23
Object.entries(await chrome.storage.sync.get(OPEN_SAUCED_AUTH_TOKEN_KEY)).length !== 0;

export const getAuthToken = async () => (await chrome.storage.sync.get(OPEN_SAUCED_AUTH_TOKEN_KEY))[OPEN_SAUCED_AUTH_TOKEN_KEY];


42 changes: 42 additions & 0 deletions src/utils/dom-utils/repoVotingButtons.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { GITHUB_REPO_ACTIONS_SELECTOR } from "../../constants";
import { VoteRepoButton } from "../../content-scripts/components/RepoVoting/RepoVoteButton";
import { RepoUnvoteButton } from "../../content-scripts/components/RepoVoting/RepoUnvoteButton";
import { isLoggedIn, getAuthToken } from "../checkAuthentication";
import {
checkUserVotedRepo,
repoExistsOnOpenSauced,
} from "../fetchOpenSaucedApiData";

const injectRepoVotingButtons = async (ownerName: string, repoName: string) => {
if (!(await isLoggedIn())) {
return;
}
if (!(await repoExistsOnOpenSauced(ownerName, repoName))) {
return;
}
const repoActions = document.querySelector(GITHUB_REPO_ACTIONS_SELECTOR);

if (!repoActions) {
return;
}

const voteRepoButton = VoteRepoButton(ownerName, repoName);
const repoUnvoteButton = RepoUnvoteButton(ownerName, repoName);
const userToken = await getAuthToken();

const userVotedRepo = await checkUserVotedRepo(userToken, repoName);

if (userVotedRepo) {
if (repoActions.lastChild?.isEqualNode(repoUnvoteButton)) {
return;
}
repoActions.appendChild(repoUnvoteButton);
} else {
if (repoActions.lastChild?.isEqualNode(voteRepoButton)) {
return;
}
repoActions.appendChild(voteRepoButton);
}
};

export default injectRepoVotingButtons;
51 changes: 50 additions & 1 deletion src/utils/fetchOpenSaucedApiData.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { cachedFetch } from "./cache";
import { OPEN_SAUCED_USERS_ENDPOINT, OPEN_SAUCED_SESSION_ENDPOINT } from "../constants";
import { OPEN_SAUCED_USERS_ENDPOINT, OPEN_SAUCED_SESSION_ENDPOINT, OPEN_SAUCED_REPOS_ENDPOINT } from "../constants";

export const isOpenSaucedUser = async (username: string) => {
try {
Expand Down Expand Up @@ -50,3 +50,52 @@ export const getUserPRData = async (userName: string, forceRefresh: boolean = fa
return resp?.json();
})
.then(json => json);

const getUserVotes = async (userToken: string, page: number = 1, limit: number = 1000, repos: any[] = []): Promise<any[]> => {
const response = await fetch(
`${OPEN_SAUCED_REPOS_ENDPOINT}/listUserVoted?page=${page}&limit=${limit}`,
{
method: "GET",
headers: { Authorization: `Bearer ${userToken}` },
},
);

if (response.status === 200) {
const votesData = await response.json();

const newRepos = repos.concat(votesData.data);

if (votesData.data.length === limit) {
return getUserVotes(userToken, page + 1, limit, newRepos);
}
return newRepos;
}
return repos;
};


export const checkUserVotedRepo = async (userToken: string, repoName: string) => {
const userVotes = await getUserVotes(userToken);

return userVotes.some((repo: any) => repo.name === repoName);
};

export const repoExistsOnOpenSauced = async (ownerName: string, repoName: string) => {
const response = await fetch(
`${OPEN_SAUCED_REPOS_ENDPOINT}/${ownerName}/${repoName}`,
);

return response.status === 200;
};

export const voteOrUnvoteRepo = async (userToken: string, ownerName: string, repoName: string, vote: boolean) => {
const response = await fetch(
`${OPEN_SAUCED_REPOS_ENDPOINT}/${ownerName}/${repoName}/vote`,
{
method: vote ? "POST" : "DELETE",
headers: { Authorization: `Bearer ${userToken}` },
},
);

return response.status === 200;
};
7 changes: 7 additions & 0 deletions src/utils/urlMatchers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,10 @@ export const isGithubProfilePage = (url: string) => {

return githubProfilePattern.test(url);
};


export const isGithubRepoPage = (url: string) => {
const githubRepoPattern = /github\.com\/[^/]+\/[^/]+$/;

return githubRepoPattern.test(url);
};

0 comments on commit ff2c8f4

Please sign in to comment.