Closed eddiejaoude closed 1 year ago
To reduce notifications, issues are locked until they are https://github.com/EddieHubCommunity/LinkFree/labels/%F0%9F%8F%81%20status%3A%20ready%20for%20dev and to be assigned. You can learn more in our contributing guide https://github.com/EddieHubCommunity/LinkFree/blob/main/CONTRIBUTING.md
The issue has been unlocked and is now ready for dev. If you would like to work on this issue, you can comment to have it assigned to you. You can learn more in our contributing guide https://github.com/EddieHubCommunity/LinkFree/blob/main/CONTRIBUTING.md
Hi, can i work on this issue.
@kunalshokeen051 great you are keen to contribute to the project, but this is a Points: 8
Issue so it is best if it done by someone who is more familiar with the project. We recommend new contributors start of with Issues which have Points: 1 to Points: 3
Ok, Thanks for the reply.
Please assign this issue to me. I guess I have spent a decent amount of time understanding the Linkfree codebase. I feel like I can work on this issue.
Please assign this issue to me. I guess I have spent a decent amount of time understanding the Linkfree codebase. I feel like I can work on this issue.
@akash19coder I have assigned this to you
Approach:
Step 1: Github Username Monitoring
id
to detect username changes in BioDrop’s user GitHubAccount because it remains constant for a GitHub User. We will periodically perform checks to detect if username has changed GithubProvider({
clientId: serverEnv.GITHUB_ID,
clientSecret: serverEnv.GITHUB_SECRET,
profile(profile) {
return {
id: profile.id.toString(), //we are talking about this id
name: profile.name ?? profile.login,
username: profile.login,
email: profile.email,
image: profile.avatar_url,
};
},
})
Step 2: Alerting Mechanism
Step 3:
Step 4: URL Redirection (could be a separate issue)
Step 1: Github Username Monitoring Step 2: Alerting Mechanism
No monitoring is required, when they log in, we know their GitHub username, if this doesn't match their profile username we should show the alert. The alert should only be showed on the account statistics page
Step 3: When user clicks on the rename button, a warning message will pop up telling user that their BioDrop link will change. The user clicks on it and their new Github username will be fetched.
We have their new username, when they accept the warning message, we need to update all references to their old username to their new username
If we don't implement monitoring, we can only track the change in username if and only if the session has expired and the user re-logs in. Did I understand it correctly?
yes that is correct 👍 - I don't think people will change their username often, plus let's keep it simple for now, because at this moment in time we have nothing - we can add more functionality later if we need
[...nextAuth.js] file
import NextAuth from "next-auth";
import GithubProvider from "next-auth/providers/github";
import { serverEnv } from "@config/schemas/serverSchema";
import DbAdapter from "./db-adapter";
import connectMongo from "@config/mongo";
import { Account, Profile } from "@models/index";
import {
getAccountByProviderAccountId,
associateProfileWithAccount,
} from "../account/account";
let profileUsername = "";
let githubProfileUsername = "";
let profileID = "";
export const authOptions = {
adapter: DbAdapter(connectMongo),
providers: [
GithubProvider({
clientId: serverEnv.GITHUB_ID,
clientSecret: serverEnv.GITHUB_SECRET,
profile(profile) {
return {
id: profile.id.toString(),
name: profile.name ?? profile.login,
username: profile.login,
email: profile.email,
image: profile.avatar_url,
};
},
}),
],
session: {
strategy: "jwt",
},
callbacks: {
async signIn({ user, profile: githubProfile }) {
// console.log(user);
// console.log(githubProfile);
await Account.findOneAndUpdate(
{ userId: user._id },
{
github: {
company: githubProfile.company,
publicRepos: githubProfile.public_repos,
followers: githubProfile.followers,
following: githubProfile.following,
},
},
{ upsert: true }
);
return true;
},
async redirect({ baseUrl }) {
return `${baseUrl}/account/statistics`;
},
async jwt({ token, account, profile }) {
// Persist the OAuth access_token and or the user id to the token right after signin
if (account) {
account = await getAccountByProviderAccountId(profile.id);
profile = await Profile.findOne({
_id: account.profiles[0],
// username: githubProfile.username,
});
token.accessToken = account.access_token;
token.id = profile.id;
token.username = profile.username;
}
return token;
},
async session({ session, token }) {
// Send properties to the client, like an access_token and user id from a provider.
session.accessToken = token.accessToken;
session.user.id = token.id;
session.username = token.username;
return session;
},
},
pages: {
signIn: "/auth/signin",
},
events: {
async signIn({ profile: githubProfile }) {
// associate BioDrop profile to BioDrop account
const account = await getAccountByProviderAccountId(githubProfile.id);
const profile = await Profile.findOne({
_id: account.profiles[0],
});
profileUsername = profile.username;
githubProfileUsername = githubProfile.username;
profileID = profile._id.toString();
console.log(profileUsername);
console.log(githubProfileUsername);
console.log(profileID);
if (profile) {
await associateProfileWithAccount(account, profile._id);
}
},
},
};
export { profileUsername, githubProfileUsername, profileID };
export default NextAuth(authOptions);
Statistics.js file
import { authOptions } from "../api/auth/[...nextauth]";
import { useState } from "react";
import { getServerSession } from "next-auth/next";
import dynamic from "next/dynamic";
import ProgressBar from "@components/statistics/ProgressBar";
import { getUserApi } from "../api/profiles/[username]";
import { clientEnv } from "@config/schemas/clientSchema";
import { getStats } from "../api/account/statistics";
import logger from "@config/logger";
import Alert from "@components/Alert";
import Page from "@components/Page";
import PageHead from "@components/PageHead";
import { abbreviateNumber } from "@services/utils/abbreviateNumbers";
import Navigation from "@components/account/manage/Navigation";
import UserMini from "@components/user/UserMini";
import ConfirmDialog from "@components/ConfirmDialog";
import { PROJECT_NAME } from "@constants/index";
import {
profileUsername,
githubProfileUsername,
profileID,
} from "../api/auth/[...nextauth]";
import { Profile } from "@models/index";
console.log(Profile);
const DynamicChart = dynamic(
() => import("../../components/statistics/StatsChart"),
{ ssr: false }
);
export async function getServerSideProps(context) {
const { req, res } = context;
const session = await getServerSession(req, res, authOptions);
if (!session) {
return {
redirect: {
destination: "/auth/signin",
permanent: false,
},
};
}
const username = session.username;
const { status, profile } = await getUserApi(req, res, username);
if (status !== 200) {
logger.error(
profile.error,
`profile loading failed for username: ${username}`
);
return {
redirect: {
destination: "/account/no-profile",
permanent: false,
},
};
}
let data = {};
let profileSections = [
"links",
"milestones",
"tags",
"socials",
"testimonials",
];
let progress = {
percentage: 0,
missing: [],
};
try {
data = await getStats(username);
} catch (e) {
logger.error(e, "ERROR get user's account statistics");
}
progress.missing = profileSections.filter(
(property) => !profile[property]?.length
);
progress.percentage = (
((profileSections.length - progress.missing.length) /
profileSections.length) *
100
).toFixed(0);
data.links.individual = data.links.individual.filter((link) =>
profile.links.some((pLink) => pLink.url === link.url)
);
const totalClicks = data.links.individual.reduce((acc, link) => {
return acc + link.clicks;
}, 0);
data.links.clicks = totalClicks;
data.profile.daily = data.profile.daily.map((day) => {
return {
views: day.views,
date: day.date,
};
});
return {
props: {
data,
profile,
progress,
BASE_URL: clientEnv.NEXT_PUBLIC_BASE_URL,
},
};
}
export default function Statistics({ data, profile, progress, BASE_URL }) {
const [isConfirmDialogOpen, setIsConfirmDialogOpen] = useState(false);
const openConfirmDialog = () => {
setIsConfirmDialogOpen(true);
};
const handleRename = async () => {
openConfirmDialog();
};
const renameUsername = async (
profileUsername,
githubProfileUsername,
profileID
) => {
if (profileUsername !== githubProfileUsername) {
try {
const updatedProfile = await Profile.findByIdAndUpdate(
{ _id: profileID },
{ username: githubProfileUsername },
{ new: true }
);
if (updatedProfile) {
console.log(`Updated username to: ${updatedProfile.username}`);
} else {
console.log(`Profile with ID ${profileID} not found.`);
}
} catch (error) {
console.error(`Error updating profile: ${error}`);
}
} else {
console.log(`Profile username is already "${profileUsername}"`);
}
};
return (
<>
<PageHead
title={PROJECT_NAME + " Statistics"}
description="Private statistics for your account"
/>
<Page>
<Navigation />
<Alert
type="info"
message="We've detected change in your Github username. Please rename your biodrop profile with new changes"
buttonLabel="Rename"
onButtonAction={handleRename}
/>
<ConfirmDialog
open={isConfirmDialogOpen}
setOpen={setIsConfirmDialogOpen}
title="Your profile URL will change"
description="Are you sure you want to rename?"
buttonText="Rename"
action={async () => {
try {
await renameUsername(
profileUsername,
githubProfileUsername,
profileID
);
setIsConfirmDialogOpen(false);
} catch (error) {
console.error("Error renaming username:", error);
}
}}
/>
<UserMini
BASE_URL={BASE_URL}
username={profile.username}
name={profile.name}
bio={profile.bio}
monthly={data.profile.monthly}
total={data.profile.total}
clicks={data.links.clicks}
rank={data.profile.rank}
/>
<div className="w-full border p-4 my-6 dark:border-primary-medium">
<span className="flex flex-row flex-wrap justify-between">
<span className="text-lg font-medium text-primary-medium dark:text-primary-low">
Profile Completion: {progress.percentage}%
</span>
{progress.missing.length > 0 && (
<span className="text-primary-medium-low">
(missing sections in your profile are:{" "}
{progress.missing.join(", ")})
</span>
)}
</span>
<ProgressBar progress={progress} />
</div>
{!data.links && (
<Alert type="warning" message="You don't have a profile yet." />
)}
{data.profile.daily.length > 0 && (
<div className="border mb-6 dark:border-primary-medium">
<div className="border-b border-primary-low bg-white dark:bg-primary-high dark:border-primary-medium px-4 py-5 mb-2 sm:px-6">
<h3 className="text-lg font-medium leading-6 text-primary-high">
Profile views
</h3>
<p className="mt-1 text-sm text-primary-medium dark:text-primary-medium-low">
Number of Profile visits per day.
</p>
</div>
<DynamicChart data={data.profile.daily} />
</div>
)}
<table className="min-w-full divide-y divide-primary-medium-low">
<thead className="bg-primary-low dark:bg-primary-medium">
<tr>
<th
scope="col"
className="py-3.5 pl-4 pr-3 text-left text-sm font-semibold text-primary-high dark:text-primary-low sm:pl-6"
>
Your Links ({data.links.individual.length})
</th>
<th
scope="col"
className="px-3 py-3.5 text-left text-sm font-semibold text-primary-high"
>
Clicks ({abbreviateNumber(data.links.clicks)})
</th>
</tr>
</thead>
<tbody className="divide-y divide-primary-low dark:divide-primary-medium bg-white dark:bg-primary-high">
{data.links &&
data.links.individual.map((link) => (
<tr key={link.url}>
<td className="md:whitespace-nowrap py-4 pl-4 pr-3 text-sm font-medium text-primary-high dark:text-primary-low sm:pl-6">
{link.url}
</td>
<td className="whitespace-nowrap px-3 py-4 text-sm text-primary-medium dark:text-primary-low">
{abbreviateNumber(link.clicks)}
</td>
</tr>
))}
</tbody>
</table>
</Page>
</>
);
}
@eddiejaoude I am getting an error like this. I tried to look it up but couldn't resolve it alone.
When I click on the rename button error occurs.
Please can we discuss in EddieHub Discord, it is easier than here.
Oh I just got a notification there also
@SaraJaoude I have unassigned myself because I have been a bit busy with my university tests and assignments for the past few weeks so I haven't been able to progress much on the issue and don't think I will be able to for the upcoming 10 days. Therefore, I have decided to unassign myself so that others can work if they want. I will ask for re-assignment if the issue is still unassigned after 10 days or definitely work on another if it's not 😅
Hey can i work on this issue. I have gone through the codebase we need to make changes here
async signIn({ profile: githubProfile }) { await connectMongo(); // associate BioDrop profile to account const account = await getAccountByProviderAccountId(githubProfile.id); const user = await User.findOne({ _id: account.userId });
// associate User to Profile for premium flag
const profile = await Profile.findOneAndUpdate(
{
username: githubProfile.username,
},
{
user: account.userId,
},
{
new: true,
}
);
if (profile) {
await associateProfileWithAccount(account, profile._id);
}
// Create a stripe customer for the user with their email address
if (!user.stripeCustomerId) {
logger.info("user stripe customer id not found for: ", user.email);
const customer = await stripe.customers.create({
email: user.email,
name: user.name,
metadata: {
userId: account.userId,
github: githubProfile.username,
},
});
await User.findOneAndUpdate(
{ _id: new ObjectId(account.userId) },
{ stripeCustomerId: customer.id, type: "free" }
);
}
},
}, };
we will compare both the userId that we got while authentication and the userId in MongoDb and if they both are different we will show the warning message.
Hey can i work on this issue. I have gone through the codebase we need to make changes here
async signIn({ profile: githubProfile }) { await connectMongo(); // associate BioDrop profile to account const account = await getAccountByProviderAccountId(githubProfile.id); const user = await User.findOne({ _id: account.userId });
// associate User to Profile for premium flag const profile = await Profile.findOneAndUpdate( { username: githubProfile.username, }, { user: account.userId, }, { new: true, } ); if (profile) { await associateProfileWithAccount(account, profile._id); } // Create a stripe customer for the user with their email address if (!user.stripeCustomerId) { logger.info("user stripe customer id not found for: ", user.email); const customer = await stripe.customers.create({ email: user.email, name: user.name, metadata: { userId: account.userId, github: githubProfile.username, }, }); await User.findOneAndUpdate( { _id: new ObjectId(account.userId) }, { stripeCustomerId: customer.id, type: "free" } ); } },
}, };
we will compare both the userId that we got while authentication and the userId in MongoDb and if they both are different we will show the warning message.
Yep that's right @btme0011 . Currently, when there is mismatch user are unable to login. We wanna make little bit of changes so that user can login when there is username mismatch. May be something like this:
const profile = await Profile.findOne({
_id: account.profiles[0],
});
Then display a warning that username has changed with an option to update username. When user chooses to updata, may be execute a function that will updata database username with github username.
What do you think about it?
yes same thing is in my mind also.
Are you working on this isssue?
yes same thing is in my mind also.
Are you working on this isssue?
Not right now and don't think will be able to work in upcoming 5 days.
Thanks for the discussion. I am not sure whether you are looking to work on this issue @btme0011 but as you already have an issue assigned to you (we only assign one issue per contributor as shown in our Contributing Guide) I could not assign this to you. Also this has become higher priority so I am adding the staff
label.
Closing because a duplicate of https://github.com/EddieHubCommunity/BioDrop/issues/7349
Description
When people change their GitHub username that does not match their LinkFree profile an alert should be shown with a button to rename their profile - with a warning that their url will change
Screenshots
No response
Additional information
No response