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
c4cfd351c4
commit
1c1968b1ae
@ -17,7 +17,7 @@ export default defineEventHandler(async (event) => {
|
|||||||
currentUserId = u.id;
|
currentUserId = u.id;
|
||||||
} catch {}
|
} catch {}
|
||||||
|
|
||||||
const [profile, score, followRelation, recentPosts, metaMap] =
|
const [profile, score, followRelation, recentPosts, metaMap, postsCount, followingCount] =
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
getProfile(targetUserId),
|
getProfile(targetUserId),
|
||||||
getUserScore(targetUserId),
|
getUserScore(targetUserId),
|
||||||
@ -38,6 +38,12 @@ export default defineEventHandler(async (event) => {
|
|||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
getUsersMeta([targetUserId]),
|
getUsersMeta([targetUserId]),
|
||||||
|
usePrisma().communityPost.count({
|
||||||
|
where: { userId: targetUserId, isModerated: false },
|
||||||
|
}),
|
||||||
|
usePrisma().userFollow.count({
|
||||||
|
where: { followerId: targetUserId },
|
||||||
|
}),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if (!profile)
|
if (!profile)
|
||||||
@ -52,8 +58,8 @@ export default defineEventHandler(async (event) => {
|
|||||||
avatar: meta.avatar,
|
avatar: meta.avatar,
|
||||||
bio: (profile as any).bio ?? null,
|
bio: (profile as any).bio ?? null,
|
||||||
followersCount: profile.followersCount ?? 0,
|
followersCount: profile.followersCount ?? 0,
|
||||||
followingCount: (profile as any).followingCount ?? 0,
|
followingCount,
|
||||||
postsCount: (profile as any).postsCount ?? 0,
|
postsCount,
|
||||||
tier: score?.tier ?? "beginner",
|
tier: score?.tier ?? "beginner",
|
||||||
totalPoints: score?.totalPoints ?? 0,
|
totalPoints: score?.totalPoints ?? 0,
|
||||||
isFollowing: !!followRelation,
|
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