Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
4da1951
chore: add grafana image
eulixir Feb 18, 2026
6af3b42
chore: update docker compose
eulixir Feb 18, 2026
b667803
Merge branch 'main' of github.com:plotwist-app/plotwist into chore/ad…
eulixir Feb 18, 2026
aa342ea
feat: integrate OpenTelemetry for enhanced observability
eulixir Feb 18, 2026
22485e8
chore: encapluslate controllers inside a telemetry macro
eulixir Feb 18, 2026
721e95f
feat: implement service tracing for various domain services
eulixir Feb 18, 2026
2c59e67
feat: implement telemetry tracing for database and adapter operations
eulixir Feb 18, 2026
ae9840c
feat: add HTTP request metrics and telemetry dashboard
eulixir Feb 18, 2026
5b14ce2
feat: integrate OpenTelemetry for enhanced observability in backend s…
eulixir Feb 19, 2026
f207bcc
refactor: remove telemetry tracing from repository functions and expo…
eulixir Feb 19, 2026
1336294
refactor: remove telemetry tracing from adapter functions and expose …
eulixir Feb 19, 2026
71c47fb
feat: add @kubiks/otel-drizzle for enhanced OpenTelemetry integration…
eulixir Feb 19, 2026
77ff92e
style: format code for improved readability across multiple files
eulixir Feb 19, 2026
b6c0912
Merge branch 'main' of github.com:plotwist-app/plotwist into chore/ad…
eulixir Feb 19, 2026
771f741
refactor: consolidate user repository imports in delete-user service
eulixir Feb 19, 2026
ab5fc88
test: update user total hours test to use matchObject for improved as…
eulixir Feb 19, 2026
a4392b0
Merge branch 'main' of github.com:plotwist-app/plotwist into chore/ad…
eulixir Feb 19, 2026
c690797
Merge branch 'main' of github.com:plotwist-app/plotwist into chore/ad…
eulixir Feb 19, 2026
39521d6
chore: prepare to receive monitors
eulixir Feb 19, 2026
f026279
feat: implement monitoring functionality with cron jobs
eulixir Feb 22, 2026
ddc5764
feat: add new monitoring functions for user and subscription metrics
eulixir Feb 22, 2026
219e30d
feat: implement telemetry metrics monitoring for users and subscriptions
eulixir Feb 22, 2026
0211cec
feat: enhance telemetry dashboard with new panels for total subscript…
eulixir Feb 22, 2026
b7d007f
feat: add host metrics monitoring and update telemetry dashboard conf…
eulixir Feb 23, 2026
552d0fc
feat: update telemetry dashboard to include CPU and memory usage metr…
eulixir Feb 23, 2026
f405821
refactor: clean up config and telemetry files, improve formatting in …
eulixir Feb 23, 2026
06f6423
feat: add @opentelemetry/host-metrics dependency for enhanced host mo…
eulixir Feb 23, 2026
f239913
feat: add telemetry configuration and monitoring support with OpenTel…
eulixir Feb 23, 2026
c2698d2
refactor: simplify HTTP request metrics configuration and enhance OTL…
eulixir Feb 23, 2026
d216c8b
refactor: update telemetry configuration to use simplified endpoint a…
eulixir Feb 23, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions apps/backend/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,11 @@ ENABLE_CRON_JOBS=false

# OpenAI
OPENAI_API_KEY=

# Monitors
ENABLE_MONITORS=true
MONITOR_CRON_TIME="*/30 * * * *"

# Telemetry
OTEL_EXPORTER_OTLP_ENDPOINT=localhost
OTEL_EXPORTER_OTLP_HEADERS=
10 changes: 10 additions & 0 deletions apps/backend/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,16 @@ services:
- localstack_data:/var/lib/localstack
networks:
- plotwist_network

grafana:
image: grafana/otel-lgtm
container_name: plotwist_grafana
ports:
- 12345:3000
- 4317:4317
- 4318:4318
networks:
- plotwist_network

