Skip to content

Commit

Permalink
Fix/missing user (#4472)
Browse files Browse the repository at this point in the history
* Add method to reliably upsert farcaster users

* Add display name defaults

* Fix linting error

* Address PR feedback
  • Loading branch information
motechFR authored Aug 21, 2024
1 parent 19e2a09 commit de5cc3c
Show file tree
Hide file tree
Showing 11 changed files with 273 additions and 258 deletions.
137 changes: 11 additions & 126 deletions @connect-shared/lib/projects/createProject.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import type { OptionalPrismaTransaction, Prisma, Project, ProjectSource } from '
import { prisma } from '@charmverse/core/prisma-client';
import type { StatusAPIResponse } from '@farcaster/auth-client';
import { resolveENSName } from '@root/lib/blockchain';
import { ensureFarcasterUserExists } from '@root/lib/farcaster/ensureFarcasterUserExists';
import { getFarcasterUsers } from '@root/lib/farcaster/getFarcasterUsers';
import { generatePagePathFromPathAndTitle } from '@root/lib/pages/utils';
import { stringToValidPath, uid } from '@root/lib/utils/strings';
Expand Down Expand Up @@ -43,131 +44,15 @@ export async function createProject({
}
}

const farcasterAccounts = await tx.farcasterUser.findMany({
where: {
fid: {
in: input.projectMembers.map(({ farcasterId }) => farcasterId)
}
},
select: {
userId: true,
fid: true,
account: true
}
});

const teamLeadFarcasterAccount = farcasterAccounts.find(
(account) => account.userId === userId
) as (typeof farcasterAccounts)[0];

const farcasterAccountsRecord: Record<
number,
{
userId: string;
account: StatusAPIResponse;
}
> = farcasterAccounts.reduce<
Record<
number,
{
userId: string;
account: StatusAPIResponse;
}
>
>((acc, { fid, userId: _userId, account }) => {
acc[fid] = {
userId: _userId,
account: account as unknown as StatusAPIResponse
};
return acc;
}, {});

const projectMembers = (
await Promise.all(
input.projectMembers.map(async (member) => {
if (farcasterAccountsRecord[member.farcasterId]) {
return {
userId: farcasterAccountsRecord[member.farcasterId].userId,
name: farcasterAccountsRecord[member.farcasterId].account.displayName as string,
farcasterId: member.farcasterId
};
}
try {
const [farcasterProfile] = await getFarcasterUsers({
fids: [member.farcasterId]
});
if (!farcasterProfile) {
return null;
}

const farcasterWalletUser = await tx.user.findFirst({
where: {
wallets: {
some: {
address: {
in: [
farcasterProfile.custody_address,
...(farcasterProfile.verified_addresses ? farcasterProfile.verified_addresses.eth_addresses : [])
].map((address) => address.toLowerCase())
}
}
}
},
select: {
id: true
}
});

if (farcasterWalletUser) {
return {
userId: farcasterWalletUser.id,
name: farcasterProfile.display_name,
farcasterId: member.farcasterId
};
}
const username = farcasterProfile.username;
const displayName = farcasterProfile.display_name;
const bio = farcasterProfile.profile.bio.text;
const pfpUrl = farcasterProfile.pfp_url;
const fid = member.farcasterId;

const newUser = await tx.user.create({
data: {
id: v4(),
username,
identityType: 'Farcaster',
claimed: false,
avatar: farcasterProfile.pfp_url,
farcasterUser: {
create: {
account: { username, displayName, bio, pfpUrl },
fid
}
},
path: uid(),
profile: {
create: {
...(bio && { description: bio || '' })
}
}
}
});

return {
userId: newUser.id,
name: displayName,
farcasterId: member.farcasterId
};
} catch (err) {
log.error('Error creating user', {
fid: member.farcasterId,
err
});
return null;
}
const projectMembers = await Promise.all(
input.projectMembers.map((member) =>
ensureFarcasterUserExists({
fid: member.farcasterId
})
)
).filter(isTruthy);
);

const teamLeadFarcasterAccount = projectMembers.find((member) => member.userId === userId);

let path = stringToValidPath({ input: input.name ?? '', wordSeparator: '-', autoReplaceEmpty: false });

Expand All @@ -189,12 +74,12 @@ export async function createProject({

const projectMembersToCreate: Omit<Prisma.ProjectMemberCreateManyInput, 'projectId'>[] = [
...projectMembers.map((member) => ({
teamLead: member.farcasterId === teamLeadFarcasterAccount.fid,
teamLead: member.fid === teamLeadFarcasterAccount?.fid,
updatedBy: userId,
userId: member.userId,
// This is necessary because some test data fids do not have a corresponding farcaster profile
name: member.name || '',
farcasterId: member.farcasterId
name: (member.account.displayName || member.account.username)?.trim() || '',
farcasterId: member.fid
}))
];

Expand Down
131 changes: 6 additions & 125 deletions @connect-shared/lib/projects/editProject.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,9 @@
import { InvalidInputError } from '@charmverse/core/errors';
import { log } from '@charmverse/core/log';
import type { Project } from '@charmverse/core/prisma-client';
import { prisma } from '@charmverse/core/prisma-client';
import type { StatusAPIResponse } from '@farcaster/auth-kit';
import { resolveENSName } from '@root/lib/blockchain';
import { getFarcasterUsers } from '@root/lib/farcaster/getFarcasterUsers';
import { uid } from '@root/lib/utils/strings';
import { ensureFarcasterUserExists } from '@root/lib/farcaster/ensureFarcasterUserExists';
import { isTruthy } from '@root/lib/utils/types';
import { v4 } from 'uuid';

import type { FormValues } from './projectSchema';

Expand All @@ -27,7 +23,7 @@ export async function editProject({ userId, input }: { input: EditProjectValues;
}
}

const [currentProjectMembers, inputProjectMembers] = await Promise.all([
const [currentProjectMembers] = await Promise.all([
prisma.projectMember.findMany({
where: {
projectId: input.projectId,
Expand All @@ -45,18 +41,6 @@ export async function editProject({ userId, input }: { input: EditProjectValues;
}
}
}
}),
prisma.farcasterUser.findMany({
where: {
fid: {
in: input.projectMembers.map(({ farcasterId }) => farcasterId)
}
},
select: {
userId: true,
fid: true,
account: true
}
})
]);

Expand All @@ -70,112 +54,9 @@ export async function editProject({ userId, input }: { input: EditProjectValues;
!currentProjectMembers.some((projectMember) => member.farcasterId === projectMember.user?.farcasterUser?.fid)
);

const farcasterAccountsRecord: Record<
number,
{
userId: string;
account: StatusAPIResponse;
}
> = inputProjectMembers.reduce<
Record<
number,
{
userId: string;
account: StatusAPIResponse;
}
>
>((acc, { fid, userId: _userId, account }) => {
acc[fid] = {
userId: _userId,
account: account as unknown as StatusAPIResponse
};
return acc;
}, {});

const projectMembers = (
await Promise.all(
newProjectMembers.map(async (member) => {
if (farcasterAccountsRecord[member.farcasterId]) {
return {
userId: farcasterAccountsRecord[member.farcasterId].userId,
name: farcasterAccountsRecord[member.farcasterId].account.displayName as string,
farcasterId: member.farcasterId
};
}
try {
const [farcasterProfile] = await getFarcasterUsers({
fids: [member.farcasterId]
});

if (!farcasterProfile) {
return null;
}

const farcasterWalletUser = await prisma.user.findFirst({
where: {
wallets: {
some: {
address: {
in: [
farcasterProfile.custody_address,
...(farcasterProfile.verified_addresses ? farcasterProfile.verified_addresses.eth_addresses : [])
].map((address) => address.toLowerCase())
}
}
}
}
});

if (farcasterWalletUser) {
return {
userId: farcasterWalletUser.id,
name: farcasterProfile.display_name,
farcasterId: member.farcasterId
};
}
const username = farcasterProfile.username;
const displayName = farcasterProfile.display_name;
const bio = farcasterProfile.profile.bio.text;
const pfpUrl = farcasterProfile.pfp_url;
const fid = member.farcasterId;

const newUser = await prisma.user.create({
data: {
id: v4(),
username,
identityType: 'Farcaster',
claimed: false,
avatar: farcasterProfile.pfp_url,
farcasterUser: {
create: {
account: { username, displayName, bio, pfpUrl },
fid
}
},
path: uid(),
profile: {
create: {
...(bio && { description: bio || '' })
}
}
}
});

return {
userId: newUser.id,
name: displayName,
farcasterId: member.farcasterId
};
} catch (err) {
log.error('Error creating user', {
fid: member.farcasterId,
err
});
return null;
}
})
)
).filter(isTruthy);
const projectMembers = await Promise.all(
newProjectMembers.map((member) => ensureFarcasterUserExists({ fid: member.farcasterId }))
);

const [editedProject] = await prisma.$transaction([
prisma.project.update({
Expand Down Expand Up @@ -206,7 +87,7 @@ export async function editProject({ userId, input }: { input: EditProjectValues;
data: projectMembers.map((member) => ({
teamLead: false,
updatedBy: userId,
name: member.name,
name: member.account.displayName || member.account.username,
userId: member.userId,
projectId: input.projectId
}))
Expand Down
2 changes: 1 addition & 1 deletion @connect-shared/lib/projects/findProject.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ export async function findProject({ id, path }: { id?: string; path?: string }):
...member.user,
userId: member.userId,
teamLead: member.teamLead,
name: farcasterUser?.displayName || '',
name: farcasterUser?.displayName || farcasterUser?.username || '',
farcasterId: member.user?.farcasterUser?.fid as number,
farcasterUser: {
fid: member.user?.farcasterUser?.fid,
Expand Down
2 changes: 1 addition & 1 deletion apps/connect/components/profile/ProfilePage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export async function ProfilePage({ user }: { user: LoggedInUser }) {
<Box gap={3} display='flex' flexDirection='column' mt={{ md: 2 }}>
<FarcasterCard
fid={user.farcasterUser?.fid}
name={farcasterDetails?.displayName}
name={farcasterDetails?.displayName || farcasterDetails?.username}
username={farcasterDetails?.username}
avatar={farcasterDetails?.pfpUrl}
bio={farcasterDetails?.bio}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export async function ProfileDetailsPage({ user }: { user: Pick<LoggedInUser, 'f
<Box gap={2} display='flex' flexDirection='column'>
<FarcasterCard
fid={user.farcasterUser?.fid}
name={farcasterDetails?.displayName}
name={farcasterDetails?.displayName || farcasterDetails?.username}
username={farcasterDetails?.username}
avatar={farcasterDetails?.pfpUrl}
bio={farcasterDetails?.bio}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ export function AddProjectMembersForm({
<Typography variant='h6'>Team</Typography>
<FarcasterCard
fid={user.farcasterUser?.fid}
name={farcasterDetails?.displayName}
name={farcasterDetails?.displayName || farcasterDetails?.username}
username={farcasterDetails?.username}
avatar={farcasterDetails?.pfpUrl}
avatarSize='large'
Expand Down
2 changes: 1 addition & 1 deletion apps/sunnyawards/components/profile/ProfilePage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export async function ProfilePage({ user }: { user: LoggedInUser }) {
<Box gap={3} display='flex' flexDirection='column' mt={{ md: 2 }}>
<FarcasterCard
fid={user.farcasterUser?.fid}
name={farcasterDetails?.displayName}
name={farcasterDetails?.displayName || farcasterDetails?.username}
username={farcasterDetails?.username}
avatar={farcasterDetails?.pfpUrl}
bio={farcasterDetails?.bio}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export async function ProfileDetailsPage({ user }: { user: Pick<LoggedInUser, 'f
<Box gap={2} display='flex' flexDirection='column'>
<FarcasterCard
fid={user.farcasterUser?.fid}
name={farcasterDetails?.displayName}
name={farcasterDetails?.displayName || farcasterDetails?.username}
username={farcasterDetails?.username}
avatar={farcasterDetails?.pfpUrl}
bio={farcasterDetails?.bio}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export function AddProjectMembersForm({
<FormLabel id='search-farcaster-user'>Team</FormLabel>
<FarcasterCard
fid={user.farcasterUser?.fid}
name={farcasterDetails?.displayName}
name={farcasterDetails?.displayName || farcasterDetails?.username}
username={farcasterDetails?.username}
avatar={farcasterDetails?.pfpUrl}
avatarSize='large'
Expand Down
Loading

0 comments on commit de5cc3c

Please sign in to comment.