fix(social): compute postsCount + followingCount live (were hardcoded 0)
Endpoint /api/social/profile/[userId] returned (profile as any).postsCount ?? 0
und (profile as any).followingCount ?? 0 — Profile-schema hat aber weder
postsCount noch followingCount columns. Daher zeigte UI immer 0 obwohl User
Posts hatte.
Fix: 2 zusätzliche COUNT-queries in Promise.all:
- usePrisma().communityPost.count({ userId, isModerated: false }) → postsCount
- usePrisma().userFollow.count({ followerId: userId }) → followingCount
followersCount bleibt unverändert (wird via trigger denormalisiert in profile-row).
Tests: backend/tests/social/profile-counts.test.ts — 4 Cases
(posts>0, posts=0, following count, followers passthrough). 4/4 grün.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
0e94ddb68a
commit
5264dba257
@ -17,7 +17,7 @@ export default defineEventHandler(async (event) => {
|
||||
currentUserId = u.id;
|
||||
} catch {}
|
||||
|
||||
const [profile, score, followRelation, recentPosts, metaMap] =
|
||||
const [profile, score, followRelation, recentPosts, metaMap, postsCount, followingCount] =
|
||||
await Promise.all([
|
||||
getProfile(targetUserId),
|
||||
getUserScore(targetUserId),
|
||||
@ -38,6 +38,12 @@ export default defineEventHandler(async (event) => {
|
||||
},
|
||||
}),
|
||||
getUsersMeta([targetUserId]),
|
||||
usePrisma().communityPost.count({
|
||||
where: { userId: targetUserId, isModerated: false },
|
||||
}),
|
||||
usePrisma().userFollow.count({
|
||||
where: { followerId: targetUserId },
|
||||
}),
|
||||
]);
|
||||
|
||||
if (!profile)
|
||||
@ -52,8 +58,8 @@ export default defineEventHandler(async (event) => {
|
||||
avatar: meta.avatar,
|
||||
bio: (profile as any).bio ?? null,
|
||||
followersCount: profile.followersCount ?? 0,
|
||||
followingCount: (profile as any).followingCount ?? 0,
|
||||
postsCount: (profile as any).postsCount ?? 0,
|
||||
followingCount,
|
||||
postsCount,
|
||||
tier: score?.tier ?? "beginner",
|
||||
totalPoints: score?.totalPoints ?? 0,
|
||||
isFollowing: !!followRelation,
|
||||
|
||||
136
backend/tests/social/profile-counts.test.ts
Normal file
136
backend/tests/social/profile-counts.test.ts
Normal file
@ -0,0 +1,136 @@
|
||||
/**
|
||||
* Tests for GET /api/social/profile/[userId] — postsCount + followingCount
|
||||
*
|
||||
* Covers:
|
||||
* - postsCount reflects live communityPost.count (not 0 when posts exist)
|
||||
* - followingCount reflects live userFollow.count
|
||||
* - followersCount passes through from profile row (denormalized)
|
||||
*/
|
||||
import { describe, expect, it, vi, beforeEach } from "vitest";
|
||||
|
||||
const mocks = vi.hoisted(() => ({
|
||||
communityPost: {
|
||||
findMany: vi.fn(),
|
||||
count: vi.fn(),
|
||||
},
|
||||
userFollow: {
|
||||
findUnique: vi.fn(),
|
||||
count: vi.fn(),
|
||||
},
|
||||
profile: {
|
||||
findUnique: vi.fn(),
|
||||
},
|
||||
userScore: {
|
||||
findUnique: vi.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock("../../server/utils/prisma", () => ({
|
||||
usePrisma: () => ({
|
||||
communityPost: mocks.communityPost,
|
||||
userFollow: mocks.userFollow,
|
||||
profile: mocks.profile,
|
||||
userScore: mocks.userScore,
|
||||
}),
|
||||
}));
|
||||
|
||||
vi.mock("../../server/utils/auth", () => ({
|
||||
requireUser: vi.fn().mockRejectedValue(
|
||||
Object.assign(new Error("Unauthorized"), { statusCode: 401 }),
|
||||
),
|
||||
}));
|
||||
|
||||
vi.mock("../../server/db/profile", () => ({
|
||||
getProfile: vi.fn().mockResolvedValue({
|
||||
id: "user-1",
|
||||
username: "testuser",
|
||||
followersCount: 3,
|
||||
createdAt: new Date("2026-01-01T00:00:00Z"),
|
||||
}),
|
||||
}));
|
||||
|
||||
vi.mock("../../server/db/scores", () => ({
|
||||
getUserScore: vi.fn().mockResolvedValue({ tier: "beginner", totalPoints: 0 }),
|
||||
}));
|
||||
|
||||
vi.mock("../../server/db/social", () => ({
|
||||
getFollowRelation: vi.fn().mockResolvedValue(null),
|
||||
}));
|
||||
|
||||
vi.mock("../../server/utils/getUsersMeta", () => ({
|
||||
getUsersMeta: vi.fn().mockResolvedValue({
|
||||
"user-1": { nickname: "Tester", avatar: null },
|
||||
}),
|
||||
}));
|
||||
|
||||
// Stub Nitro globals needed by the endpoint file
|
||||
const g = globalThis as Record<string, unknown>;
|
||||
if (typeof g.getRouterParam === "undefined") {
|
||||
g.getRouterParam = (_event: unknown, key: string) =>
|
||||
key === "userId" ? "user-1" : undefined;
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
mocks.communityPost.findMany.mockResolvedValue([]);
|
||||
mocks.communityPost.count.mockResolvedValue(0);
|
||||
mocks.userFollow.count.mockResolvedValue(0);
|
||||
mocks.userFollow.findUnique.mockResolvedValue(null);
|
||||
});
|
||||
|
||||
async function callHandler() {
|
||||
const mod = await import("../../server/api/social/profile/[userId].get");
|
||||
const handler =
|
||||
typeof mod.default === "function"
|
||||
? mod.default
|
||||
: (mod.default as { handler?: unknown }).handler;
|
||||
return (handler as (e: unknown) => Promise<unknown>)({ body: null });
|
||||
}
|
||||
|
||||
describe("postsCount", () => {
|
||||
it("returns postsCount > 0 when user has unmoderated posts", async () => {
|
||||
mocks.communityPost.count.mockResolvedValueOnce(5);
|
||||
mocks.userFollow.count.mockResolvedValueOnce(2);
|
||||
|
||||
const result = (await callHandler()) as Record<string, unknown>;
|
||||
|
||||
expect(result.postsCount).toBe(5);
|
||||
expect(mocks.communityPost.count).toHaveBeenCalledWith({
|
||||
where: { userId: "user-1", isModerated: false },
|
||||
});
|
||||
});
|
||||
|
||||
it("returns postsCount = 0 when user has no posts", async () => {
|
||||
mocks.communityPost.count.mockResolvedValueOnce(0);
|
||||
mocks.userFollow.count.mockResolvedValueOnce(0);
|
||||
|
||||
const result = (await callHandler()) as Record<string, unknown>;
|
||||
|
||||
expect(result.postsCount).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe("followingCount", () => {
|
||||
it("returns followingCount from userFollow.count", async () => {
|
||||
mocks.communityPost.count.mockResolvedValueOnce(2);
|
||||
mocks.userFollow.count.mockResolvedValueOnce(7);
|
||||
|
||||
const result = (await callHandler()) as Record<string, unknown>;
|
||||
|
||||
expect(result.followingCount).toBe(7);
|
||||
expect(mocks.userFollow.count).toHaveBeenCalledWith({
|
||||
where: { followerId: "user-1" },
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("followersCount", () => {
|
||||
it("passes through denormalized followersCount from profile row", async () => {
|
||||
mocks.communityPost.count.mockResolvedValueOnce(0);
|
||||
mocks.userFollow.count.mockResolvedValueOnce(0);
|
||||
|
||||
const result = (await callHandler()) as Record<string, unknown>;
|
||||
|
||||
expect(result.followersCount).toBe(3);
|
||||
});
|
||||
});
|
||||
Loading…
x
Reference in New Issue
Block a user