Field-mask the scoreboard self-user read (R5)
getScoreboard loaded the full user doc just to use 5 fields. Add getUserForScoreboard using getAll with a fieldMask so only displayName, photoUrl, tierPoints, favoriteTeamCode, and rankSnapshot are fetched, cutting per-request bandwidth. The short-lived me-rank cache (meRankCache, 30s) already covers the rank-count side of R5.
This commit is contained in:
parent
fa7767d172
commit
f0b01a2dc0
@ -36,6 +36,44 @@ export async function getUser(uid: string): Promise<User | null> {
|
|||||||
return snap.exists ? (snap.data() as User) : null;
|
return snap.exists ? (snap.data() as User) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 스코어보드 "me" 계산에 필요한 최소 필드만 담는 형태. */
|
||||||
|
export interface ScoreboardSelfUser {
|
||||||
|
displayName: string;
|
||||||
|
photoUrl?: string;
|
||||||
|
tierPoints?: number;
|
||||||
|
favoriteTeamCode?: TeamCode;
|
||||||
|
rankSnapshot?: RankSnapshot;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 스코어보드 응답의 "me" 계산에 필요한 필드만 fieldMask로 읽어온다.
|
||||||
|
* 전체 user doc(streak/티켓/notifications 등) 대신 5개 필드만 전송받아 bandwidth를 줄인다.
|
||||||
|
*
|
||||||
|
* @returns 필요한 필드 부분집합. 문서가 없으면 `null`.
|
||||||
|
*/
|
||||||
|
export async function getUserForScoreboard(
|
||||||
|
uid: string
|
||||||
|
): Promise<ScoreboardSelfUser | null> {
|
||||||
|
const ref = firestore.collection(COLLECTION).doc(uid);
|
||||||
|
const [snap] = await firestore.getAll(ref, {
|
||||||
|
fieldMask: [
|
||||||
|
"displayName",
|
||||||
|
"photoUrl",
|
||||||
|
"tierPoints",
|
||||||
|
"favoriteTeamCode",
|
||||||
|
"rankSnapshot",
|
||||||
|
],
|
||||||
|
});
|
||||||
|
if (!snap.exists) return null;
|
||||||
|
const data = snap.data() as Partial<User>;
|
||||||
|
const self: ScoreboardSelfUser = { displayName: data.displayName ?? "" };
|
||||||
|
if (data.photoUrl) self.photoUrl = data.photoUrl;
|
||||||
|
if (data.tierPoints !== undefined) self.tierPoints = data.tierPoints;
|
||||||
|
if (data.favoriteTeamCode) self.favoriteTeamCode = data.favoriteTeamCode;
|
||||||
|
if (data.rankSnapshot) self.rankSnapshot = data.rankSnapshot;
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
export interface ScoreboardUserEntry {
|
export interface ScoreboardUserEntry {
|
||||||
uid: string;
|
uid: string;
|
||||||
displayName: string;
|
displayName: string;
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { MemCache } from "../lib/memCache";
|
import { MemCache } from "../lib/memCache";
|
||||||
import {
|
import {
|
||||||
countUsersAboveTierPoints,
|
countUsersAboveTierPoints,
|
||||||
getUser,
|
getUserForScoreboard,
|
||||||
} from "../repositories/userRepository";
|
} from "../repositories/userRepository";
|
||||||
import {
|
import {
|
||||||
readScope,
|
readScope,
|
||||||
@ -51,7 +51,7 @@ export async function getScoreboard(
|
|||||||
uid: string,
|
uid: string,
|
||||||
type: ScoreboardType
|
type: ScoreboardType
|
||||||
): Promise<ScoreboardResponse> {
|
): Promise<ScoreboardResponse> {
|
||||||
const user = await getUser(uid);
|
const user = await getUserForScoreboard(uid);
|
||||||
if (!user) throw new HttpError(404, `user not found: ${uid}`);
|
if (!user) throw new HttpError(404, `user not found: ${uid}`);
|
||||||
|
|
||||||
const teamCode = type === "team" ? user.favoriteTeamCode : undefined;
|
const teamCode = type === "team" ? user.favoriteTeamCode : undefined;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user