Skip to content

Commit 7b22fc4

Browse files
committed
fix(analytics): use followerId to count outbound follows in totalFollows
The totalFollows query in /api/analytics/overview was filtering by argetUsername: username (the authenticated user's own DevCard username), but follow.ts writes followLog rows with argetUsername = the external platform handle being followed and ollowerId = the actor's user ID. This mismatch caused totalFollows to always be 0 for real users because no followLog row ever has targetUsername equal to a DevCard username — they hold external handles like GitHub usernames. Fix: query by ollowerId: userId to correctly count outbound follow actions performed by the authenticated user, which is also what the existing code comment ('Follows performed BY this user') intended. Also removes the now-unused username destructure from request.user.
1 parent 52e4df1 commit 7b22fc4

2 files changed

Lines changed: 64 additions & 19 deletions

File tree

apps/backend/src/__tests__/analytics.test.ts

Lines changed: 61 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,10 @@
1-
import {
2-
describe,
3-
it,
4-
expect,
5-
beforeEach,
6-
afterEach,
7-
vi,
8-
} from 'vitest';
9-
10-
import Fastify, {
11-
type FastifyInstance,
12-
} from 'fastify';
13-
14-
import type { PrismaClient } from '@prisma/client';
1+
import Fastify, { type FastifyInstance, } from 'fastify';
2+
import { describe, it, expect, beforeEach, afterEach, vi, } from 'vitest';
153

164
import { analyticsRoutes } from '../routes/analytics';
175

6+
import type { PrismaClient } from '@prisma/client';
7+
188
// ─── Shared mock data ────────────────────────────────────────────────────────
199

2010
const MOCK_USER_ID = 'user-001';
@@ -34,7 +24,7 @@ const prismaMock = {
3424

3525
// ─── App factory ─────────────────────────────────────────────────────────────
3626

37-
let mockJwtVerify = vi.fn();
27+
const mockJwtVerify = vi.fn();
3828

3929
async function buildApp(): Promise<FastifyInstance> {
4030
const app = Fastify({
@@ -217,6 +207,62 @@ describe(
217207
}
218208
);
219209

210+
it(
211+
'totalFollows counts rows by followerId (outbound follows), not by targetUsername',
212+
async () => {
213+
prismaMock.cardView.count
214+
.mockResolvedValueOnce(0) // totalViews
215+
.mockResolvedValueOnce(0); // viewsToday
216+
217+
// The user performed 3 outbound follow actions
218+
prismaMock.followLog.count.mockResolvedValue(3);
219+
220+
prismaMock.cardView.findMany.mockResolvedValue([]);
221+
222+
const res = await app.inject({
223+
method: 'GET',
224+
url: '/api/analytics/overview',
225+
headers: authHeader(),
226+
});
227+
228+
expect(res.statusCode).toBe(200);
229+
expect(res.json().totalFollows).toBe(3);
230+
231+
// Assert the query used followerId, not targetUsername
232+
const followCountCall = prismaMock.followLog.count.mock.calls[0][0];
233+
expect(followCountCall).toMatchObject({
234+
where: {
235+
followerId: MOCK_USER_ID,
236+
status: 'success',
237+
},
238+
});
239+
expect(followCountCall.where).not.toHaveProperty('targetUsername');
240+
}
241+
);
242+
243+
it(
244+
'totalFollows is 0 when user has no successful outbound follows',
245+
async () => {
246+
prismaMock.cardView.count
247+
.mockResolvedValueOnce(50)
248+
.mockResolvedValueOnce(5);
249+
250+
// No successful follow rows for this user
251+
prismaMock.followLog.count.mockResolvedValue(0);
252+
253+
prismaMock.cardView.findMany.mockResolvedValue([]);
254+
255+
const res = await app.inject({
256+
method: 'GET',
257+
url: '/api/analytics/overview',
258+
headers: authHeader(),
259+
});
260+
261+
expect(res.statusCode).toBe(200);
262+
expect(res.json().totalFollows).toBe(0);
263+
}
264+
);
265+
220266
it(
221267
'401 — rejects unauthenticated request',
222268
async () => {

apps/backend/src/routes/analytics.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,7 @@ export async function analyticsRoutes(
1919
request: FastifyRequest,
2020
_reply: FastifyReply
2121
) => {
22-
const userId = request.user.id;
23-
const username = request.user.username;
22+
const userId = (request.user as any).id;
2423

2524
const today = new Date();
2625
today.setHours(0, 0, 0, 0);
@@ -39,10 +38,10 @@ export async function analyticsRoutes(
3938
},
4039
}),
4140

42-
// Follows performed BY this user
41+
// Follows performed BY this user (outbound follow actions where this user is the actor)
4342
app.prisma.followLog.count({
4443
where: {
45-
targetUsername: username,
44+
followerId: userId,
4645
status: 'success',
4746
},
4847
}),

0 commit comments

Comments
 (0)