Skip to content

Commit f318479

Browse files
authored
Merge pull request #500 from plotwist-app/chore/improve-telemetry
Chore/improve telemetry
2 parents 75dcc43 + 0e32b19 commit f318479

33 files changed

Lines changed: 504 additions & 143 deletions

apps/backend/package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,11 +39,14 @@
3939
"@fastify/swagger-ui": "^5.2.4",
4040
"@kubiks/otel-drizzle": "^2.1.0",
4141
"@opentelemetry/api": "^1.9.0",
42+
"@opentelemetry/api-logs": "^0.211.0",
43+
"@opentelemetry/exporter-logs-otlp-proto": "^0.211.0",
4244
"@opentelemetry/exporter-metrics-otlp-proto": "^0.211.0",
4345
"@opentelemetry/exporter-trace-otlp-proto": "^0.211.0",
4446
"@opentelemetry/host-metrics": "^0.38.2",
4547
"@opentelemetry/instrumentation-http": "^0.212.0",
4648
"@opentelemetry/resources": "^2.5.0",
49+
"@opentelemetry/sdk-logs": "^0.211.0",
4750
"@opentelemetry/sdk-metrics": "^2.5.0",
4851
"@opentelemetry/sdk-node": "^0.211.0",
4952
"@opentelemetry/semantic-conventions": "^1.39.0",
@@ -73,8 +76,6 @@
7376
"jwks-rsa": "^3.1.0",
7477
"node-cron": "^4.2.1",
7578
"openai": "^6.15.0",
76-
"pino": "^10.1.0",
77-
"pino-pretty": "^13.1.3",
7879
"postgres": "^3.4.7",
7980
"puppeteer": "^24.34.0",
8081
"react": "^19.2.3",

apps/backend/src/domain/services/imports/get-detailed-user-import-by-id.spec.ts

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,20 @@ import { makeUser } from '@/test/factories/make-user'
44
import { makeUserImport } from '@/test/factories/make-user-import'
55
import { getDetailedUserImportById } from './get-detailed-user-import-by-id'
66

