Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions convex/_generated/api.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,10 @@ import type * as _model_tournaments_queries_getTournaments from "../_model/tourn
import type * as _model_tournaments_queries_getTournamentsByStatus from "../_model/tournaments/queries/getTournamentsByStatus.js";
import type * as _model_tournaments_queries_getTournamentsByUser from "../_model/tournaments/queries/getTournamentsByUser.js";
import type * as _model_users__helpers_checkUserAuth from "../_model/users/_helpers/checkUserAuth.js";
import type * as _model_users__helpers_checkUserRelationshipLevel from "../_model/users/_helpers/checkUserRelationshipLevel.js";
import type * as _model_users__helpers_checkUserTournamentRelationship from "../_model/users/_helpers/checkUserTournamentRelationship.js";
import type * as _model_users__helpers_compareVisibilityLevels from "../_model/users/_helpers/compareVisibilityLevels.js";
import type * as _model_users__helpers_formatUserRealName from "../_model/users/_helpers/formatUserRealName.js";
import type * as _model_users__helpers_getShallowUser from "../_model/users/_helpers/getShallowUser.js";
import type * as _model_users__helpers_redactUser from "../_model/users/_helpers/redactUser.js";
import type * as _model_users_actions_setUserDefaultAvatar from "../_model/users/actions/setUserDefaultAvatar.js";
Expand Down Expand Up @@ -334,7 +337,10 @@ declare const fullApi: ApiFromModules<{
"_model/tournaments/queries/getTournamentsByStatus": typeof _model_tournaments_queries_getTournamentsByStatus;
"_model/tournaments/queries/getTournamentsByUser": typeof _model_tournaments_queries_getTournamentsByUser;
"_model/users/_helpers/checkUserAuth": typeof _model_users__helpers_checkUserAuth;
"_model/users/_helpers/checkUserRelationshipLevel": typeof _model_users__helpers_checkUserRelationshipLevel;
"_model/users/_helpers/checkUserTournamentRelationship": typeof _model_users__helpers_checkUserTournamentRelationship;
"_model/users/_helpers/compareVisibilityLevels": typeof _model_users__helpers_compareVisibilityLevels;
"_model/users/_helpers/formatUserRealName": typeof _model_users__helpers_formatUserRealName;
"_model/users/_helpers/getShallowUser": typeof _model_users__helpers_getShallowUser;
"_model/users/_helpers/redactUser": typeof _model_users__helpers_redactUser;
"_model/users/actions/setUserDefaultAvatar": typeof _model_users_actions_setUserDefaultAvatar;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,8 @@ export const sortTournamentCompetitorsByName = (
if (competitor.teamName) {
return competitor.teamName;
}
if (competitor.players[0]?.user.familyName) {
return competitor.players[0].user.familyName;
}
if (competitor.players[0]?.user.username) {
return competitor.players[0].user.username;
if (competitor.players[0]?.user.displayName) {
return competitor.players[0].user.displayName;
}
return '';
};
Expand Down
48 changes: 48 additions & 0 deletions convex/_model/users/_helpers/checkUserRelationshipLevel.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { getAuthUserId } from '@convex-dev/auth/server';

import { Doc, Id } from '../../../_generated/dataModel';
import { QueryCtx } from '../../../_generated/server';
import { UserDataVisibilityLevel } from '../../../common/userDataVisibilityLevel';
import { checkUserTournamentRelationship } from './checkUserTournamentRelationship';

export const checkUserRelationshipLevel = async (
ctx: QueryCtx,
user: Doc<'users'>,
queryUserId?: Id<'users'> | null,
): Promise<UserDataVisibilityLevel> => {
const userId = queryUserId ?? await getAuthUserId(ctx);

// RETURN CLOSEST RELATIONSHIPS FIRST!

// 0 - Hidden
const isSelf = userId === user._id;
if (isSelf) {
return 'hidden';
}

// 1 - Friends
const hasFriendRelationship = false;
if (hasFriendRelationship) {
return 'friends';
}

// 2 - Club
const hasClubsRelationship = false;
if (hasClubsRelationship) {
return 'clubs';
}

// 3 - Tournaments
const hasTournamentRelationship = await checkUserTournamentRelationship(ctx, userId, user._id);
if (hasTournamentRelationship) {
return 'tournaments';
}

// 4 - Community
if (userId) {
return 'community';
}

// 5 - Public
return 'public';
};
26 changes: 26 additions & 0 deletions convex/_model/users/_helpers/compareVisibilityLevels.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { UserDataVisibilityLevel } from '../../../common/userDataVisibilityLevel';

/**
* Compares two user data visibility levels to determine if the querying user's relationship level satisfies the subject's required level.
*
* @param requiredLevel
* @param relationshipLevel
* @returns True if the relationship level is sufficient to satisfy the subject's required level
*/
export const compareVisibilityLevels = (
requiredLevel: UserDataVisibilityLevel,
relationshipLevel: UserDataVisibilityLevel,
): boolean => {

// Levels, ordered from least close to most
const orderedLevels: Record<UserDataVisibilityLevel, number> = {
public: 0,
community: 1,
tournaments: 2,
clubs: 3,
friends: 4,
hidden: 9999,
};

return orderedLevels[relationshipLevel] >= orderedLevels[requiredLevel];
};
11 changes: 11 additions & 0 deletions convex/_model/users/_helpers/formatUserRealName.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { Doc } from '../../../_generated/dataModel';

/**
* Joins a users given and family names together with a space.
*
* @param user - The subject user
* @returns A string of the user's real name
*/
export const formatUserRealName = (
user: Doc<'users'>,
): string => `${user.givenName} ${user.familyName}`;
60 changes: 23 additions & 37 deletions convex/_model/users/_helpers/redactUser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,21 @@ import { getAuthUserId } from '@convex-dev/auth/server';
import { Doc } from '../../../_generated/dataModel';
import { QueryCtx } from '../../../_generated/server';
import { getStorageUrl } from '../../common/_helpers/getStorageUrl';
import { checkUserTournamentRelationship } from './checkUserTournamentRelationship';
import { checkUserRelationshipLevel } from './checkUserRelationshipLevel';
import { compareVisibilityLevels } from './compareVisibilityLevels';
import { formatUserRealName } from './formatUserRealName';

/**
* User with some personal information hidden based on their preferences.
*/
export type LimitedUser = Omit<Doc<'users'>, 'givenName' | 'familyName' | 'countryCode'> & {
givenName?: string;
familyName?: string;
countryCode?: string;
export type LimitedUser = Pick<Doc<'users'>, '_id' | 'username'> & {
avatarUrl?: string;
countryCode?: string;
displayName: string;
};

/**
* Removes a users's real name or location based on their preferences, also adds avatarUrl.
* Removes a users's real name or location based on their preferences, also adds avatarUrl and displayName.
*
* @remarks
* This is essentially the user equivalent to the deepen[Resource]() pattern.
Expand All @@ -32,43 +33,28 @@ export const redactUser = async (
const userId = await getAuthUserId(ctx);

// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { givenName, familyName, countryCode, ...restFields } = user;
const { givenName, familyName, countryCode, email, ...restFields } = user;
const avatarUrl = await getStorageUrl(ctx, user.avatarStorageId);

const limitedUser: LimitedUser = {
...restFields,
avatarUrl,
};

// If user is querying own profile, simply return it
if (userId === user._id) {
return { ...user, avatarUrl };
return {
...user,
avatarUrl,
displayName: formatUserRealName(user),
};
}

// If user is querying someone they are in a friendship or club with
const hasFriendRelationship = false;
// Otherwise check for relationships:
const relationshipLevel = await checkUserRelationshipLevel(ctx, user, userId);

// If user is querying someone they are in a tournament with
const hasTournamentRelationship = await checkUserTournamentRelationship(ctx, userId, user._id);
const nameVisible = compareVisibilityLevels(user?.nameVisibility ?? 'hidden', relationshipLevel);
const locationVisible = compareVisibilityLevels(user?.locationVisibility ?? 'hidden', relationshipLevel);

// Add name information if allowed
if (
(user?.nameVisibility === 'public') ||
(user?.nameVisibility === 'friends' && hasFriendRelationship) ||
(user?.nameVisibility === 'tournaments' && (hasFriendRelationship || hasTournamentRelationship))
) {
limitedUser.givenName = user.givenName;
limitedUser.familyName = user.familyName;
}

// Add location information if allowed
if (
(user?.locationVisibility === 'public') ||
(user?.locationVisibility === 'friends' && hasFriendRelationship) ||
(user?.locationVisibility === 'tournaments' && (hasFriendRelationship || hasTournamentRelationship))
) {
limitedUser.countryCode = user.countryCode;
}

return limitedUser;
return {
...restFields,
avatarUrl,
displayName: nameVisible ? formatUserRealName(user) : user.username ?? 'Ghost',
countryCode: locationVisible ? user.countryCode : undefined,
};
};
4 changes: 3 additions & 1 deletion convex/_model/users/actions/setUserDefaultAvatar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ export const setUserDefaultAvatar = async (
const user = await ctx.runQuery(api.users.getUser, {
id: args.userId,
});
if (!user || !!user.avatarStorageId) {

// If user is not found, or it already has an avatar set:
if (!user || !!user.avatarUrl) {
return;
}

Expand Down
1 change: 1 addition & 0 deletions convex/_model/users/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ export {
updateUserAvatarNoAuthArgs,
} from './mutations/updateUserAvatarNoAuth';
export {
type CurrentUser,
getCurrentUser,
} from './queries/getCurrentUser';
export {
Expand Down
23 changes: 18 additions & 5 deletions convex/_model/users/queries/getCurrentUser.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
import { getAuthUserId } from '@convex-dev/auth/server';
import { Doc, getAuthUserId } from '@convex-dev/auth/server';

import { QueryCtx } from '../../../_generated/server';
import { LimitedUser } from '../_helpers/redactUser';
import { getUser } from './getUser';
import { getStorageUrl } from '../../common/_helpers/getStorageUrl';
import { formatUserRealName } from '../_helpers/formatUserRealName';

export type CurrentUser = Doc<'users'> & {
avatarUrl?: string;
displayName: string;
};

/**
* Gets the querying user.
Expand All @@ -14,10 +19,18 @@ import { getUser } from './getUser';
*/
export const getCurrentUser = async (
ctx: QueryCtx,
): Promise<LimitedUser | null> => {
): Promise<CurrentUser | null> => {
const userId = await getAuthUserId(ctx);
if (!userId) {
return null;
}
return await getUser(ctx, { id: userId });
const user = await ctx.db.get(userId);
if (!user) {
return null;
}
return {
...user,
avatarUrl : await getStorageUrl(ctx, user.avatarStorageId),
displayName: formatUserRealName(user),
};
};
3 changes: 1 addition & 2 deletions convex/_model/users/queries/getUsers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,7 @@ export const getUsers = async (
const limitedUsers = await Promise.all(users.map(async (user) => await redactUser(ctx, user)));
if (args.search) {
return filterWithSearchTerm(limitedUsers, args.search, [
'familyName',
'givenName',
'displayName',
'username',
]);
}
Expand Down
1 change: 1 addition & 0 deletions convex/common/userDataVisibilityLevel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export const userDataVisibilityLevel = v.union(
v.literal('friends'),
v.literal('clubs'),
v.literal('tournaments'),
v.literal('community'),
v.literal('public'),
);

Expand Down
1 change: 1 addition & 0 deletions src/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ export {

// Users
export {
type CurrentUser,
type LimitedUser as User,
type UserId,
} from '../convex/_model/users';
Expand Down
9 changes: 5 additions & 4 deletions src/components/AccountMenu/AccountMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,17 @@ import { Avatar } from '~/components/generic/Avatar';
import { Separator } from '~/components/generic/Separator';
import { useSignOut } from '~/services/auth/useSignOut';
import { PATHS } from '~/settings';
import { getUserDisplayNameString } from '~/utils/common/getUserDisplayNameString';

import styles from './AccountMenu.module.scss';

export const AccountMenu = (): JSX.Element => {
export const AccountMenu = (): JSX.Element | null => {
const navigate = useNavigate();
const user = useAuth();
const { signOut } = useSignOut();

const displayName = user ? getUserDisplayNameString(user) : 'Unknown User';
if (!user) {
return null;
}

const items = [
{
Expand Down Expand Up @@ -50,7 +51,7 @@ export const AccountMenu = (): JSX.Element => {
</Popover.Trigger>
<Popover.Content className={styles.Content} align="end">
<div className={styles.UserDisplayName}>
{displayName}
{user.displayName}
</div>
<Separator />
<div className={styles.Items} >
Expand Down
4 changes: 2 additions & 2 deletions src/components/AuthProvider/AuthProvider.context.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { createContext } from 'react';

import { User } from '~/api';
import { CurrentUser } from '~/api';

export const AuthContext = createContext<User | null | undefined>(undefined);
export const AuthContext = createContext<CurrentUser | null | undefined>(undefined);
2 changes: 1 addition & 1 deletion src/components/AvatarEditable/AvatarEditable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export const AvatarEditable = (): JSX.Element => {
<Popover.Close asChild>
<Button variant="ghost">
<label className={styles.UploadButton} htmlFor="single">
{user?.avatarStorageId ? 'Replace' : 'Upload'}
{user?.avatarUrl ? 'Replace' : 'Upload'}
</label>
</Button>
</Popover.Close>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { Identity } from '~/components/IdentityBadge';
import { getUserDisplayNameString } from '~/utils/common/getUserDisplayNameString';
import { FowV4MatchResultDetailsData } from './FowV4MatchResultDetails.types';

type UserPlayerNameResult = {
Expand All @@ -23,7 +22,7 @@ export const usePlayerName = (
if (user) {
return {
loading: false,
displayName: getUserDisplayNameString(user),
displayName: user.displayName,
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import {
UserId,
} from '~/api';
import { useAuth } from '~/components/AuthProvider';
import { getUserDisplayNameString } from '~/utils/common/getUserDisplayNameString';

export const usePlayerDisplayName = ({ userId, placeholder }: { userId?: UserId, placeholder?: string }): string => {
const currentUser = useAuth();
Expand All @@ -19,7 +18,7 @@ export const usePlayerDisplayName = ({ userId, placeholder }: { userId?: UserId,
if (user?._id === currentUser?._id) {
return 'You';
}
return getUserDisplayNameString(user);
return user?.displayName ?? 'Unknown Player';
}
return 'Unknown Player';
};
Expand Down
Loading