volumes:
redis-data:
Expand Down
13 changes: 12 additions & 1 deletion apps/backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,21 @@
"@fastify/cors": "^11.2.0",
"@fastify/jwt": "^10.0.0",
"@fastify/multipart": "^9.3.0",
"@fastify/otel": "^0.16.0",
"@fastify/rate-limit": "^10.3.0",
"@fastify/redis": "^7.1.0",
"@fastify/swagger": "^9.6.1",
"@fastify/swagger-ui": "^5.2.4",
"@kubiks/otel-drizzle": "^2.1.0",
"@opentelemetry/api": "^1.9.0",
"@opentelemetry/exporter-metrics-otlp-proto": "^0.211.0",
"@opentelemetry/exporter-trace-otlp-proto": "^0.211.0",
"@opentelemetry/host-metrics": "^0.38.2",
"@opentelemetry/instrumentation-http": "^0.212.0",
"@opentelemetry/resources": "^2.5.0",
"@opentelemetry/sdk-metrics": "^2.5.0",
"@opentelemetry/sdk-node": "^0.211.0",
"@opentelemetry/semantic-conventions": "^1.39.0",
"@plotwist_app/tmdb": "^0.2.5",
"@react-email/components": "^1.0.3",
"@swc/core": "^1.15.8",
Expand All @@ -57,9 +68,9 @@
"fastify-type-provider-zod": "^6.1.0",
"google-auth-library": "^9.14.0",
"https": "^1.0.0",
"ioredis": "^5.8.2",
"jsonwebtoken": "^9.0.2",
"jwks-rsa": "^3.1.0",
"ioredis": "^5.8.2",
"node-cron": "^4.2.1",
"openai": "^6.15.0",
"pino": "^10.1.0",
Expand Down
19 changes: 19 additions & 0 deletions apps/backend/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ export const config = {
myAnimeList: loadMALEnvs(),
openai: loadOpenAIEnvs(),
google: loadGoogleEnvs(),
monitors: loadMonitorsEnvs(),
telemetry: loadTelemetryEnvs(),
}

