Skip to content

Commit

Permalink
feat: implement sync with GitHub Sync button (#141)
Browse files Browse the repository at this point in the history
* feat: added API endpoint for sync

* feat: implement synchronization logic

* feat: profile supports sync with github action
  • Loading branch information
sboy99 committed Apr 1, 2023
1 parent 04ce708 commit 211b940
Show file tree
Hide file tree
Showing 5 changed files with 158 additions and 36 deletions.
54 changes: 33 additions & 21 deletions contribution/mishba-ai.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -17,32 +17,44 @@ I love to contributing to OpenSource and getting involved in awesome tech commun
<Skills>
<Skill
name="html"
icon={
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-brand-html5" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<title>html</title>
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path d="M20 4l-2 14.5l-6 2l-6 -2l-2 -14.5z"></path>
<path d="M15.5 8h-7l.5 4h6l-.5 3.5l-2.5 .75l-2.5 -.75l-.1 -.5"></path>
</svg>

}
level="Intermediate"
icon={
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
stroke="currentColor"
fill="none"
>
<title>html</title>
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path d="M20 4l-2 14.5l-6 2l-6 -2l-2 -14.5z"></path>
<path d="M15.5 8h-7l.5 4h6l-.5 3.5l-2.5 .75l-2.5 -.75l-.1 -.5"></path>
</svg>
}
level="Intermediate"
>
Html
Html
</Skill>
<Skill
name="CSS"
icon={
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-brand-css3" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<title>CSS</title>
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path d="M20 4l-2 14.5l-6 2l-6 -2l-2 -14.5z"></path>
<path d="M8.5 8h7l-4.5 4h4l-.5 3.5l-2.5 .75l-2.5 -.75l-.1 -.5"></path>
</svg>

}level="Intermediate"
icon={
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
stroke="currentColor"
fill="none"
>
<title>CSS</title>
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path d="M20 4l-2 14.5l-6 2l-6 -2l-2 -14.5z"></path>
<path d="M8.5 8h7l-4.5 4h4l-.5 3.5l-2.5 .75l-2.5 -.75l-.1 -.5"></path>
</svg>
}
level="Intermediate"
>
CSS
</Skill>
</Skills>

90 changes: 76 additions & 14 deletions src/components/contributorProfilePage/Profile.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
import type { Contribution } from '@/types/client/Contributors';
import type { TContributor } from '@/types/server/Contributors';
import type { FC } from 'react';
import { FC, useEffect, useState } from 'react';

import { BriefcaseIcon, MapPinIcon } from '@heroicons/react/24/outline';
import axios from '@/config/axios';
import {
ArrowPathIcon,
BriefcaseIcon,
MapPinIcon,
} from '@heroicons/react/24/outline';
import ToolTip from '@utilities/Tooltip';
import TypoComp from '@utilities/TypoComponent';
import { useRouter } from 'next/router';
import ProfileImage from './ProfileImage';

export type ProfileProps = {
Expand All @@ -12,6 +19,43 @@ export type ProfileProps = {
};

const Profile: FC<ProfileProps> = ({ meta, contributor }) => {
const [isSyncing, setIsSyncing] = useState<boolean>(false);
const [isSyncActive, setIsSyncActive] = useState<boolean>(false);
const router = useRouter();
const { contId } = router.query;

useEffect(() => {
async function fetchContributor() {
try {
const { data: contributor } = await axios.get(
`/contributors/${contId}`
);
const lastSynced = new Date(contributor.updatedAt);
const currentTime = new Date(Date.now());
const timegap = Math.abs(currentTime.getTime() - lastSynced.getTime());
const diffDay = Math.floor(timegap / 1000 / 60 / 60 / 24);
if (diffDay > 2) setIsSyncActive(true);
} catch (error) {
console.log(error);
setIsSyncActive(false);
}
}
fetchContributor();
}, [contId]);

async function syncWithGithub() {
setIsSyncing(true);
try {
await axios.patch(`/contributors/sync/${contId}`);
setIsSyncActive(false);
} catch (error) {
console.log(error);
} finally {
setIsSyncing(false);
}
}

// JSX
return (
<div className="relative z-0 pb-12">
<div className="bg-squares -mt-4"></div>
Expand All @@ -26,21 +70,39 @@ const Profile: FC<ProfileProps> = ({ meta, contributor }) => {
{/* top */}
<TypoComp className="pt-20 sm:pt-24">
{/* Name */} {/* occupation */}
<h1>
{meta.author}{' '}
{meta.pronouns && (
<span className="text-base font-semibold text-skin-muted">
{meta.pronouns}
</span>
)}
</h1>
<div className="flex h-fit gap-x-4">
<h1>
{meta.author}{' '}
{meta.pronouns && (
<span className="text-base font-semibold text-skin-muted">
{meta.pronouns}
</span>
)}
</h1>
{/* sync button */}
<ToolTip tip="Sync with Github">
<button
disabled={!isSyncActive || isSyncing}
onClick={syncWithGithub}
className={`rounded-full p-2 transition-all duration-150 ease-in-out hover:bg-skin-shine ${
isSyncActive
? 'text-skin-base hover:text-accent'
: 'text-skin-muted/50'
} ${isSyncing && `animate-spin`}`}
>
<ArrowPathIcon className={`h-6 w-6`} />
</button>
</ToolTip>
</div>
<p>{contributor.bio ? contributor.bio : `${meta.author}'s bio`}</p>
<div className="not-prose flex flex-wrap items-center gap-x-6 gap-y-2 text-skin-muted xs:prose">
{/* location */}
<div className="flex items-center gap-x-2">
<MapPinIcon className="h-6 w-6" />
<p>{contributor.location}</p>
</div>
{contributor.location && (
<div className="flex items-center gap-x-2">
<MapPinIcon className="h-6 w-6" />
<p>{contributor.location}</p>
</div>
)}
{/* twitter */}
<div className="flex items-center gap-x-2">
<BriefcaseIcon className="h-6 w-6" />
Expand Down
2 changes: 1 addition & 1 deletion src/pages/api/v1/contributors/[contId].ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
import connectDB from '@/server/db/connectDB';
import { NextApiHandler } from 'next';

// end-point: origin/api/v1/contributors
// end-point: origin/api/v1/contributors/:contId
const handler: NextApiHandler = async (req, res) => {
const { method } = req;
if (method === ('GET' as 'GET')) {
Expand Down
19 changes: 19 additions & 0 deletions src/pages/api/v1/contributors/sync/[contId].ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { syncWithGithub } from '@/server/controllers/contributors';
import connectDB from '@/server/db/connectDB';
import { NextApiHandler } from 'next';

// end-point: origin/api/v1/contributors/sync/:contId
const handler: NextApiHandler = async (req, res) => {
console.log('Hii');

const { method } = req;
if (method === 'PATCH') {
// handle post request
return syncWithGithub(req, res);
} else {
// error no supported method
res.status(405).json({ message: `method is not supported` });
}
};

export default connectDB(handler);
29 changes: 29 additions & 0 deletions src/server/controllers/contributors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import errorHandler from '&/middlewares/errorHandler';
import Contributor from '&/models/contributors';
import { assertIsString } from '&/validator/assertionGuards';
import ContV from '&validation/contributor.validation';
import octokit from '@/server/config/octokit';
import type { TContributor } from '@/types/server/Contributors';
import type { NextApiHandler } from 'next';

Expand Down Expand Up @@ -102,6 +103,10 @@ export const updateContributor: NextApiHandler = errorHandler(
'email',
'profile_views',
'location',
'followers',
'following',
'html_url',
'name',
]);
const findContributor = await Contributor.findOne({
gh_username: contId,
Expand Down Expand Up @@ -130,3 +135,27 @@ export const deleteContributor: NextApiHandler = errorHandler(
res.status(200).json({ message: `Contributor deleted` });
}
);

export const syncWithGithub: NextApiHandler = errorHandler(async (req, res) => {
const { contId } = req.query;

assertIsString(contId, `Route does not exist`);
const contributor = await Contributor.findOne({ gh_username: contId });
if (!contributor) {
throw new ERR.Not_Found(`Contributor ${contId} has not contributed yet`);
}
const { data: ghUser } = await octokit.request('GET /users/{username}', {
username: contId,
});

contributor.html_url = ghUser.html_url;
contributor.name = ghUser.name as string;
contributor.bio = ghUser.bio;
contributor.email = ghUser.email;
contributor.location = ghUser.location;
contributor.followers = ghUser.followers;
contributor.following = ghUser.following;

const mContributor = await contributor.save();
res.status(200).json(mContributor);
});

1 comment on commit 211b940

@vercel
Copy link

@vercel vercel bot commented on 211b940 Apr 1, 2023

Choose a reason for hiding this comment

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

Please sign in to comment.