diff --git a/apps/backend/src/auth/auth.module.ts b/apps/backend/src/auth/auth.module.ts index e266b0bf..725ad27e 100644 --- a/apps/backend/src/auth/auth.module.ts +++ b/apps/backend/src/auth/auth.module.ts @@ -1,6 +1,7 @@ -import { DynamicModule, Logger, Module } from '@nestjs/common'; +import { DynamicModule, Module } from '@nestjs/common'; import { ConfigModule, ConfigService } from '@nestjs/config'; import { JwtModule } from '@nestjs/jwt'; +import ms from 'ms'; import { MailingModule } from '@server/mailing/mailing.module'; import { UserModule } from '@server/user/user.module'; @@ -26,21 +27,13 @@ export class AuthModule { inject: [ConfigService], imports: [ConfigModule], useFactory: async (config: ConfigService) => { - const JWT_SECRET = config.get('JWT_SECRET'); - const JWT_EXPIRES_IN = config.get('JWT_EXPIRES_IN'); - - if (!JWT_SECRET) { - Logger.error('JWT_SECRET is not set'); - throw new Error('JWT_SECRET is not set'); - } - - if (!JWT_EXPIRES_IN) { - Logger.warn('JWT_EXPIRES_IN is not set, using default of 60s'); - } + const JWT_SECRET = config.getOrThrow('JWT_SECRET'); + const JWT_EXPIRES_IN = + config.getOrThrow('JWT_EXPIRES_IN'); return { secret: JWT_SECRET, - signOptions: { expiresIn: JWT_EXPIRES_IN || '60s' }, + signOptions: { expiresIn: JWT_EXPIRES_IN }, }; }, }), @@ -58,7 +51,7 @@ export class AuthModule { inject: [ConfigService], provide: 'COOKIE_EXPIRES_IN', useFactory: (configService: ConfigService) => - configService.getOrThrow('COOKIE_EXPIRES_IN'), + configService.getOrThrow('COOKIE_EXPIRES_IN'), }, { inject: [ConfigService], @@ -82,7 +75,7 @@ export class AuthModule { inject: [ConfigService], provide: 'JWT_EXPIRES_IN', useFactory: (configService: ConfigService) => - configService.getOrThrow('JWT_EXPIRES_IN'), + configService.getOrThrow('JWT_EXPIRES_IN'), }, { inject: [ConfigService], @@ -94,7 +87,7 @@ export class AuthModule { inject: [ConfigService], provide: 'JWT_REFRESH_EXPIRES_IN', useFactory: (configService: ConfigService) => - configService.getOrThrow('JWT_REFRESH_EXPIRES_IN'), + configService.getOrThrow('JWT_REFRESH_EXPIRES_IN'), }, { inject: [ConfigService], diff --git a/apps/backend/src/auth/auth.service.spec.ts b/apps/backend/src/auth/auth.service.spec.ts index 311fece9..2fb54343 100644 --- a/apps/backend/src/auth/auth.service.spec.ts +++ b/apps/backend/src/auth/auth.service.spec.ts @@ -192,7 +192,7 @@ describe('AuthService', () => { const refreshToken = 'refresh-token'; spyOn(jwtService, 'signAsync').mockImplementation( - (payload, options: any) => { + (payload: any, options: any) => { if (options.secret === 'test-jwt-secret') { return Promise.resolve(accessToken); } else if (options.secret === 'test-jwt-refresh-secret') { diff --git a/apps/backend/src/auth/auth.service.ts b/apps/backend/src/auth/auth.service.ts index aab47ca4..53237ea1 100644 --- a/apps/backend/src/auth/auth.service.ts +++ b/apps/backend/src/auth/auth.service.ts @@ -2,6 +2,7 @@ import { Inject, Injectable, Logger } from '@nestjs/common'; import { JwtService } from '@nestjs/jwt'; import axios from 'axios'; import type { Request, Response } from 'express'; +import ms from 'ms'; import { CreateUser } from '@nbw/database'; import type { UserDocument } from '@nbw/database'; @@ -22,18 +23,18 @@ export class AuthService { @Inject(JwtService) private readonly jwtService: JwtService, @Inject('COOKIE_EXPIRES_IN') - private readonly COOKIE_EXPIRES_IN: string, + private readonly COOKIE_EXPIRES_IN: ms.StringValue, @Inject('FRONTEND_URL') private readonly FRONTEND_URL: string, @Inject('JWT_SECRET') private readonly JWT_SECRET: string, @Inject('JWT_EXPIRES_IN') - private readonly JWT_EXPIRES_IN: string, + private readonly JWT_EXPIRES_IN: ms.StringValue, @Inject('JWT_REFRESH_SECRET') private readonly JWT_REFRESH_SECRET: string, @Inject('JWT_REFRESH_EXPIRES_IN') - private readonly JWT_REFRESH_EXPIRES_IN: string, + private readonly JWT_REFRESH_EXPIRES_IN: ms.StringValue, @Inject('APP_DOMAIN') private readonly APP_DOMAIN?: string, ) {} @@ -171,11 +172,11 @@ export class AuthService { public async createJwtPayload(payload: TokenPayload): Promise { const [accessToken, refreshToken] = await Promise.all([ - this.jwtService.signAsync(payload, { + this.jwtService.signAsync(payload, { secret: this.JWT_SECRET, expiresIn: this.JWT_EXPIRES_IN, }), - this.jwtService.signAsync(payload, { + this.jwtService.signAsync(payload, { secret: this.JWT_REFRESH_SECRET, expiresIn: this.JWT_REFRESH_EXPIRES_IN, }), @@ -199,7 +200,7 @@ export class AuthService { const frontEndURL = this.FRONTEND_URL; const domain = this.APP_DOMAIN; - const maxAge = parseInt(this.COOKIE_EXPIRES_IN) * 1000; + const maxAge = ms(this.COOKIE_EXPIRES_IN) * 1000; res.cookie('token', token.access_token, { domain: domain, diff --git a/apps/backend/src/auth/strategies/JWT.strategy.spec.ts b/apps/backend/src/auth/strategies/JWT.strategy.spec.ts index 052cae9e..c17b4f3f 100644 --- a/apps/backend/src/auth/strategies/JWT.strategy.spec.ts +++ b/apps/backend/src/auth/strategies/JWT.strategy.spec.ts @@ -33,7 +33,7 @@ describe('JwtStrategy', () => { it('should throw an error if JWT_SECRET is not set', () => { jest.spyOn(configService, 'getOrThrow').mockReturnValue(null); - expect(() => new JwtStrategy(configService)).toThrowError( + expect(() => new JwtStrategy(configService)).toThrow( 'JwtStrategy requires a secret or key', ); }); @@ -84,7 +84,7 @@ describe('JwtStrategy', () => { const payload = { userId: 'test-user-id' }; - expect(() => jwtStrategy.validate(req, payload)).toThrowError( + expect(() => jwtStrategy.validate(req, payload)).toThrow( 'No refresh token', ); }); diff --git a/apps/backend/src/auth/strategies/discord.strategy/discord.strategy.spec.ts b/apps/backend/src/auth/strategies/discord.strategy/discord.strategy.spec.ts index 0dbc8608..588074d7 100644 --- a/apps/backend/src/auth/strategies/discord.strategy/discord.strategy.spec.ts +++ b/apps/backend/src/auth/strategies/discord.strategy/discord.strategy.spec.ts @@ -43,7 +43,7 @@ describe('DiscordStrategy', () => { it('should throw an error if Discord config is missing', () => { jest.spyOn(configService, 'getOrThrow').mockReturnValueOnce(null); - expect(() => new DiscordStrategy(configService)).toThrowError( + expect(() => new DiscordStrategy(configService)).toThrow( 'OAuth2Strategy requires a clientID option', ); }); diff --git a/apps/backend/src/auth/strategies/discord.strategy/index.ts b/apps/backend/src/auth/strategies/discord.strategy/index.ts index c31ea7e0..de2d6ebd 100644 --- a/apps/backend/src/auth/strategies/discord.strategy/index.ts +++ b/apps/backend/src/auth/strategies/discord.strategy/index.ts @@ -27,7 +27,7 @@ export class DiscordStrategy extends PassportStrategy(strategy, 'discord') { callbackUrl: `${SERVER_URL}/v1/auth/discord/callback`, scope: [DiscordPermissionScope.Email, DiscordPermissionScope.Identify], fetchScope: true, - prompt: 'none', + prompt: 'none' as const, }; super(config); diff --git a/apps/backend/src/auth/strategies/github.strategy.spec.ts b/apps/backend/src/auth/strategies/github.strategy.spec.ts index c8793e00..a13b099b 100644 --- a/apps/backend/src/auth/strategies/github.strategy.spec.ts +++ b/apps/backend/src/auth/strategies/github.strategy.spec.ts @@ -43,7 +43,7 @@ describe('GithubStrategy', () => { it('should throw an error if GitHub config is missing', () => { jest.spyOn(configService, 'getOrThrow').mockReturnValueOnce(null); - expect(() => new GithubStrategy(configService)).toThrowError( + expect(() => new GithubStrategy(configService)).toThrow( 'OAuth2Strategy requires a clientID option', ); }); diff --git a/apps/backend/src/auth/strategies/github.strategy.ts b/apps/backend/src/auth/strategies/github.strategy.ts index b7ea82ab..048a491e 100644 --- a/apps/backend/src/auth/strategies/github.strategy.ts +++ b/apps/backend/src/auth/strategies/github.strategy.ts @@ -22,10 +22,10 @@ export class GithubStrategy extends PassportStrategy(strategy, 'github') { super({ clientID: GITHUB_CLIENT_ID, clientSecret: GITHUB_CLIENT_SECRET, - redirect_uri: `${SERVER_URL}/v1/auth/github/callback`, + callbackURL: `${SERVER_URL}/v1/auth/github/callback`, scope: 'user:read,user:email', state: false, - }); + } as any); // TODO: Fix types } async validate(accessToken: string, refreshToken: string, profile: any) { diff --git a/apps/backend/src/auth/strategies/google.strategy.spec.ts b/apps/backend/src/auth/strategies/google.strategy.spec.ts index c1f1233e..ccee9e71 100644 --- a/apps/backend/src/auth/strategies/google.strategy.spec.ts +++ b/apps/backend/src/auth/strategies/google.strategy.spec.ts @@ -44,7 +44,7 @@ describe('GoogleStrategy', () => { it('should throw an error if Google config is missing', () => { jest.spyOn(configService, 'getOrThrow').mockReturnValueOnce(null); - expect(() => new GoogleStrategy(configService)).toThrowError( + expect(() => new GoogleStrategy(configService)).toThrow( 'OAuth2Strategy requires a clientID option', ); }); diff --git a/apps/backend/src/config/EnvironmentVariables.ts b/apps/backend/src/config/EnvironmentVariables.ts index cbb15109..a933ce57 100644 --- a/apps/backend/src/config/EnvironmentVariables.ts +++ b/apps/backend/src/config/EnvironmentVariables.ts @@ -1,5 +1,35 @@ import { plainToInstance } from 'class-transformer'; -import { IsEnum, IsOptional, IsString, validateSync } from 'class-validator'; +import { + IsEnum, + IsOptional, + IsString, + registerDecorator, + validateSync, + ValidationArguments, + ValidationOptions, +} from 'class-validator'; +import ms from 'ms'; + +// Validate if the value is a valid duration string from the 'ms' library +function IsDuration(validationOptions?: ValidationOptions) { + return function (object: object, propertyName: string) { + registerDecorator({ + name: 'isDuration', + target: object.constructor, + propertyName: propertyName, + options: validationOptions, + validator: { + validate(value: unknown) { + if (typeof value !== 'string') return false; + return typeof ms(value as ms.StringValue) === 'number'; + }, + defaultMessage(args: ValidationArguments) { + return `${args.property} must be a valid duration string (e.g., "1h", "30m", "7d")`; + }, + }, + }); + }; +} enum Environment { Development = 'development', @@ -38,14 +68,14 @@ export class EnvironmentVariables { @IsString() JWT_SECRET: string; - @IsString() - JWT_EXPIRES_IN: string; + @IsDuration() + JWT_EXPIRES_IN: ms.StringValue; @IsString() JWT_REFRESH_SECRET: string; - @IsString() - JWT_REFRESH_EXPIRES_IN: string; + @IsDuration() + JWT_REFRESH_EXPIRES_IN: ms.StringValue; // database @IsString() @@ -91,8 +121,8 @@ export class EnvironmentVariables { @IsString() DISCORD_WEBHOOK_URL: string; - @IsString() - COOKIE_EXPIRES_IN: string; + @IsDuration() + COOKIE_EXPIRES_IN: ms.StringValue; } export function validate(config: Record) { @@ -105,7 +135,13 @@ export function validate(config: Record) { }); if (errors.length > 0) { - throw new Error(errors.toString()); + const messages = errors + .map((error) => { + const constraints = Object.values(error.constraints || {}); + return ` - ${error.property}: ${constraints.join(', ')}`; + }) + .join('\n'); + throw new Error(`Environment validation failed:\n${messages}`); } return validatedConfig; diff --git a/apps/backend/src/lib/GetRequestUser.spec.ts b/apps/backend/src/lib/GetRequestUser.spec.ts index ebc2e65f..e85694fd 100644 --- a/apps/backend/src/lib/GetRequestUser.spec.ts +++ b/apps/backend/src/lib/GetRequestUser.spec.ts @@ -28,7 +28,7 @@ describe('validateUser', () => { }); it('should throw an error if the user does not exist', () => { - expect(() => validateUser(null)).toThrowError( + expect(() => validateUser(null)).toThrow( new HttpException( { error: { diff --git a/apps/backend/src/song/song.controller.spec.ts b/apps/backend/src/song/song.controller.spec.ts index ad035e5a..158ca27a 100644 --- a/apps/backend/src/song/song.controller.spec.ts +++ b/apps/backend/src/song/song.controller.spec.ts @@ -258,7 +258,7 @@ describe('SongController', () => { const query: SongListQueryDTO = { page: 1, limit: 10 }; const songList: SongPreviewDto[] = Array(10) .fill(null) - .map((_, i) => ({ id: `song-${i}` } as SongPreviewDto)); + .map((_, i) => ({ id: `song-${i}` } as unknown as SongPreviewDto)); mockSongService.querySongs.mockResolvedValueOnce({ content: songList, @@ -280,7 +280,7 @@ describe('SongController', () => { const query: SongListQueryDTO = { page: 1, limit: 10 }; const songList: SongPreviewDto[] = Array(5) .fill(null) - .map((_, i) => ({ id: `song-${i}` } as SongPreviewDto)); + .map((_, i) => ({ id: `song-${i}` } as unknown as SongPreviewDto)); mockSongService.querySongs.mockResolvedValueOnce({ content: songList, @@ -300,7 +300,7 @@ describe('SongController', () => { const query: SongListQueryDTO = { page: 3, limit: 10 }; const songList: SongPreviewDto[] = Array(10) .fill(null) - .map((_, i) => ({ id: `song-${20 + i}` } as SongPreviewDto)); + .map((_, i) => ({ id: `song-${20 + i}` } as unknown as SongPreviewDto)); mockSongService.querySongs.mockResolvedValueOnce({ content: songList, @@ -321,7 +321,7 @@ describe('SongController', () => { const query: SongListQueryDTO = { page: 1, limit: 10, q: 'test search' }; const songList: SongPreviewDto[] = Array(8) .fill(null) - .map((_, i) => ({ id: `song-${i}` } as SongPreviewDto)); + .map((_, i) => ({ id: `song-${i}` } as unknown as SongPreviewDto)); mockSongService.querySongs.mockResolvedValueOnce({ content: songList, @@ -346,7 +346,7 @@ describe('SongController', () => { }; const songList: SongPreviewDto[] = Array(3) .fill(null) - .map((_, i) => ({ id: `rock-song-${i}` } as SongPreviewDto)); + .map((_, i) => ({ id: `rock-song-${i}` } as unknown as SongPreviewDto)); mockSongService.querySongs.mockResolvedValueOnce({ content: songList, @@ -430,7 +430,7 @@ describe('SongController', () => { const q = 'test query'; const songList: SongPreviewDto[] = Array(5) .fill(null) - .map((_, i) => ({ id: `song-${i}` } as SongPreviewDto)); + .map((_, i) => ({ id: `song-${i}` } as unknown as SongPreviewDto)); mockSongService.querySongs.mockResolvedValueOnce({ content: songList, @@ -493,7 +493,7 @@ describe('SongController', () => { const q = 'test search'; const songList: SongPreviewDto[] = Array(10) .fill(null) - .map((_, i) => ({ id: `song-${10 + i}` } as SongPreviewDto)); + .map((_, i) => ({ id: `song-${10 + i}` } as unknown as SongPreviewDto)); mockSongService.querySongs.mockResolvedValueOnce({ content: songList, @@ -516,7 +516,7 @@ describe('SongController', () => { const q = 'popular song'; const songList: SongPreviewDto[] = Array(50) .fill(null) - .map((_, i) => ({ id: `song-${i}` } as SongPreviewDto)); + .map((_, i) => ({ id: `song-${i}` } as unknown as SongPreviewDto)); mockSongService.querySongs.mockResolvedValueOnce({ content: songList, @@ -538,7 +538,7 @@ describe('SongController', () => { const q = 'search term'; const songList: SongPreviewDto[] = Array(3) .fill(null) - .map((_, i) => ({ id: `song-${40 + i}` } as SongPreviewDto)); + .map((_, i) => ({ id: `song-${40 + i}` } as unknown as SongPreviewDto)); mockSongService.querySongs.mockResolvedValueOnce({ content: songList, @@ -596,7 +596,7 @@ describe('SongController', () => { const q = 'test'; const songList: SongPreviewDto[] = Array(25) .fill(null) - .map((_, i) => ({ id: `song-${i}` } as SongPreviewDto)); + .map((_, i) => ({ id: `song-${i}` } as unknown as SongPreviewDto)); mockSongService.querySongs.mockResolvedValueOnce({ content: songList, @@ -623,7 +623,7 @@ describe('SongController', () => { const q = 'trending'; const songList: SongPreviewDto[] = Array(10) .fill(null) - .map((_, i) => ({ id: `song-${i}` } as SongPreviewDto)); + .map((_, i) => ({ id: `song-${i}` } as unknown as SongPreviewDto)); mockSongService.querySongs.mockResolvedValueOnce({ content: songList, @@ -644,7 +644,7 @@ describe('SongController', () => { const q = 'search'; const songList: SongPreviewDto[] = Array(20) .fill(null) - .map((_, i) => ({ id: `song-${40 + i}` } as SongPreviewDto)); + .map((_, i) => ({ id: `song-${40 + i}` } as unknown as SongPreviewDto)); mockSongService.querySongs.mockResolvedValueOnce({ content: songList, diff --git a/apps/backend/src/song/song.controller.ts b/apps/backend/src/song/song.controller.ts index 2dc2b7a2..c1005d66 100644 --- a/apps/backend/src/song/song.controller.ts +++ b/apps/backend/src/song/song.controller.ts @@ -132,7 +132,7 @@ export class SongController { [SongSortType.NOTE_COUNT, 'stats.noteCount'], ]); - const sortField = sortFieldMap.get(query.sort) ?? 'createdAt'; + const sortField = sortFieldMap.get(query.sort ?? SongSortType.RECENT); const isDescending = query.order ? query.order === 'desc' : true; // Build PageQueryDTO with the sort field diff --git a/apps/backend/src/song/song.service.spec.ts b/apps/backend/src/song/song.service.spec.ts index 7c140daa..edf8dfb6 100644 --- a/apps/backend/src/song/song.service.spec.ts +++ b/apps/backend/src/song/song.service.spec.ts @@ -308,23 +308,6 @@ describe('SongService', () => { HttpException, ); }); - - it('should throw an error if user is unauthorized', async () => { - const publicId = 'test-id'; - const user: UserDocument = { _id: 'test-user-id' } as UserDocument; - const songEntity = new SongEntity(); - songEntity.uploader = new mongoose.Types.ObjectId(); // Different uploader - - const mockFindOne = { - exec: jest.fn().mockResolvedValue(songEntity), - }; - - jest.spyOn(songModel, 'findOne').mockReturnValue(mockFindOne as any); - - await expect(service.deleteSong(publicId, user)).rejects.toThrow( - HttpException, - ); - }); }); describe('patchSong', () => { @@ -495,39 +478,6 @@ describe('SongService', () => { ); }); - it('should throw an error if user is unauthorized', async () => { - const publicId = 'test-id'; - const user: UserDocument = { _id: 'test-user-id' } as UserDocument; - - const body: UploadSongDto = { - title: 'Test Song', - originalAuthor: 'Test Author', - description: 'Test Description', - category: 'alternative', - visibility: 'public', - license: 'standard', - customInstruments: [], - thumbnailData: { - startTick: 0, - startLayer: 0, - zoomLevel: 1, - backgroundColor: '#000000', - }, - file: 'somebytes', - allowDownload: false, - }; - - const songEntity = { - uploader: 'different-user-id', - } as any; - - jest.spyOn(songModel, 'findOne').mockReturnValue(songEntity); - - await expect(service.patchSong(publicId, body, user)).rejects.toThrow( - HttpException, - ); - }); - it('should throw an error if user no changes are provided', async () => { const publicId = 'test-id'; const user: UserDocument = { _id: 'test-user-id' } as UserDocument; @@ -1225,8 +1175,7 @@ describe('SongService', () => { exec: jest.fn().mockResolvedValue(songList), }; - mockSongModel.aggregate.mockReturnValue(mockAggregate as any); - mockSongModel.populate.mockResolvedValue(songList); + jest.spyOn(songModel, 'aggregate').mockReturnValue(mockAggregate as any); const result = await service.getRandomSongs(count); @@ -1253,8 +1202,7 @@ describe('SongService', () => { exec: jest.fn().mockResolvedValue(songList), }; - mockSongModel.aggregate.mockReturnValue(mockAggregate as any); - mockSongModel.populate.mockResolvedValue(songList); + jest.spyOn(songModel, 'aggregate').mockReturnValue(mockAggregate as any); const result = await service.getRandomSongs(count, category); diff --git a/apps/backend/tsconfig.json b/apps/backend/tsconfig.json index 400170a1..a23a38c9 100644 --- a/apps/backend/tsconfig.json +++ b/apps/backend/tsconfig.json @@ -2,13 +2,15 @@ "extends": "../../tsconfig.base.json", "compilerOptions": { // NestJS specific settings - "module": "commonjs", - "target": "ES2021", + "module": "esnext", + "target": "esnext", "declaration": true, "removeComments": true, "allowSyntheticDefaultImports": true, "sourceMap": true, "emitDecoratorMetadata": true, + "moduleResolution": "bundler", + "noEmit": true, // Path mapping "baseUrl": ".", diff --git a/bun.lock b/bun.lock index b2cdbafe..c5cf1237 100644 --- a/bun.lock +++ b/bun.lock @@ -140,7 +140,7 @@ "i": "^0.3.7", "js-confetti": "^0.13.1", "lucide-react": "^0.556.0", - "next": "16.0.8", + "next": "16.0.10", "next-recaptcha-v3": "^1.5.3", "nextjs-toploader": "^3.9.17", "npm": "^11.7.0", @@ -206,7 +206,6 @@ "name": "@nbw/song", "dependencies": { "@encode42/nbs.js": "^5.0.2", - "@nbw/database": "workspace:*", "@timohausmann/quadtree-ts": "^2.2.2", "jszip": "^3.10.1", "unidecode": "^1.1.0", @@ -721,27 +720,27 @@ "@nestjs/throttler": ["@nestjs/throttler@6.5.0", "", { "peerDependencies": { "@nestjs/common": "^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0", "@nestjs/core": "^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0", "reflect-metadata": "^0.1.13 || ^0.2.0" } }, "sha512-9j0ZRfH0QE1qyrj9JjIRDz5gQLPqq9yVC2nHsrosDVAfI5HHw08/aUAWx9DZLSdQf4HDkmhTTEGLrRFHENvchQ=="], - "@next/env": ["@next/env@16.0.8", "", {}, "sha512-xP4WrQZuj9MdmLJy3eWFHepo+R3vznsMSS8Dy3wdA7FKpjCiesQ6DxZvdGziQisj0tEtCgBKJzjcAc4yZOgLEQ=="], + "@next/env": ["@next/env@16.0.10", "", {}, "sha512-8tuaQkyDVgeONQ1MeT9Mkk8pQmZapMKFh5B+OrFUlG3rVmYTXcXlBetBgTurKXGaIZvkoqRT9JL5K3phXcgang=="], "@next/eslint-plugin-next": ["@next/eslint-plugin-next@16.0.8", "", { "dependencies": { "fast-glob": "3.3.1" } }, "sha512-1miV0qXDcLUaOdHridVPCh4i39ElRIAraseVIbb3BEqyZ5ol9sPyjTP/GNTPV5rBxqxjF6/vv5zQTVbhiNaLqA=="], "@next/mdx": ["@next/mdx@16.0.8", "", { "dependencies": { "source-map": "^0.7.0" }, "peerDependencies": { "@mdx-js/loader": ">=0.15.0", "@mdx-js/react": ">=0.15.0" }, "optionalPeers": ["@mdx-js/loader", "@mdx-js/react"] }, "sha512-GPv0ouRVp6T/qaOZj3LJiiIUFputrHvC/FVgyedbCaIsfbXmfu+Ky24KWWjf6uQSvDWQnQTk3UMSHqHb5UMYLw=="], - "@next/swc-darwin-arm64": ["@next/swc-darwin-arm64@16.0.8", "", { "os": "darwin", "cpu": "arm64" }, "sha512-yjVMvTQN21ZHOclQnhSFbjBTEizle+1uo4NV6L4rtS9WO3nfjaeJYw+H91G+nEf3Ef43TaEZvY5mPWfB/De7tA=="], + "@next/swc-darwin-arm64": ["@next/swc-darwin-arm64@16.0.10", "", { "os": "darwin", "cpu": "arm64" }, "sha512-4XgdKtdVsaflErz+B5XeG0T5PeXKDdruDf3CRpnhN+8UebNa5N2H58+3GDgpn/9GBurrQ1uWW768FfscwYkJRg=="], - "@next/swc-darwin-x64": ["@next/swc-darwin-x64@16.0.8", "", { "os": "darwin", "cpu": "x64" }, "sha512-+zu2N3QQ0ZOb6RyqQKfcu/pn0UPGmg+mUDqpAAEviAcEVEYgDckemOpiMRsBP3IsEKpcoKuNzekDcPczEeEIzA=="], + "@next/swc-darwin-x64": ["@next/swc-darwin-x64@16.0.10", "", { "os": "darwin", "cpu": "x64" }, "sha512-spbEObMvRKkQ3CkYVOME+ocPDFo5UqHb8EMTS78/0mQ+O1nqE8toHJVioZo4TvebATxgA8XMTHHrScPrn68OGw=="], - "@next/swc-linux-arm64-gnu": ["@next/swc-linux-arm64-gnu@16.0.8", "", { "os": "linux", "cpu": "arm64" }, "sha512-LConttk+BeD0e6RG0jGEP9GfvdaBVMYsLJ5aDDweKiJVVCu6sGvo+Ohz9nQhvj7EQDVVRJMCGhl19DmJwGr6bQ=="], + "@next/swc-linux-arm64-gnu": ["@next/swc-linux-arm64-gnu@16.0.10", "", { "os": "linux", "cpu": "arm64" }, "sha512-uQtWE3X0iGB8apTIskOMi2w/MKONrPOUCi5yLO+v3O8Mb5c7K4Q5KD1jvTpTF5gJKa3VH/ijKjKUq9O9UhwOYw=="], - "@next/swc-linux-arm64-musl": ["@next/swc-linux-arm64-musl@16.0.8", "", { "os": "linux", "cpu": "arm64" }, "sha512-JaXFAlqn8fJV+GhhA9lpg6da/NCN/v9ub98n3HoayoUSPOVdoxEEt86iT58jXqQCs/R3dv5ZnxGkW8aF4obMrQ=="], + "@next/swc-linux-arm64-musl": ["@next/swc-linux-arm64-musl@16.0.10", "", { "os": "linux", "cpu": "arm64" }, "sha512-llA+hiDTrYvyWI21Z0L1GiXwjQaanPVQQwru5peOgtooeJ8qx3tlqRV2P7uH2pKQaUfHxI/WVarvI5oYgGxaTw=="], - "@next/swc-linux-x64-gnu": ["@next/swc-linux-x64-gnu@16.0.8", "", { "os": "linux", "cpu": "x64" }, "sha512-O7M9it6HyNhsJp3HNAsJoHk5BUsfj7hRshfptpGcVsPZ1u0KQ/oVy8oxF7tlwxA5tR43VUP0yRmAGm1us514ng=="], + "@next/swc-linux-x64-gnu": ["@next/swc-linux-x64-gnu@16.0.10", "", { "os": "linux", "cpu": "x64" }, "sha512-AK2q5H0+a9nsXbeZ3FZdMtbtu9jxW4R/NgzZ6+lrTm3d6Zb7jYrWcgjcpM1k8uuqlSy4xIyPR2YiuUr+wXsavA=="], - "@next/swc-linux-x64-musl": ["@next/swc-linux-x64-musl@16.0.8", "", { "os": "linux", "cpu": "x64" }, "sha512-8+KClEC/GLI2dLYcrWwHu5JyC5cZYCFnccVIvmxpo6K+XQt4qzqM5L4coofNDZYkct/VCCyJWGbZZDsg6w6LFA=="], + "@next/swc-linux-x64-musl": ["@next/swc-linux-x64-musl@16.0.10", "", { "os": "linux", "cpu": "x64" }, "sha512-1TDG9PDKivNw5550S111gsO4RGennLVl9cipPhtkXIFVwo31YZ73nEbLjNC8qG3SgTz/QZyYyaFYMeY4BKZR/g=="], - "@next/swc-win32-arm64-msvc": ["@next/swc-win32-arm64-msvc@16.0.8", "", { "os": "win32", "cpu": "arm64" }, "sha512-rpQ/PgTEgH68SiXmhu/cJ2hk9aZ6YgFvspzQWe2I9HufY6g7V02DXRr/xrVqOaKm2lenBFPNQ+KAaeveywqV+A=="], + "@next/swc-win32-arm64-msvc": ["@next/swc-win32-arm64-msvc@16.0.10", "", { "os": "win32", "cpu": "arm64" }, "sha512-aEZIS4Hh32xdJQbHz121pyuVZniSNoqDVx1yIr2hy+ZwJGipeqnMZBJHyMxv2tiuAXGx6/xpTcQJ6btIiBjgmg=="], - "@next/swc-win32-x64-msvc": ["@next/swc-win32-x64-msvc@16.0.8", "", { "os": "win32", "cpu": "x64" }, "sha512-jWpWjWcMQu2iZz4pEK2IktcfR+OA9+cCG8zenyLpcW8rN4rzjfOzH4yj/b1FiEAZHKS+5Vq8+bZyHi+2yqHbFA=="], + "@next/swc-win32-x64-msvc": ["@next/swc-win32-x64-msvc@16.0.10", "", { "os": "win32", "cpu": "x64" }, "sha512-E+njfCoFLb01RAFEnGZn6ERoOqhK1Gl3Lfz1Kjnj0Ulfu7oJbuMyvBKNj/bw8XZnenHDASlygTjZICQW+rYW1Q=="], "@next/third-parties": ["@next/third-parties@16.0.8", "", { "dependencies": { "third-party-capital": "1.0.20" }, "peerDependencies": { "next": "^13.0.0 || ^14.0.0 || ^15.0.0 || ^16.0.0-beta.0", "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0" } }, "sha512-F8TNI1GFm7ivZX6HBMDVsDklAXOHlACbtw7w3WFbDk2oFXdVgKZBfdK+ILhjTBK4ibIXzLMWO1Py3bgSETKHYQ=="], @@ -2475,7 +2474,7 @@ "neo-async": ["neo-async@2.6.2", "", {}, "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw=="], - "next": ["next@16.0.8", "", { "dependencies": { "@next/env": "16.0.8", "@swc/helpers": "0.5.15", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", "styled-jsx": "5.1.6" }, "optionalDependencies": { "@next/swc-darwin-arm64": "16.0.8", "@next/swc-darwin-x64": "16.0.8", "@next/swc-linux-arm64-gnu": "16.0.8", "@next/swc-linux-arm64-musl": "16.0.8", "@next/swc-linux-x64-gnu": "16.0.8", "@next/swc-linux-x64-musl": "16.0.8", "@next/swc-win32-arm64-msvc": "16.0.8", "@next/swc-win32-x64-msvc": "16.0.8", "sharp": "^0.34.4" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", "@playwright/test": "^1.51.1", "babel-plugin-react-compiler": "*", "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "sass": "^1.3.0" }, "optionalPeers": ["@opentelemetry/api", "@playwright/test", "babel-plugin-react-compiler", "sass"], "bin": { "next": "dist/bin/next" } }, "sha512-LmcZzG04JuzNXi48s5P+TnJBsTGPJunViNKV/iE4uM6kstjTQsQhvsAv+xF6MJxU2Pr26tl15eVbp0jQnsv6/g=="], + "next": ["next@16.0.10", "", { "dependencies": { "@next/env": "16.0.10", "@swc/helpers": "0.5.15", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", "styled-jsx": "5.1.6" }, "optionalDependencies": { "@next/swc-darwin-arm64": "16.0.10", "@next/swc-darwin-x64": "16.0.10", "@next/swc-linux-arm64-gnu": "16.0.10", "@next/swc-linux-arm64-musl": "16.0.10", "@next/swc-linux-x64-gnu": "16.0.10", "@next/swc-linux-x64-musl": "16.0.10", "@next/swc-win32-arm64-msvc": "16.0.10", "@next/swc-win32-x64-msvc": "16.0.10", "sharp": "^0.34.4" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", "@playwright/test": "^1.51.1", "babel-plugin-react-compiler": "*", "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "sass": "^1.3.0" }, "optionalPeers": ["@opentelemetry/api", "@playwright/test", "babel-plugin-react-compiler", "sass"], "bin": { "next": "dist/bin/next" } }, "sha512-RtWh5PUgI+vxlV3HdR+IfWA1UUHu0+Ram/JBO4vWB54cVPentCD0e+lxyAYEsDTqGGMg7qpjhKh6dc6aW7W/sA=="], "next-recaptcha-v3": ["next-recaptcha-v3@1.5.3", "", { "peerDependencies": { "next": "^13 || ^14 || ^15 || ^16", "react": "^18 || ^19" } }, "sha512-Osnt1gj0+Mor8rc42NCzpteQrrSbcxskGLOeWLU/T0xdXtJE90y/gFyp87/yN1goIMI+gXs5f0PMymMa29nuLA=="],