From e344bbaa7b4ffd41f81af51a1b01c944f1fc906c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Pedro=20Alves?= Date: Sun, 15 Feb 2026 02:30:31 -0300 Subject: [PATCH 1/7] chore: apply minor changes --- 23 | 0 apps/backend/docker-compose.yml | 3 --- apps/backend/package.json | 1 + apps/backend/src/config.ts | 4 +++- apps/backend/src/http/routes/healthcheck.ts | 1 + apps/backend/src/http/routes/index.ts | 15 +++++++++++++++ apps/backend/src/http/routes/webhook.ts | 1 + apps/backend/src/http/server.ts | 14 ++++++++++++++ pnpm-lock.yaml | 12 ++++++++++++ 9 files changed, 47 insertions(+), 4 deletions(-) delete mode 100644 23 diff --git a/23 b/23 deleted file mode 100644 index e69de29bb..000000000 diff --git a/apps/backend/docker-compose.yml b/apps/backend/docker-compose.yml index 1e214dfd3..4ada73309 100644 --- a/apps/backend/docker-compose.yml +++ b/apps/backend/docker-compose.yml @@ -26,8 +26,6 @@ services: POSTGRES_PASSWORD: postgres POSTGRES_DB: plotwist_db POSTGRESQL_REPLICATION_USE_PASSFILE: "no" - volumes: - - postgres_data:/var/lib/postgresql/data ports: - 5432:5432 networks: @@ -60,7 +58,6 @@ services: - plotwist_network volumes: - postgres_data: redis-data: localstack_data: driver: local diff --git a/apps/backend/package.json b/apps/backend/package.json index 7a92a730a..fea1569af 100644 --- a/apps/backend/package.json +++ b/apps/backend/package.json @@ -32,6 +32,7 @@ "@fastify/cors": "^11.2.0", "@fastify/jwt": "^10.0.0", "@fastify/multipart": "^9.3.0", + "@fastify/rate-limit": "^10.3.0", "@fastify/redis": "^7.1.0", "@fastify/swagger": "^9.6.1", "@fastify/swagger-ui": "^5.2.4", diff --git a/apps/backend/src/config.ts b/apps/backend/src/config.ts index a016dba76..37c527fec 100644 --- a/apps/backend/src/config.ts +++ b/apps/backend/src/config.ts @@ -34,7 +34,7 @@ function loadServicesEnvs() { function loadDatabaseEnvs() { const schema = z.object({ - DATABASE_URL: z.string().url(), + DATABASE_URL: z.string(), }) return schema.parse(process.env) @@ -47,6 +47,8 @@ function loadAppEnvs() { PORT: z.coerce.number().default(3333), BASE_URL: z.string().default('http://localhost:3333'), JWT_SECRET: z.string(), + RATE_LIMIT_MAX: z.coerce.number().optional().default(100), + RATE_LIMIT_TIME_WINDOW_MS: z.coerce.number().optional().default(60_000), }) return schema.parse(process.env) diff --git a/apps/backend/src/http/routes/healthcheck.ts b/apps/backend/src/http/routes/healthcheck.ts index 03efca73a..752bb74ba 100644 --- a/apps/backend/src/http/routes/healthcheck.ts +++ b/apps/backend/src/http/routes/healthcheck.ts @@ -4,6 +4,7 @@ export const healthCheck = (app: FastifyInstance) => app.route({ method: 'GET', url: '/health', + config: { rateLimit: false }, handler: (_request, reply) => { reply.send({ alive: true }) }, diff --git a/apps/backend/src/http/routes/index.ts b/apps/backend/src/http/routes/index.ts index b64711771..cb00f6db6 100644 --- a/apps/backend/src/http/routes/index.ts +++ b/apps/backend/src/http/routes/index.ts @@ -1,6 +1,7 @@ import fastifyCors from '@fastify/cors' import fastifyJwt from '@fastify/jwt' import fastifyMultipart from '@fastify/multipart' +import fastifyRateLimit from '@fastify/rate-limit' import fastifyRedis from '@fastify/redis' import fastifySwaggerUi from '@fastify/swagger-ui' @@ -52,6 +53,20 @@ export function routes(app: FastifyInstance) { url: config.redis.REDIS_URL, }) + app.register(async instance => { + await instance.register(fastifyRateLimit, { + redis: instance.redis, + max: config.app.RATE_LIMIT_MAX, + timeWindow: config.app.RATE_LIMIT_TIME_WINDOW_MS, + skipOnError: true, + errorResponseBuilder: (_request, context) => ({ + statusCode: 429, + error: 'Too Many Requests', + message: `Rate limit exceeded, retry in ${Math.ceil(context.after / 1000)} seconds`, + }), + }) + }) + app.register(usersRoute) app.register(listsRoute) app.register(loginRoute) diff --git a/apps/backend/src/http/routes/webhook.ts b/apps/backend/src/http/routes/webhook.ts index 8e69dcc11..a7ad56ba7 100644 --- a/apps/backend/src/http/routes/webhook.ts +++ b/apps/backend/src/http/routes/webhook.ts @@ -21,6 +21,7 @@ export async function webhookRoutes(app: FastifyInstance) { app.withTypeProvider().route({ method: 'POST', url: '/complete-stripe-subscription', + config: { rateLimit: false }, schema: { description: 'Webhook route', tags: ['Webhook'], diff --git a/apps/backend/src/http/server.ts b/apps/backend/src/http/server.ts index 77b78cadd..83220cbbf 100644 --- a/apps/backend/src/http/server.ts +++ b/apps/backend/src/http/server.ts @@ -67,6 +67,20 @@ export function startServer() { .send({ message: 'You hit the rate limit! Slow down please!' }) } + if ( + typeof (error as { statusCode?: number }).statusCode === 'number' && + (error as { statusCode: number }).statusCode === 429 + ) { + if (!reply.sent) { + return reply + .code(429) + .send( + (error as { message?: string }).message ?? 'Rate limit exceeded.' + ) + } + return + } + console.error({ error }) return reply.status(500).send({ message: 'Internal server error.' }) }) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b729d45a5..288e4e775 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -44,6 +44,9 @@ importers: '@fastify/multipart': specifier: ^9.3.0 version: 9.4.0 + '@fastify/rate-limit': + specifier: ^10.3.0 + version: 10.3.0 '@fastify/redis': specifier: ^7.1.0 version: 7.2.0 @@ -2150,6 +2153,9 @@ packages: '@fastify/proxy-addr@5.1.0': resolution: {integrity: sha512-INS+6gh91cLUjB+PVHfu1UqcB76Sqtpyp7bnL+FYojhjygvOPA9ctiD/JDKsyD9Xgu4hUhCSJBPig/w7duNajw==} + '@fastify/rate-limit@10.3.0': + resolution: {integrity: sha512-eIGkG9XKQs0nyynatApA3EVrojHOuq4l6fhB4eeCk4PIOeadvOJz9/4w3vGI44Go17uaXOWEcPkaD8kuKm7g6Q==} + '@fastify/redis@7.2.0': resolution: {integrity: sha512-Ql8emmkajVBCCz0pRjPFZRFI2069Jrp/iufKb5TJwgBeu7B+oZp+GeZIgf+4WX3EX7Vi/h3KTyGf+5DUWI1oFw==} @@ -10574,6 +10580,12 @@ snapshots: '@fastify/forwarded': 3.0.1 ipaddr.js: 2.3.0 + '@fastify/rate-limit@10.3.0': + dependencies: + '@lukeed/ms': 2.0.2 + fastify-plugin: 5.1.0 + toad-cache: 3.7.0 + '@fastify/redis@7.2.0': dependencies: fastify-plugin: 5.1.0 From c9f87abfcb67f48d9e307931c7eb6d13dd02ebdb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Pedro=20Alves?= Date: Sun, 15 Feb 2026 14:31:31 -0300 Subject: [PATCH 2/7] chore: add rate limit setup --- apps/backend/src/config.ts | 1 + apps/backend/src/http/client-guard.ts | 78 +++++++++++++++++++++++++++ apps/backend/src/http/rate-limit.ts | 19 +++++++ apps/backend/src/http/routes/index.ts | 45 +++------------- apps/backend/src/http/server.ts | 2 + 5 files changed, 107 insertions(+), 38 deletions(-) create mode 100644 apps/backend/src/http/client-guard.ts create mode 100644 apps/backend/src/http/rate-limit.ts diff --git a/apps/backend/src/config.ts b/apps/backend/src/config.ts index 37c527fec..11a6017e8 100644 --- a/apps/backend/src/config.ts +++ b/apps/backend/src/config.ts @@ -44,6 +44,7 @@ function loadAppEnvs() { const schema = z.object({ APP_ENV: z.enum(['dev', 'test', 'production']).optional().default('dev'), CLIENT_URL: z.string(), + CLIENT_TOKEN: z.string().optional().default(''), PORT: z.coerce.number().default(3333), BASE_URL: z.string().default('http://localhost:3333'), JWT_SECRET: z.string(), diff --git a/apps/backend/src/http/client-guard.ts b/apps/backend/src/http/client-guard.ts new file mode 100644 index 000000000..5d980c9e7 --- /dev/null +++ b/apps/backend/src/http/client-guard.ts @@ -0,0 +1,78 @@ +import { timingSafeEqual } from 'node:crypto' +import type { FastifyInstance, FastifyReply } from 'fastify' +import { config } from '@/config' + +const SKIP_PATHS = ['/health', '/complete-stripe-subscription'] + +const ALLOWED_ORIGINS = ['https://plotwist.app'] + +function getPath(url: string): string { + return url.split('?')[0] +} + +function isPathSkipped(path: string): boolean { + return SKIP_PATHS.some(skip => path === skip || path.endsWith(skip)) +} + +function getAllowedOrigins(): string[] { + return [...new Set([config.app.CLIENT_URL, ...ALLOWED_ORIGINS])] +} + +function isTokenValid(received: string, expected: string): boolean { + if (received.length !== expected.length) return false + const a = Buffer.from(received, 'utf8') + const b = Buffer.from(expected, 'utf8') + return a.length === b.length && timingSafeEqual(a, b) +} + +function isOriginAllowed( + origin: string | undefined, + referer: string | undefined, + allowed: string[] +): boolean { + if (typeof origin === 'string' && allowed.some(o => origin === o)) return true + if (typeof referer === 'string' && allowed.some(o => referer.startsWith(o))) + return true + return false +} + +function forbidden(reply: FastifyReply) { + return reply.status(403).send({ + statusCode: 403, + error: 'Forbidden', + message: 'Request not allowed from this client.', + }) +} + +/** + * Production-only guard: + * - If X-Client-Token is present: validate against CLIENT_TOKEN; allow only if valid. + * - If X-Client-Token is absent: allow only if Origin is in allowed list (e.g. plotwist.app). + */ +export function registerClientGuard(app: FastifyInstance) { + app.addHook('onRequest', async (request, reply) => { + if (config.app.APP_ENV !== 'production') return + if (isPathSkipped(getPath(request.url))) return + + const tokenHeader = request.headers['x-client-token'] + const hasToken = typeof tokenHeader === 'string' && tokenHeader.length > 0 + + if (hasToken) { + const expectedToken = config.app.CLIENT_TOKEN + if (!expectedToken) { + app.log.warn('X-Client-Token received but CLIENT_TOKEN is not set.') + return forbidden(reply) + } + if (isTokenValid(tokenHeader, expectedToken)) return + return forbidden(reply) + } + + const allowed = getAllowedOrigins() + if ( + isOriginAllowed(request.headers.origin, request.headers.referer, allowed) + ) + return + + return forbidden(reply) + }) +} diff --git a/apps/backend/src/http/rate-limit.ts b/apps/backend/src/http/rate-limit.ts new file mode 100644 index 000000000..2e6d9b1bf --- /dev/null +++ b/apps/backend/src/http/rate-limit.ts @@ -0,0 +1,19 @@ +import fastifyRateLimit from '@fastify/rate-limit' +import type { FastifyInstance } from 'fastify' +import { config } from '@/config' + +export function registerRateLimit(app: FastifyInstance) { + app.register(async instance => { + await instance.register(fastifyRateLimit, { + redis: instance.redis, + max: config.app.RATE_LIMIT_MAX, + timeWindow: config.app.RATE_LIMIT_TIME_WINDOW_MS, + skipOnError: true, + errorResponseBuilder: (_request: unknown, context: { after: string }) => ({ + statusCode: 429, + error: 'Too Many Requests', + message: `Rate limit exceeded, retry in ${Math.ceil(Number(context.after) / 1000)} seconds`, + }), + }) + }) +} diff --git a/apps/backend/src/http/routes/index.ts b/apps/backend/src/http/routes/index.ts index cb00f6db6..3fe3a9010 100644 --- a/apps/backend/src/http/routes/index.ts +++ b/apps/backend/src/http/routes/index.ts @@ -1,13 +1,13 @@ import fastifyCors from '@fastify/cors' import fastifyJwt from '@fastify/jwt' import fastifyMultipart from '@fastify/multipart' -import fastifyRateLimit from '@fastify/rate-limit' import fastifyRedis from '@fastify/redis' import fastifySwaggerUi from '@fastify/swagger-ui' import type { FastifyInstance } from 'fastify' import { config } from '@/config' +import { registerRateLimit } from '../rate-limit' import { followsRoutes } from './follow' import { healthCheck } from './healthcheck' import { imagesRoutes } from './images' @@ -39,8 +39,9 @@ export function routes(app: FastifyInstance) { app.register(fastifyCors, { origin: getCorsOrigin(), methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'], - allowedHeaders: ['Content-Type', 'Authorization'], + allowedHeaders: ['Content-Type', 'Authorization', 'X-Client-Token'], credentials: true, + strictPreflight: false, }) app.register(fastifyJwt, { @@ -53,19 +54,7 @@ export function routes(app: FastifyInstance) { url: config.redis.REDIS_URL, }) - app.register(async instance => { - await instance.register(fastifyRateLimit, { - redis: instance.redis, - max: config.app.RATE_LIMIT_MAX, - timeWindow: config.app.RATE_LIMIT_TIME_WINDOW_MS, - skipOnError: true, - errorResponseBuilder: (_request, context) => ({ - statusCode: 429, - error: 'Too Many Requests', - message: `Rate limit exceeded, retry in ${Math.ceil(context.after / 1000)} seconds`, - }), - }) - }) + registerRateLimit(app) app.register(usersRoute) app.register(listsRoute) @@ -92,36 +81,16 @@ export function routes(app: FastifyInstance) { return } -function getCorsOrigin() { +function getCorsOrigin(): true | string[] { if (config.app.APP_ENV !== 'production') { - return true // Permite todas as origens em dev/test + return true } - // Em produção, permite múltiplas origens const allowedOrigins = [ config.app.CLIENT_URL, 'https://plotwist.app', 'https://www.plotwist.app', ] - // Remove duplicatas caso CLIENT_URL já seja plotwist.app - const uniqueOrigins = [...new Set(allowedOrigins)] - - return ( - origin: string | undefined, - callback: (err: Error | null, allow?: boolean) => void - ) => { - // Apps nativos (iOS/Android) podem não enviar header Origin - // Nesse caso, permitimos a requisição - if (!origin) { - callback(null, true) - return - } - - if (uniqueOrigins.includes(origin)) { - callback(null, true) - } else { - callback(new Error('Not allowed by CORS'), false) - } - } + return [...new Set(allowedOrigins)] } diff --git a/apps/backend/src/http/server.ts b/apps/backend/src/http/server.ts index 83220cbbf..b0b73fd01 100644 --- a/apps/backend/src/http/server.ts +++ b/apps/backend/src/http/server.ts @@ -9,6 +9,7 @@ import { ZodError } from 'zod' import { logger } from '@/adapters/logger' import { DomainError } from '@/domain/errors/domain-error' import { config } from '../config' +import { registerClientGuard } from './client-guard' import { routes } from './routes' import { transformSwaggerSchema } from './transform-schema' @@ -85,6 +86,7 @@ export function startServer() { return reply.status(500).send({ message: 'Internal server error.' }) }) + registerClientGuard(app) routes(app) app From 63c13158597127aea62e7c9e534f44eab58e4147 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Pedro=20Alves?= Date: Sun, 15 Feb 2026 15:10:55 -0300 Subject: [PATCH 3/7] feat: implement iOS token handling for client requests --- apps/backend/.env.example | 1 + apps/backend/fly.toml | 24 ------ apps/backend/src/config.ts | 2 +- apps/backend/src/http/client-guard.ts | 82 +++++++++---------- .../Plotwist.xcodeproj/project.pbxproj | 4 + .../Plotwist/Services/AnalyticsService.swift | 1 + .../Plotwist/Services/AuthService.swift | 9 ++ .../Plotwist/Services/ReviewService.swift | 9 +- .../Plotwist/Services/SocialAuthService.swift | 1 + .../Plotwist/Services/TMDBService.swift | 30 +++++++ .../Services/UserEpisodeService.swift | 3 + .../Plotwist/Services/UserItemService.swift | 9 ++ .../Plotwist/Services/UserStatsService.swift | 5 ++ .../Plotwist/Plotwist/Utils/Constants.swift | 11 +++ 14 files changed, 119 insertions(+), 72 deletions(-) delete mode 100644 apps/backend/fly.toml diff --git a/apps/backend/.env.example b/apps/backend/.env.example index 2cc6ee318..561e28c7f 100644 --- a/apps/backend/.env.example +++ b/apps/backend/.env.example @@ -5,6 +5,7 @@ PORT=3333 BASE_URL=http://localhost:3333 JWT_SECRET= CLIENT_URL=http://localhost:3000 +IOS_TOKEN= # Database DATABASE_URL="postgresql://postgres:postgres@localhost:5432" diff --git a/apps/backend/fly.toml b/apps/backend/fly.toml deleted file mode 100644 index a4e6c42ca..000000000 --- a/apps/backend/fly.toml +++ /dev/null @@ -1,24 +0,0 @@ -# fly.toml app configuration file generated for plotwist-backend on 2024-11-29T23:07:42Z -# -# See https://fly.io/docs/reference/configuration/ for information about how to use this file. -# - -app_name = "plotwist-backend" -app = 'plotwist-backend' -primary_region = 'gru' - -[build] - -[http_service] - internal_port = 3000 - force_https = true - auto_stop_machines = false - auto_start_machines = true - min_machines_running = 0 - processes = ['app'] - -[[vm]] - memory = '1gb' - cpu_kind = 'shared' - cpus = 1 - memory_mb = 1024 diff --git a/apps/backend/src/config.ts b/apps/backend/src/config.ts index 11a6017e8..0637da963 100644 --- a/apps/backend/src/config.ts +++ b/apps/backend/src/config.ts @@ -44,7 +44,7 @@ function loadAppEnvs() { const schema = z.object({ APP_ENV: z.enum(['dev', 'test', 'production']).optional().default('dev'), CLIENT_URL: z.string(), - CLIENT_TOKEN: z.string().optional().default(''), + IOS_TOKEN: z.string().optional().default(''), PORT: z.coerce.number().default(3333), BASE_URL: z.string().default('http://localhost:3333'), JWT_SECRET: z.string(), diff --git a/apps/backend/src/http/client-guard.ts b/apps/backend/src/http/client-guard.ts index 5d980c9e7..caec0daf0 100644 --- a/apps/backend/src/http/client-guard.ts +++ b/apps/backend/src/http/client-guard.ts @@ -1,39 +1,27 @@ import { timingSafeEqual } from 'node:crypto' -import type { FastifyInstance, FastifyReply } from 'fastify' +import type { FastifyInstance, FastifyReply, FastifyRequest } from 'fastify' import { config } from '@/config' const SKIP_PATHS = ['/health', '/complete-stripe-subscription'] - const ALLOWED_ORIGINS = ['https://plotwist.app'] -function getPath(url: string): string { - return url.split('?')[0] -} - -function isPathSkipped(path: string): boolean { - return SKIP_PATHS.some(skip => path === skip || path.endsWith(skip)) +function pathMatches(path: string) { + return SKIP_PATHS.some(s => path === s || path.endsWith(s)) } -function getAllowedOrigins(): string[] { - return [...new Set([config.app.CLIENT_URL, ...ALLOWED_ORIGINS])] +function timingSafeCompare(a: string, b: string): boolean { + if (a.length !== b.length) return false + const bufA = Buffer.from(a, 'utf8') + const bufB = Buffer.from(b, 'utf8') + return bufA.length === bufB.length && timingSafeEqual(bufA, bufB) } -function isTokenValid(received: string, expected: string): boolean { - if (received.length !== expected.length) return false - const a = Buffer.from(received, 'utf8') - const b = Buffer.from(expected, 'utf8') - return a.length === b.length && timingSafeEqual(a, b) -} - -function isOriginAllowed( - origin: string | undefined, - referer: string | undefined, - allowed: string[] -): boolean { - if (typeof origin === 'string' && allowed.some(o => origin === o)) return true - if (typeof referer === 'string' && allowed.some(o => referer.startsWith(o))) - return true - return false +function allowedOrigin(origin: string | undefined, referer: string | undefined): boolean { + const allowed = [...new Set([config.app.CLIENT_URL, ...ALLOWED_ORIGINS])] + return ( + (typeof origin === 'string' && allowed.includes(origin)) || + (typeof referer === 'string' && allowed.some(o => referer.startsWith(o))) + ) } function forbidden(reply: FastifyReply) { @@ -44,35 +32,41 @@ function forbidden(reply: FastifyReply) { }) } +function validIosToken(request: FastifyRequest, app: FastifyInstance): boolean { + const expected = config.app.IOS_TOKEN + if (!expected) { + app.log.warn('X-IOS-Token received but IOS_TOKEN is not set.') + return false + } + const received = request.headers['x-ios-token'] + return typeof received === 'string' && timingSafeCompare(received, expected) +} + /** * Production-only guard: - * - If X-Client-Token is present: validate against CLIENT_TOKEN; allow only if valid. - * - If X-Client-Token is absent: allow only if Origin is in allowed list (e.g. plotwist.app). + * - X-IOS-Token present → must match IOS_TOKEN. + * - X-Android-Token present → not implemented (403). + * - Otherwise → allow only if Origin/Referer is in allowed list (e.g. plotwist.app). */ export function registerClientGuard(app: FastifyInstance) { app.addHook('onRequest', async (request, reply) => { if (config.app.APP_ENV !== 'production') return - if (isPathSkipped(getPath(request.url))) return + const path = request.url.split('?')[0] + if (pathMatches(path)) return - const tokenHeader = request.headers['x-client-token'] - const hasToken = typeof tokenHeader === 'string' && tokenHeader.length > 0 + const iosToken = request.headers['x-ios-token'] + if (typeof iosToken === 'string' && iosToken.length > 0) { + if (!validIosToken(request, app)) return forbidden(reply) + return + } - if (hasToken) { - const expectedToken = config.app.CLIENT_TOKEN - if (!expectedToken) { - app.log.warn('X-Client-Token received but CLIENT_TOKEN is not set.') - return forbidden(reply) - } - if (isTokenValid(tokenHeader, expectedToken)) return + const androidToken = request.headers['x-android-token'] + if (typeof androidToken === 'string' && androidToken.length > 0) { + app.log.warn('X-Android-Token received but not implemented yet.') return forbidden(reply) } - const allowed = getAllowedOrigins() - if ( - isOriginAllowed(request.headers.origin, request.headers.referer, allowed) - ) - return - + if (allowedOrigin(request.headers.origin, request.headers.referer)) return return forbidden(reply) }) } diff --git a/apps/ios/Plotwist/Plotwist.xcodeproj/project.pbxproj b/apps/ios/Plotwist/Plotwist.xcodeproj/project.pbxproj index d133f0198..bb904b1a9 100644 --- a/apps/ios/Plotwist/Plotwist.xcodeproj/project.pbxproj +++ b/apps/ios/Plotwist/Plotwist.xcodeproj/project.pbxproj @@ -272,6 +272,8 @@ DEVELOPMENT_TEAM = 54XPVTP5PA; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; + IOS_TOKEN = ""; + INFOPLIST_KEY_IOS_TOKEN = "$(IOS_TOKEN)"; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchScreen_Generation = YES; @@ -301,6 +303,8 @@ DEVELOPMENT_TEAM = 54XPVTP5PA; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; + IOS_TOKEN = ""; + INFOPLIST_KEY_IOS_TOKEN = "$(IOS_TOKEN)"; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchScreen_Generation = YES; diff --git a/apps/ios/Plotwist/Plotwist/Services/AnalyticsService.swift b/apps/ios/Plotwist/Plotwist/Services/AnalyticsService.swift index be52c7b52..8d83f8022 100644 --- a/apps/ios/Plotwist/Plotwist/Services/AnalyticsService.swift +++ b/apps/ios/Plotwist/Plotwist/Services/AnalyticsService.swift @@ -215,6 +215,7 @@ class AnalyticsService { else { return } var request = URLRequest(url: url) + API.addIOSTokenHeader(to: &request) request.httpMethod = "POST" request.setValue("application/json", forHTTPHeaderField: "Content-Type") diff --git a/apps/ios/Plotwist/Plotwist/Services/AuthService.swift b/apps/ios/Plotwist/Plotwist/Services/AuthService.swift index 5ae5b22ba..c2a21c5e2 100644 --- a/apps/ios/Plotwist/Plotwist/Services/AuthService.swift +++ b/apps/ios/Plotwist/Plotwist/Services/AuthService.swift @@ -29,6 +29,7 @@ class AuthService { } var request = URLRequest(url: url) + API.addIOSTokenHeader(to: &request) request.httpMethod = "POST" request.setValue("application/json", forHTTPHeaderField: "Content-Type") request.httpBody = try JSONEncoder().encode(["login": login, "password": password]) @@ -66,6 +67,7 @@ class AuthService { } var request = URLRequest(url: url) + API.addIOSTokenHeader(to: &request) request.httpMethod = "POST" request.setValue("application/json", forHTTPHeaderField: "Content-Type") @@ -139,6 +141,7 @@ class AuthService { } var request = URLRequest(url: url) + API.addIOSTokenHeader(to: &request) request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization") let (data, response) = try await URLSession.shared.data(for: request) @@ -165,6 +168,7 @@ class AuthService { } var request = URLRequest(url: url) + API.addIOSTokenHeader(to: &request) request.httpMethod = "PATCH" request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization") request.setValue("application/json", forHTTPHeaderField: "Content-Type") @@ -210,6 +214,7 @@ class AuthService { } var request = URLRequest(url: url) + API.addIOSTokenHeader(to: &request) request.httpMethod = "GET" let (_, response) = try await URLSession.shared.data(for: request) @@ -237,6 +242,7 @@ class AuthService { } var request = URLRequest(url: url) + API.addIOSTokenHeader(to: &request) request.httpMethod = "GET" request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization") @@ -270,6 +276,7 @@ class AuthService { } var request = URLRequest(url: url) + API.addIOSTokenHeader(to: &request) request.httpMethod = "PATCH" request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization") request.setValue("application/json", forHTTPHeaderField: "Content-Type") @@ -299,6 +306,7 @@ class AuthService { } var request = URLRequest(url: url) + API.addIOSTokenHeader(to: &request) request.httpMethod = "GET" let (data, response) = try await URLSession.shared.data(for: request) @@ -319,6 +327,7 @@ class AuthService { } var request = URLRequest(url: url) + API.addIOSTokenHeader(to: &request) request.httpMethod = "GET" let (data, response) = try await URLSession.shared.data(for: request) diff --git a/apps/ios/Plotwist/Plotwist/Services/ReviewService.swift b/apps/ios/Plotwist/Plotwist/Services/ReviewService.swift index 13398eba6..bfc941ce5 100644 --- a/apps/ios/Plotwist/Plotwist/Services/ReviewService.swift +++ b/apps/ios/Plotwist/Plotwist/Services/ReviewService.swift @@ -68,6 +68,7 @@ class ReviewService { } var request = URLRequest(url: url) + API.addIOSTokenHeader(to: &request) request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization") let (data, response) = try await URLSession.shared.data(for: request) @@ -105,6 +106,7 @@ class ReviewService { } var request = URLRequest(url: url) + API.addIOSTokenHeader(to: &request) request.httpMethod = "POST" request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization") request.setValue("application/json", forHTTPHeaderField: "Content-Type") @@ -161,6 +163,7 @@ class ReviewService { } var request = URLRequest(url: url) + API.addIOSTokenHeader(to: &request) request.httpMethod = "PUT" request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization") request.setValue("application/json", forHTTPHeaderField: "Content-Type") @@ -203,6 +206,7 @@ class ReviewService { } var request = URLRequest(url: url) + API.addIOSTokenHeader(to: &request) request.httpMethod = "DELETE" request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization") @@ -258,11 +262,10 @@ class ReviewService { } var request = URLRequest(url: url) - + API.addIOSTokenHeader(to: &request) if let token = UserDefaults.standard.string(forKey: "token") { request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization") } - let (data, response) = try await URLSession.shared.data(for: request) guard let http = response as? HTTPURLResponse, http.statusCode == 200 else { @@ -299,7 +302,7 @@ class ReviewService { } var request = URLRequest(url: url) - + API.addIOSTokenHeader(to: &request) // Add token if available (optional auth) if let token = UserDefaults.standard.string(forKey: "token") { request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization") diff --git a/apps/ios/Plotwist/Plotwist/Services/SocialAuthService.swift b/apps/ios/Plotwist/Plotwist/Services/SocialAuthService.swift index 15e2d3a1f..5fbac5eeb 100644 --- a/apps/ios/Plotwist/Plotwist/Services/SocialAuthService.swift +++ b/apps/ios/Plotwist/Plotwist/Services/SocialAuthService.swift @@ -185,6 +185,7 @@ class SocialAuthService { } var request = URLRequest(url: url) + API.addIOSTokenHeader(to: &request) request.httpMethod = "POST" request.setValue("application/json", forHTTPHeaderField: "Content-Type") request.httpBody = try JSONEncoder().encode(body) diff --git a/apps/ios/Plotwist/Plotwist/Services/TMDBService.swift b/apps/ios/Plotwist/Plotwist/Services/TMDBService.swift index bcaa8bd97..9a28fb595 100644 --- a/apps/ios/Plotwist/Plotwist/Services/TMDBService.swift +++ b/apps/ios/Plotwist/Plotwist/Services/TMDBService.swift @@ -28,6 +28,7 @@ class TMDBService { } var request = URLRequest(url: url) + API.addIOSTokenHeader(to: &request) request.setValue("application/json", forHTTPHeaderField: "Accept") let (data, response) = try await URLSession.shared.data(for: request) @@ -48,6 +49,7 @@ class TMDBService { } var request = URLRequest(url: url) + API.addIOSTokenHeader(to: &request) request.setValue("application/json", forHTTPHeaderField: "Accept") let (data, response) = try await URLSession.shared.data(for: request) @@ -76,6 +78,7 @@ class TMDBService { } var request = URLRequest(url: url) + API.addIOSTokenHeader(to: &request) request.setValue("application/json", forHTTPHeaderField: "Accept") let (data, response) = try await URLSession.shared.data(for: request) @@ -103,6 +106,7 @@ class TMDBService { } var request = URLRequest(url: url) + API.addIOSTokenHeader(to: &request) request.setValue("application/json", forHTTPHeaderField: "Accept") let (data, response) = try await URLSession.shared.data(for: request) @@ -130,6 +134,7 @@ class TMDBService { } var request = URLRequest(url: url) + API.addIOSTokenHeader(to: &request) request.setValue("application/json", forHTTPHeaderField: "Accept") let (data, response) = try await URLSession.shared.data(for: request) @@ -156,6 +161,7 @@ class TMDBService { } var request = URLRequest(url: url) + API.addIOSTokenHeader(to: &request) request.setValue("application/json", forHTTPHeaderField: "Accept") let (data, response) = try await URLSession.shared.data(for: request) @@ -184,6 +190,7 @@ class TMDBService { } var request = URLRequest(url: url) + API.addIOSTokenHeader(to: &request) request.setValue("application/json", forHTTPHeaderField: "Accept") let (data, response) = try await URLSession.shared.data(for: request) @@ -212,6 +219,7 @@ class TMDBService { } var request = URLRequest(url: url) + API.addIOSTokenHeader(to: &request) request.setValue("application/json", forHTTPHeaderField: "Accept") let (data, response) = try await URLSession.shared.data(for: request) @@ -240,6 +248,7 @@ class TMDBService { } var request = URLRequest(url: url) + API.addIOSTokenHeader(to: &request) request.setValue("application/json", forHTTPHeaderField: "Accept") let (data, response) = try await URLSession.shared.data(for: request) @@ -271,6 +280,7 @@ class TMDBService { } var request = URLRequest(url: url) + API.addIOSTokenHeader(to: &request) request.setValue("application/json", forHTTPHeaderField: "Accept") let (data, response) = try await URLSession.shared.data(for: request) @@ -302,6 +312,7 @@ class TMDBService { } var request = URLRequest(url: url) + API.addIOSTokenHeader(to: &request) request.setValue("application/json", forHTTPHeaderField: "Accept") let (data, response) = try await URLSession.shared.data(for: request) @@ -333,6 +344,7 @@ class TMDBService { } var request = URLRequest(url: url) + API.addIOSTokenHeader(to: &request) request.setValue("application/json", forHTTPHeaderField: "Accept") let (data, response) = try await URLSession.shared.data(for: request) @@ -366,6 +378,7 @@ class TMDBService { } var request = URLRequest(url: url) + API.addIOSTokenHeader(to: &request) request.setValue("application/json", forHTTPHeaderField: "Accept") let (data, response) = try await URLSession.shared.data(for: request) @@ -407,6 +420,7 @@ class TMDBService { } var request = URLRequest(url: url) + API.addIOSTokenHeader(to: &request) request.setValue("application/json", forHTTPHeaderField: "Accept") let (data, response) = try await URLSession.shared.data(for: request) @@ -448,6 +462,7 @@ class TMDBService { } var request = URLRequest(url: url) + API.addIOSTokenHeader(to: &request) request.setValue("application/json", forHTTPHeaderField: "Accept") let (data, response) = try await URLSession.shared.data(for: request) @@ -485,6 +500,7 @@ class TMDBService { } var request = URLRequest(url: url) + API.addIOSTokenHeader(to: &request) request.setValue("application/json", forHTTPHeaderField: "Accept") let (data, response) = try await URLSession.shared.data(for: request) @@ -522,6 +538,7 @@ class TMDBService { } var request = URLRequest(url: url) + API.addIOSTokenHeader(to: &request) request.setValue("application/json", forHTTPHeaderField: "Accept") let (data, response) = try await URLSession.shared.data(for: request) @@ -559,6 +576,7 @@ class TMDBService { } var request = URLRequest(url: url) + API.addIOSTokenHeader(to: &request) request.setValue("application/json", forHTTPHeaderField: "Accept") let (data, response) = try await URLSession.shared.data(for: request) @@ -591,6 +609,7 @@ class TMDBService { } var request = URLRequest(url: url) + API.addIOSTokenHeader(to: &request) request.setValue("application/json", forHTTPHeaderField: "Accept") let (data, response) = try await URLSession.shared.data(for: request) @@ -616,6 +635,7 @@ class TMDBService { } var request = URLRequest(url: url) + API.addIOSTokenHeader(to: &request) request.setValue("application/json", forHTTPHeaderField: "Accept") let (data, response) = try await URLSession.shared.data(for: request) @@ -636,6 +656,7 @@ class TMDBService { } var request = URLRequest(url: url) + API.addIOSTokenHeader(to: &request) request.setValue("application/json", forHTTPHeaderField: "Accept") let (data, response) = try await URLSession.shared.data(for: request) @@ -661,6 +682,7 @@ class TMDBService { } var request = URLRequest(url: url) + API.addIOSTokenHeader(to: &request) request.setValue("application/json", forHTTPHeaderField: "Accept") let (data, response) = try await URLSession.shared.data(for: request) @@ -682,6 +704,7 @@ class TMDBService { } var request = URLRequest(url: url) + API.addIOSTokenHeader(to: &request) request.setValue("application/json", forHTTPHeaderField: "Accept") let (data, response) = try await URLSession.shared.data(for: request) @@ -703,6 +726,7 @@ class TMDBService { } var request = URLRequest(url: url) + API.addIOSTokenHeader(to: &request) request.setValue("application/json", forHTTPHeaderField: "Accept") let (data, response) = try await URLSession.shared.data(for: request) @@ -723,6 +747,7 @@ class TMDBService { } var request = URLRequest(url: url) + API.addIOSTokenHeader(to: &request) request.setValue("application/json", forHTTPHeaderField: "Accept") let (data, response) = try await URLSession.shared.data(for: request) @@ -746,6 +771,7 @@ class TMDBService { } var request = URLRequest(url: url) + API.addIOSTokenHeader(to: &request) request.setValue("application/json", forHTTPHeaderField: "Accept") let (data, response) = try await URLSession.shared.data(for: request) @@ -767,6 +793,7 @@ class TMDBService { } var request = URLRequest(url: url) + API.addIOSTokenHeader(to: &request) request.setValue("application/json", forHTTPHeaderField: "Accept") let (data, response) = try await URLSession.shared.data(for: request) @@ -818,6 +845,7 @@ class TMDBService { } var request = URLRequest(url: url) + API.addIOSTokenHeader(to: &request) request.setValue("application/json", forHTTPHeaderField: "Accept") let (data, response) = try await URLSession.shared.data(for: request) @@ -839,6 +867,7 @@ class TMDBService { } var request = URLRequest(url: url) + API.addIOSTokenHeader(to: &request) request.setValue("application/json", forHTTPHeaderField: "Accept") let (data, response) = try await URLSession.shared.data(for: request) @@ -869,6 +898,7 @@ class TMDBService { } var request = URLRequest(url: url) + API.addIOSTokenHeader(to: &request) request.setValue("application/json", forHTTPHeaderField: "Accept") let (data, response) = try await URLSession.shared.data(for: request) diff --git a/apps/ios/Plotwist/Plotwist/Services/UserEpisodeService.swift b/apps/ios/Plotwist/Plotwist/Services/UserEpisodeService.swift index 18ecf4283..bf517555c 100644 --- a/apps/ios/Plotwist/Plotwist/Services/UserEpisodeService.swift +++ b/apps/ios/Plotwist/Plotwist/Services/UserEpisodeService.swift @@ -64,6 +64,7 @@ class UserEpisodeService { } var request = URLRequest(url: url) + API.addIOSTokenHeader(to: &request) request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization") let (data, response) = try await URLSession.shared.data(for: request) @@ -96,6 +97,7 @@ class UserEpisodeService { } var request = URLRequest(url: url) + API.addIOSTokenHeader(to: &request) request.httpMethod = "POST" request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization") request.setValue("application/json", forHTTPHeaderField: "Content-Type") @@ -158,6 +160,7 @@ class UserEpisodeService { } var request = URLRequest(url: url) + API.addIOSTokenHeader(to: &request) request.httpMethod = "DELETE" request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization") request.setValue("application/json", forHTTPHeaderField: "Content-Type") diff --git a/apps/ios/Plotwist/Plotwist/Services/UserItemService.swift b/apps/ios/Plotwist/Plotwist/Services/UserItemService.swift index 489f3616a..5769239a3 100644 --- a/apps/ios/Plotwist/Plotwist/Services/UserItemService.swift +++ b/apps/ios/Plotwist/Plotwist/Services/UserItemService.swift @@ -39,6 +39,7 @@ class UserItemService { } var request = URLRequest(url: url) + API.addIOSTokenHeader(to: &request) request.setValue("application/json", forHTTPHeaderField: "Accept") let (data, response) = try await URLSession.shared.data(for: request) @@ -61,6 +62,7 @@ class UserItemService { } var request = URLRequest(url: url) + API.addIOSTokenHeader(to: &request) request.setValue("application/json", forHTTPHeaderField: "Accept") let (data, response) = try await URLSession.shared.data(for: request) @@ -92,6 +94,7 @@ class UserItemService { } var request = URLRequest(url: url) + API.addIOSTokenHeader(to: &request) request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization") let (data, response) = try await URLSession.shared.data(for: request) @@ -132,6 +135,7 @@ class UserItemService { } var request = URLRequest(url: url) + API.addIOSTokenHeader(to: &request) request.httpMethod = "PUT" request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization") request.setValue("application/json", forHTTPHeaderField: "Content-Type") @@ -175,6 +179,7 @@ class UserItemService { } var request = URLRequest(url: url) + API.addIOSTokenHeader(to: &request) request.httpMethod = "DELETE" request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization") @@ -201,6 +206,7 @@ class UserItemService { } var request = URLRequest(url: url) + API.addIOSTokenHeader(to: &request) request.httpMethod = "PUT" request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization") request.setValue("application/json", forHTTPHeaderField: "Content-Type") @@ -232,6 +238,7 @@ class UserItemService { } var request = URLRequest(url: url) + API.addIOSTokenHeader(to: &request) request.httpMethod = "POST" request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization") request.setValue("application/json", forHTTPHeaderField: "Content-Type") @@ -271,6 +278,7 @@ class UserItemService { } var request = URLRequest(url: url) + API.addIOSTokenHeader(to: &request) request.httpMethod = "DELETE" request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization") @@ -294,6 +302,7 @@ class UserItemService { } var request = URLRequest(url: url) + API.addIOSTokenHeader(to: &request) request.httpMethod = "PUT" request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization") request.setValue("application/json", forHTTPHeaderField: "Content-Type") diff --git a/apps/ios/Plotwist/Plotwist/Services/UserStatsService.swift b/apps/ios/Plotwist/Plotwist/Services/UserStatsService.swift index 1a88db02d..970b74f8b 100644 --- a/apps/ios/Plotwist/Plotwist/Services/UserStatsService.swift +++ b/apps/ios/Plotwist/Plotwist/Services/UserStatsService.swift @@ -16,6 +16,7 @@ class UserStatsService { } var request = URLRequest(url: url) + API.addIOSTokenHeader(to: &request) request.setValue("application/json", forHTTPHeaderField: "Accept") let (data, response) = try await URLSession.shared.data(for: request) @@ -38,6 +39,7 @@ class UserStatsService { } var request = URLRequest(url: url) + API.addIOSTokenHeader(to: &request) request.setValue("application/json", forHTTPHeaderField: "Accept") let (data, response) = try await URLSession.shared.data(for: request) @@ -61,6 +63,7 @@ class UserStatsService { } var request = URLRequest(url: url) + API.addIOSTokenHeader(to: &request) request.setValue("application/json", forHTTPHeaderField: "Accept") let (data, response) = try await URLSession.shared.data(for: request) @@ -82,6 +85,7 @@ class UserStatsService { } var request = URLRequest(url: url) + API.addIOSTokenHeader(to: &request) request.setValue("application/json", forHTTPHeaderField: "Accept") let (data, response) = try await URLSession.shared.data(for: request) @@ -102,6 +106,7 @@ class UserStatsService { } var request = URLRequest(url: url) + API.addIOSTokenHeader(to: &request) request.setValue("application/json", forHTTPHeaderField: "Accept") let (data, response) = try await URLSession.shared.data(for: request) diff --git a/apps/ios/Plotwist/Plotwist/Utils/Constants.swift b/apps/ios/Plotwist/Plotwist/Utils/Constants.swift index d0b842632..260ca9c31 100644 --- a/apps/ios/Plotwist/Plotwist/Utils/Constants.swift +++ b/apps/ios/Plotwist/Plotwist/Utils/Constants.swift @@ -7,4 +7,15 @@ import Foundation enum API { static let baseURL = "https://plotwist-api-production.up.railway.app" + + /// Token for production client guard. Set in Info.plist key `IOS_TOKEN` (e.g. via xcconfig). + static var iosToken: String? { + Bundle.main.object(forInfoDictionaryKey: "IOS_TOKEN") as? String + } + + static func addIOSTokenHeader(to request: inout URLRequest) { + if let token = iosToken, !token.isEmpty { + request.setValue(token, forHTTPHeaderField: "X-IOS-Token") + } + } } From f3f4d2c673210f47b16aaa8efea5c44d9daee911 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Pedro=20Alves?= Date: Sun, 15 Feb 2026 15:18:03 -0300 Subject: [PATCH 4/7] style: apply lint --- .../db/repositories/user-item-repository.ts | 5 +-- .../user-items/get-user-items-count.ts | 4 +- .../user-stats/get-user-total-hours.ts | 4 +- apps/backend/src/http/client-guard.ts | 5 ++- .../http/controllers/user-items-controller.ts | 15 ++++--- apps/backend/src/http/rate-limit.ts | 5 ++- apps/backend/src/http/routes/watch-entries.ts | 2 +- .../AccentColor.colorset/Contents.json | 10 ++--- .../AppIcon.appiconset/Contents.json | 44 +++++++++---------- .../Plotwist/Assets.xcassets/Contents.json | 6 +-- .../FilmStrip.imageset/Contents.json | 28 ++++++------ .../tmdb-logo.imageset/Contents.json | 18 ++++---- apps/web/next-env.d.ts | 2 +- .../_components/user-activities.tsx | 12 ++--- 14 files changed, 87 insertions(+), 73 deletions(-) diff --git a/apps/backend/src/db/repositories/user-item-repository.ts b/apps/backend/src/db/repositories/user-item-repository.ts index 4e24c9af8..3bcec6dac 100644 --- a/apps/backend/src/db/repositories/user-item-repository.ts +++ b/apps/backend/src/db/repositories/user-item-repository.ts @@ -182,10 +182,7 @@ export async function reorderUserItems( .update(schema.userItems) .set({ position: index }) .where( - and( - eq(schema.userItems.id, id), - eq(schema.userItems.userId, userId) - ) + and(eq(schema.userItems.id, id), eq(schema.userItems.userId, userId)) ) ) diff --git a/apps/backend/src/domain/services/user-items/get-user-items-count.ts b/apps/backend/src/domain/services/user-items/get-user-items-count.ts index 417524c41..bfea8e9b9 100644 --- a/apps/backend/src/domain/services/user-items/get-user-items-count.ts +++ b/apps/backend/src/domain/services/user-items/get-user-items-count.ts @@ -4,7 +4,9 @@ type GetUserItemsCountInput = { userId: string } -export async function getUserItemsCountService({ userId }: GetUserItemsCountInput) { +export async function getUserItemsCountService({ + userId, +}: GetUserItemsCountInput) { const count = await selectUserItemsCount(userId) return { diff --git a/apps/backend/src/domain/services/user-stats/get-user-total-hours.ts b/apps/backend/src/domain/services/user-stats/get-user-total-hours.ts index e495218e4..811a4ced4 100644 --- a/apps/backend/src/domain/services/user-stats/get-user-total-hours.ts +++ b/apps/backend/src/domain/services/user-stats/get-user-total-hours.ts @@ -34,7 +34,9 @@ async function getMovieRuntimes( watchedItems: Awaited>, redis: FastifyRedis ) { - const movies = watchedItems.userItems.filter(item => item.mediaType === 'MOVIE') + const movies = watchedItems.userItems.filter( + item => item.mediaType === 'MOVIE' + ) return processInBatches(movies, async item => { const { runtime } = await getTMDBMovieService(redis, { diff --git a/apps/backend/src/http/client-guard.ts b/apps/backend/src/http/client-guard.ts index caec0daf0..e43a8f8d1 100644 --- a/apps/backend/src/http/client-guard.ts +++ b/apps/backend/src/http/client-guard.ts @@ -16,7 +16,10 @@ function timingSafeCompare(a: string, b: string): boolean { return bufA.length === bufB.length && timingSafeEqual(bufA, bufB) } -function allowedOrigin(origin: string | undefined, referer: string | undefined): boolean { +function allowedOrigin( + origin: string | undefined, + referer: string | undefined +): boolean { const allowed = [...new Set([config.app.CLIENT_URL, ...ALLOWED_ORIGINS])] return ( (typeof origin === 'string' && allowed.includes(origin)) || diff --git a/apps/backend/src/http/controllers/user-items-controller.ts b/apps/backend/src/http/controllers/user-items-controller.ts index adbd2679e..1e64e1120 100644 --- a/apps/backend/src/http/controllers/user-items-controller.ts +++ b/apps/backend/src/http/controllers/user-items-controller.ts @@ -15,9 +15,9 @@ import { getAllUserItemsService } from '@/domain/services/user-items/get-all-use import { getUserItemService } from '@/domain/services/user-items/get-user-item' import { getUserItemsService } from '@/domain/services/user-items/get-user-items' import { getUserItemsCountService } from '@/domain/services/user-items/get-user-items-count' +import { reorderUserItemsService } from '@/domain/services/user-items/reorder-user-items' import { upsertUserItemService } from '@/domain/services/user-items/upsert-user-item' import { invalidateUserStatsCache } from '@/domain/services/user-stats/cache-utils' -import { reorderUserItemsService } from '@/domain/services/user-items/reorder-user-items' import { deleteUserItemParamsSchema, getAllUserItemsQuerySchema, @@ -91,18 +91,23 @@ export async function upsertUserItemController( }, }) - // Invalidate user stats cache since item status changed await invalidateUserStatsCache(redis, request.user.id) - // Fetch the user item via Drizzle ORM to ensure consistent format with getUserItem const result = await getUserItemService({ mediaType, tmdbId, userId: request.user.id, }) - // Ensure dates are serialized as ISO strings - const userItem = result.userItem! + const userItem = result.userItem + if (!userItem) { + return reply.status(500).send({ + statusCode: 500, + error: 'Internal Server Error', + message: 'User item could not be retrieved after upsert.', + }) + } + return reply.status(201).send({ userItem: { id: userItem.id, diff --git a/apps/backend/src/http/rate-limit.ts b/apps/backend/src/http/rate-limit.ts index 2e6d9b1bf..678d6c91b 100644 --- a/apps/backend/src/http/rate-limit.ts +++ b/apps/backend/src/http/rate-limit.ts @@ -9,7 +9,10 @@ export function registerRateLimit(app: FastifyInstance) { max: config.app.RATE_LIMIT_MAX, timeWindow: config.app.RATE_LIMIT_TIME_WINDOW_MS, skipOnError: true, - errorResponseBuilder: (_request: unknown, context: { after: string }) => ({ + errorResponseBuilder: ( + _request: unknown, + context: { after: string } + ) => ({ statusCode: 429, error: 'Too Many Requests', message: `Rate limit exceeded, retry in ${Math.ceil(Number(context.after) / 1000)} seconds`, diff --git a/apps/backend/src/http/routes/watch-entries.ts b/apps/backend/src/http/routes/watch-entries.ts index 2046845f1..4e8130cfb 100644 --- a/apps/backend/src/http/routes/watch-entries.ts +++ b/apps/backend/src/http/routes/watch-entries.ts @@ -1,12 +1,12 @@ import type { FastifyInstance } from 'fastify' import type { ZodTypeProvider } from 'fastify-type-provider-zod' -import { verifyJwt } from '../middlewares/verify-jwt' import { createWatchEntryController, deleteWatchEntryController, getWatchEntriesController, updateWatchEntryController, } from '../controllers/watch-entries-controller' +import { verifyJwt } from '../middlewares/verify-jwt' import { createWatchEntryBodySchema, createWatchEntryResponseSchema, diff --git a/apps/ios/Plotwist/Plotwist/Assets.xcassets/AccentColor.colorset/Contents.json b/apps/ios/Plotwist/Plotwist/Assets.xcassets/AccentColor.colorset/Contents.json index eb8789700..0afb3cf0e 100644 --- a/apps/ios/Plotwist/Plotwist/Assets.xcassets/AccentColor.colorset/Contents.json +++ b/apps/ios/Plotwist/Plotwist/Assets.xcassets/AccentColor.colorset/Contents.json @@ -1,11 +1,11 @@ { - "colors" : [ + "colors": [ { - "idiom" : "universal" + "idiom": "universal" } ], - "info" : { - "author" : "xcode", - "version" : 1 + "info": { + "author": "xcode", + "version": 1 } } diff --git a/apps/ios/Plotwist/Plotwist/Assets.xcassets/AppIcon.appiconset/Contents.json b/apps/ios/Plotwist/Plotwist/Assets.xcassets/AppIcon.appiconset/Contents.json index 87d401528..e59070cb9 100644 --- a/apps/ios/Plotwist/Plotwist/Assets.xcassets/AppIcon.appiconset/Contents.json +++ b/apps/ios/Plotwist/Plotwist/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -1,38 +1,38 @@ { - "images" : [ + "images": [ { - "filename" : "AppIcon.png", - "idiom" : "universal", - "platform" : "ios", - "size" : "1024x1024" + "filename": "AppIcon.png", + "idiom": "universal", + "platform": "ios", + "size": "1024x1024" }, { - "appearances" : [ + "appearances": [ { - "appearance" : "luminosity", - "value" : "dark" + "appearance": "luminosity", + "value": "dark" } ], - "filename" : "AppIcon.png", - "idiom" : "universal", - "platform" : "ios", - "size" : "1024x1024" + "filename": "AppIcon.png", + "idiom": "universal", + "platform": "ios", + "size": "1024x1024" }, { - "appearances" : [ + "appearances": [ { - "appearance" : "luminosity", - "value" : "tinted" + "appearance": "luminosity", + "value": "tinted" } ], - "filename" : "AppIcon.png", - "idiom" : "universal", - "platform" : "ios", - "size" : "1024x1024" + "filename": "AppIcon.png", + "idiom": "universal", + "platform": "ios", + "size": "1024x1024" } ], - "info" : { - "author" : "xcode", - "version" : 1 + "info": { + "author": "xcode", + "version": 1 } } diff --git a/apps/ios/Plotwist/Plotwist/Assets.xcassets/Contents.json b/apps/ios/Plotwist/Plotwist/Assets.xcassets/Contents.json index 73c00596a..74d6a722c 100644 --- a/apps/ios/Plotwist/Plotwist/Assets.xcassets/Contents.json +++ b/apps/ios/Plotwist/Plotwist/Assets.xcassets/Contents.json @@ -1,6 +1,6 @@ { - "info" : { - "author" : "xcode", - "version" : 1 + "info": { + "author": "xcode", + "version": 1 } } diff --git a/apps/ios/Plotwist/Plotwist/Assets.xcassets/FilmStrip.imageset/Contents.json b/apps/ios/Plotwist/Plotwist/Assets.xcassets/FilmStrip.imageset/Contents.json index 67f6f1003..182574113 100644 --- a/apps/ios/Plotwist/Plotwist/Assets.xcassets/FilmStrip.imageset/Contents.json +++ b/apps/ios/Plotwist/Plotwist/Assets.xcassets/FilmStrip.imageset/Contents.json @@ -1,25 +1,25 @@ { - "images" : [ + "images": [ { - "idiom" : "universal", - "scale" : "1x" + "idiom": "universal", + "scale": "1x" }, { - "idiom" : "universal", - "scale" : "2x" + "idiom": "universal", + "scale": "2x" }, { - "filename" : "film-strip.png", - "idiom" : "universal", - "scale" : "3x" + "filename": "film-strip.png", + "idiom": "universal", + "scale": "3x" } ], - "info" : { - "author" : "xcode", - "version" : 1 + "info": { + "author": "xcode", + "version": 1 }, - "properties" : { - "preserves-vector-representation" : true, - "template-rendering-intent" : "original" + "properties": { + "preserves-vector-representation": true, + "template-rendering-intent": "original" } } diff --git a/apps/ios/Plotwist/Plotwist/Assets.xcassets/tmdb-logo.imageset/Contents.json b/apps/ios/Plotwist/Plotwist/Assets.xcassets/tmdb-logo.imageset/Contents.json index 8fc747dab..bfbca785b 100644 --- a/apps/ios/Plotwist/Plotwist/Assets.xcassets/tmdb-logo.imageset/Contents.json +++ b/apps/ios/Plotwist/Plotwist/Assets.xcassets/tmdb-logo.imageset/Contents.json @@ -1,16 +1,16 @@ { - "images" : [ + "images": [ { - "filename" : "tmdb.svg", - "idiom" : "universal" + "filename": "tmdb.svg", + "idiom": "universal" } ], - "info" : { - "author" : "xcode", - "version" : 1 + "info": { + "author": "xcode", + "version": 1 }, - "properties" : { - "preserves-vector-representation" : true, - "template-rendering-intent" : "original" + "properties": { + "preserves-vector-representation": true, + "template-rendering-intent": "original" } } diff --git a/apps/web/next-env.d.ts b/apps/web/next-env.d.ts index c4b7818fb..a3e4680c7 100644 --- a/apps/web/next-env.d.ts +++ b/apps/web/next-env.d.ts @@ -1,6 +1,6 @@ /// /// -import "./.next/dev/types/routes.d.ts"; +import './.next/dev/types/routes.d.ts' // NOTE: This file should not be edited // see https://nextjs.org/docs/app/api-reference/config/typescript for more information. diff --git a/apps/web/src/app/[lang]/[username]/_components/user-activities.tsx b/apps/web/src/app/[lang]/[username]/_components/user-activities.tsx index 2a51e09c9..c8bdfdb5d 100644 --- a/apps/web/src/app/[lang]/[username]/_components/user-activities.tsx +++ b/apps/web/src/app/[lang]/[username]/_components/user-activities.tsx @@ -299,11 +299,13 @@ export function WatchEpisodeActivity({
    - {episodes.map((episode: { seasonNumber: number; episodeNumber: number }) => ( -
  • - • S{episode.seasonNumber}, EP{episode.episodeNumber} -
  • - ))} + {episodes.map( + (episode: { seasonNumber: number; episodeNumber: number }) => ( +
  • + • S{episode.seasonNumber}, EP{episode.episodeNumber} +
  • + ) + )}
From f31900ee285bc2b04072e320366ceff637337628 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Pedro=20Alves?= Date: Sun, 15 Feb 2026 21:58:33 -0300 Subject: [PATCH 5/7] refactor: remove iOS token header handling from network requests --- .../Plotwist.xcodeproj/project.pbxproj | 4 --- .../Plotwist/Services/AnalyticsService.swift | 1 - .../Plotwist/Services/AuthService.swift | 9 ------ .../Plotwist/Services/ReviewService.swift | 6 ---- .../Plotwist/Services/SocialAuthService.swift | 1 - .../Plotwist/Services/TMDBService.swift | 30 ------------------- .../Services/UserEpisodeService.swift | 3 -- .../Plotwist/Services/UserItemService.swift | 9 ------ .../Plotwist/Services/UserStatsService.swift | 5 ---- .../Plotwist/Plotwist/Utils/Constants.swift | 11 ------- 10 files changed, 79 deletions(-) diff --git a/apps/ios/Plotwist/Plotwist.xcodeproj/project.pbxproj b/apps/ios/Plotwist/Plotwist.xcodeproj/project.pbxproj index bb904b1a9..d133f0198 100644 --- a/apps/ios/Plotwist/Plotwist.xcodeproj/project.pbxproj +++ b/apps/ios/Plotwist/Plotwist.xcodeproj/project.pbxproj @@ -272,8 +272,6 @@ DEVELOPMENT_TEAM = 54XPVTP5PA; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; - IOS_TOKEN = ""; - INFOPLIST_KEY_IOS_TOKEN = "$(IOS_TOKEN)"; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchScreen_Generation = YES; @@ -303,8 +301,6 @@ DEVELOPMENT_TEAM = 54XPVTP5PA; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; - IOS_TOKEN = ""; - INFOPLIST_KEY_IOS_TOKEN = "$(IOS_TOKEN)"; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchScreen_Generation = YES; diff --git a/apps/ios/Plotwist/Plotwist/Services/AnalyticsService.swift b/apps/ios/Plotwist/Plotwist/Services/AnalyticsService.swift index 8d83f8022..be52c7b52 100644 --- a/apps/ios/Plotwist/Plotwist/Services/AnalyticsService.swift +++ b/apps/ios/Plotwist/Plotwist/Services/AnalyticsService.swift @@ -215,7 +215,6 @@ class AnalyticsService { else { return } var request = URLRequest(url: url) - API.addIOSTokenHeader(to: &request) request.httpMethod = "POST" request.setValue("application/json", forHTTPHeaderField: "Content-Type") diff --git a/apps/ios/Plotwist/Plotwist/Services/AuthService.swift b/apps/ios/Plotwist/Plotwist/Services/AuthService.swift index c2a21c5e2..5ae5b22ba 100644 --- a/apps/ios/Plotwist/Plotwist/Services/AuthService.swift +++ b/apps/ios/Plotwist/Plotwist/Services/AuthService.swift @@ -29,7 +29,6 @@ class AuthService { } var request = URLRequest(url: url) - API.addIOSTokenHeader(to: &request) request.httpMethod = "POST" request.setValue("application/json", forHTTPHeaderField: "Content-Type") request.httpBody = try JSONEncoder().encode(["login": login, "password": password]) @@ -67,7 +66,6 @@ class AuthService { } var request = URLRequest(url: url) - API.addIOSTokenHeader(to: &request) request.httpMethod = "POST" request.setValue("application/json", forHTTPHeaderField: "Content-Type") @@ -141,7 +139,6 @@ class AuthService { } var request = URLRequest(url: url) - API.addIOSTokenHeader(to: &request) request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization") let (data, response) = try await URLSession.shared.data(for: request) @@ -168,7 +165,6 @@ class AuthService { } var request = URLRequest(url: url) - API.addIOSTokenHeader(to: &request) request.httpMethod = "PATCH" request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization") request.setValue("application/json", forHTTPHeaderField: "Content-Type") @@ -214,7 +210,6 @@ class AuthService { } var request = URLRequest(url: url) - API.addIOSTokenHeader(to: &request) request.httpMethod = "GET" let (_, response) = try await URLSession.shared.data(for: request) @@ -242,7 +237,6 @@ class AuthService { } var request = URLRequest(url: url) - API.addIOSTokenHeader(to: &request) request.httpMethod = "GET" request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization") @@ -276,7 +270,6 @@ class AuthService { } var request = URLRequest(url: url) - API.addIOSTokenHeader(to: &request) request.httpMethod = "PATCH" request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization") request.setValue("application/json", forHTTPHeaderField: "Content-Type") @@ -306,7 +299,6 @@ class AuthService { } var request = URLRequest(url: url) - API.addIOSTokenHeader(to: &request) request.httpMethod = "GET" let (data, response) = try await URLSession.shared.data(for: request) @@ -327,7 +319,6 @@ class AuthService { } var request = URLRequest(url: url) - API.addIOSTokenHeader(to: &request) request.httpMethod = "GET" let (data, response) = try await URLSession.shared.data(for: request) diff --git a/apps/ios/Plotwist/Plotwist/Services/ReviewService.swift b/apps/ios/Plotwist/Plotwist/Services/ReviewService.swift index bfc941ce5..226d3806c 100644 --- a/apps/ios/Plotwist/Plotwist/Services/ReviewService.swift +++ b/apps/ios/Plotwist/Plotwist/Services/ReviewService.swift @@ -68,7 +68,6 @@ class ReviewService { } var request = URLRequest(url: url) - API.addIOSTokenHeader(to: &request) request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization") let (data, response) = try await URLSession.shared.data(for: request) @@ -106,7 +105,6 @@ class ReviewService { } var request = URLRequest(url: url) - API.addIOSTokenHeader(to: &request) request.httpMethod = "POST" request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization") request.setValue("application/json", forHTTPHeaderField: "Content-Type") @@ -163,7 +161,6 @@ class ReviewService { } var request = URLRequest(url: url) - API.addIOSTokenHeader(to: &request) request.httpMethod = "PUT" request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization") request.setValue("application/json", forHTTPHeaderField: "Content-Type") @@ -206,7 +203,6 @@ class ReviewService { } var request = URLRequest(url: url) - API.addIOSTokenHeader(to: &request) request.httpMethod = "DELETE" request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization") @@ -262,7 +258,6 @@ class ReviewService { } var request = URLRequest(url: url) - API.addIOSTokenHeader(to: &request) if let token = UserDefaults.standard.string(forKey: "token") { request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization") } @@ -302,7 +297,6 @@ class ReviewService { } var request = URLRequest(url: url) - API.addIOSTokenHeader(to: &request) // Add token if available (optional auth) if let token = UserDefaults.standard.string(forKey: "token") { request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization") diff --git a/apps/ios/Plotwist/Plotwist/Services/SocialAuthService.swift b/apps/ios/Plotwist/Plotwist/Services/SocialAuthService.swift index 5fbac5eeb..15e2d3a1f 100644 --- a/apps/ios/Plotwist/Plotwist/Services/SocialAuthService.swift +++ b/apps/ios/Plotwist/Plotwist/Services/SocialAuthService.swift @@ -185,7 +185,6 @@ class SocialAuthService { } var request = URLRequest(url: url) - API.addIOSTokenHeader(to: &request) request.httpMethod = "POST" request.setValue("application/json", forHTTPHeaderField: "Content-Type") request.httpBody = try JSONEncoder().encode(body) diff --git a/apps/ios/Plotwist/Plotwist/Services/TMDBService.swift b/apps/ios/Plotwist/Plotwist/Services/TMDBService.swift index 9a28fb595..bcaa8bd97 100644 --- a/apps/ios/Plotwist/Plotwist/Services/TMDBService.swift +++ b/apps/ios/Plotwist/Plotwist/Services/TMDBService.swift @@ -28,7 +28,6 @@ class TMDBService { } var request = URLRequest(url: url) - API.addIOSTokenHeader(to: &request) request.setValue("application/json", forHTTPHeaderField: "Accept") let (data, response) = try await URLSession.shared.data(for: request) @@ -49,7 +48,6 @@ class TMDBService { } var request = URLRequest(url: url) - API.addIOSTokenHeader(to: &request) request.setValue("application/json", forHTTPHeaderField: "Accept") let (data, response) = try await URLSession.shared.data(for: request) @@ -78,7 +76,6 @@ class TMDBService { } var request = URLRequest(url: url) - API.addIOSTokenHeader(to: &request) request.setValue("application/json", forHTTPHeaderField: "Accept") let (data, response) = try await URLSession.shared.data(for: request) @@ -106,7 +103,6 @@ class TMDBService { } var request = URLRequest(url: url) - API.addIOSTokenHeader(to: &request) request.setValue("application/json", forHTTPHeaderField: "Accept") let (data, response) = try await URLSession.shared.data(for: request) @@ -134,7 +130,6 @@ class TMDBService { } var request = URLRequest(url: url) - API.addIOSTokenHeader(to: &request) request.setValue("application/json", forHTTPHeaderField: "Accept") let (data, response) = try await URLSession.shared.data(for: request) @@ -161,7 +156,6 @@ class TMDBService { } var request = URLRequest(url: url) - API.addIOSTokenHeader(to: &request) request.setValue("application/json", forHTTPHeaderField: "Accept") let (data, response) = try await URLSession.shared.data(for: request) @@ -190,7 +184,6 @@ class TMDBService { } var request = URLRequest(url: url) - API.addIOSTokenHeader(to: &request) request.setValue("application/json", forHTTPHeaderField: "Accept") let (data, response) = try await URLSession.shared.data(for: request) @@ -219,7 +212,6 @@ class TMDBService { } var request = URLRequest(url: url) - API.addIOSTokenHeader(to: &request) request.setValue("application/json", forHTTPHeaderField: "Accept") let (data, response) = try await URLSession.shared.data(for: request) @@ -248,7 +240,6 @@ class TMDBService { } var request = URLRequest(url: url) - API.addIOSTokenHeader(to: &request) request.setValue("application/json", forHTTPHeaderField: "Accept") let (data, response) = try await URLSession.shared.data(for: request) @@ -280,7 +271,6 @@ class TMDBService { } var request = URLRequest(url: url) - API.addIOSTokenHeader(to: &request) request.setValue("application/json", forHTTPHeaderField: "Accept") let (data, response) = try await URLSession.shared.data(for: request) @@ -312,7 +302,6 @@ class TMDBService { } var request = URLRequest(url: url) - API.addIOSTokenHeader(to: &request) request.setValue("application/json", forHTTPHeaderField: "Accept") let (data, response) = try await URLSession.shared.data(for: request) @@ -344,7 +333,6 @@ class TMDBService { } var request = URLRequest(url: url) - API.addIOSTokenHeader(to: &request) request.setValue("application/json", forHTTPHeaderField: "Accept") let (data, response) = try await URLSession.shared.data(for: request) @@ -378,7 +366,6 @@ class TMDBService { } var request = URLRequest(url: url) - API.addIOSTokenHeader(to: &request) request.setValue("application/json", forHTTPHeaderField: "Accept") let (data, response) = try await URLSession.shared.data(for: request) @@ -420,7 +407,6 @@ class TMDBService { } var request = URLRequest(url: url) - API.addIOSTokenHeader(to: &request) request.setValue("application/json", forHTTPHeaderField: "Accept") let (data, response) = try await URLSession.shared.data(for: request) @@ -462,7 +448,6 @@ class TMDBService { } var request = URLRequest(url: url) - API.addIOSTokenHeader(to: &request) request.setValue("application/json", forHTTPHeaderField: "Accept") let (data, response) = try await URLSession.shared.data(for: request) @@ -500,7 +485,6 @@ class TMDBService { } var request = URLRequest(url: url) - API.addIOSTokenHeader(to: &request) request.setValue("application/json", forHTTPHeaderField: "Accept") let (data, response) = try await URLSession.shared.data(for: request) @@ -538,7 +522,6 @@ class TMDBService { } var request = URLRequest(url: url) - API.addIOSTokenHeader(to: &request) request.setValue("application/json", forHTTPHeaderField: "Accept") let (data, response) = try await URLSession.shared.data(for: request) @@ -576,7 +559,6 @@ class TMDBService { } var request = URLRequest(url: url) - API.addIOSTokenHeader(to: &request) request.setValue("application/json", forHTTPHeaderField: "Accept") let (data, response) = try await URLSession.shared.data(for: request) @@ -609,7 +591,6 @@ class TMDBService { } var request = URLRequest(url: url) - API.addIOSTokenHeader(to: &request) request.setValue("application/json", forHTTPHeaderField: "Accept") let (data, response) = try await URLSession.shared.data(for: request) @@ -635,7 +616,6 @@ class TMDBService { } var request = URLRequest(url: url) - API.addIOSTokenHeader(to: &request) request.setValue("application/json", forHTTPHeaderField: "Accept") let (data, response) = try await URLSession.shared.data(for: request) @@ -656,7 +636,6 @@ class TMDBService { } var request = URLRequest(url: url) - API.addIOSTokenHeader(to: &request) request.setValue("application/json", forHTTPHeaderField: "Accept") let (data, response) = try await URLSession.shared.data(for: request) @@ -682,7 +661,6 @@ class TMDBService { } var request = URLRequest(url: url) - API.addIOSTokenHeader(to: &request) request.setValue("application/json", forHTTPHeaderField: "Accept") let (data, response) = try await URLSession.shared.data(for: request) @@ -704,7 +682,6 @@ class TMDBService { } var request = URLRequest(url: url) - API.addIOSTokenHeader(to: &request) request.setValue("application/json", forHTTPHeaderField: "Accept") let (data, response) = try await URLSession.shared.data(for: request) @@ -726,7 +703,6 @@ class TMDBService { } var request = URLRequest(url: url) - API.addIOSTokenHeader(to: &request) request.setValue("application/json", forHTTPHeaderField: "Accept") let (data, response) = try await URLSession.shared.data(for: request) @@ -747,7 +723,6 @@ class TMDBService { } var request = URLRequest(url: url) - API.addIOSTokenHeader(to: &request) request.setValue("application/json", forHTTPHeaderField: "Accept") let (data, response) = try await URLSession.shared.data(for: request) @@ -771,7 +746,6 @@ class TMDBService { } var request = URLRequest(url: url) - API.addIOSTokenHeader(to: &request) request.setValue("application/json", forHTTPHeaderField: "Accept") let (data, response) = try await URLSession.shared.data(for: request) @@ -793,7 +767,6 @@ class TMDBService { } var request = URLRequest(url: url) - API.addIOSTokenHeader(to: &request) request.setValue("application/json", forHTTPHeaderField: "Accept") let (data, response) = try await URLSession.shared.data(for: request) @@ -845,7 +818,6 @@ class TMDBService { } var request = URLRequest(url: url) - API.addIOSTokenHeader(to: &request) request.setValue("application/json", forHTTPHeaderField: "Accept") let (data, response) = try await URLSession.shared.data(for: request) @@ -867,7 +839,6 @@ class TMDBService { } var request = URLRequest(url: url) - API.addIOSTokenHeader(to: &request) request.setValue("application/json", forHTTPHeaderField: "Accept") let (data, response) = try await URLSession.shared.data(for: request) @@ -898,7 +869,6 @@ class TMDBService { } var request = URLRequest(url: url) - API.addIOSTokenHeader(to: &request) request.setValue("application/json", forHTTPHeaderField: "Accept") let (data, response) = try await URLSession.shared.data(for: request) diff --git a/apps/ios/Plotwist/Plotwist/Services/UserEpisodeService.swift b/apps/ios/Plotwist/Plotwist/Services/UserEpisodeService.swift index bf517555c..18ecf4283 100644 --- a/apps/ios/Plotwist/Plotwist/Services/UserEpisodeService.swift +++ b/apps/ios/Plotwist/Plotwist/Services/UserEpisodeService.swift @@ -64,7 +64,6 @@ class UserEpisodeService { } var request = URLRequest(url: url) - API.addIOSTokenHeader(to: &request) request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization") let (data, response) = try await URLSession.shared.data(for: request) @@ -97,7 +96,6 @@ class UserEpisodeService { } var request = URLRequest(url: url) - API.addIOSTokenHeader(to: &request) request.httpMethod = "POST" request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization") request.setValue("application/json", forHTTPHeaderField: "Content-Type") @@ -160,7 +158,6 @@ class UserEpisodeService { } var request = URLRequest(url: url) - API.addIOSTokenHeader(to: &request) request.httpMethod = "DELETE" request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization") request.setValue("application/json", forHTTPHeaderField: "Content-Type") diff --git a/apps/ios/Plotwist/Plotwist/Services/UserItemService.swift b/apps/ios/Plotwist/Plotwist/Services/UserItemService.swift index 5769239a3..489f3616a 100644 --- a/apps/ios/Plotwist/Plotwist/Services/UserItemService.swift +++ b/apps/ios/Plotwist/Plotwist/Services/UserItemService.swift @@ -39,7 +39,6 @@ class UserItemService { } var request = URLRequest(url: url) - API.addIOSTokenHeader(to: &request) request.setValue("application/json", forHTTPHeaderField: "Accept") let (data, response) = try await URLSession.shared.data(for: request) @@ -62,7 +61,6 @@ class UserItemService { } var request = URLRequest(url: url) - API.addIOSTokenHeader(to: &request) request.setValue("application/json", forHTTPHeaderField: "Accept") let (data, response) = try await URLSession.shared.data(for: request) @@ -94,7 +92,6 @@ class UserItemService { } var request = URLRequest(url: url) - API.addIOSTokenHeader(to: &request) request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization") let (data, response) = try await URLSession.shared.data(for: request) @@ -135,7 +132,6 @@ class UserItemService { } var request = URLRequest(url: url) - API.addIOSTokenHeader(to: &request) request.httpMethod = "PUT" request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization") request.setValue("application/json", forHTTPHeaderField: "Content-Type") @@ -179,7 +175,6 @@ class UserItemService { } var request = URLRequest(url: url) - API.addIOSTokenHeader(to: &request) request.httpMethod = "DELETE" request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization") @@ -206,7 +201,6 @@ class UserItemService { } var request = URLRequest(url: url) - API.addIOSTokenHeader(to: &request) request.httpMethod = "PUT" request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization") request.setValue("application/json", forHTTPHeaderField: "Content-Type") @@ -238,7 +232,6 @@ class UserItemService { } var request = URLRequest(url: url) - API.addIOSTokenHeader(to: &request) request.httpMethod = "POST" request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization") request.setValue("application/json", forHTTPHeaderField: "Content-Type") @@ -278,7 +271,6 @@ class UserItemService { } var request = URLRequest(url: url) - API.addIOSTokenHeader(to: &request) request.httpMethod = "DELETE" request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization") @@ -302,7 +294,6 @@ class UserItemService { } var request = URLRequest(url: url) - API.addIOSTokenHeader(to: &request) request.httpMethod = "PUT" request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization") request.setValue("application/json", forHTTPHeaderField: "Content-Type") diff --git a/apps/ios/Plotwist/Plotwist/Services/UserStatsService.swift b/apps/ios/Plotwist/Plotwist/Services/UserStatsService.swift index 970b74f8b..1a88db02d 100644 --- a/apps/ios/Plotwist/Plotwist/Services/UserStatsService.swift +++ b/apps/ios/Plotwist/Plotwist/Services/UserStatsService.swift @@ -16,7 +16,6 @@ class UserStatsService { } var request = URLRequest(url: url) - API.addIOSTokenHeader(to: &request) request.setValue("application/json", forHTTPHeaderField: "Accept") let (data, response) = try await URLSession.shared.data(for: request) @@ -39,7 +38,6 @@ class UserStatsService { } var request = URLRequest(url: url) - API.addIOSTokenHeader(to: &request) request.setValue("application/json", forHTTPHeaderField: "Accept") let (data, response) = try await URLSession.shared.data(for: request) @@ -63,7 +61,6 @@ class UserStatsService { } var request = URLRequest(url: url) - API.addIOSTokenHeader(to: &request) request.setValue("application/json", forHTTPHeaderField: "Accept") let (data, response) = try await URLSession.shared.data(for: request) @@ -85,7 +82,6 @@ class UserStatsService { } var request = URLRequest(url: url) - API.addIOSTokenHeader(to: &request) request.setValue("application/json", forHTTPHeaderField: "Accept") let (data, response) = try await URLSession.shared.data(for: request) @@ -106,7 +102,6 @@ class UserStatsService { } var request = URLRequest(url: url) - API.addIOSTokenHeader(to: &request) request.setValue("application/json", forHTTPHeaderField: "Accept") let (data, response) = try await URLSession.shared.data(for: request) diff --git a/apps/ios/Plotwist/Plotwist/Utils/Constants.swift b/apps/ios/Plotwist/Plotwist/Utils/Constants.swift index 260ca9c31..d0b842632 100644 --- a/apps/ios/Plotwist/Plotwist/Utils/Constants.swift +++ b/apps/ios/Plotwist/Plotwist/Utils/Constants.swift @@ -7,15 +7,4 @@ import Foundation enum API { static let baseURL = "https://plotwist-api-production.up.railway.app" - - /// Token for production client guard. Set in Info.plist key `IOS_TOKEN` (e.g. via xcconfig). - static var iosToken: String? { - Bundle.main.object(forInfoDictionaryKey: "IOS_TOKEN") as? String - } - - static func addIOSTokenHeader(to request: inout URLRequest) { - if let token = iosToken, !token.isEmpty { - request.setValue(token, forHTTPHeaderField: "X-IOS-Token") - } - } } From 933f10f6bd7569093cf2f2e3f079a2b0c6f37ab2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Pedro=20Alves?= Date: Sun, 15 Feb 2026 21:59:59 -0300 Subject: [PATCH 6/7] chore: comment out client guard registration in server setup --- apps/backend/src/http/server.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/backend/src/http/server.ts b/apps/backend/src/http/server.ts index b0b73fd01..a81130a6d 100644 --- a/apps/backend/src/http/server.ts +++ b/apps/backend/src/http/server.ts @@ -9,7 +9,6 @@ import { ZodError } from 'zod' import { logger } from '@/adapters/logger' import { DomainError } from '@/domain/errors/domain-error' import { config } from '../config' -import { registerClientGuard } from './client-guard' import { routes } from './routes' import { transformSwaggerSchema } from './transform-schema' @@ -86,7 +85,8 @@ export function startServer() { return reply.status(500).send({ message: 'Internal server error.' }) }) - registerClientGuard(app) + // TODO: Uncomment this when we have a client guard + // registerClientGuard(app) routes(app) app From 1ad6baa934d2a0f92364a51d78d93acb00758139 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Pedro=20Alves?= Date: Sun, 15 Feb 2026 22:01:41 -0300 Subject: [PATCH 7/7] fix: import UserNotFoundError in createFeedbackService --- apps/backend/src/domain/services/feedback/create-feedback.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/backend/src/domain/services/feedback/create-feedback.ts b/apps/backend/src/domain/services/feedback/create-feedback.ts index b090b4987..54079d326 100644 --- a/apps/backend/src/domain/services/feedback/create-feedback.ts +++ b/apps/backend/src/domain/services/feedback/create-feedback.ts @@ -1,7 +1,7 @@ import { insertFeedback } from '@/db/repositories/feedback-repository' import { isForeignKeyViolation } from '@/db/utils/postgres-errors' -import { UserNotFoundError } from '@/domain/errors/user-not-found' import type { InsertFeedbackModel } from '@/domain/entities/feedback' +import { UserNotFoundError } from '@/domain/errors/user-not-found' export async function createFeedbackService(params: InsertFeedbackModel) { try {