7+
function sortImportResult<
8+
T extends { series?: { id: string }[]; movies?: { id: string }[] },
9+
>(r: T): T {
10+
return {
11+
...r,
12+
...(r.series && {
13+
series: [...r.series].sort((a, b) => a.id.localeCompare(b.id)),
14+
}),
15+
...(r.movies && {
16+
movies: [...r.movies].sort((a, b) => a.id.localeCompare(b.id)),
17+
}),
18+
}
19+
}
20+
721
describe('get user import', () => {
822
it('should be able to get user import by id', async () => {
923
const { id: userId } = await makeUser({})
@@ -17,7 +31,7 @@ describe('get user import', () => {
1731

1832
const sut = await getDetailedUserImportById(result.id)
1933

20-
expect(sut).toEqual(result)
34+
expect(sortImportResult(sut)).toEqual(sortImportResult(result))
2135
})
2236

2337
it('should be able to get user import by id when movies are empty', async () => {
@@ -33,7 +47,7 @@ describe('get user import', () => {
3347

3448
const sut = await getDetailedUserImportById(result.id)
3549

36-
expect(sut).toEqual(result)
50+
expect(sortImportResult(sut)).toEqual(sortImportResult(result))
3751
})
3852

3953
it('should be able to get user import by id when series are empty', async () => {
@@ -46,6 +60,6 @@ describe('get user import', () => {
4660

4761
const sut = await getDetailedUserImportById(result.id)
4862

49-
expect(sut).toEqual(result)
63+
expect(sortImportResult(sut)).toEqual(sortImportResult(result))
5064
})
5165
})

apps/backend/src/domain/services/user-stats/get-user-best-reviews.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import type { FastifyRedis } from '@fastify/redis'
22
import type { Language } from '@plotwist_app/tmdb'
3-
import type { StatsPeriod } from '@/infra/http/schemas/common'
43
import { selectBestReviews } from '@/infra/db/repositories/reviews-repository'
4+
import type { StatsPeriod } from '@/infra/http/schemas/common'
55
import { getTMDBMovieService } from '../tmdb/get-tmdb-movie'
66
import { getTMDBTvSeriesService } from '../tmdb/get-tmdb-tv-series'
77
import { processInBatches } from './batch-utils'

apps/backend/src/domain/services/user-stats/get-user-most-watched-series.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import type { FastifyRedis } from '@fastify/redis'
22
import type { Language } from '@plotwist_app/tmdb'
3-
import type { StatsPeriod } from '@/infra/http/schemas/common'
43
import { selectMostWatched } from '@/infra/db/repositories/user-episode'
4+
import type { StatsPeriod } from '@/infra/http/schemas/common'
55
import { getTMDBTvSeriesService } from '../tmdb/get-tmdb-tv-series'
66
import { processInBatches } from './batch-utils'
77

apps/backend/src/domain/services/user-stats/get-user-total-hours.spec.ts

Lines changed: 78 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,12 +131,89 @@ describe('get user total hours count', () => {
131131
movieHours: INCEPTION.runtime,
132132
seriesHours: CHERNOBYL.runtime,
133133
})
134-
expect(sut.monthlyHours).toHaveLength(6)
134+
expect(sut.monthlyHours).toHaveLength(12)
135135
expect(
136136
sut.monthlyHours.every(
137137
(m: { month: string; hours: number }) =>
138138
typeof m.month === 'string' && typeof m.hours === 'number'
139139
)
140140
).toBe(true)
141+
expect(Array.isArray(sut.dailyActivity)).toBe(true)
142+
expect(
143+
sut.dailyActivity.every(
144+
(d: { day: string; hours: number }) =>
145+
typeof d.day === 'string' && typeof d.hours === 'number'
146+
)
147+
).toBe(true)
148+
expect(sut.dailyActivity.length).toBeGreaterThan(0)
149+
})
150+
151+
it('should return empty dailyActivity for period "all" when user has no watched items', async () => {
152+
const user = await makeUser()
153+
154+
const sut = await getUserTotalHoursService(user.id, redisClient, 'all')
155+
156+
expect(sut.dailyActivity).toEqual([])
157+
})
158+
159+
it('should return dailyActivity array for period "month"', async () => {
160+
const user = await makeUser()
161+
162+
await makeUserItem({
163+
userId: user.id,
164+
tmdbId: INCEPTION.tmdbId,
165+
mediaType: INCEPTION.mediaType,
166+
status: 'WATCHED',
167+
})
168+
169+
const sut = await getUserTotalHoursService(user.id, redisClient, 'month')
170+
171+
expect(Array.isArray(sut.dailyActivity)).toBe(true)
172+
expect(
173+
sut.dailyActivity.every(
174+
(d: { day: string; hours: number }) =>
175+
typeof d.day === 'string' && typeof d.hours === 'number'
176+
)
177+
).toBe(true)
178+
expect(sut.movieHours).toBe(INCEPTION.runtime)
179+
})
180+
181+
it('should return dailyActivity array for period "year"', async () => {
182+
const user = await makeUser()
183+
184+
await makeUserItem({
185+
userId: user.id,
186+
tmdbId: INCEPTION.tmdbId,
187+
mediaType: INCEPTION.mediaType,
188+
status: 'WATCHED',
189+
})
190+
191+
const sut = await getUserTotalHoursService(user.id, redisClient, 'year')
192+
193+
expect(Array.isArray(sut.dailyActivity)).toBe(true)
194+
expect(
195+
sut.dailyActivity.every(
196+
(d: { day: string; hours: number }) =>
197+
typeof d.day === 'string' && typeof d.hours === 'number'
198+
)
199+
).toBe(true)
200+
})
201+
202+
it('should return dailyActivity array for period "last_month"', async () => {
203+
const user = await makeUser()
204+
205+
const sut = await getUserTotalHoursService(
206+
user.id,
207+
redisClient,
208+
'last_month'
209+
)
210+
211+
expect(Array.isArray(sut.dailyActivity)).toBe(true)
212+
expect(
213+
sut.dailyActivity.every(
214+
(d: { day: string; hours: number }) =>
215+
typeof d.day === 'string' && typeof d.hours === 'number'
216+
)
217+
).toBe(true)
141218
})
142219
})

apps/backend/src/domain/services/user-stats/get-user-total-hours.ts

Lines changed: 39 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -200,39 +200,52 @@ function computeDailyBreakdown(
200200
}))
201201
}
202202