function loadRedisEnvs() {
Expand Down Expand Up @@ -121,3 +123,20 @@ function loadGoogleEnvs() {

return schema.parse(process.env)
}

function loadMonitorsEnvs() {
const schema = z.object({
ENABLE_MONITORS: z.string().default('false'),
MONITOR_CRON_TIME: z.string().default('0 0 * * *'),
})

return schema.parse(process.env)
}

function loadTelemetryEnvs() {
const schema = z.object({
OTEL_EXPORTER_OTLP_ENDPOINT: z.string().optional(),
OTEL_EXPORTER_OTLP_HEADERS: z.string().optional(),
})
return schema.parse(process.env)
}
1 change: 1 addition & 0 deletions apps/backend/src/domain/services/tmdb/get-tmdb-data.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { FastifyRedis } from '@fastify/redis'

import type { Language } from '@plotwist_app/tmdb'
import { tmdb } from '@/infra/adapters/tmdb'

Expand Down
3 changes: 2 additions & 1 deletion apps/backend/src/domain/services/users/update-user.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { z } from 'zod'
import { NoValidFieldsError } from '@/domain/errors/no-valid-fields'
import { UserNotFoundError } from '@/domain/errors/user-not-found'
import { UsernameAlreadyRegisteredError } from '@/domain/errors/username-already-registered'
Expand All @@ -8,7 +9,7 @@ import {
import { isUniqueViolation } from '@/infra/db/utils/postgres-errors'
import type { updateUserBodySchema } from '@/infra/http/schemas/users'

export type UpdateUserInput = typeof updateUserBodySchema._type
export type UpdateUserInput = z.infer<typeof updateUserBodySchema>

export async function updateUserService({
userId,
Expand Down
2 changes: 1 addition & 1 deletion apps/backend/src/infra/adapters/r2-storage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ async function uploadImage({

const R2Storage: CloudStorage = {
deleteOldImages: prefix => deleteOldImages(prefix),
uploadImage: uploadImageInput => uploadImage(uploadImageInput),
uploadImage: input => uploadImage(input),
}

export { R2Storage }
3 changes: 3 additions & 0 deletions apps/backend/src/infra/db/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { instrumentDrizzleClient } from '@kubiks/otel-drizzle'
import { drizzle } from 'drizzle-orm/postgres-js'
import postgres from 'postgres'
import { config } from '@/config'
Expand All @@ -6,3 +7,5 @@ import * as schema from './schema'

export const client = postgres(config.db.DATABASE_URL)
export const db = drizzle(client, { schema })

instrumentDrizzleClient(db, { dbSystem: 'postgresql' })
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ export async function selectReviews({
)
.leftJoin(schema.users, eq(schema.reviews.userId, schema.users.id))
.orderBy(...orderCriteria)
.limit(limit + 1) // Fetch one extra to check if there are more
.limit(limit + 1)
.offset(offset)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,6 @@ export async function reorderUserItems(
_status: string,
orderedIds: string[]
) {
// Update position for each item based on array order
const updates = orderedIds.map((id, index) =>
db
.update(schema.userItems)
Expand Down
2 changes: 0 additions & 2 deletions apps/backend/src/infra/db/repositories/user-stats.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@ import { db } from '..'
import { schema } from '../schema'

export async function selectUserStats(userId: string) {
// Run all independent queries in parallel instead of sequential transaction
// This improves performance as these are all read-only operations
const [
[{ count: followersCount }],
[{ count: followingCount }],
Expand Down
1 change: 1 addition & 0 deletions apps/backend/src/infra/http/routes/follow.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { FastifyInstance } from 'fastify'
import type { ZodTypeProvider } from 'fastify-type-provider-zod'

import {
createFollowController,
deleteFollowController,
Expand Down
1 change: 1 addition & 0 deletions apps/backend/src/infra/http/routes/images.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { FastifyInstance } from 'fastify'
import type { ZodTypeProvider } from 'fastify-type-provider-zod'

import { createImageController } from '../controllers/images-controller'
import { verifyJwt } from '../middlewares/verify-jwt'
import {
Expand Down
1 change: 1 addition & 0 deletions apps/backend/src/infra/http/routes/import.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { FastifyInstance } from 'fastify'
import type { ZodTypeProvider } from 'fastify-type-provider-zod'

import {
createImportController,
getDetailedImportController,
Expand Down
1 change: 1 addition & 0 deletions apps/backend/src/infra/http/routes/likes.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { FastifyInstance } from 'fastify'
import type { ZodTypeProvider } from 'fastify-type-provider-zod'

import {
createLikeController,
deleteLikeController,
Expand Down
1 change: 1 addition & 0 deletions apps/backend/src/infra/http/routes/list-item.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { FastifyInstance } from 'fastify'
import type { ZodTypeProvider } from 'fastify-type-provider-zod'

import {
createListItemController,
deleteListItemController,
Expand Down
1 change: 1 addition & 0 deletions apps/backend/src/infra/http/routes/lists.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { FastifyInstance } from 'fastify'
import type { ZodTypeProvider } from 'fastify-type-provider-zod'

import {
createListController,
deleteListController,
Expand Down
1 change: 1 addition & 0 deletions apps/backend/src/infra/http/routes/login.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { FastifyInstance } from 'fastify'
import type { ZodTypeProvider } from 'fastify-type-provider-zod'

import { loginController } from '../controllers/login-controller'
import { loginBodySchema, loginResponseSchema } from '../schemas/login'

Expand Down
1 change: 1 addition & 0 deletions apps/backend/src/infra/http/routes/social-auth.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { FastifyInstance } from 'fastify'
import type { ZodTypeProvider } from 'fastify-type-provider-zod'

import {
appleAuthController,
googleAuthController,
Expand Down
1 change: 1 addition & 0 deletions apps/backend/src/infra/http/routes/social-links.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { FastifyInstance } from 'fastify'
import type { ZodTypeProvider } from 'fastify-type-provider-zod'

import {
getSocialLinksController,
upsertSocialLinksController,
Expand Down
1 change: 1 addition & 0 deletions apps/backend/src/infra/http/routes/subscriptions.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { FastifyInstance } from 'fastify'

import { deleteSubscriptionController } from '../controllers/subscriptions-controller'
import { verifyJwt } from '../middlewares/verify-jwt'
import {
Expand Down
1 change: 1 addition & 0 deletions apps/backend/src/infra/http/routes/tmdb-proxy.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { FastifyInstance } from 'fastify'
import https from 'https'

import { config } from '@/config'

const TMDB_BASE_URL = 'https://api.themoviedb.org/3'
Expand Down
1 change: 1 addition & 0 deletions apps/backend/src/infra/http/routes/user-activities.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { FastifyInstance } from 'fastify'
import type { ZodTypeProvider } from 'fastify-type-provider-zod'

import {
deleteUserActivityController,
getUserActivitiesController,
Expand Down
1 change: 1 addition & 0 deletions apps/backend/src/infra/http/routes/user-episodes.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { FastifyInstance } from 'fastify'
import type { ZodTypeProvider } from 'fastify-type-provider-zod'

import {
createUserEpisodesController,
deleteUserEpisodesController,
Expand Down
1 change: 1 addition & 0 deletions apps/backend/src/infra/http/routes/user-items.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { FastifyInstance } from 'fastify'
import type { ZodTypeProvider } from 'fastify-type-provider-zod'

import {
deleteUserItemController,
getAllUserItemsController,
Expand Down
1 change: 1 addition & 0 deletions apps/backend/src/infra/http/routes/user-stats.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { FastifyInstance } from 'fastify'
import type { ZodTypeProvider } from 'fastify-type-provider-zod'

import {
getUserBestReviewsController,
getUserItemsStatusController,
Expand Down
1 change: 1 addition & 0 deletions apps/backend/src/infra/http/routes/users.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { FastifyInstance } from 'fastify'
import type { ZodTypeProvider } from 'fastify-type-provider-zod'

import {
createUserController,
deleteUserController,
Expand Down
1 change: 1 addition & 0 deletions apps/backend/src/infra/http/routes/watch-entries.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { FastifyInstance } from 'fastify'
import type { ZodTypeProvider } from 'fastify-type-provider-zod'

import {
createWatchEntryController,
deleteWatchEntryController,
Expand Down
1 change: 1 addition & 0 deletions apps/backend/src/infra/http/routes/webhook.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { FastifyInstance } from 'fastify'
import type { ZodTypeProvider } from 'fastify-type-provider-zod'

import { stripeWebhookController } from '../controllers/stripe-webhook-controller'

export async function webhookRoutes(app: FastifyInstance) {
Expand Down
23 changes: 13 additions & 10 deletions apps/backend/src/infra/http/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,19 @@ import {
validatorCompiler,
} from 'fastify-type-provider-zod'
import { ZodError } from 'zod'
import { config } from '@/config'
import { DomainError } from '@/domain/errors/domain-error'
import { logger } from '@/infra/adapters/logger'
import { config } from '../../config'
import { registerHttpRequestMetrics } from '@/infra/telemetry/http-request-metrics'
import { fastifyOtel } from '@/infra/telemetry/otel'
import { routes } from './routes'
import { transformSwaggerSchema } from './transform-schema'

const app: FastifyInstance = fastify()

export function startServer() {
export async function startServer() {
await app.register(fastifyOtel.plugin())

app.setValidatorCompiler(validatorCompiler)
app.setSerializerCompiler(serializerCompiler)

Expand Down Expand Up @@ -85,16 +89,15 @@ export function startServer() {
return reply.status(500).send({ message: 'Internal server error.' })
})

registerHttpRequestMetrics(app)

// TODO: Uncomment this when we have a client guard
// registerClientGuard(app)
routes(app)

app
.listen({
port: config.app.PORT,
host: '0.0.0.0',
})
.then(() => {
logger.info(`HTTP server running at ${config.app.BASE_URL}`)
})
await app.listen({
port: config.app.PORT,
host: '0.0.0.0',
})
logger.info(`HTTP server running at ${config.app.BASE_URL}`)
}
2 changes: 1 addition & 1 deletion apps/backend/src/infra/http/transform-schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ export function transformSwaggerSchema(
): ReturnType<typeof jsonSchemaTransform> {
const { schema, url } = jsonSchemaTransform(data)

if (schema.consumes?.includes('multipart/form-data')) {
if (schema?.consumes?.includes('multipart/form-data')) {
if (schema.body === undefined) {
schema.body = {
type: 'object',
Expand Down
Loading