Compare commits

...

2 Commits

Author SHA1 Message Date
09d2039cf0 Don't require SENTRY_DSN to boot in production
A missing Sentry DSN meant "no error tracking configured yet," not
"the API should refuse to start" — but env validation treated it as
the latter, hard-failing startup in production if it wasn't set.
Sentry initialization was already conditional on SENTRY_DSN being
present (src/main.ts), so this only affects the boot-time validation
gate, not whether Sentry actually turns on when configured.
2026-08-26 23:58:46 +05:30
8ef764e664 Accept expirationTime on push subscription save
PushSubscription.toJSON() includes an expirationTime field (often
null, sometimes a number for renewable subscriptions), but
SavePushSubscriptionDto didn't declare it, so saving a push
subscription 400'd under the whitelist validation pipe whenever the
browser included it.
2026-08-26 23:58:46 +05:30
2 changed files with 12 additions and 6 deletions

View File

@ -59,11 +59,10 @@ export const envValidationSchema = Joi.object({
AUTO_SYNC_LOOKBACK_DAYS: Joi.number().integer().min(1).max(365).default(7), AUTO_SYNC_LOOKBACK_DAYS: Joi.number().integer().min(1).max(365).default(7),
AUTO_SYNC_MAX_USERS_PER_RUN: Joi.number().integer().min(1).max(500).default(25), AUTO_SYNC_MAX_USERS_PER_RUN: Joi.number().integer().min(1).max(500).default(25),
SENTRY_DSN: Joi.when("NODE_ENV", { // Strongly recommended in production for error visibility, but not
is: "production", // required to boot: a missing Sentry DSN should mean "no error tracking
then: Joi.string().uri().required(), // yet", not "the whole API refuses to start."
otherwise: Joi.string().uri().optional().allow(""), SENTRY_DSN: Joi.string().uri().optional().allow(""),
}),
SENTRY_TRACES_SAMPLE_RATE: Joi.number().min(0).max(1).default(0), SENTRY_TRACES_SAMPLE_RATE: Joi.number().min(0).max(1).default(0),
GOOGLE_CLIENT_ID: Joi.string().optional().allow(""), GOOGLE_CLIENT_ID: Joi.string().optional().allow(""),

View File

@ -1,4 +1,4 @@
import { IsBoolean, IsIn, IsOptional, IsString, IsUrl, ValidateNested } from "class-validator"; import { IsBoolean, IsIn, IsNumber, IsOptional, IsString, IsUrl, ValidateNested } from "class-validator";
import { Type } from "class-transformer"; import { Type } from "class-transformer";
export class UpdateNotificationPreferencesDto { export class UpdateNotificationPreferencesDto {
@ -30,4 +30,11 @@ export class SavePushSubscriptionDto {
@ValidateNested() @ValidateNested()
@Type(() => PushKeysDto) @Type(() => PushKeysDto)
keys!: PushKeysDto; keys!: PushKeysDto;
// Included by the browser's PushSubscription.toJSON() when the push
// service provides an expiry; not currently used server-side, but must be
// declared or the whitelist validation pipe rejects the whole request.
@IsOptional()
@IsNumber()
expirationTime?: number | null;
} }