203+
function getDateRangeForPeriod(
204+
period: StatsPeriod,
205+
now: Date,
206+
movieData: { date: Date | null }[],
207+
episodes: { watchedAt: Date | null }[]
208+
): { startDate: Date; endDate: Date } | null {
209+
if (period === 'month') {
210+
return {
211+
startDate: new Date(now.getFullYear(), now.getMonth(), 1),
212+
endDate: now,
213+
}
214+
}
215+
if (period === 'last_month') {
216+
return {
217+
startDate: new Date(now.getFullYear(), now.getMonth() - 1, 1),
218+
endDate: new Date(now.getFullYear(), now.getMonth(), 0),
219+
}
220+
}
221+
if (period === 'year') {
222+
return {
223+
startDate: new Date(now.getFullYear(), 0, 1),
224+
endDate: now,
225+
}
226+
}
227+
const allTimestamps = [
228+
...movieData.flatMap(m => (m.date ? [m.date.getTime()] : [])),
229+
...episodes.flatMap(e => (e.watchedAt ? [e.watchedAt.getTime()] : [])),
230+
]
231+
if (allTimestamps.length === 0) return null
232+
const start = new Date(Math.min(...allTimestamps))
233+
return {
234+
startDate: new Date(start.getFullYear(), start.getMonth(), start.getDate()),
235+
endDate: now,
236+
}
237+
}
238+
203239
function computeAllDailyActivity(
204240
movieData: { runtime: number; date: Date | null }[],
205241
episodes: { runtime: number; watchedAt: Date | null }[],
206242
period: StatsPeriod
207243
) {
208244
const now = new Date()
209-
let startDate: Date
210-
let endDate: Date
211-
212-
if (period === 'month') {
213-
startDate = new Date(now.getFullYear(), now.getMonth(), 1)
214-
endDate = now
215-
} else if (period === 'last_month') {
216-
startDate = new Date(now.getFullYear(), now.getMonth() - 1, 1)
217-
endDate = new Date(now.getFullYear(), now.getMonth(), 0)
218-
} else if (period === 'year') {
219-
startDate = new Date(now.getFullYear(), 0, 1)
220-
endDate = now
221-
} else {
222-
const allDates = [
223-
...movieData.filter(m => m.date).map(m => m.date!.getTime()),
224-
...episodes.filter(e => e.watchedAt).map(e => e.watchedAt!.getTime()),
225-
]
226-
if (allDates.length === 0) return []
227-
startDate = new Date(Math.min(...allDates))
228-
startDate = new Date(
229-
startDate.getFullYear(),
230-
startDate.getMonth(),
231-
startDate.getDate()
232-
)
233-
endDate = now
234-
}
245+
const range = getDateRangeForPeriod(period, now, movieData, episodes)
246+
if (!range) return []
235247

248+
const { startDate, endDate } = range
236249
const dayMap = new Map<string, number>()
237250
const cursor = new Date(startDate)
238251
while (cursor <= endDate) {

apps/backend/src/domain/services/user-stats/get-user-watched-cast.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import type { FastifyRedis } from '@fastify/redis'
2-
import type { StatsPeriod } from '@/infra/http/schemas/common'
32
import { selectAllUserItemsByStatus } from '@/infra/db/repositories/user-item-repository'
3+
import type { StatsPeriod } from '@/infra/http/schemas/common'
44
import { getTMDBCredits } from '../tmdb/get-tmdb-credits'
55
import { processInBatches } from './batch-utils'
66
import { getCachedStats, getUserStatsCacheKey } from './cache-utils'
@@ -18,7 +18,12 @@ export async function getUserWatchedCastService({
1818
dateRange,
1919
period = 'all',
2020
}: GetUserWatchedCastServiceInput) {
21-
const cacheKey = getUserStatsCacheKey(userId, 'watched-cast', undefined, period)
21+
const cacheKey = getUserStatsCacheKey(
22+
userId,
23+
'watched-cast',
24+
undefined,
25+
period
26+
)
2227

2328
return getCachedStats(redis, cacheKey, async () => {
2429
const watchedItems = await selectAllUserItemsByStatus({

apps/backend/src/domain/services/user-stats/get-user-watched-countries.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import type { FastifyRedis } from '@fastify/redis'
22
import type { Language } from '@plotwist_app/tmdb'
3-
import type { StatsPeriod } from '@/infra/http/schemas/common'
43
import { selectAllUserItemsByStatus } from '@/infra/db/repositories/user-item-repository'
4+
import type { StatsPeriod } from '@/infra/http/schemas/common'
55
import { getTMDBMovieService } from '../tmdb/get-tmdb-movie'
66
import { getTMDBTvSeriesService } from '../tmdb/get-tmdb-tv-series'
77
import { processInBatches } from './batch-utils'
@@ -22,7 +22,12 @@ export async function getUserWatchedCountriesService({
2222
dateRange,
2323
period = 'all',
2424
}: GetUserWatchedCountriesServiceInput) {
25-
const cacheKey = getUserStatsCacheKey(userId, 'watched-countries', language, period)
25+
const cacheKey = getUserStatsCacheKey(
26+
userId,
27+
'watched-countries',
28+
language,
29+
period
30+
)
2631

2732
return getCachedStats(redis, cacheKey, async () => {
2833
const watchedItems = await selectAllUserItemsByStatus({

0 commit comments

Comments
 (0)