diff --git a/apps/scoutgame/__e2e__/UserPage/buyNft.spec.ts b/apps/scoutgame/__e2e__/UserPage/buyNft.spec.ts index 1d9d4aeb64..b115a729c6 100644 --- a/apps/scoutgame/__e2e__/UserPage/buyNft.spec.ts +++ b/apps/scoutgame/__e2e__/UserPage/buyNft.spec.ts @@ -1,10 +1,6 @@ -import { prisma } from '@charmverse/core/prisma-client'; import { getBuilderContractAddress } from '@packages/scoutgame/builderNfts/constants'; import { currentSeason } from '@packages/scoutgame/dates'; import { mockBuilder, mockBuilderNft } from '@packages/scoutgame/testing/database'; -import { delay } from '@root/lib/utils/async'; -import { custom, http } from 'viem'; -import { optimism } from 'viem/chains'; import { expect, test } from '../test'; @@ -37,7 +33,10 @@ test.describe('Buy Nft', () => { }); await userPage.mockNftAPIs({ - builder, + builder: { + id: builder.id, + path: builder.path! + }, isSuccess: true }); @@ -45,8 +44,8 @@ test.describe('Buy Nft', () => { await page.goto(`/home`); await page.waitForURL('**/home'); - await page.goto(`/u/${builder.username}`); - await page.waitForURL(`**/u/${builder.username}`); + await page.goto(`/u/${builder.path}`); + await page.waitForURL(`**/u/${builder.path}`); // Card CTA button const scoutButton = page.locator('data-test=scout-button').first(); diff --git a/apps/scoutgame/__e2e__/po/UserPage.po.ts b/apps/scoutgame/__e2e__/po/UserPage.po.ts index 59772ce09d..94af1afe16 100644 --- a/apps/scoutgame/__e2e__/po/UserPage.po.ts +++ b/apps/scoutgame/__e2e__/po/UserPage.po.ts @@ -7,13 +7,7 @@ export class UserPage extends GeneralPageLayout { super(page); } - async mockNftAPIs({ - builder, - isSuccess - }: { - builder: { id: string; username?: string | null; path?: string | null }; - isSuccess: boolean; - }) { + async mockNftAPIs({ builder, isSuccess }: { builder: { id: string; path: string }; isSuccess: boolean }) { // Used for debugging all routes. Keep caution as the next page.route() function will not run anymore. // await page.route('**', (route) => { // console.log('Intercepted URL:', route.request().url()); @@ -139,7 +133,7 @@ export class UserPage extends GeneralPageLayout { }); // Mocking server action to handle the pending transaction and mint the NFT without calling decent - await this.page.route(`**/u/${builder.username}`, async (route) => { + await this.page.route(`**/u/${builder.path}`, async (route) => { const method = route.request().method(); const body = route.request().postDataJSON()?.[0]; diff --git a/apps/scoutgame/app/(general)/claim/page.tsx b/apps/scoutgame/app/(general)/claim/page.tsx index 04983e590e..69348f3e83 100644 --- a/apps/scoutgame/app/(general)/claim/page.tsx +++ b/apps/scoutgame/app/(general)/claim/page.tsx @@ -25,7 +25,7 @@ export default async function Claim({ searchParams }: { searchParams: { tab: str return ( diff --git a/apps/scoutgame/app/(general)/profile/page.tsx b/apps/scoutgame/app/(general)/profile/page.tsx index 051864173a..a70c3ff6df 100644 --- a/apps/scoutgame/app/(general)/profile/page.tsx +++ b/apps/scoutgame/app/(general)/profile/page.tsx @@ -44,7 +44,7 @@ export default async function Profile({ { - const user = await getUserByPath(params.username); +export async function generateMetadata({ params }: Props, parent: ResolvingMetadata): Promise { + const user = await getUserByPath(params.path); if (!user) { return {}; @@ -24,19 +24,19 @@ export async function generateMetadata({ params, searchParams }: Props, parent: const previousOg = previousMetadata.openGraph || ({} as ResolvedOpenGraph); return { - title: `${user.username} user profile`, + title: `${user.displayName} user profile`, openGraph: { images: user.nftImageUrl || user.avatar || previousOg.images || '', - title: `${user.username} user profile` + title: `${user.displayName} user profile` }, twitter: { - title: `${user.username} user profile` + title: `${user.displayName} user profile` } }; } export default async function Profile({ params, searchParams }: Props) { - const user = await getUserByPath(params.username); + const user = await getUserByPath(params.path); const tab = searchParams.tab || (user?.builderStatus === 'approved' ? 'builder' : 'scout'); if (!user || typeof tab !== 'string') { diff --git a/apps/scoutgame/app/api/builders/search/route.ts b/apps/scoutgame/app/api/builders/search/route.ts index 52a7801aeb..9eb2cc7b14 100644 --- a/apps/scoutgame/app/api/builders/search/route.ts +++ b/apps/scoutgame/app/api/builders/search/route.ts @@ -4,18 +4,18 @@ import { searchBuilders } from 'lib/builders/searchBuilders'; export async function GET(request: Request) { const { searchParams } = new URL(request.url); - const username = searchParams.get('username'); - if (typeof username !== 'string') { - return new Response('username is required', { status: 400 }); + const search = searchParams.get('search'); + if (typeof search !== 'string') { + return new Response('path is required', { status: 400 }); } try { const result = await searchBuilders({ - username + search }); return Response.json(result); } catch (error) { - log.error('Error requesting user from farcaster', { error, username }); + log.error('Error requesting user from farcaster', { error, search }); return new Response(`Unknown error: ${(error as Error).message}`, { status: 500 }); } } diff --git a/apps/scoutgame/app/api/session/refresh/route.ts b/apps/scoutgame/app/api/session/refresh/route.ts index 8f66ebc842..5f7fb3e835 100644 --- a/apps/scoutgame/app/api/session/refresh/route.ts +++ b/apps/scoutgame/app/api/session/refresh/route.ts @@ -20,7 +20,7 @@ export async function GET(req: NextRequest) { select: { bio: true, displayName: true, - username: true, + path: true, farcasterId: true } }); @@ -35,13 +35,12 @@ export async function GET(req: NextRequest) { }); if (profile) { const bio = profile.profile.bio.text; - const displayName = profile.display_name || profile.username; - const username = profile.username; + const displayName = profile.display_name; const hasProfileChanged = // Re-enable this once Neynar fixes their caching mechanism // scout.avatar !== profile.body.avatarUrl || - scout.bio !== bio || scout.displayName !== displayName || scout.username !== username; + scout.bio !== bio || scout.displayName !== displayName; if (hasProfileChanged) { await prisma.scout.update({ @@ -52,8 +51,7 @@ export async function GET(req: NextRequest) { // Re-enable this once Neynar fixes their caching mechanism // avatar: profile.pfp_url, bio, - displayName, - username + displayName } }); log.info('Updated Farcaster profile', { userId, profile }); diff --git a/apps/scoutgame/components/[username]/FarcasterMetadata.tsx b/apps/scoutgame/components/[path]/FarcasterMetadata.tsx similarity index 95% rename from apps/scoutgame/components/[username]/FarcasterMetadata.tsx rename to apps/scoutgame/components/[path]/FarcasterMetadata.tsx index 424771cdc3..2799c0a0cb 100644 --- a/apps/scoutgame/components/[username]/FarcasterMetadata.tsx +++ b/apps/scoutgame/components/[path]/FarcasterMetadata.tsx @@ -18,7 +18,7 @@ export function FarcasterMetadata({ {/* Button 1 */} - + ); } diff --git a/apps/scoutgame/components/[username]/PublicProfilePage.tsx b/apps/scoutgame/components/[path]/PublicProfilePage.tsx similarity index 97% rename from apps/scoutgame/components/[username]/PublicProfilePage.tsx rename to apps/scoutgame/components/[path]/PublicProfilePage.tsx index eabc51e813..58f8f6bcf2 100644 --- a/apps/scoutgame/components/[username]/PublicProfilePage.tsx +++ b/apps/scoutgame/components/[path]/PublicProfilePage.tsx @@ -34,7 +34,7 @@ export function PublicProfilePage({ user, tab }: { user: UserProfile; tab: strin {tab === 'builder' ? ( diff --git a/apps/scoutgame/components/[username]/PublicProfileTabsMenu.tsx b/apps/scoutgame/components/[path]/PublicProfileTabsMenu.tsx similarity index 82% rename from apps/scoutgame/components/[username]/PublicProfileTabsMenu.tsx rename to apps/scoutgame/components/[path]/PublicProfileTabsMenu.tsx index e3450595f8..485eac5378 100644 --- a/apps/scoutgame/components/[username]/PublicProfileTabsMenu.tsx +++ b/apps/scoutgame/components/[path]/PublicProfileTabsMenu.tsx @@ -7,20 +7,20 @@ import { TabsMenu } from 'components/common/Tabs/TabsMenu'; export function PublicProfileTabsMenu({ tab, - username, + path, isApprovedBuilder }: { tab: string; - username: string; + path: string; isApprovedBuilder?: boolean; }) { const router = useRouter(); useEffect(() => { if (!isApprovedBuilder && tab === 'builder') { - router.push(`/u/${username}/?tab=scout`); + router.push(`/u/${path}/?tab=scout`); } - }, [isApprovedBuilder, tab, username]); + }, [isApprovedBuilder, tab, path]); return ( {totalUnclaimedPoints === 0 ? null : ( diff --git a/apps/scoutgame/components/claim/components/BuilderRewardsScreen/BuilderRewardsTable.tsx b/apps/scoutgame/components/claim/components/BuilderRewardsScreen/BuilderRewardsTable.tsx index 3b474c1bd8..4dd53e4a6f 100644 --- a/apps/scoutgame/components/claim/components/BuilderRewardsScreen/BuilderRewardsTable.tsx +++ b/apps/scoutgame/components/claim/components/BuilderRewardsScreen/BuilderRewardsTable.tsx @@ -13,11 +13,11 @@ function BuilderRewardsTableRow({ reward }: { reward: BuilderReward }) { return ( - + - + - {reward.username} + {reward.displayName} @@ -64,7 +64,7 @@ export function BuilderRewardsTable({ }} > {builderRewards.map((reward) => ( - + ))} diff --git a/apps/scoutgame/components/claim/components/PointsClaimScreen/PointsClaimScreen.tsx b/apps/scoutgame/components/claim/components/PointsClaimScreen/PointsClaimScreen.tsx index f43ac532a1..cb2699a526 100644 --- a/apps/scoutgame/components/claim/components/PointsClaimScreen/PointsClaimScreen.tsx +++ b/apps/scoutgame/components/claim/components/PointsClaimScreen/PointsClaimScreen.tsx @@ -40,11 +40,11 @@ function PointsClaimSuccessModal({ export function PointsClaimScreen({ totalUnclaimedPoints, - username, + displayName, bonusPartners }: { totalUnclaimedPoints: number; - username: string; + displayName: string; bonusPartners: string[]; }) { const { executeAsync, isExecuting, result } = useAction(claimPointsAction); @@ -98,7 +98,7 @@ export function PointsClaimScreen({ > - {username} will receive + {displayName} will receive @@ -123,7 +123,7 @@ export function PointsClaimScreen({ ) : ( <> - Hey {username}, + Hey {displayName}, You have no rewards to claim. diff --git a/apps/scoutgame/components/common/Card/BuilderCard/BuilderCard.tsx b/apps/scoutgame/components/common/Card/BuilderCard/BuilderCard.tsx index f3b231b60e..91342887b0 100644 --- a/apps/scoutgame/components/common/Card/BuilderCard/BuilderCard.tsx +++ b/apps/scoutgame/components/common/Card/BuilderCard/BuilderCard.tsx @@ -9,7 +9,7 @@ import { ScoutButton } from '../../ScoutButton/ScoutButton'; import { BuilderCardNftDisplay } from './BuilderCardNftDisplay'; import { BuilderCardStats } from './BuilderCardStats'; -type RequiredBuilderInfoFields = 'username' | 'builderStatus' | 'id' | 'path'; +type RequiredBuilderInfoFields = 'displayName' | 'builderStatus' | 'id' | 'path'; export function BuilderCard({ builder, @@ -36,7 +36,7 @@ export function BuilderCard({ > theme.breakpoints.down('sm'), { noSsr: true }); - let gemHeight = size === 'x-small' ? 12 : size === 'small' ? 13.5 : size === 'medium' ? 14.5 : 16; - if (isMobile) { - gemHeight *= 0.75; - } + const gemHeight = size === 'x-small' || size === 'small' ? 12.5 : size === 'medium' ? 14.5 : 16; return ( <> }> @@ -28,7 +25,7 @@ export function BuilderCardActivity({ flexDirection='row' gap={{ xs: 0.75, - md: 1.25 + md: size === 'medium' || size === 'large' ? 1.25 : 0.75 }} width='100%' height={gemHeight} diff --git a/apps/scoutgame/components/common/Card/BuilderCard/BuilderCardNftDisplay.tsx b/apps/scoutgame/components/common/Card/BuilderCard/BuilderCardNftDisplay.tsx index 763d498f77..2913e3c832 100644 --- a/apps/scoutgame/components/common/Card/BuilderCard/BuilderCardNftDisplay.tsx +++ b/apps/scoutgame/components/common/Card/BuilderCard/BuilderCardNftDisplay.tsx @@ -24,12 +24,12 @@ const nftDisplaySize = { export function BuilderCardNftDisplay({ nftImageUrl, children, - username, + path, showHotIcon = false, size = 'medium', hideDetails = false }: { - username: string | null; + path: string; nftImageUrl?: string | null; showHotIcon?: boolean; children?: React.ReactNode; @@ -43,7 +43,7 @@ export function BuilderCardNftDisplay({ - @{username} + {displayName} {typeof builderPoints === 'number' && ( @@ -50,7 +51,7 @@ export function BuilderCardStats({ sx={{ fontSize: { xs: '12px', - md: '14px' + md: mdFontSize } }} component='span' @@ -69,7 +70,7 @@ export function BuilderCardStats({ sx={{ fontSize: { xs: '12px', - md: '14px' + md: mdFontSize } }} component='span' @@ -87,7 +88,7 @@ export function BuilderCardStats({ sx={{ fontSize: { xs: '12px', - md: '14px' + md: mdFontSize } }} component='span' @@ -107,7 +108,7 @@ export function BuilderCardStats({ color: 'text.secondary', fontSize: { xs: '7.5px', - md: '10px' + md: size === 'medium' || size === 'large' ? '10px' : '8px' } }} > diff --git a/apps/scoutgame/components/common/Card/ScoutCard.tsx b/apps/scoutgame/components/common/Card/ScoutCard.tsx index 19d3787a68..9d235bb4e3 100644 --- a/apps/scoutgame/components/common/Card/ScoutCard.tsx +++ b/apps/scoutgame/components/common/Card/ScoutCard.tsx @@ -51,17 +51,6 @@ export function ScoutCard({ scout }: { scout: ScoutInfo }) { > {scout.displayName} - - {scout.username} - - {scout.nfts} Nfts diff --git a/apps/scoutgame/components/common/Gallery/BuildersGallery.tsx b/apps/scoutgame/components/common/Gallery/BuildersGallery.tsx index fb3ecf706e..10857f67c5 100644 --- a/apps/scoutgame/components/common/Gallery/BuildersGallery.tsx +++ b/apps/scoutgame/components/common/Gallery/BuildersGallery.tsx @@ -23,7 +23,7 @@ export function BuildersGallery({ columns={{ xs: 2, sm: 3, md: builders.length < columns ? builders.length : columns }} > {builders.map((builder) => ( - + {builder.nftsSoldToScout !== undefined && builder.nftsSoldToScout > 0 && ( diff --git a/apps/scoutgame/components/common/Gallery/ScoutsGallery.tsx b/apps/scoutgame/components/common/Gallery/ScoutsGallery.tsx index ba9527ce6f..5cf2b4e935 100644 --- a/apps/scoutgame/components/common/Gallery/ScoutsGallery.tsx +++ b/apps/scoutgame/components/common/Gallery/ScoutsGallery.tsx @@ -9,8 +9,8 @@ export function ScoutsGallery({ scouts }: { scouts: ScoutInfo[] }) { {scouts.map((scout) => ( - - + + diff --git a/apps/scoutgame/components/common/Header/Header.tsx b/apps/scoutgame/components/common/Header/Header.tsx index 9d8934387f..f7fad912c7 100644 --- a/apps/scoutgame/components/common/Header/Header.tsx +++ b/apps/scoutgame/components/common/Header/Header.tsx @@ -16,8 +16,6 @@ import { Hidden } from 'components/common/Hidden'; import { SiteNavigation } from 'components/common/SiteNavigation'; import { useUser } from 'components/layout/UserProvider'; -import { InstallAppMenuItem } from './components/InstallAppMenuItem'; - export function Header() { const router = useRouter(); const { user, refreshUser } = useUser(); @@ -109,7 +107,7 @@ export function Header() { alt='Scout Game points icon' priority={true} /> - + - {user.username} + {user.displayName} logoutUser()} data-test='sign-out-button'> Sign Out diff --git a/apps/scoutgame/components/common/NFTPurchaseDialog/NFTPurchaseDialog.tsx b/apps/scoutgame/components/common/NFTPurchaseDialog/NFTPurchaseDialog.tsx index 3c34383fcd..2d30ccc53f 100644 --- a/apps/scoutgame/components/common/NFTPurchaseDialog/NFTPurchaseDialog.tsx +++ b/apps/scoutgame/components/common/NFTPurchaseDialog/NFTPurchaseDialog.tsx @@ -43,7 +43,7 @@ function NFTPurchaseDialogComponent(props: NFTPurchaseDialogProps) { fullScreen={!isDesktop} open={props.open && !!address} onClose={props.onClose} - title={`Scout @${props.builder.username}`} + title={`Scout ${props.builder.displayName}`} maxWidth='md' > diff --git a/apps/scoutgame/components/common/NFTPurchaseDialog/components/NFTPurchaseForm.tsx b/apps/scoutgame/components/common/NFTPurchaseDialog/components/NFTPurchaseForm.tsx index 69a6dc34c3..a418c5a1ce 100644 --- a/apps/scoutgame/components/common/NFTPurchaseDialog/components/NFTPurchaseForm.tsx +++ b/apps/scoutgame/components/common/NFTPurchaseDialog/components/NFTPurchaseForm.tsx @@ -343,7 +343,7 @@ export function NFTPurchaseFormContent({ builder }: NFTPurchaseProps) { {builder.nftImageUrl ? ( {builder.username Congratulations! - You scouted @{builder.username} + You scouted {builder.displayName} diff --git a/apps/scoutgame/components/common/Profile/UserProfile.tsx b/apps/scoutgame/components/common/Profile/UserProfile.tsx index 04d3e16ecc..a1a0a4c5b9 100644 --- a/apps/scoutgame/components/common/Profile/UserProfile.tsx +++ b/apps/scoutgame/components/common/Profile/UserProfile.tsx @@ -11,10 +11,10 @@ import type { AvatarSize } from '../Avatar'; import { Avatar } from '../Avatar'; // Use a unique type since sometimes this prop comes from the session user, but sometimes it comes from the builder queries -export type UserProfileData = Pick & { +export type UserProfileData = Pick & { bio?: string | null; avatar?: string | null; - displayName?: string | null; + displayName: string; githubLogin?: string; }; @@ -25,7 +25,7 @@ type UserProfileProps = { export function UserProfile({ user, avatarSize = 'xLarge' }: UserProfileProps) { const isDesktop = useMdScreen(); - const { displayName, username, bio, avatar, githubLogin } = user; + const { displayName, path, bio, avatar, githubLogin } = user; const isMounted = useIsMounted(); // We are using the mounted flag here because MUI media query returns false on the server and true on the client and it throws warnings @@ -51,38 +51,35 @@ export function UserProfile({ user, avatarSize = 'xLarge' }: UserProfileProps) { sm: 'flex-start' }} > - + ) : null} - {displayName || 'N/A'} - {username || 'N/A'} - - + {displayName} + + warpcast icon + + {githubLogin ? ( + warpcast icon - {githubLogin ? ( - - github icon - - ) : null} - + ) : null} )} - setAuthPopup(false)} path={`/u/${builder.username}`} /> + setAuthPopup(false)} path={`/u/${builder.path}`} /> ); diff --git a/apps/scoutgame/components/home/components/HomePageTable/components/ActivityTable.tsx b/apps/scoutgame/components/home/components/HomePageTable/components/ActivityTable.tsx index 8eebac25f4..19c31091aa 100644 --- a/apps/scoutgame/components/home/components/HomePageTable/components/ActivityTable.tsx +++ b/apps/scoutgame/components/home/components/HomePageTable/components/ActivityTable.tsx @@ -55,8 +55,8 @@ export function BuilderActivityAction({ activity }: { activity: BuilderActivity } }} > - - {activity.scout} + + {activity.scout.displayName} )} @@ -125,14 +125,14 @@ export function ActivityTable({ activities }: { activities: BuilderActivity[] }) - - {activity.username} + + {activity.displayName} - - {row.username} + + {row.displayName} diff --git a/apps/scoutgame/components/home/components/HomePageTable/components/TopBuildersTable.tsx b/apps/scoutgame/components/home/components/HomePageTable/components/TopBuildersTable.tsx index d556fc81eb..ed00d320b8 100644 --- a/apps/scoutgame/components/home/components/HomePageTable/components/TopBuildersTable.tsx +++ b/apps/scoutgame/components/home/components/HomePageTable/components/TopBuildersTable.tsx @@ -45,7 +45,7 @@ export function TopBuildersTable({ builders }: { builders: TopBuilderInfo[] }) { {builders.map((builder, index) => ( - - {builder.username} + + {builder.displayName} diff --git a/apps/scoutgame/components/home/components/HomePageTable/components/TopScoutsTable.tsx b/apps/scoutgame/components/home/components/HomePageTable/components/TopScoutsTable.tsx index 4f507125c4..2e157a1d38 100644 --- a/apps/scoutgame/components/home/components/HomePageTable/components/TopScoutsTable.tsx +++ b/apps/scoutgame/components/home/components/HomePageTable/components/TopScoutsTable.tsx @@ -44,7 +44,7 @@ export function TopScoutsTable({ scouts }: { scouts: TopScout[] }) { {scouts.map((scout, index) => ( - - {scout.username} + + {scout.displayName} diff --git a/apps/scoutgame/components/profile/components/BuilderProfile/BuilderActivitiesList.tsx b/apps/scoutgame/components/profile/components/BuilderProfile/BuilderActivitiesList.tsx index 21d2db6dc1..cec0021ef2 100644 --- a/apps/scoutgame/components/profile/components/BuilderProfile/BuilderActivitiesList.tsx +++ b/apps/scoutgame/components/profile/components/BuilderProfile/BuilderActivitiesList.tsx @@ -34,7 +34,7 @@ export function BuilderActivityDetail({ activity }: { activity: BuilderActivity return ( {activity.type === 'nft_purchase' ? ( - {activity.scout} + {activity.scout.displayName} ) : activity.type === 'github_event' ? ( {activity.repo} ) : null} diff --git a/apps/scoutgame/components/profile/components/BuilderProfile/BuilderProfile.tsx b/apps/scoutgame/components/profile/components/BuilderProfile/BuilderProfile.tsx index 8745e49787..fe24a6db8d 100644 --- a/apps/scoutgame/components/profile/components/BuilderProfile/BuilderProfile.tsx +++ b/apps/scoutgame/components/profile/components/BuilderProfile/BuilderProfile.tsx @@ -97,7 +97,7 @@ export async function BuilderProfile({ builder }: { builder: BuilderUserInfo }) ) : null} - + diff --git a/apps/scoutgame/components/scout/components/SearchBuildersInput.tsx b/apps/scoutgame/components/scout/components/SearchBuildersInput.tsx index 86386cf862..1a965d077c 100644 --- a/apps/scoutgame/components/scout/components/SearchBuildersInput.tsx +++ b/apps/scoutgame/components/scout/components/SearchBuildersInput.tsx @@ -56,7 +56,7 @@ export function SearchBuildersInput() { } return (
  • - + - - {option.username} + + {option.displayName}
  • @@ -79,7 +79,7 @@ export function SearchBuildersInput() { onOpen={() => setOpen(true)} onClose={() => setOpen(false)} options={searchResults ?? []} - getOptionLabel={(option) => option.username} + getOptionLabel={(option) => option.displayName} onInputChange={(event, value) => setSearchTerm(value)} renderInput={(params) => ( (username ? '/api/builders/search' : null, { - username +export function useSearchBuilders(search: string) { + return useGETImmutable(search ? '/api/builders/search' : null, { + search }); } diff --git a/apps/scoutgame/hooks/api/session.ts b/apps/scoutgame/hooks/api/session.ts index 79443c1b7a..adad79dd90 100644 --- a/apps/scoutgame/hooks/api/session.ts +++ b/apps/scoutgame/hooks/api/session.ts @@ -18,7 +18,13 @@ export function useGetUserTrigger() { } export function useGetClaimablePoints() { - return useGETImmutable<{ points: number }>('/api/session/claimable-points'); + return useGETImmutable<{ points: number }>( + '/api/session/claimable-points', + {}, + { + refreshInterval: 30000 + } + ); } export function useGetPendingNftTransactions< diff --git a/apps/scoutgame/lib/blockchain/findOrCreateWalletUser.ts b/apps/scoutgame/lib/blockchain/findOrCreateWalletUser.ts index 02fad68b33..2ea0fa784f 100644 --- a/apps/scoutgame/lib/blockchain/findOrCreateWalletUser.ts +++ b/apps/scoutgame/lib/blockchain/findOrCreateWalletUser.ts @@ -25,6 +25,6 @@ export async function findOrCreateWalletUser({ avatar: ensDetails?.avatar || undefined, walletAddress: wallet, displayName: ens || shortenHex(wallet), - username: shortenHex(wallet) + path: shortenHex(wallet) }); } diff --git a/apps/scoutgame/lib/builders/__tests__/getTopBuilders.spec.ts b/apps/scoutgame/lib/builders/__tests__/getTopBuilders.spec.ts index 69614dffb1..0551a06254 100644 --- a/apps/scoutgame/lib/builders/__tests__/getTopBuilders.spec.ts +++ b/apps/scoutgame/lib/builders/__tests__/getTopBuilders.spec.ts @@ -43,7 +43,7 @@ describe('getTopBuilders', () => { expect(result).toHaveLength(1); const topBuilder = result[0] as TopBuilderInfo; expect(topBuilder.id).toBe(builder.id); - expect(topBuilder.username).toBe(builder.username); + expect(topBuilder.path).toBe(builder.path); expect(topBuilder.seasonPoints).toBe(100); expect(topBuilder.allTimePoints).toBe(500); expect(topBuilder.scoutedBy).toBe(2); // Should count unique scouts, not total sales diff --git a/apps/scoutgame/lib/builders/getBuilderActivities.ts b/apps/scoutgame/lib/builders/getBuilderActivities.ts index d06298871c..fb43f86405 100644 --- a/apps/scoutgame/lib/builders/getBuilderActivities.ts +++ b/apps/scoutgame/lib/builders/getBuilderActivities.ts @@ -9,7 +9,10 @@ export type BuilderActivityType = 'nft_purchase' | 'merged_pull_request'; type NftPurchaseActivity = { type: 'nft_purchase'; - scout: string; + scout: { + path: string; + displayName: string; + }; }; type MergedPullRequestActivity = { @@ -59,8 +62,8 @@ export async function getBuilderActivities({ select: { scout: { select: { - username: true, - path: true + path: true, + displayName: true } }, tokensPurchased: true @@ -91,10 +94,14 @@ export async function getBuilderActivities({ if (event.type === 'nft_purchase' && event.nftPurchaseEvent) { return { ...event.builder, + path: event.builder.path!, id: event.id, createdAt: event.createdAt, type: 'nft_purchase' as const, - scout: event.nftPurchaseEvent.scout.username || '' + scout: { + path: event.nftPurchaseEvent.scout.path!, + displayName: event.nftPurchaseEvent.scout.displayName + } }; } else if ( (event.type === 'merged_pull_request' || event.type === 'daily_commit') && @@ -103,6 +110,7 @@ export async function getBuilderActivities({ ) { return { ...event.builder, + path: event.builder.path!, id: event.id, createdAt: event.createdAt, type: 'github_event' as const, diff --git a/apps/scoutgame/lib/builders/getBuilderRewards.ts b/apps/scoutgame/lib/builders/getBuilderRewards.ts index c6932ac88b..aefd6e8287 100644 --- a/apps/scoutgame/lib/builders/getBuilderRewards.ts +++ b/apps/scoutgame/lib/builders/getBuilderRewards.ts @@ -3,7 +3,8 @@ import { currentSeason } from '@packages/scoutgame/dates'; import { isTruthy } from '@root/lib/utils/types'; export type BuilderReward = { - username: string; + path: string | null; + displayName: string; avatar: string | null; points: number; rank: number | null; @@ -44,7 +45,8 @@ export async function getSeasonBuilderRewards({ userId }: { userId: string }): P builder: { select: { id: true, - username: true, + path: true, + displayName: true, avatar: true } } @@ -71,7 +73,8 @@ export async function getSeasonBuilderRewards({ userId }: { userId: string }): P if (cardsHeld) { if (!builderRewardsRecord[builderId]) { builderRewardsRecord[builderId] = { - username: builder.username || '', + path: builder.path, + displayName: builder.displayName, avatar: builder.avatar, cardsHeld, points: 0, @@ -131,7 +134,8 @@ export async function getWeeklyBuilderRewards({ builder: { select: { id: true, - username: true, + path: true, + displayName: true, avatar: true, userWeeklyStats: { where: { @@ -167,10 +171,11 @@ export async function getWeeklyBuilderRewards({ } return { rank, - username: builder.username || '', + path: builder.path, avatar: builder.avatar, points: receipt.value, - cardsHeld + cardsHeld, + displayName: builder.displayName }; }) .filter(isTruthy) diff --git a/apps/scoutgame/lib/builders/getBuilderScouts.ts b/apps/scoutgame/lib/builders/getBuilderScouts.ts index 1dd494d456..251bf717cd 100644 --- a/apps/scoutgame/lib/builders/getBuilderScouts.ts +++ b/apps/scoutgame/lib/builders/getBuilderScouts.ts @@ -15,10 +15,7 @@ export async function getBuilderScouts(builderId: string) { }, select: { scout: { - select: { - displayName: true, - ...BasicUserInfoSelect - } + select: BasicUserInfoSelect }, tokensPurchased: true } @@ -32,6 +29,7 @@ export async function getBuilderScouts(builderId: string) { if (!existingScout) { scoutsRecord[event.scout.id] = { ...event.scout, + path: event.scout.path!, nfts: 0 }; } diff --git a/apps/scoutgame/lib/builders/getLeaderboard.ts b/apps/scoutgame/lib/builders/getLeaderboard.ts index 0ba0654639..5eb97bc3c2 100644 --- a/apps/scoutgame/lib/builders/getLeaderboard.ts +++ b/apps/scoutgame/lib/builders/getLeaderboard.ts @@ -5,8 +5,8 @@ import { getCurrentWeek, currentSeason } from '@packages/scoutgame/dates'; export type LeaderBoardRow = { id: string; avatar: string | null; - username: string | null; - path: string | null; + displayName: string; + path: string; builderStatus: BuilderStatus; progress: number; gemsCollected: number; @@ -37,8 +37,8 @@ export async function getLeaderboard({ limit = 10 }: { limit: number }): Promise select: { id: true, avatar: true, - username: true, path: true, + displayName: true, builderStatus: true, builderNfts: { select: { @@ -61,8 +61,8 @@ export async function getLeaderboard({ limit = 10 }: { limit: number }): Promise return { id: weeklyTopBuilder.user.id, avatar: weeklyTopBuilder.user.avatar, - username: weeklyTopBuilder.user.username, - path: weeklyTopBuilder.user.path, + displayName: weeklyTopBuilder.user.displayName, + path: weeklyTopBuilder.user.path!, builderStatus: weeklyTopBuilder.user.builderStatus!, gemsCollected: weeklyTopBuilder.gemsCollected, progress, diff --git a/apps/scoutgame/lib/builders/getSortedBuilders.ts b/apps/scoutgame/lib/builders/getSortedBuilders.ts index 0fd54e9729..18188692d6 100644 --- a/apps/scoutgame/lib/builders/getSortedBuilders.ts +++ b/apps/scoutgame/lib/builders/getSortedBuilders.ts @@ -44,8 +44,8 @@ export async function getSortedBuilders({ cursor: cursor ? { id: cursor.userId } : undefined, select: { id: true, - username: true, path: true, + displayName: true, builderStatus: true, createdAt: true, builderNfts: { @@ -92,8 +92,8 @@ export async function getSortedBuilders({ return scouts.map((scout) => ({ id: scout.id, nftImageUrl: scout.builderNfts[0]?.imageUrl, - username: scout.username, - path: scout.path, + path: scout.path!, + displayName: scout.displayName, builderPoints: scout.userAllTimeStats[0]?.pointsEarnedAsBuilder ?? 0, price: scout.builderNfts?.[0]?.currentPrice ?? 0, scoutedBy: scout.builderNfts?.[0]?.nftSoldEvents?.length ?? 0, @@ -142,8 +142,8 @@ export async function getSortedBuilders({ user: { select: { id: true, - username: true, path: true, + displayName: true, builderStatus: true, builderNfts: { where: { @@ -184,8 +184,8 @@ export async function getSortedBuilders({ id: stat.user.id, rank: stat.rank ?? -1, nftImageUrl: stat.user.builderNfts[0]?.imageUrl, - username: stat.user.username, - path: stat.user.path, + path: stat.user.path!, + displayName: stat.user.displayName, builderPoints: stat.user.userAllTimeStats[0]?.pointsEarnedAsBuilder ?? 0, price: stat.user.builderNfts?.[0]?.currentPrice ?? 0, scoutedBy: stat.user.builderNfts?.[0]?.nftSoldEvents?.length ?? 0, @@ -235,8 +235,8 @@ export async function getSortedBuilders({ user: { select: { id: true, - username: true, path: true, + displayName: true, builderStatus: true, userAllTimeStats: { select: { @@ -274,8 +274,8 @@ export async function getSortedBuilders({ id: stat.user.id, rank: stat.rank ?? -1, nftImageUrl: stat.user.builderNfts[0]?.imageUrl, - username: stat.user.username, - path: stat.user.path, + path: stat.user.path!, + displayName: stat.user.displayName, builderPoints: stat.user.userAllTimeStats[0]?.pointsEarnedAsBuilder ?? 0, price: stat.user.builderNfts?.[0]?.currentPrice ?? 0, nftsSold: stat.user.userSeasonStats[0]?.nftsSold ?? 0, diff --git a/apps/scoutgame/lib/builders/getTodaysHotBuilders.ts b/apps/scoutgame/lib/builders/getTodaysHotBuilders.ts index d5aeedcb4b..2b605ff7b5 100644 --- a/apps/scoutgame/lib/builders/getTodaysHotBuilders.ts +++ b/apps/scoutgame/lib/builders/getTodaysHotBuilders.ts @@ -26,7 +26,7 @@ export async function getTodaysHotBuilders(): Promise { const builders = await prisma.scout.findMany({ where: { builderStatus: 'approved', - username: { + path: { in: preselectedBuilderUsernames } }, @@ -70,8 +70,8 @@ export async function getTodaysHotBuilders(): Promise { .map((builder) => { return { id: builder.id, - username: builder.username || '', - path: builder.path, + path: builder.path!, + displayName: builder.displayName, builderPoints: builder.userSeasonStats[0]?.pointsEarnedAsBuilder || 0, price: builder.builderNfts[0]?.currentPrice ?? 0, nftImageUrl: builder.builderNfts[0]?.imageUrl, @@ -158,8 +158,8 @@ export async function getTodaysHotBuilders(): Promise { const user = builder.user; return { id: user.id, - username: user.username || '', - path: user.path, + path: user.path!, + displayName: user.displayName, builderPoints: user.userSeasonStats[0]?.pointsEarnedAsBuilder || 0, price: user.builderNfts[0]?.currentPrice ?? 0, nftImageUrl: user.builderNfts[0]?.imageUrl, diff --git a/apps/scoutgame/lib/builders/getTopBuilders.ts b/apps/scoutgame/lib/builders/getTopBuilders.ts index aaea4cc924..76b0685d6e 100644 --- a/apps/scoutgame/lib/builders/getTopBuilders.ts +++ b/apps/scoutgame/lib/builders/getTopBuilders.ts @@ -6,8 +6,8 @@ import { isTruthy } from '@root/lib/utils/types'; export type TopBuilderInfo = { id: string; - username: string | null; - path: string | null; + displayName: string; + path: string; avatar: string | null; seasonPoints: number; builderStatus: BuilderStatus; @@ -38,8 +38,8 @@ export async function getTopBuilders({ select: { id: true, builderStatus: true, - username: true, path: true, + displayName: true, avatar: true, userAllTimeStats: { select: { @@ -69,7 +69,7 @@ export async function getTopBuilders({ return topBuilders .map((builder) => { const { user, pointsEarnedAsBuilder } = builder; - const { id, username, path, avatar } = user; + const { id, path, displayName, avatar } = user; const nft = user.builderNfts[0]; if (!nft) { return null; @@ -77,8 +77,8 @@ export async function getTopBuilders({ return { id, - username, - path, + path: path!, + displayName, avatar, nftImageUrl: nft.imageUrl, seasonPoints: pointsEarnedAsBuilder, diff --git a/apps/scoutgame/lib/builders/searchBuilders.ts b/apps/scoutgame/lib/builders/searchBuilders.ts index a3f63c30be..e01f398154 100644 --- a/apps/scoutgame/lib/builders/searchBuilders.ts +++ b/apps/scoutgame/lib/builders/searchBuilders.ts @@ -3,7 +3,8 @@ import { currentSeason } from '@packages/scoutgame/dates'; export type BuilderSearchResult = { id: string; - username: string; + path: string; + displayName: string; avatar: string | null; seasonPoints: number; allTimePoints: number; @@ -12,31 +13,42 @@ export type BuilderSearchResult = { }; export async function searchBuilders({ - username, + search, limit = 10 }: { - username: string; + search: string; limit?: number; }): Promise { const builders = await prisma.scout.findMany({ where: { - username: { - contains: username, - mode: 'insensitive' - } + OR: [ + { + path: { + contains: search, + mode: 'insensitive' + } + }, + { + displayName: { + contains: search, + mode: 'insensitive' + } + } + ] }, take: limit, orderBy: { // Sort by similarity to the query _relevance: { - fields: ['username'], - search: username, + fields: ['path', 'displayName'], + search, sort: 'desc' } }, select: { id: true, - username: true, + path: true, + displayName: true, avatar: true, userSeasonStats: { where: { @@ -67,7 +79,8 @@ export async function searchBuilders({ return builders.map((builder) => ({ id: builder.id, - username: builder.username || '', + path: builder.path!, + displayName: builder.displayName!, avatar: builder.avatar, seasonPoints: builder.userSeasonStats?.[0]?.pointsEarnedAsBuilder ?? 0, allTimePoints: builder.userAllTimeStats?.[0]?.pointsEarnedAsBuilder ?? 0, diff --git a/apps/scoutgame/lib/farcaster/findOrCreateFarcasterUser.ts b/apps/scoutgame/lib/farcaster/findOrCreateFarcasterUser.ts index b980edbef4..9b1fac6679 100644 --- a/apps/scoutgame/lib/farcaster/findOrCreateFarcasterUser.ts +++ b/apps/scoutgame/lib/farcaster/findOrCreateFarcasterUser.ts @@ -23,8 +23,8 @@ export async function findOrCreateFarcasterUser({ avatar: profile.pfp_url, bio: profile.profile.bio.text, walletAddress: profile.verifications[0], - displayName: profile.display_name || profile.username, - username: profile.username, + displayName: profile.display_name, + path: profile.username, tierOverride }); } diff --git a/apps/scoutgame/lib/scouts/getScoutedBuilders.ts b/apps/scoutgame/lib/scouts/getScoutedBuilders.ts index 165c290aad..198c2be1d3 100644 --- a/apps/scoutgame/lib/scouts/getScoutedBuilders.ts +++ b/apps/scoutgame/lib/scouts/getScoutedBuilders.ts @@ -76,8 +76,8 @@ export async function getScoutedBuilders({ scoutId }: { scoutId: string }): Prom return { id: builder.id, nftImageUrl: builder.builderNfts[0]?.imageUrl, - username: builder.username || '', - path: builder.path, + path: builder.path!, + displayName: builder.displayName, builderStatus: builder.builderStatus!, builderPoints: builder.userSeasonStats[0]?.pointsEarnedAsBuilder ?? 0, nftsSold: builder.userSeasonStats[0]?.nftsSold ?? 0, diff --git a/apps/scoutgame/lib/scouts/getTopScouts.ts b/apps/scoutgame/lib/scouts/getTopScouts.ts index 0a5261a86c..f0777554fd 100644 --- a/apps/scoutgame/lib/scouts/getTopScouts.ts +++ b/apps/scoutgame/lib/scouts/getTopScouts.ts @@ -3,7 +3,8 @@ import { currentSeason } from '@packages/scoutgame/dates'; export type TopScout = { id: string; - username: string; + path: string; + displayName: string; avatar: string | null; buildersScouted: number; nftsHeld: number; @@ -25,7 +26,8 @@ export async function getTopScouts({ limit }: { limit: number }): Promise { }, select: { id: true, - username: true, path: true, displayName: true, avatar: true, diff --git a/apps/scoutgame/lib/session/getUserFromSession.ts b/apps/scoutgame/lib/session/getUserFromSession.ts index 3e56b08b03..d1debf3cf7 100644 --- a/apps/scoutgame/lib/session/getUserFromSession.ts +++ b/apps/scoutgame/lib/session/getUserFromSession.ts @@ -6,7 +6,6 @@ import { cacheGetUser, getUser } from './getUser'; export type SessionUser = Pick< Scout, | 'id' - | 'username' | 'path' | 'displayName' | 'avatar' diff --git a/apps/scoutgame/lib/users/findOrCreateUser.ts b/apps/scoutgame/lib/users/findOrCreateUser.ts index 49ecd86a5f..ae1c7cc30f 100644 --- a/apps/scoutgame/lib/users/findOrCreateUser.ts +++ b/apps/scoutgame/lib/users/findOrCreateUser.ts @@ -34,7 +34,7 @@ export async function findOrCreateUser({ avatar?: string; bio?: string; displayName: string; - username: string; + path: string; tierOverride?: ConnectWaitlistTier; }): Promise { if (!farcasterId && !walletAddress) { @@ -130,7 +130,8 @@ export async function findOrCreateUser({ trackUserAction('sign_up', { userId: newScout.id, - username: userProps.username, + path: userProps.path!, + displayName: userProps.displayName, fid: farcasterId }); diff --git a/apps/scoutgame/lib/users/getUserByPath.ts b/apps/scoutgame/lib/users/getUserByPath.ts index c11163ceac..e05996e3b3 100644 --- a/apps/scoutgame/lib/users/getUserByPath.ts +++ b/apps/scoutgame/lib/users/getUserByPath.ts @@ -5,7 +5,7 @@ import { cache } from 'react'; import type { BasicUserInfo } from './interfaces'; import { BasicUserInfoSelect } from './queries'; -export async function getUserByUsernamePath(username: string): Promise< +async function _getUserByPath(path: string): Promise< | (BasicUserInfo & { nftImageUrl?: string; congratsImageUrl?: string | null; @@ -16,7 +16,7 @@ export async function getUserByUsernamePath(username: string): Promise< > { const user = await prisma.scout.findFirst({ where: { - username + path }, select: { ...BasicUserInfoSelect, displayName: true, builderNfts: true } }); @@ -27,10 +27,11 @@ export async function getUserByUsernamePath(username: string): Promise< return { ...user, + path: user.path!, nftImageUrl: user?.builderNfts[0]?.imageUrl, congratsImageUrl: user?.builderNfts[0]?.congratsImageUrl, githubLogin: user?.githubUser[0]?.login }; } -export const getUserByPath = cache(getUserByUsernamePath); +export const getUserByPath = cache(_getUserByPath); diff --git a/apps/scoutgame/lib/users/interfaces.ts b/apps/scoutgame/lib/users/interfaces.ts index 22f9180c5a..57a818ac5c 100644 --- a/apps/scoutgame/lib/users/interfaces.ts +++ b/apps/scoutgame/lib/users/interfaces.ts @@ -2,8 +2,8 @@ import type { BuilderStatus } from '@charmverse/core/prisma'; export type MinimalUserInfo = { id: string; - username: string | null; - path: string | null; + displayName: string; + path: string; avatar?: string | null; }; diff --git a/apps/scoutgame/lib/users/queries.ts b/apps/scoutgame/lib/users/queries.ts index 17845aa8c8..b720262339 100644 --- a/apps/scoutgame/lib/users/queries.ts +++ b/apps/scoutgame/lib/users/queries.ts @@ -2,8 +2,8 @@ import type { Prisma } from '@charmverse/core/prisma-client'; export const BasicUserInfoSelect = { id: true, - username: true, path: true, + displayName: true, avatar: true, bio: true, githubUser: { @@ -16,7 +16,7 @@ export const BasicUserInfoSelect = { export const MinimalScoutInfoSelect = { id: true, - username: true, + path: true, avatar: true, displayName: true } satisfies Prisma.ScoutSelect; diff --git a/apps/scoutgame/middleware.ts b/apps/scoutgame/middleware.ts index afaa1f431e..0b66d4e386 100644 --- a/apps/scoutgame/middleware.ts +++ b/apps/scoutgame/middleware.ts @@ -2,7 +2,7 @@ import { getSession } from '@connect-shared/lib/session/getSession'; import type { NextRequest } from 'next/server'; import { NextResponse } from 'next/server'; -const privateLinks = ['/profile', '/notifications', '/welcome']; +const privateLinks = ['/profile', '/notifications', '/welcome', '/claim']; export async function middleware(request: NextRequest) { const session = await getSession(); const isLoggedIn = !!session.scoutId; diff --git a/apps/scoutgame/scripts/onboardScouts.ts b/apps/scoutgame/scripts/onboardScouts.ts index ba78ffbef1..9efae54097 100644 --- a/apps/scoutgame/scripts/onboardScouts.ts +++ b/apps/scoutgame/scripts/onboardScouts.ts @@ -20,7 +20,7 @@ async function onboardScouts({ fids, tierOverride }: { fids: number[]; tierOverr const fid = fidsRequiringAccount[i]; console.log(`Creating user ${i + 1} / ${totalFidsToProcess}`); const user = await findOrCreateFarcasterUser({ fid, tierOverride: 'mythic' }); - console.log(`Created user ${user.id}. View: https://scoutgame.xyz/u/${user.username}`); + console.log(`Created user ${user.id}. View: https://scoutgame.xyz/u/${user.path}`); } } diff --git a/apps/scoutgameadmin/app/api/partners/celo/route.ts b/apps/scoutgameadmin/app/api/partners/celo/route.ts index 2b71e244d3..3747cd10e3 100644 --- a/apps/scoutgameadmin/app/api/partners/celo/route.ts +++ b/apps/scoutgameadmin/app/api/partners/celo/route.ts @@ -25,7 +25,7 @@ export async function GET() { builder: { select: { email: true, - username: true + displayName: true } }, githubEvent: { @@ -43,7 +43,7 @@ export async function GET() { } }); const rows = events.map((event) => ({ - 'Farcaster Name': event.builder.username, + 'Farcaster Name': event.builder.displayName, Email: event.builder.email, Repo: `${event.githubEvent!.repo.owner}/${event.githubEvent!.repo.name}`, Date: event.createdAt.toDateString(), diff --git a/apps/scoutgameadmin/app/api/partners/moxie/route.ts b/apps/scoutgameadmin/app/api/partners/moxie/route.ts index 5a5aeef056..8e45a540e7 100644 --- a/apps/scoutgameadmin/app/api/partners/moxie/route.ts +++ b/apps/scoutgameadmin/app/api/partners/moxie/route.ts @@ -10,11 +10,11 @@ export const dynamic = 'force-dynamic'; type MoxieBonusRow = { 'Builder FID': number; - 'Builder username': string; + 'Builder path': string; 'Builder event': string; 'Scout FID': number; 'Scout email': string; - 'Scout username': string; + 'Scout path': string; }; export async function GET() { @@ -27,7 +27,7 @@ export async function GET() { }, select: { farcasterId: true, - username: true, + path: true, events: { where: { type: { @@ -46,7 +46,7 @@ export async function GET() { scout: { select: { farcasterId: true, - username: true + path: true } } } @@ -82,9 +82,9 @@ export async function GET() { rows.push({ 'Scout FID': scoutFid!, 'Scout email': scout.email || '', - 'Scout username': scout.username, + 'Scout path': scout.path!, 'Builder FID': builder.farcasterId, - 'Builder username': builder.username, + 'Builder path': builder.path!, 'Builder event': (builder.events[0]!.type === 'merged_pull_request' ? `PR on ` : `Commit on `) + builder.events[0]!.createdAt.toDateString() diff --git a/apps/scoutgameadmin/app/api/users/export/route.ts b/apps/scoutgameadmin/app/api/users/export/route.ts index 816e87abd5..c9b4ffcbcd 100644 --- a/apps/scoutgameadmin/app/api/users/export/route.ts +++ b/apps/scoutgameadmin/app/api/users/export/route.ts @@ -8,7 +8,7 @@ export const dynamic = 'force-dynamic'; type ScoutWithGithubUser = { id: string; - username: string; + path: string; createdAt: string; email?: string; tokenId?: number; @@ -29,7 +29,7 @@ export async function GET() { const users = await prisma.scout.findMany({ select: { id: true, - username: true, + path: true, sendMarketing: true, createdAt: true, avatar: true, @@ -50,7 +50,7 @@ export async function GET() { }); const rows: ScoutWithGithubUser[] = users.map((user) => ({ id: user.id, - username: user.username, + path: user.path!, createdAt: user.createdAt.toDateString(), email: user.email || undefined, optedInToMarketing: user.sendMarketing ? 'Yes' : '', diff --git a/apps/scoutgameadmin/lib/users/getUser.ts b/apps/scoutgameadmin/lib/users/getUser.ts index 8a0f741c01..1a5764ea45 100644 --- a/apps/scoutgameadmin/lib/users/getUser.ts +++ b/apps/scoutgameadmin/lib/users/getUser.ts @@ -42,7 +42,7 @@ export async function getUser({ searchString }: { searchString: string }): Promi } const user = await prisma.scout.findUnique({ where: { - username: searchString + path: searchString }, include: { githubUser: true diff --git a/apps/scoutgamecron/src/scripts/createBuilder.ts b/apps/scoutgamecron/src/scripts/createBuilder.ts index 80c65cf2ed..95f5ede499 100644 --- a/apps/scoutgamecron/src/scripts/createBuilder.ts +++ b/apps/scoutgamecron/src/scripts/createBuilder.ts @@ -38,12 +38,12 @@ async function createBuilder({ fid, githubLogin }: { fid: number; githubLogin: s // } const builder = await prisma.scout.upsert({ where: { - username + path: username }, update: {}, create: { displayName, - username, + path: username, avatar: avatarUrl, bio, builderStatus: 'applied', @@ -63,7 +63,7 @@ async function createBuilder({ fid, githubLogin }: { fid: number; githubLogin: s }); console.log('Created a builder record', builder); await approveBuilder({ builderId: builder.id, season: currentSeason }); - console.log('Builder NFT created. View profile here:', 'https://scoutgame.xyz/u/' + builder.username); + console.log('Builder NFT created. View profile here:', 'https://scoutgame.xyz/u/' + builder.path); process.exit(0); } diff --git a/apps/scoutgamecron/src/scripts/createBuilders.ts b/apps/scoutgamecron/src/scripts/createBuilders.ts index aaf74d2fff..6b1ce57247 100644 --- a/apps/scoutgamecron/src/scripts/createBuilders.ts +++ b/apps/scoutgamecron/src/scripts/createBuilders.ts @@ -35,21 +35,21 @@ async function createBuilders() { continue; } const displayName = profile.display_name; - const username = profile.username; + const path = profile.username; const avatarUrl = profile.pfp_url; const bio = profile.profile.bio.text; - if (!username) { + if (!path) { log.info(`No username found for ${login} with fid ${fid}`); continue; } const builder = await prisma.scout.upsert({ where: { - username + path }, update: {}, create: { displayName, - username, + path, avatar: avatarUrl, bio, builderStatus: 'applied', diff --git a/apps/scoutgamecron/src/scripts/createScouts.ts b/apps/scoutgamecron/src/scripts/createScouts.ts index 5885d1059e..38279f2457 100644 --- a/apps/scoutgamecron/src/scripts/createScouts.ts +++ b/apps/scoutgamecron/src/scripts/createScouts.ts @@ -17,12 +17,12 @@ async function createScouts(farcasterUsernames: string[]) { const scout = await prisma.scout.upsert({ where: { - username: farcasterUsername + path: farcasterUsername }, update: {}, create: { displayName, - username: farcasterUsername, + path: farcasterUsername, avatar: avatarUrl, bio, farcasterId: fid, diff --git a/apps/scoutgamecron/src/scripts/deleteBuilderAndRedistributePoints.ts b/apps/scoutgamecron/src/scripts/deleteBuilderAndRedistributePoints.ts index af64e4e9ff..aa01911e33 100644 --- a/apps/scoutgamecron/src/scripts/deleteBuilderAndRedistributePoints.ts +++ b/apps/scoutgamecron/src/scripts/deleteBuilderAndRedistributePoints.ts @@ -2,15 +2,15 @@ import { prisma } from '@charmverse/core/prisma-client'; import { currentSeason } from '@packages/scoutgame/dates'; import { sendPoints } from '@packages/scoutgame/points/sendPoints'; -async function deleteBuilderAndRedistributePoints({ builderUsername }: { builderUsername: string }) { +async function deleteBuilderAndRedistributePoints({ builderPath }: { builderPath: string }) { const builder = await prisma.scout.findUnique({ where: { - username: builderUsername + path: builderPath } }); if (!builder) { - throw new Error(`Builder with username ${builderUsername} not found`); + throw new Error(`Builder with path ${builderPath} not found`); } const nftPurchaseEvents = await prisma.nFTPurchaseEvent.findMany({ @@ -18,7 +18,7 @@ async function deleteBuilderAndRedistributePoints({ builderUsername }: { builder builderNFT: { season: currentSeason, builder: { - username: builderUsername + path: builderPath } } }, @@ -46,7 +46,7 @@ async function deleteBuilderAndRedistributePoints({ builderUsername }: { builder async (tx) => { await prisma.scout.delete({ where: { - username: builderUsername + path: builderPath } }); await prisma.nFTPurchaseEvent.deleteMany({ @@ -88,5 +88,5 @@ async function deleteBuilderAndRedistributePoints({ builderUsername }: { builder } deleteBuilderAndRedistributePoints({ - builderUsername: '' + builderPath: 'path' }); diff --git a/apps/scoutgamecron/src/scripts/fixArtwork.ts b/apps/scoutgamecron/src/scripts/fixArtwork.ts index ef64a01428..d0c0533c79 100644 --- a/apps/scoutgamecron/src/scripts/fixArtwork.ts +++ b/apps/scoutgamecron/src/scripts/fixArtwork.ts @@ -3,7 +3,7 @@ import { prisma } from '@charmverse/core/prisma-client'; import { uploadMetadata } from '@packages/scoutgame/builderNfts/artwork/uploadMetadata'; import { builderContractReadonlyApiClient } from '@packages/scoutgame/builderNfts/clients/builderContractReadClient'; import { getBuilderContractAddress } from '@packages/scoutgame/builderNfts/constants'; -import { uploadArtwork, uploadArtworkCongrats } from '@packages/scoutgame/builderNfts/artwork/uploadArtwork'; +import { uploadArtwork } from '@packages/scoutgame/builderNfts/artwork/uploadArtwork'; import { currentSeason } from '@packages/scoutgame/dates'; async function refreshArtworks() { @@ -15,7 +15,8 @@ async function refreshArtworks() { builder: { select: { avatar: true, - username: true + path: true, + displayName: true } } }, @@ -53,7 +54,7 @@ async function refreshArtworks() { avatar, season: currentSeason, tokenId: BigInt(tokenId), - username: nft.builder.username + displayName: nft.builder.displayName }); await prisma.builderNft.update({ @@ -68,7 +69,7 @@ async function refreshArtworks() { const metadataPath = await uploadMetadata({ season: currentSeason, tokenId: BigInt(tokenId), - username: nft.builder.username, + path: nft.builder.path!, attributes: [] }); diff --git a/apps/scoutgamecron/src/scripts/issuePoints.ts b/apps/scoutgamecron/src/scripts/issuePoints.ts index b295c00f37..5ab89c89c6 100644 --- a/apps/scoutgamecron/src/scripts/issuePoints.ts +++ b/apps/scoutgamecron/src/scripts/issuePoints.ts @@ -48,7 +48,7 @@ async function issuePoints({ points }: { points: number }) { tx }); - await refreshPointStatsFromHistory({ userIdOrUsername: scout.id, tx }); + await refreshPointStatsFromHistory({ userIdOrPath: scout.id, tx }); }, { timeout: 15000 } ); diff --git a/apps/scoutgamecron/src/scripts/refreshPointsFromTransactionHistory.ts b/apps/scoutgamecron/src/scripts/refreshPointsFromTransactionHistory.ts index 9c3da84fcf..43ac90321a 100644 --- a/apps/scoutgamecron/src/scripts/refreshPointsFromTransactionHistory.ts +++ b/apps/scoutgamecron/src/scripts/refreshPointsFromTransactionHistory.ts @@ -5,7 +5,7 @@ import { prettyPrint } from '@packages/utils/strings'; async function refreshPointsFromTransactionHistory() { const scouts = await prisma.scout.findMany({ - select: { id: true, username: true }, + select: { id: true, path: true }, orderBy: { id: 'asc' }, @@ -19,11 +19,11 @@ async function refreshPointsFromTransactionHistory() { for (let i = 0; i < scouts.length; i++) { const scout = scouts[i]; try { - log.info(`Fixing points for ${scout.username} ${i + 1} / ${scouts.length}`); - const stats = await refreshPointStatsFromHistory({ userIdOrUsername: scout.id }); - log.info(`Successfully fixed points for ${scout.username}. New balance: ${stats.balance}`); + log.info(`Fixing points for ${scout.path} ${i + 1} / ${scouts.length}`); + const stats = await refreshPointStatsFromHistory({ userIdOrPath: scout.id }); + log.info(`Successfully fixed points for ${scout.path}. New balance: ${stats.balance}`); } catch (error) { - log.error(`Failed to fix points for ${scout.username}: ${prettyPrint(error)}`); + log.error(`Failed to fix points for ${scout.path}: ${prettyPrint(error)}`); } } } diff --git a/apps/scoutgamecron/src/scripts/seeder/generateBuilder.ts b/apps/scoutgamecron/src/scripts/seeder/generateBuilder.ts index 50b1b19b55..226dc58078 100644 --- a/apps/scoutgamecron/src/scripts/seeder/generateBuilder.ts +++ b/apps/scoutgamecron/src/scripts/seeder/generateBuilder.ts @@ -1,6 +1,6 @@ import fs from 'fs/promises'; import { fileURLToPath } from 'url'; -import path from 'path'; +import {dirname, join} from 'path'; import type { Prisma } from '@charmverse/core/prisma-client'; import { prisma } from '@charmverse/core/prisma-client'; import { faker } from '@faker-js/faker'; @@ -12,7 +12,7 @@ export async function generateBuilder({ index }: { index: number }) { const firstName = faker.person.firstName(); const lastName = faker.person.lastName(); const displayName = `${firstName} ${lastName}`; - const username = faker.internet + const path = faker.internet .userName({ firstName, lastName @@ -28,7 +28,7 @@ export async function generateBuilder({ index }: { index: number }) { const githubUser = { id: faker.number.int({ min: 10000000, max: 25000000 }), - login: username, + login: path, email, displayName }; @@ -41,13 +41,13 @@ export async function generateBuilder({ index }: { index: number }) { const imageUrl = faker.datatype.boolean() ? avatar : faker.image.url(); const nftImageBuffer = await generateNftImage({ avatar: imageUrl, - username + displayName }); // images will be hosted by the const __filename = fileURLToPath(import.meta.url); - const __dirname = path.dirname(__filename); - const scoutgamePublicFolder = path.join( + const __dirname = dirname(__filename); + const scoutgamePublicFolder = join( __dirname, '..', '..', @@ -93,7 +93,7 @@ export async function generateBuilder({ index }: { index: number }) { } const builder = await prisma.scout.create({ data: { - username, + path, displayName, email, avatar, diff --git a/apps/scoutgamecron/src/scripts/seeder/generateNftPurchaseEvents.ts b/apps/scoutgamecron/src/scripts/seeder/generateNftPurchaseEvents.ts index 6147e95988..24dcf480db 100644 --- a/apps/scoutgamecron/src/scripts/seeder/generateNftPurchaseEvents.ts +++ b/apps/scoutgamecron/src/scripts/seeder/generateNftPurchaseEvents.ts @@ -1,10 +1,11 @@ import { prisma } from '@charmverse/core/prisma-client'; import { faker } from '@faker-js/faker'; -import { currentSeason, getWeekFromDate } from '@packages/scoutgame/dates'; -import { BuilderInfo } from './generateSeedData'; +import { builderTokenDecimals } from '@packages/scoutgame/builderNfts/constants'; +import { recordNftMintWithoutRefresh } from '@packages/scoutgame/builderNfts/recordNftMint'; +import { getWeekFromDate } from '@packages/scoutgame/dates'; import { DateTime } from 'luxon'; +import { BuilderInfo } from './generateSeedData'; import { randomTimeOfDay } from './generator'; -import { builderTokenDecimals } from '@packages/scoutgame/builderNfts/constants'; export async function generateNftPurchaseEvents(scoutId: string, assignedBuilders: BuilderInfo[], date: DateTime) { const week = getWeekFromDate(date.toJSDate()); @@ -25,47 +26,20 @@ export async function generateNftPurchaseEvents(scoutId: string, assignedBuilder id: builder.builderNftId }, data: { - currentPrice: Math.ceil(nftPrice + nftPrice * 0.1) + currentPrice: Math.ceil(nftPrice + nftPrice * 0.1), } - }), - await tx.nFTPurchaseEvent.create({ - data: { - id: faker.string.uuid(), - scoutId, - tokensPurchased: nftsPurchased, - txHash: faker.finance.ethereumAddress(), - // Converting points to fiat equivalent in order to reduce the number of points earned - pointsValue, - paidInPoints: false, - builderNftId, - activities: { - create: { - recipientType: 'builder', - type: 'nft_purchase', - userId: builder.id, - createdAt - } - }, - builderEvent: { - create: { - id: faker.string.uuid(), - builderId: builder.id, - season: currentSeason, - week, - type: 'nft_purchase', - createdAt, - pointsReceipts: { - create: { - id: faker.string.uuid(), - recipientId: builder.id, - value: pointsValue * 0.1, - createdAt - } - } - } - } - } - }); + }) + + await recordNftMintWithoutRefresh({ + builderNftId, + amount: nftsPurchased, + paidWithPoints: false, + pointsValue, + scoutId, + mintTxHash: faker.finance.ethereumAddress(), + recipientAddress: faker.finance.ethereumAddress(), + createdAt + }); }); } } diff --git a/apps/scoutgamecron/src/scripts/seeder/generateScout.ts b/apps/scoutgamecron/src/scripts/seeder/generateScout.ts index e14a3e96a0..56134a66a9 100644 --- a/apps/scoutgamecron/src/scripts/seeder/generateScout.ts +++ b/apps/scoutgamecron/src/scripts/seeder/generateScout.ts @@ -5,7 +5,7 @@ export async function generateScout({ index }: { index: number }) { const firstName = faker.person.firstName(); const lastName = faker.person.lastName(); const displayName = `${firstName} ${lastName}`; - const username = faker.internet + const path = faker.internet .userName({ firstName, lastName @@ -20,7 +20,7 @@ export async function generateScout({ index }: { index: number }) { const scout = await prisma.scout.create({ data: { - username, + path, displayName, email, avatar, diff --git a/apps/scoutgamecron/src/scripts/seeder/generateSeedData.ts b/apps/scoutgamecron/src/scripts/seeder/generateSeedData.ts index 4c7d88bd68..fcf33ce0bf 100644 --- a/apps/scoutgamecron/src/scripts/seeder/generateSeedData.ts +++ b/apps/scoutgamecron/src/scripts/seeder/generateSeedData.ts @@ -123,8 +123,6 @@ export async function generateSeedData() { totalGithubEvents += dailyGithubEvents; } - await updateBuildersRank({ week }); - for (const scout of scouts) { // Do not purchase your own nft const dailyNftsPurchased = await generateNftPurchaseEvents( @@ -136,9 +134,10 @@ export async function generateSeedData() { } await updateBuilderCardActivity(date.minus({ days: 1 })); - + // Check if we are at the end of the week if (date.weekday === 7) { + await updateBuildersRank({ week }); const topWeeklyBuilders = await getBuildersLeaderboard({ quantity: 100, week }); for (const { builder, gemsCollected, rank } of topWeeklyBuilders) { try { @@ -173,6 +172,8 @@ export async function generateSeedData() { } } + await updateBuildersRank({ week: getWeekFromDate(endDate.toJSDate()) }); + log.info('generated seed data', { totalUsers, totalBuilders, diff --git a/apps/scoutgamecron/src/scripts/seeder/seedWithRealCharmverseGithubData.ts b/apps/scoutgamecron/src/scripts/seeder/seedWithRealCharmverseGithubData.ts index a46525bc3d..19d38d8346 100644 --- a/apps/scoutgamecron/src/scripts/seeder/seedWithRealCharmverseGithubData.ts +++ b/apps/scoutgamecron/src/scripts/seeder/seedWithRealCharmverseGithubData.ts @@ -82,7 +82,7 @@ export async function seedWithRealCharmverseGithubData() { builder: { create: { displayName: builder, - username: builder + Math.random().toString().replace('.', '').slice(0, 6), + path: builder + Math.random().toString().replace('.', '').slice(0, 6), builderStatus: 'approved', avatar } @@ -98,7 +98,7 @@ export async function seedWithRealCharmverseGithubData() { builder: { create: { displayName: builder, - username: builder, + path: builder, builderStatus: 'approved', avatar: avatar } diff --git a/apps/scoutgamecron/src/tasks/processGemsPayout/sendGemsPayoutEmails/sendGemsPayoutEmails.ts b/apps/scoutgamecron/src/tasks/processGemsPayout/sendGemsPayoutEmails/sendGemsPayoutEmails.ts index 714e14c5f0..203092ce08 100644 --- a/apps/scoutgamecron/src/tasks/processGemsPayout/sendGemsPayoutEmails/sendGemsPayoutEmails.ts +++ b/apps/scoutgamecron/src/tasks/processGemsPayout/sendGemsPayoutEmails/sendGemsPayoutEmails.ts @@ -15,7 +15,6 @@ export async function sendGemsPayoutEmails({ week }: { week: string }) { }, select: { id: true, - username: true, displayName: true, email: true } diff --git a/apps/scoutgamecron/src/tasks/updateMixpanelProfilesTask.ts b/apps/scoutgamecron/src/tasks/updateMixpanelProfilesTask.ts index f5b11d62df..d283771bfe 100644 --- a/apps/scoutgamecron/src/tasks/updateMixpanelProfilesTask.ts +++ b/apps/scoutgamecron/src/tasks/updateMixpanelProfilesTask.ts @@ -8,7 +8,7 @@ function getMixpanelUserProfile(user: Scout): MixPanelUserProfile { return { $name: user.displayName, $email: user.email, - username: user.username, + path: user.path!, onboarded: !!user.onboardedAt, 'Agreed To TOS': !!user.agreedToTermsAt, 'Enable Marketing': user.sendMarketing, diff --git a/package-lock.json b/package-lock.json index 5d6ce6998a..958beb3191 100644 --- a/package-lock.json +++ b/package-lock.json @@ -122103,4 +122103,4 @@ } } } -} +} \ No newline at end of file diff --git a/package.json b/package.json index 43f7080da0..33039c3c57 100644 --- a/package.json +++ b/package.json @@ -468,4 +468,4 @@ "msw": { "workerDirectory": "public" } -} +} \ No newline at end of file diff --git a/packages/mixpanel/src/interfaces.ts b/packages/mixpanel/src/interfaces.ts index 87a43b3e44..b4482ed7b8 100644 --- a/packages/mixpanel/src/interfaces.ts +++ b/packages/mixpanel/src/interfaces.ts @@ -9,7 +9,8 @@ export interface BaseEventWithoutGroup { } export type UserSignupEvent = BaseEventWithoutGroup & { - username: string; + displayName: string; + path: string; fid?: number; }; diff --git a/packages/mixpanel/src/updateUserProfile.ts b/packages/mixpanel/src/updateUserProfile.ts index 1854e90ac5..0bbc5e6721 100644 --- a/packages/mixpanel/src/updateUserProfile.ts +++ b/packages/mixpanel/src/updateUserProfile.ts @@ -6,7 +6,7 @@ import { getApiKey } from './mixpanel'; export type MixPanelUserProfile = { $name: string; $email: string | null; - username: string; + path: string; onboarded: boolean; 'Agreed To TOS': boolean; 'Enable Marketing': boolean; diff --git a/packages/scoutgame/src/__tests__/getBuildersLeaderboard.spec.ts b/packages/scoutgame/src/__tests__/getBuildersLeaderboard.spec.ts index a7c7945541..a577439c41 100644 --- a/packages/scoutgame/src/__tests__/getBuildersLeaderboard.spec.ts +++ b/packages/scoutgame/src/__tests__/getBuildersLeaderboard.spec.ts @@ -100,14 +100,14 @@ describe('getBuildersLeaderboard', () => { it('should sort builders by username when gems collected and events are the same', async () => { const testWeek = v4(); const builders = await Promise.all([ - mockBuilder({ username: `charlie-${v4()}` }), - mockBuilder({ username: `alice-${v4()}` }), - mockBuilder({ username: `bob-${v4()}` }), - mockBuilder({ username: `david-${v4()}` }), - mockBuilder({ username: `eve-${v4()}` }) + mockBuilder({ path: `charlie-${v4()}` }), + mockBuilder({ path: `alice-${v4()}` }), + mockBuilder({ path: `bob-${v4()}` }), + mockBuilder({ path: `david-${v4()}` }), + mockBuilder({ path: `eve-${v4()}` }) ]); - const sortedBuilders = builders.sort((a, b) => a.username.localeCompare(b.username)); + const sortedBuilders = builders.sort((a, b) => a.displayName.localeCompare(b.displayName)); // Create weekly stats with 0 gems collected for all builders await Promise.all( @@ -129,7 +129,7 @@ describe('getBuildersLeaderboard', () => { // Check if builders are sorted by username in ascending order sortedBuilders.forEach((builder, index) => { - expect(topBuilders[index].builder.username).toBe(builder.username); + expect(topBuilders[index].builder.displayName).toBe(builder.displayName); }); // Verify that all builders have 0 gems collected @@ -146,11 +146,12 @@ describe('getBuildersLeaderboard', () => { it('should only include builders with approved status', async () => { const testWeek = v4(); const builders = await Promise.all([ - mockBuilder({ username: `charlie-${v4()}`, builderStatus: 'approved' }), - mockBuilder({ username: `alice-${v4()}`, builderStatus: 'applied' }), - mockBuilder({ username: `bob-${v4()}`, builderStatus: 'rejected' }), - mockBuilder({ username: `david-${v4()}`, builderStatus: 'approved' }), - mockBuilder({ username: `eve-${v4()}`, builderStatus: 'approved' }) + mockBuilder({ builderStatus: 'approved', displayName: 'Charlie' }), + mockBuilder({ builderStatus: 'approved', displayName: 'David' }), + mockBuilder({ builderStatus: 'approved', displayName: 'Eve' }), + mockBuilder({ builderStatus: 'applied', displayName: 'Alice' }), + mockBuilder({ builderStatus: 'banned', displayName: 'Foo' }), + mockBuilder({ builderStatus: 'rejected', displayName: 'Bob' }) ]); // Create weekly stats with 0 gems collected for all builders diff --git a/packages/scoutgame/src/builderNfts/artwork/generateNftImage.tsx b/packages/scoutgame/src/builderNfts/artwork/generateNftImage.tsx index b7dd37cc32..d595898dc2 100644 --- a/packages/scoutgame/src/builderNfts/artwork/generateNftImage.tsx +++ b/packages/scoutgame/src/builderNfts/artwork/generateNftImage.tsx @@ -82,13 +82,81 @@ function calculateFontSize(text: string, maxWidth: number, initialFontSize: numb return minFontSize; } +export async function updateNftImage({ + displayName, + currentNftImage +}: { + currentNftImage: string; + displayName: string; +}): Promise { + const cutoutWidth = 300; + const cutoutHeight = 400; + + const baseImage = new ImageResponse( + ( +
    + +
    +

    + {displayName} +

    +
    +
    + ), + { + width: cutoutWidth, + height: cutoutHeight + } + ); + + const baseImageBuffer = await baseImage.arrayBuffer(); + const imageBuffer = sharp(Buffer.from(baseImageBuffer)).png().toBuffer(); + + return imageBuffer; +} + export async function generateNftImage({ avatar, - username, + displayName, imageHostingBaseUrl }: { avatar: string | null; - username: string; + displayName: string; imageHostingBaseUrl?: string; // when running inside of next.js, we need to use the server url }): Promise { const { overlaysBase64, noPfpAvatarBase64, font } = await getAssets(imageHostingBaseUrl); @@ -145,13 +213,13 @@ export async function generateNftImage({ style={{ color: 'white', textAlign: 'center', - fontSize: `${calculateFontSize(username, 280, 24)}px`, + fontSize: `${calculateFontSize(displayName, 280, 24)}px`, whiteSpace: 'nowrap', maxWidth: `${280}px`, fontFamily: 'K2D' }} > - {username} + {displayName}

    diff --git a/packages/scoutgame/src/builderNfts/artwork/uploadArtwork.ts b/packages/scoutgame/src/builderNfts/artwork/uploadArtwork.ts index 42e17bd2dc..6544714978 100644 --- a/packages/scoutgame/src/builderNfts/artwork/uploadArtwork.ts +++ b/packages/scoutgame/src/builderNfts/artwork/uploadArtwork.ts @@ -2,7 +2,7 @@ import { S3Client } from '@aws-sdk/client-s3'; import type { PutObjectCommandInput, S3ClientConfig } from '@aws-sdk/client-s3'; import { Upload } from '@aws-sdk/lib-storage'; -import { generateNftImage, generateNftCongrats } from './generateNftImage'; +import { generateNftImage, generateNftCongrats, updateNftImage } from './generateNftImage'; import { getNftCongratsFilePath, getNftFilePath, imageDomain } from './utils'; function getS3ClientConfig() { @@ -26,19 +26,26 @@ export async function uploadArtwork({ avatar, tokenId, season, - username + displayName, + currentNftImage }: { imageHostingBaseUrl?: string; - username: string; + displayName: string; season: string; avatar: string | null; tokenId: bigint | number; + currentNftImage?: string; }) { - const imageBuffer = await generateNftImage({ - avatar, - username, - imageHostingBaseUrl - }); + const imageBuffer = currentNftImage + ? await updateNftImage({ + displayName, + currentNftImage + }) + : await generateNftImage({ + avatar, + displayName, + imageHostingBaseUrl + }); const imagePath = getNftFilePath({ season, tokenId: Number(tokenId), type: 'artwork.png' }); diff --git a/packages/scoutgame/src/builderNfts/artwork/uploadMetadata.ts b/packages/scoutgame/src/builderNfts/artwork/uploadMetadata.ts index 109e1c165a..4f9aca4c9c 100644 --- a/packages/scoutgame/src/builderNfts/artwork/uploadMetadata.ts +++ b/packages/scoutgame/src/builderNfts/artwork/uploadMetadata.ts @@ -100,7 +100,7 @@ const client = new S3Client(getS3ClientConfig()); * Uploads OpenSea metadata to S3. * * @param {Object} params - Parameters for creating the OpenSea metadata. - * @param {string} params.username - The username of the NFT owner. + * @param {string} params.path - The path of the NFT owner. * @param {string} params.season - The season of the NFT. * @param {string | null} params.avatar - The avatar image URL for the NFT. * @param {bigint | number} params.tokenId - The unique token ID of the NFT. @@ -111,12 +111,12 @@ const client = new S3Client(getS3ClientConfig()); * @returns {Promise} - The URL of the uploaded metadata JSON. */ export async function uploadMetadata({ - username, + path, season, tokenId, attributes }: { - username: string; + path: string; season: string; tokenId: bigint | number; attributes?: { trait_type: string; value: string | number }[]; @@ -128,7 +128,7 @@ export async function uploadMetadata({ const metadata: OpenSeaMetadata = { name: `ScoutGame Builders NFT #${tokenId}`, description: '', - external_url: `${process.env.DOMAIN}/u/${username}`, + external_url: `${process.env.DOMAIN}/u/${path}`, image: `${imageDomain}/${getNftFilePath({ season, tokenId: Number(tokenId), type: 'artwork.png' })}`, attributes: attributes || [] }; diff --git a/packages/scoutgame/src/builderNfts/createBuilderNft.ts b/packages/scoutgame/src/builderNfts/createBuilderNft.ts index e7ccac9449..e6ae12e2fa 100644 --- a/packages/scoutgame/src/builderNfts/createBuilderNft.ts +++ b/packages/scoutgame/src/builderNfts/createBuilderNft.ts @@ -12,10 +12,12 @@ export async function createBuilderNft({ avatar, tokenId, builderId, - username + displayName, + path }: { imageHostingBaseUrl?: string; - username: string; + displayName: string; + path: string; avatar: string | null; tokenId: bigint; builderId: string; @@ -26,7 +28,7 @@ export async function createBuilderNft({ const fileUrl = await uploadArtwork({ imageHostingBaseUrl, - username, + displayName, season: currentSeason, avatar, tokenId @@ -42,7 +44,7 @@ export async function createBuilderNft({ await uploadMetadata({ season: currentSeason, tokenId, - username + path }); const builderNft = await prisma.builderNft.create({ diff --git a/packages/scoutgame/src/builderNfts/recordNftMint.ts b/packages/scoutgame/src/builderNfts/recordNftMint.ts index b6f38ea901..0b7f3c834f 100644 --- a/packages/scoutgame/src/builderNfts/recordNftMint.ts +++ b/packages/scoutgame/src/builderNfts/recordNftMint.ts @@ -9,23 +9,8 @@ import { getCurrentWeek } from '@packages/scoutgame/dates'; import type { MintNFTParams } from './mintNFT'; -export async function recordNftMint(params: MintNFTParams & { mintTxHash: string }) { - const { amount, builderNftId, paidWithPoints, pointsValue, recipientAddress, scoutId, mintTxHash } = params; - - if (!mintTxHash.trim().startsWith('0x')) { - throw new InvalidInputError(`Mint transaction hash is required`); - } - - const existingTx = await prisma.nFTPurchaseEvent.findFirst({ - where: { - txHash: mintTxHash - } - }); - - if (existingTx) { - log.warn(`Tried to record duplicate tx ${mintTxHash}`, { params, existingTx }); - return; - } +export async function recordNftMintWithoutRefresh(params: MintNFTParams & { createdAt?: Date; mintTxHash: string }) { + const { createdAt, amount, builderNftId, paidWithPoints, pointsValue, scoutId, mintTxHash } = params; const builderNft = await prisma.builderNft.findFirstOrThrow({ where: { @@ -44,17 +29,19 @@ export async function recordNftMint(params: MintNFTParams & { mintTxHash: string }); // The builder receives 20% of the points value, regardless of whether the purchase was paid with points or not - const pointsReceipts: { value: number; recipientId?: string; senderId?: string }[] = [ + const pointsReceipts: { value: number; recipientId?: string; senderId?: string; createdAt?: Date }[] = [ { value: Math.floor(pointsValue * 0.2), - recipientId: builderNft.builderId + recipientId: builderNft.builderId, + createdAt } ]; if (paidWithPoints) { pointsReceipts.push({ value: pointsValue, - senderId: scoutId + senderId: scoutId, + createdAt }); } @@ -81,6 +68,7 @@ export async function recordNftMint(params: MintNFTParams & { mintTxHash: string nftPurchaseEvent: { create: { pointsValue, + createdAt, tokensPurchased: amount, paidInPoints: paidWithPoints, txHash: mintTxHash?.toLowerCase(), @@ -90,7 +78,8 @@ export async function recordNftMint(params: MintNFTParams & { mintTxHash: string create: { recipientType: 'builder', type: 'nft_purchase', - userId: builderNft.builderId + userId: builderNft.builderId, + createdAt } } } @@ -164,6 +153,33 @@ export async function recordNftMint(params: MintNFTParams & { mintTxHash: string return builderEvent.nftPurchaseEvent; }); + + return { + builderNft, + mintTxHash, + nftPurchaseEvent: nftPurchaseEvent as NFTPurchaseEvent + }; +} + +export async function recordNftMint(params: MintNFTParams & { mintTxHash: string }) { + const { amount, builderNftId, paidWithPoints, recipientAddress, scoutId, mintTxHash } = params; + + if (!mintTxHash.trim().startsWith('0x')) { + throw new InvalidInputError(`Mint transaction hash is required`); + } + + const existingTx = await prisma.nFTPurchaseEvent.findFirst({ + where: { + txHash: mintTxHash + } + }); + + if (existingTx) { + log.warn(`Tried to record duplicate tx ${mintTxHash}`, { params, existingTx }); + return; + } + + const { builderNft, nftPurchaseEvent } = await recordNftMintWithoutRefresh(params); log.info('Minted NFT', { builderNftId, recipientAddress, tokenId: builderNft.tokenId, amount, userId: scoutId }); trackUserAction('nft_purchase', { userId: builderNft.builderId, diff --git a/packages/scoutgame/src/builderNfts/registerBuilderNFT.ts b/packages/scoutgame/src/builderNfts/registerBuilderNFT.ts index 22b9a72c14..27ffd2f31f 100644 --- a/packages/scoutgame/src/builderNfts/registerBuilderNFT.ts +++ b/packages/scoutgame/src/builderNfts/registerBuilderNFT.ts @@ -44,7 +44,8 @@ export async function registerBuilderNFT({ select: { githubUser: true, avatar: true, - username: true, + path: true, + displayName: true, builderStatus: true } }); @@ -70,7 +71,8 @@ export async function registerBuilderNFT({ tokenId: existingTokenId, builderId, avatar: builder.avatar, - username: builder.username || '' + path: builder.path!, + displayName: builder.displayName }); const nftWithRefreshedPrice = await refreshBuilderNftPrice({ builderId, season }); diff --git a/packages/scoutgame/src/builderNfts/syncUserNFTsFromOnchainData.ts b/packages/scoutgame/src/builderNfts/syncUserNFTsFromOnchainData.ts index d38f507f46..77bc88953d 100644 --- a/packages/scoutgame/src/builderNfts/syncUserNFTsFromOnchainData.ts +++ b/packages/scoutgame/src/builderNfts/syncUserNFTsFromOnchainData.ts @@ -9,24 +9,24 @@ import { getTokenPurchasePrice } from './getTokenPurchasePrice'; import { handlePendingTransaction } from './handlePendingTransaction'; export async function syncUserNFTsFromOnchainData({ - username, + path, scoutId, fromBlock }: { - username?: string; + path?: string; scoutId?: string; fromBlock?: number; }): Promise { - if (!username && !scoutId) { - throw new Error('Either username or scoutId must be provided'); - } else if (username && scoutId) { - throw new Error('Only one of username or scoutId can be provided'); + if (!path && !scoutId) { + throw new Error('Either path or scoutId must be provided'); + } else if (path && scoutId) { + throw new Error('Only one of path or scoutId can be provided'); } const scout = await prisma.scout.findFirstOrThrow({ where: { id: scoutId, - username + path } }); diff --git a/packages/scoutgame/src/getBuildersLeaderboard.ts b/packages/scoutgame/src/getBuildersLeaderboard.ts index b58f7f94ea..003598af43 100644 --- a/packages/scoutgame/src/getBuildersLeaderboard.ts +++ b/packages/scoutgame/src/getBuildersLeaderboard.ts @@ -3,7 +3,8 @@ import { prisma } from '@charmverse/core/prisma-client'; export type LeaderboardBuilder = { builder: { id: string; - username: string; + path: string; + displayName: string; }; gemsCollected: number; rank: number; @@ -30,7 +31,8 @@ export async function getBuildersLeaderboard({ user: { select: { id: true, - username: true, + path: true, + displayName: true, events: { where: { type: 'merged_pull_request' @@ -57,7 +59,7 @@ export async function getBuildersLeaderboard({ const userAEvent = a.user.events[0]?.createdAt.getTime() ?? 0; const userBEvent = b.user.events[0]?.createdAt.getTime() ?? 0; if (userBEvent === userAEvent) { - return a.user.username.localeCompare(b.user.username); + return a.user.displayName.localeCompare(b.user.displayName); } return userAEvent - userBEvent; @@ -67,11 +69,12 @@ export async function getBuildersLeaderboard({ .map((userWeeklyStat, index) => ({ builder: { id: userWeeklyStat.user.id, - username: userWeeklyStat.user.username + path: userWeeklyStat.user.path, + displayName: userWeeklyStat.user.displayName }, gemsCollected: userWeeklyStat.gemsCollected, rank: index + 1 - })); + })) as LeaderboardBuilder[]; if (quantity) { return topBuilders.slice(0, quantity); diff --git a/packages/scoutgame/src/points/__tests__/getPointStatsFromHistory.spec.ts b/packages/scoutgame/src/points/__tests__/getPointStatsFromHistory.spec.ts index 7f479fdda2..e7ef85d76c 100644 --- a/packages/scoutgame/src/points/__tests__/getPointStatsFromHistory.spec.ts +++ b/packages/scoutgame/src/points/__tests__/getPointStatsFromHistory.spec.ts @@ -1,5 +1,5 @@ import { InvalidInputError } from '@charmverse/core/errors'; -import type { PointsReceipt, Prisma, Scout } from '@charmverse/core/prisma-client'; +import type { PointsReceipt, Scout } from '@charmverse/core/prisma-client'; import { prisma } from '@charmverse/core/prisma-client'; import { jest } from '@jest/globals'; import { v4 as uuid } from 'uuid'; @@ -12,11 +12,11 @@ describe('getPointStatsFromHistory', () => { let user: Scout; beforeAll(async () => { - user = await mockScout({ username: uuid() }); + user = await mockScout({ path: `user-${uuid()}` }); }); it('should return point stats when valid UUID is provided', async () => { - const stats = await getPointStatsFromHistory({ userIdOrUsername: user.id }); + const stats = await getPointStatsFromHistory({ userIdOrPath: user.id }); expect(stats).toMatchObject({ userId: user.id, pointsSpent: expect.any(Number), @@ -31,7 +31,7 @@ describe('getPointStatsFromHistory', () => { // @TODO: Redo the find by username logic it('should return point stats when valid username is provided', async () => { - const stats = await getPointStatsFromHistory({ userIdOrUsername: user.id }); + const stats = await getPointStatsFromHistory({ userIdOrPath: user.path! }); expect(stats).toMatchObject({ userId: user.id, pointsSpent: expect.any(Number), @@ -83,7 +83,7 @@ describe('getPointStatsFromHistory', () => { jest.spyOn(prisma.pointsReceipt, 'findMany').mockResolvedValueOnce(allPointsReceivedRecords as PointsReceipt[]); - const pointStats = await getPointStatsFromHistory({ userIdOrUsername: user.id }); + const pointStats = await getPointStatsFromHistory({ userIdOrPath: user.id }); // Sanity check that the points add up expect(pointStats.claimedPoints + pointStats.unclaimedPoints).toEqual( @@ -103,12 +103,12 @@ describe('getPointStatsFromHistory', () => { }); it('should throw InvalidInputError when userIdOrUsername is empty', async () => { - await expect(getPointStatsFromHistory({ userIdOrUsername: '' })).rejects.toThrow(InvalidInputError); + await expect(getPointStatsFromHistory({ userIdOrPath: '' })).rejects.toThrow(InvalidInputError); }); it('should throw an error when userIdOrUsername is invalid UUID and does not exist as a username', async () => { const nonExistentUserId = uuid(); - await expect(getPointStatsFromHistory({ userIdOrUsername: nonExistentUserId })).rejects.toThrow(); + await expect(getPointStatsFromHistory({ userIdOrPath: nonExistentUserId })).rejects.toThrow(); }); it('should throw an assertion error if point records for individual categories do not match the full list of point records', async () => { @@ -134,6 +134,6 @@ describe('getPointStatsFromHistory', () => { jest.spyOn(prisma.pointsReceipt, 'findMany').mockResolvedValueOnce(bonusPointsReceivedRecords as PointsReceipt[]); // Mismatch points jest.spyOn(prisma.pointsReceipt, 'findMany').mockResolvedValueOnce(allPointsReceivedRecords as PointsReceipt[]); // Mismatch points - await expect(getPointStatsFromHistory({ userIdOrUsername: user.id })).rejects.toThrow(); + await expect(getPointStatsFromHistory({ userIdOrPath: user.id })).rejects.toThrow(); }); }); diff --git a/packages/scoutgame/src/points/getPointStatsFromHistory.ts b/packages/scoutgame/src/points/getPointStatsFromHistory.ts index db79070260..420b83ef68 100644 --- a/packages/scoutgame/src/points/getPointStatsFromHistory.ts +++ b/packages/scoutgame/src/points/getPointStatsFromHistory.ts @@ -22,19 +22,19 @@ const include: Prisma.PointsReceiptInclude = { }; export async function getPointStatsFromHistory({ - userIdOrUsername, + userIdOrPath, tx = prisma }: { - userIdOrUsername: string; + userIdOrPath: string; tx?: Prisma.TransactionClient; }): Promise { - if (!userIdOrUsername) { - throw new InvalidInputError('userIdOrUsername is required'); + if (!userIdOrPath) { + throw new InvalidInputError('userIdOrPath is required'); } const userId = await tx.scout .findUniqueOrThrow({ - where: { id: userIdOrUsername }, + where: isUuid(userIdOrPath) ? { id: userIdOrPath } : { path: userIdOrPath }, select: { id: true } diff --git a/packages/scoutgame/src/points/refreshPointStatsFromHistory.ts b/packages/scoutgame/src/points/refreshPointStatsFromHistory.ts index 7cdf3187fd..05b3c31662 100644 --- a/packages/scoutgame/src/points/refreshPointStatsFromHistory.ts +++ b/packages/scoutgame/src/points/refreshPointStatsFromHistory.ts @@ -7,16 +7,16 @@ import { getPointStatsFromHistory, type PointStats } from './getPointStatsFromHi import { setPointsEarnedStats } from './updatePointsEarned'; export async function refreshPointStatsFromHistory({ - userIdOrUsername, + userIdOrPath, season = currentSeason, tx }: { - userIdOrUsername: string; + userIdOrPath: string; season?: string; tx?: Prisma.TransactionClient; }): Promise { async function txHandler(_tx: Prisma.TransactionClient) { - const stats = await getPointStatsFromHistory({ userIdOrUsername, tx: _tx }); + const stats = await getPointStatsFromHistory({ userIdOrPath, tx: _tx }); await setPointsEarnedStats({ season, diff --git a/packages/scoutgame/src/scripts/generateActivities.ts b/packages/scoutgame/src/scripts/generateActivities.ts deleted file mode 100644 index 1a22e289e6..0000000000 --- a/packages/scoutgame/src/scripts/generateActivities.ts +++ /dev/null @@ -1,452 +0,0 @@ -import { log } from '@charmverse/core/log'; -import { GithubEventType } from '@charmverse/core/prisma'; -import { PointsDirection, prisma } from '@charmverse/core/prisma-client'; - -import type { BuilderEvent } from '@charmverse/core/prisma'; -import { getBuilderContractAddress, builderNftChain } from '../builderNfts/constants'; -import type { ActivityToRecord } from '../recordGameActivity'; -import { recordGameActivity } from '../recordGameActivity'; -import { refreshUserStats } from '../refreshUserStats'; -import { - mockBuilderStrike, - mockPointReceipt, - mockGemPayoutEvent, - mockNFTPurchaseEvent, - mockScout, - mockGithubUser -} from '../testing/database'; -import { randomLargeInt, mockSeason } from '../testing/generators'; -import { getCurrentWeek, currentSeason } from '../dates'; - -type RepoAddress = { - repoOwner?: string; - repoName?: string; -}; - -type MockEventParams = { - userId: string; - amount?: number; -}; - -async function createMockEvents({ userId, amount = 5 }: MockEventParams) { - const scout = await prisma.scout.findFirstOrThrow({ - where: { - id: userId - }, - include: { - githubUser: true - } - }); - const week = new Date().getUTCDate(); - - for (let i = 0; i < amount; i++) { - // Frequent NFT Purchase events (occur in every loop) - await mockNFTPurchaseEvent({ - builderId: userId, - scoutId: userId, - points: Math.floor(Math.random() * 100) + 1 // Random points between 1 and 100 - }); - - // Rare Builder Strike events (10% chance of occurrence) - if (Math.random() < 0.1) { - await mockBuilderStrike({ - builderId: userId, - pullRequestNumber: i + 1, - repoOwner: 'test_owner', - repoName: 'test_repo' - }); - } - - // Point Receipt events - await mockPointReceipt({ - builderId: userId, - amount: Math.floor(Math.random() * 50) + 1, // Random points between 1 and 50 - senderId: scout.id, - recipientId: undefined - }); - - // Gems Payout events - await mockGemPayoutEvent({ - builderId: userId, - week: `W-${week}-${i}`, - amount: Math.floor(Math.random() * 10) + 1 // Random points between 1 and 10 - }); - } -} - -async function ensureGithubRepoExists({ - repoOwner = `acme-${randomLargeInt()}`, - repoName = `acme-repo-${randomLargeInt()}`, - id -}: Partial & { id?: number }) { - const repo = await prisma.githubRepo.findFirst({ - where: { - owner: repoOwner, - name: repoName - } - }); - - if (repo) { - return repo; - } - - return prisma.githubRepo.create({ - data: { - id: id ?? randomLargeInt(), - owner: repoOwner, - name: repoName, - defaultBranch: 'main' - } - }); -} - -async function ensureGithubUserExists( - { login, builderId }: { login?: string; builderId?: string } = { login: `github:${randomLargeInt()}` } -) { - const githubUser = await prisma.githubUser.findFirst({ where: { login } }); - - if (githubUser) { - return githubUser; - } - - const id = randomLargeInt(); - - const name = `github_user:${id}`; - return prisma.githubUser.create({ - data: { - login: name, - builderId, - displayName: name, - id - } - }); -} - -async function ensureMergedGithubPullRequestExists({ - repoOwner, - repoName, - pullRequestNumber = randomLargeInt(), - githubUserId, - builderId -}: { - pullRequestNumber?: number; - githubUserId: number; - builderId?: string; - id?: number; -} & RepoAddress): Promise { - builderId = builderId ?? (await mockScout().then((scout) => scout.id)); - - const builderGithubUser = await mockGithubUser({ builderId }); - - // Ensure the repository exists - const repo = await ensureGithubRepoExists({ repoOwner, repoName }); - - const pullRequest = await prisma.githubEvent.findFirst({ - where: { - repoId: repo.id, - pullRequestNumber, - githubUser: { - id: builderGithubUser.id - }, - type: 'merged_pull_request' - }, - select: { - builderEvent: true, - repoId: true - } - }); - - if (pullRequest?.builderEvent) { - return pullRequest.builderEvent as BuilderEvent; - } - - const githubEvent = await prisma.githubEvent - .findFirstOrThrow({ - where: { - repoId: repo.id, - pullRequestNumber, - createdBy: githubUserId, - type: GithubEventType.merged_pull_request - } - }) - .catch((err) => { - return prisma.githubEvent.create({ - data: { - repoId: repo.id, - pullRequestNumber, - title: `Mock Pull Request ${pullRequestNumber}`, - type: 'merged_pull_request', - createdBy: builderGithubUser.id, - url: `` - } - }); - }); - - const builderEvent = await prisma.builderEvent.create({ - data: { - builderId: builderId as string, - season: mockSeason, - type: 'merged_pull_request', - githubEventId: githubEvent.id, - week: getCurrentWeek() - } - }); - - return builderEvent; -} - -function getRandomDateWithinLast30Days() { - const now = new Date(); - const pastDate = new Date(now); - pastDate.setDate(now.getDate() - 30); - return new Date(pastDate.getTime() + Math.random() * (now.getTime() - pastDate.getTime())); -} - -export async function generateActivities({ userId }: { userId: string }) { - const mints = await prisma.nFTPurchaseEvent.findMany({ - where: { - scoutId: userId - } - }); - - const builderStrikeEvents = await prisma.builderStrike.findMany({ - where: { - builderId: userId - } - }); - - const pointRecepts = await prisma.pointsReceipt.findMany({ - where: { - OR: [ - { - senderId: userId - }, - { - recipientId: userId - } - ] - } - }); - - const gemPayouts = await prisma.gemsPayoutEvent.findMany({ - where: { - builderId: userId - } - }); - - const builderTokenEvents = await prisma.builderNft.findFirst({ - where: { - chainId: builderNftChain.id, - contractAddress: getBuilderContractAddress(), - builderId: userId - }, - include: { - nftSoldEvents: { - select: { - scoutId: true - } - } - } - }); - - const events: ActivityToRecord[] = [ - ...mints.map((event) => ({ - activity: { - userId: event.scoutId, - amount: event.tokensPurchased, - pointsDirection: PointsDirection.in, - createdAt: getRandomDateWithinLast30Days() - }, - sourceEvent: { - nftPurchaseEventId: event.id - } - })), - ...builderStrikeEvents.map((event) => ({ - activity: { - userId: event.builderId, - amount: 0, - pointsDirection: PointsDirection.in, - createdAt: getRandomDateWithinLast30Days() - }, - sourceEvent: { - builderStrikeId: event.id - } - })), - ...pointRecepts.flatMap((event) => { - const _events: ActivityToRecord[] = []; - - if (event.senderId) { - _events.push({ - activity: { - userId: event.senderId, - amount: event.value, - pointsDirection: PointsDirection.out, - createdAt: getRandomDateWithinLast30Days() - }, - sourceEvent: { - pointsReceiptId: event.id - } - }); - } - - if (event.recipientId) { - _events.push({ - activity: { - userId: event.recipientId, - amount: event.value, - pointsDirection: PointsDirection.in, - createdAt: getRandomDateWithinLast30Days() - }, - sourceEvent: { - pointsReceiptId: event.id - } - }); - } - - return _events; - }), - ...gemPayouts.flatMap((event) => { - const _events: ActivityToRecord[] = []; - if (event.points) { - _events.push({ - activity: { - userId: event.builderId, - amount: event.points, - pointsDirection: PointsDirection.in, - createdAt: getRandomDateWithinLast30Days() - }, - sourceEvent: { - gemsPayoutEventId: event.id - } - }); - } - - if (event.points) { - _events.push({ - activity: { - userId: event.builderId, - amount: event.points, - pointsDirection: PointsDirection.in, - createdAt: getRandomDateWithinLast30Days() - }, - sourceEvent: { - gemsPayoutEventId: event.id - } - }); - } - - return _events; - }), - ...(builderTokenEvents?.nftSoldEvents.map((event) => ({ - activity: { - userId: event.scoutId, - amount: 0, - pointsDirection: PointsDirection.in, - createdAt: getRandomDateWithinLast30Days() - }, - sourceEvent: { - builderStrikeId: event.scoutId - } - })) || []) - ]; - - // Ensure no NFTs are sold until the BuilderToken is issued - const builderTokenIssuedAt = builderTokenEvents ? getRandomDateWithinLast30Days() : null; - if (builderTokenIssuedAt) { - builderTokenEvents!.createdAt = builderTokenIssuedAt; - events.push({ - activity: { - userId: builderTokenEvents!.builderId, - amount: 0, - pointsDirection: PointsDirection.in, - createdAt: builderTokenIssuedAt - }, - sourceEvent: { - builderStrikeId: builderTokenEvents!.id - } - }); - } - - // Sort events by createdAt - events.sort((a, b) => a.activity.createdAt!.getTime() - b.activity.createdAt!.getTime()); - - // Record activities - for (const event of events) { - await recordGameActivity(event).catch((error) => log.error(`Error recording activity`, { error })); - } - - const stats = await prisma.userWeeklyStats.upsert({ - where: { - userId_week: { - userId, - week: getCurrentWeek() - } - }, - create: { - userId, - season: currentSeason, - week: getCurrentWeek(), - gemsCollected: 30 - }, - update: { - gemsCollected: 30 - } - }); -} - -const githubLogin = 'motechFR'; - -async function script() { - const githubAccount = await ensureGithubUserExists({ - login: githubLogin - }); - - let user = await prisma.scout.findFirst({ - where: { - githubUser: { - some: { - login: githubLogin - } - } - } - }); - - if (!user) { - user = await prisma.scout.create({ - data: { - displayName: githubAccount.login as string, - username: githubAccount.login as string, - builderStatus: 'approved', - githubUser: { - connect: { - id: githubAccount.id - } - } - } - }); - } - - const userId = user.id; - - const current = await prisma.scoutGameActivity.count({ - where: { - userId - } - }); - - await createMockEvents({ userId, amount: 7 }); - - await generateActivities({ userId }); - - const newCount = await prisma.scoutGameActivity.count({ - where: { - userId - } - }); - - await refreshUserStats({ userId }); - - log.info(`Created ${newCount - current}`); -} - -script(); diff --git a/packages/scoutgame/src/scripts/importScouts.ts b/packages/scoutgame/src/scripts/importScouts.ts index 3b040f7e47..85b72c3c58 100644 --- a/packages/scoutgame/src/scripts/importScouts.ts +++ b/packages/scoutgame/src/scripts/importScouts.ts @@ -1,4 +1,5 @@ import { prisma } from '@charmverse/core/prisma-client'; +import { log } from '@charmverse/core/log'; import { getFarcasterUserByIds } from '@packages/farcaster/getFarcasterUserById'; @@ -15,7 +16,7 @@ async function query() { } }); const newUsers = FIDS.filter((fid) => !w.some((scout) => scout.farcasterId === fid)); - console.log('retrieved', newUsers); + log.info(`retrieved ${newUsers.length} new users`); const users = await getFarcasterUserByIds(newUsers); for (const user of users) { const scout = await prisma.scout.findFirst({ @@ -24,20 +25,24 @@ async function query() { } }); if (scout) { - console.log('scout already exists', scout.username); + log.info(`scout already exists`, { + path: scout.path, + fid: user.fid, + displayName: user.display_name + }); continue; } const newScout = await prisma.scout.create({ data: { farcasterId: user.fid, - username: user.username, + path: user.username, displayName: user.display_name || user.username, avatar: user.pfp_url, bio: user.profile.bio.text, currentBalance: startingBalance } }); - console.log('created scout', newScout.username); + log.info(`created scout ${newScout.path}`); } } diff --git a/packages/scoutgame/src/scripts/query.ts b/packages/scoutgame/src/scripts/query.ts index 6aace143cb..3a04137350 100644 --- a/packages/scoutgame/src/scripts/query.ts +++ b/packages/scoutgame/src/scripts/query.ts @@ -25,7 +25,7 @@ async function query() { const nft = nfts[i]; console.log('Processing NFT', nft.tokenId, `--- ${i + 1} / ${nfts.length}`); await uploadArtwork({ - username: nft.builder.username, + displayName: nft.builder.displayName, season: currentSeason, avatar: nft.builder.avatar as string, tokenId: nft.tokenId @@ -34,7 +34,7 @@ async function query() { await uploadMetadata({ season: currentSeason, tokenId: nft.tokenId, - username: nft.builder.username + path: nft.builder.path! }); } } diff --git a/packages/scoutgame/src/scripts/uploadNFTArtwork.ts b/packages/scoutgame/src/scripts/uploadNFTArtwork.ts index 375ddb7167..7958026671 100644 --- a/packages/scoutgame/src/scripts/uploadNFTArtwork.ts +++ b/packages/scoutgame/src/scripts/uploadNFTArtwork.ts @@ -1,51 +1,61 @@ import { prisma } from '@charmverse/core/prisma-client'; import { uploadArtwork, uploadArtworkCongrats } from '../builderNfts/artwork/uploadArtwork'; +import { currentSeason } from '../dates'; +import { log } from '@charmverse/core/log'; async function uploadNFTArtwork() { - const scouts = await prisma.scout.findMany({ + const builders = await prisma.scout.findMany({ where: { - builderStatus: 'approved' + username: 'safwan', + builderStatus: { + in: ['approved', 'banned'] + } }, select: { avatar: true, - username: true, - builderNfts: true + displayName: true, + builderNfts: { + where: { + season: currentSeason + } + } } }); - // const w = await prisma.builderNft.deleteMany({}); - const mappedWithimage = await Promise.all( - scouts.map(async (scout) => { + + for (const builder of builders) { + const builderNft = builder.builderNfts[0]; + try { const imageUrl = await uploadArtwork({ - username: scout.username, - season: scout.builderNfts[0].season, - avatar: scout.avatar, - tokenId: scout.builderNfts[0].tokenId + displayName: 'safwan 🎩🚨', + season: currentSeason, + avatar: builder.avatar, + tokenId: builderNft.tokenId, }); const congratsImageUrl = await uploadArtworkCongrats({ - season: scout.builderNfts[0].season, - tokenId: scout.builderNfts[0].tokenId, + season: currentSeason, + tokenId: builderNft.tokenId, userImage: imageUrl }); - return { - nft: scout.builderNfts[0], - scout, - imageUrl, - congratsImageUrl - }; - }) - ); - for (const image of mappedWithimage) { - await prisma.builderNft.update({ - where: { - id: image.nft!.id - }, - data: { - imageUrl: image.imageUrl, - congratsImageUrl: image.congratsImageUrl - } - }); + await prisma.builderNft.update({ + where: { + id: builderNft.id + }, + data: { + imageUrl, + congratsImageUrl + } + }); + log.info(`Updated ${builderNft.tokenId}`, { + tokenId: builderNft.tokenId + }); + } catch (error) { + log.error(`Error updating ${builderNft.tokenId}`, { + error, + tokenId: builderNft.tokenId + }); + } } } diff --git a/packages/scoutgame/src/testing/database.ts b/packages/scoutgame/src/testing/database.ts index 67056f29ec..ea91010316 100644 --- a/packages/scoutgame/src/testing/database.ts +++ b/packages/scoutgame/src/testing/database.ts @@ -14,10 +14,10 @@ type RepoAddress = { export async function mockBuilder({ id, createdAt, + displayName = 'Test User', builderStatus = 'approved', githubUserId = randomLargeInt(), onboardedAt, - username = uuid(), path = uuid(), agreedToTermsAt = new Date(), nftSeason = mockSeason, @@ -27,9 +27,8 @@ export async function mockBuilder({ const result = await prisma.scout.create({ data: { createdAt, - username, path, - displayName: 'Test User', + displayName, builderStatus, onboardedAt, agreedToTermsAt, @@ -37,7 +36,7 @@ export async function mockBuilder({ githubUser: { create: { id: githubUserId, - login: username! + login: path! } } }, @@ -56,7 +55,7 @@ export async function mockBuilder({ export type MockBuilder = Awaited>; export async function mockScout({ - username = `user-${uuid()}`, + path = `user-${uuid()}`, displayName = 'Test Scout', agreedToTermsAt = new Date(), onboardedAt = new Date(), @@ -64,7 +63,7 @@ export async function mockScout({ season, email }: { - username?: string; + path?: string; agreedToTermsAt?: Date | null; onboardedAt?: Date | null; displayName?: string; @@ -74,7 +73,7 @@ export async function mockScout({ } = {}) { const scout = await prisma.scout.create({ data: { - username, + path, agreedToTermsAt, onboardedAt, displayName, diff --git a/scripts/migrations/2024_10_17_fixScoutScores.ts b/scripts/migrations/2024_10_17_fixScoutScores.ts index 7f14223b5c..d996249c5e 100644 --- a/scripts/migrations/2024_10_17_fixScoutScores.ts +++ b/scripts/migrations/2024_10_17_fixScoutScores.ts @@ -55,7 +55,7 @@ async function query() { select: { id: true, farcasterId: true, - username: true, + path: true, currentBalance: true, nftPurchaseEvents: true } @@ -84,14 +84,14 @@ async function query() { // console.log(event.builder.username, event.pointsReceipts[0].value, event.builder.nftPurchaseEvents.length); // } // await refreshPointStatsFromHistory({ userIdOrUsername: event.builder.id }); - const stats = await getPointStatsFromHistory({ userIdOrUsername: event.builder.id }); + const stats = await getPointStatsFromHistory({ userIdOrPath: event.builder.id }); const currentbalance = event.builder.currentBalance; const newBalance = stats.balance + event.pointsReceipts[0].value; if (newBalance === currentbalance) { - unchanged.push(event.builder.username); + unchanged.push(event.builder.path!); } if (friendIds.includes(event.builder.id)) { - _friends.push(event.builder.username); + _friends.push(event.builder.path!); } if (currentbalance === newBalance) { // good to go @@ -103,12 +103,12 @@ async function query() { userId: event.builder.id, builderEventId: event.id }); - fixable.push(event.builder.username); + fixable.push(event.builder.path!); } else if (newBalance < 0) { // good to go // console.log('Good to go:', event); console.log('Negative balance:', { - username: event.builder.username, + path: event.builder.path!, allpointsReceived: stats.unclaimedPoints + stats.claimedPoints, // unclaimedPoints: stats.unclaimedPoints, ...stats, @@ -123,11 +123,11 @@ async function query() { builderEventId: event.id }); // break; - needsFix.push(event.builder.username); + needsFix.push(event.builder.path!); } else { console.log('Good to go:', event); console.log('Will fix but check update:', { - username: event.builder.username, + path: event.builder.path!, allpointsReceived: stats.unclaimedPoints + stats.claimedPoints, // unclaimedPoints: stats.unclaimedPoints, ...stats, @@ -163,7 +163,7 @@ async function query() { userId: event.builder.id, builderEventId: event.id }); - fixableButCHeck.push(event.builder.username); + fixableButCHeck.push(event.builder.path!); } } console.log({ @@ -232,7 +232,7 @@ async function fixEvents({ } }) ]); - const stats = await getPointStatsFromHistory({ userIdOrUsername: userId }); + const stats = await getPointStatsFromHistory({ userIdOrPath: userId }); const scout = await prisma.scout.update({ where: { id: userId diff --git a/scripts/migrations/2024_10_29_migrateScoutPaths.ts b/scripts/migrations/2024_10_29_migrateScoutPaths.ts new file mode 100644 index 0000000000..47d7d2e4b2 --- /dev/null +++ b/scripts/migrations/2024_10_29_migrateScoutPaths.ts @@ -0,0 +1,25 @@ +import { log } from '@charmverse/core/log'; +import { prisma } from '@charmverse/core/prisma-client'; + +async function migrateScoutPaths() { + const scouts = await prisma.scout.findMany({ + where: { + path: null + }, + select: { + id: true, + username: true + } + }); + for (const scout of scouts) { + await prisma.scout.update({ + where: { id: scout.id }, + data: { + path: scout.username + } + }); + log.info(`Updated path for ${scout.username}`); + } +} + +migrateScoutPaths(); \ No newline at end of file