From 69268418819da541c300beef0f1d49027a411b0a Mon Sep 17 00:00:00 2001 From: Okeke Chinedu Emmanuel Date: Sun, 29 Mar 2026 06:34:05 +0100 Subject: [PATCH 1/3] feat(backend): add public user predictions endpoint --- .../users/dto/list-user-predictions.dto.ts | 65 +++++++ backend/src/users/users.controller.spec.ts | 46 +++++ backend/src/users/users.controller.ts | 24 +++ backend/src/users/users.module.ts | 3 +- backend/src/users/users.service.spec.ts | 158 ++++++++++++++++++ backend/src/users/users.service.ts | 77 +++++++++ backend/test/users.e2e-spec.ts | 43 +++++ 7 files changed, 415 insertions(+), 1 deletion(-) create mode 100644 backend/src/users/dto/list-user-predictions.dto.ts diff --git a/backend/src/users/dto/list-user-predictions.dto.ts b/backend/src/users/dto/list-user-predictions.dto.ts new file mode 100644 index 00000000..2986bf47 --- /dev/null +++ b/backend/src/users/dto/list-user-predictions.dto.ts @@ -0,0 +1,65 @@ +import { ApiPropertyOptional } from '@nestjs/swagger'; +import { Type } from 'class-transformer'; +import { IsEnum, IsInt, IsOptional, Max, Min } from 'class-validator'; + +export enum PublicPredictionOutcomeFilter { + Correct = 'correct', + Incorrect = 'incorrect', + Pending = 'pending', +} + +export class ListUserPredictionsDto { + @ApiPropertyOptional({ description: 'Page number', minimum: 1, default: 1 }) + @IsOptional() + @Type(() => Number) + @IsInt() + @Min(1) + page?: number = 1; + + @ApiPropertyOptional({ + description: 'Items per page', + minimum: 1, + maximum: 50, + default: 20, + }) + @IsOptional() + @Type(() => Number) + @IsInt() + @Min(1) + @Max(50) + limit?: number = 20; + + @ApiPropertyOptional({ + description: 'Filter predictions by computed outcome', + enum: PublicPredictionOutcomeFilter, + }) + @IsOptional() + @IsEnum(PublicPredictionOutcomeFilter) + outcome?: PublicPredictionOutcomeFilter; +} + +export interface PublicUserPredictionItem { + id: string; + chosen_outcome: string; + stake_amount_stroops: string; + payout_claimed: boolean; + payout_amount_stroops: string; + tx_hash: string | null; + submitted_at: Date; + outcome: PublicPredictionOutcomeFilter; + market: { + id: string; + title: string; + end_time: Date; + resolved_outcome: string | null; + is_resolved: boolean; + is_cancelled: boolean; + }; +} + +export interface PaginatedPublicUserPredictionsResponse { + data: PublicUserPredictionItem[]; + total: number; + page: number; + limit: number; +} diff --git a/backend/src/users/users.controller.spec.ts b/backend/src/users/users.controller.spec.ts index e12d9c5f..1cc6f3c5 100644 --- a/backend/src/users/users.controller.spec.ts +++ b/backend/src/users/users.controller.spec.ts @@ -3,6 +3,7 @@ import { NotFoundException } from '@nestjs/common'; import { UsersController } from './users.controller'; import { UsersService } from './users.service'; import { User } from './entities/user.entity'; +import { PublicPredictionOutcomeFilter } from './dto/list-user-predictions.dto'; describe('UsersController', () => { let controller: UsersController; @@ -32,6 +33,7 @@ describe('UsersController', () => { provide: UsersService, useValue: { findByAddress: jest.fn(), + findPublicPredictionsByAddress: jest.fn(), }, }, ], @@ -101,4 +103,48 @@ describe('UsersController', () => { ); }); }); + + describe('getPublicPredictions', () => { + it('should return paginated public predictions for a user', async () => { + const mockResult = { + data: [ + { + id: 'pred-1', + chosen_outcome: 'YES', + stake_amount_stroops: '100', + payout_claimed: false, + payout_amount_stroops: '0', + tx_hash: null, + submitted_at: new Date('2024-01-02'), + outcome: PublicPredictionOutcomeFilter.Correct, + market: { + id: 'market-1', + title: 'BTC > $100k?', + end_time: new Date('2024-02-01'), + resolved_outcome: 'YES', + is_resolved: true, + is_cancelled: false, + }, + }, + ], + total: 1, + page: 1, + limit: 20, + }; + jest + .spyOn(service, 'findPublicPredictionsByAddress') + .mockResolvedValue(mockResult); + + const result = await controller.getPublicPredictions( + mockUser.stellar_address, + { page: 1, limit: 20 }, + ); + + expect(service.findPublicPredictionsByAddress).toHaveBeenCalledWith( + mockUser.stellar_address, + { page: 1, limit: 20 }, + ); + expect(result).toEqual(mockResult); + }); + }); }); diff --git a/backend/src/users/users.controller.ts b/backend/src/users/users.controller.ts index 8c66b131..92afd0c7 100644 --- a/backend/src/users/users.controller.ts +++ b/backend/src/users/users.controller.ts @@ -4,6 +4,7 @@ import { Patch, Param, Body, + Query, UsePipes, ValidationPipe, } from '@nestjs/common'; @@ -16,6 +17,10 @@ import { PublicUserDto } from './dto/public-user.dto'; import { UserResponseDto } from './dto/user-response.dto'; import { UpdateUserDto } from './dto/update-user.dto'; import { User } from './entities/user.entity'; +import { + ListUserPredictionsDto, + PaginatedPublicUserPredictionsResponse, +} from './dto/list-user-predictions.dto'; @Controller('users') export class UsersController { @@ -65,4 +70,23 @@ export class UsersController { excludeExtraneousValues: true, }); } + + @Get(':address/predictions') + @Public() + @UsePipes( + new ValidationPipe({ whitelist: true, forbidNonWhitelisted: false }), + ) + @ApiOperation({ + summary: "Get a user's predictions for resolved markets (public)", + }) + @ApiResponse({ + status: 200, + description: 'Paginated predictions for resolved markets only', + }) + async getPublicPredictions( + @Param('address') address: string, + @Query() query: ListUserPredictionsDto, + ): Promise { + return this.usersService.findPublicPredictionsByAddress(address, query); + } } diff --git a/backend/src/users/users.module.ts b/backend/src/users/users.module.ts index dc8898f4..b4193a53 100644 --- a/backend/src/users/users.module.ts +++ b/backend/src/users/users.module.ts @@ -3,9 +3,10 @@ import { TypeOrmModule } from '@nestjs/typeorm'; import { User } from './entities/user.entity'; import { UsersService } from './users.service'; import { UsersController } from './users.controller'; +import { Prediction } from '../predictions/entities/prediction.entity'; @Module({ - imports: [TypeOrmModule.forFeature([User])], + imports: [TypeOrmModule.forFeature([User, Prediction])], controllers: [UsersController], providers: [UsersService], exports: [UsersService], diff --git a/backend/src/users/users.service.spec.ts b/backend/src/users/users.service.spec.ts index 1be940d7..a3c79a21 100644 --- a/backend/src/users/users.service.spec.ts +++ b/backend/src/users/users.service.spec.ts @@ -4,10 +4,13 @@ import { NotFoundException } from '@nestjs/common'; import { Repository } from 'typeorm'; import { UsersService } from './users.service'; import { User } from './entities/user.entity'; +import { Prediction } from '../predictions/entities/prediction.entity'; +import { ListUserPredictionsDto } from './dto/list-user-predictions.dto'; describe('UsersService', () => { let service: UsersService; let repository: Repository; + let predictionsRepository: Repository; const mockUser: User = { id: '123e4567-e89b-12d3-a456-426614174000', @@ -35,11 +38,20 @@ describe('UsersService', () => { findOneBy: jest.fn(), }, }, + { + provide: getRepositoryToken(Prediction), + useValue: { + createQueryBuilder: jest.fn(), + }, + }, ], }).compile(); service = module.get(UsersService); repository = module.get>(getRepositoryToken(User)); + predictionsRepository = module.get>( + getRepositoryToken(Prediction), + ); }); it('should be defined', () => { @@ -77,4 +89,150 @@ describe('UsersService', () => { ); }); }); + + describe('findPublicPredictionsByAddress', () => { + it('should return only resolved-market predictions with outcome mapping', async () => { + jest.spyOn(repository, 'findOneBy').mockResolvedValue(mockUser); + + const now = new Date('2025-02-01T00:00:00.000Z'); + const queryBuilder = { + leftJoinAndSelect: jest.fn().mockReturnThis(), + where: jest.fn().mockReturnThis(), + andWhere: jest.fn().mockReturnThis(), + orderBy: jest.fn().mockReturnThis(), + skip: jest.fn().mockReturnThis(), + take: jest.fn().mockReturnThis(), + getManyAndCount: jest.fn().mockResolvedValue([ + [ + { + id: 'pred-1', + chosen_outcome: 'YES', + stake_amount_stroops: '100', + payout_claimed: false, + payout_amount_stroops: '0', + tx_hash: null, + submitted_at: now, + market: { + id: 'mkt-1', + title: 'Resolved YES market', + end_time: now, + resolved_outcome: 'YES', + is_resolved: true, + is_cancelled: false, + }, + }, + { + id: 'pred-2', + chosen_outcome: 'NO', + stake_amount_stroops: '200', + payout_claimed: false, + payout_amount_stroops: '0', + tx_hash: null, + submitted_at: now, + market: { + id: 'mkt-2', + title: 'Resolved YES market', + end_time: now, + resolved_outcome: 'YES', + is_resolved: true, + is_cancelled: false, + }, + }, + ], + 2, + ]), + }; + + jest + .spyOn(predictionsRepository, 'createQueryBuilder') + .mockReturnValue( + queryBuilder as unknown as ReturnType< + Repository['createQueryBuilder'] + >, + ); + + const result = await service.findPublicPredictionsByAddress( + mockUser.stellar_address, + new ListUserPredictionsDto(), + ); + + expect(queryBuilder.andWhere).toHaveBeenCalledWith( + 'market.is_resolved = true', + ); + expect(result.total).toBe(2); + expect(result.data).toHaveLength(2); + expect(result.data[0].outcome).toBe('correct'); + expect(result.data[1].outcome).toBe('incorrect'); + }); + + it('should filter public predictions by outcome', async () => { + jest.spyOn(repository, 'findOneBy').mockResolvedValue(mockUser); + + const now = new Date('2025-02-01T00:00:00.000Z'); + const queryBuilder = { + leftJoinAndSelect: jest.fn().mockReturnThis(), + where: jest.fn().mockReturnThis(), + andWhere: jest.fn().mockReturnThis(), + orderBy: jest.fn().mockReturnThis(), + skip: jest.fn().mockReturnThis(), + take: jest.fn().mockReturnThis(), + getManyAndCount: jest.fn().mockResolvedValue([ + [ + { + id: 'pred-1', + chosen_outcome: 'YES', + stake_amount_stroops: '100', + payout_claimed: false, + payout_amount_stroops: '0', + tx_hash: null, + submitted_at: now, + market: { + id: 'mkt-1', + title: 'Resolved YES market', + end_time: now, + resolved_outcome: 'YES', + is_resolved: true, + is_cancelled: false, + }, + }, + { + id: 'pred-2', + chosen_outcome: 'NO', + stake_amount_stroops: '200', + payout_claimed: false, + payout_amount_stroops: '0', + tx_hash: null, + submitted_at: now, + market: { + id: 'mkt-2', + title: 'Resolved YES market', + end_time: now, + resolved_outcome: 'YES', + is_resolved: true, + is_cancelled: false, + }, + }, + ], + 2, + ]), + }; + + jest + .spyOn(predictionsRepository, 'createQueryBuilder') + .mockReturnValue( + queryBuilder as unknown as ReturnType< + Repository['createQueryBuilder'] + >, + ); + + const result = await service.findPublicPredictionsByAddress( + mockUser.stellar_address, + { outcome: 'correct' } as ListUserPredictionsDto, + ); + + expect(result.total).toBe(2); + expect(result.data).toHaveLength(1); + expect(result.data[0].outcome).toBe('correct'); + }); + }); }); diff --git a/backend/src/users/users.service.ts b/backend/src/users/users.service.ts index e10b6d1f..1a6d1fd0 100644 --- a/backend/src/users/users.service.ts +++ b/backend/src/users/users.service.ts @@ -1,6 +1,13 @@ import { Injectable, NotFoundException } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; +import { Prediction } from '../predictions/entities/prediction.entity'; +import { + ListUserPredictionsDto, + PaginatedPublicUserPredictionsResponse, + PublicPredictionOutcomeFilter, + PublicUserPredictionItem, +} from './dto/list-user-predictions.dto'; import { User } from './entities/user.entity'; import { UpdateUserDto } from './dto/update-user.dto'; @@ -9,6 +16,8 @@ export class UsersService { constructor( @InjectRepository(User) private readonly usersRepository: Repository, + @InjectRepository(Prediction) + private readonly predictionsRepository: Repository, ) {} async findAll(): Promise { @@ -33,6 +42,74 @@ export class UsersService { return user; } + async findPublicPredictionsByAddress( + stellar_address: string, + dto: ListUserPredictionsDto, + ): Promise { + const user = await this.findByAddress(stellar_address); + + const page = dto.page ?? 1; + const limit = Math.min(dto.limit ?? 20, 50); + const skip = (page - 1) * limit; + + const qb = this.predictionsRepository + .createQueryBuilder('prediction') + .leftJoinAndSelect('prediction.market', 'market') + .where('prediction.userId = :userId', { userId: user.id }) + .andWhere('market.is_resolved = true') + .orderBy('prediction.submitted_at', 'DESC') + .skip(skip) + .take(limit); + + const [predictions, total] = await qb.getManyAndCount(); + + const data = predictions + .map((prediction) => this.mapPublicPrediction(prediction)) + .filter((prediction) => { + if (!dto.outcome) return true; + return prediction.outcome === dto.outcome; + }); + + return { data, total, page, limit }; + } + + private mapPublicPrediction(prediction: Prediction): PublicUserPredictionItem { + const outcome = this.computePublicOutcome(prediction); + + return { + id: prediction.id, + chosen_outcome: prediction.chosen_outcome, + stake_amount_stroops: prediction.stake_amount_stroops, + payout_claimed: prediction.payout_claimed, + payout_amount_stroops: prediction.payout_amount_stroops, + tx_hash: prediction.tx_hash ?? null, + submitted_at: prediction.submitted_at, + outcome, + market: { + id: prediction.market.id, + title: prediction.market.title, + end_time: prediction.market.end_time, + resolved_outcome: prediction.market.resolved_outcome ?? null, + is_resolved: prediction.market.is_resolved, + is_cancelled: prediction.market.is_cancelled, + }, + }; + } + + private computePublicOutcome( + prediction: Prediction, + ): PublicPredictionOutcomeFilter { + if (prediction.market.resolved_outcome == null) { + return PublicPredictionOutcomeFilter.Pending; + } + + if (prediction.market.resolved_outcome === prediction.chosen_outcome) { + return PublicPredictionOutcomeFilter.Correct; + } + + return PublicPredictionOutcomeFilter.Incorrect; + } + async updateProfile(userId: string, dto: UpdateUserDto): Promise { const user = await this.findById(userId); diff --git a/backend/test/users.e2e-spec.ts b/backend/test/users.e2e-spec.ts index c5a8ed22..68fdae43 100644 --- a/backend/test/users.e2e-spec.ts +++ b/backend/test/users.e2e-spec.ts @@ -5,10 +5,12 @@ import { AppModule } from './../src/app.module'; import { getRepositoryToken } from '@nestjs/typeorm'; import { User } from '../src/users/entities/user.entity'; import { Repository } from 'typeorm'; +import { Prediction } from '../src/predictions/entities/prediction.entity'; describe('Users (e2e)', () => { let app: INestApplication; let usersRepository: Repository; + let predictionsRepository: Repository; const mockUser: User = { id: '123e4567-e89b-12d3-a456-426614174000', @@ -34,12 +36,19 @@ describe('Users (e2e)', () => { .useValue({ findOneBy: jest.fn(), }) + .overrideProvider(getRepositoryToken(Prediction)) + .useValue({ + createQueryBuilder: jest.fn(), + }) .compile(); app = moduleFixture.createNestApplication(); usersRepository = moduleFixture.get>( getRepositoryToken(User), ); + predictionsRepository = moduleFixture.get>( + getRepositoryToken(Prediction), + ); if (app) { await app.init(); } @@ -165,4 +174,38 @@ describe('Users (e2e)', () => { }); }); }); + + describe('GET /users/:address/predictions', () => { + it('should return public predictions from resolved markets only', () => { + jest.spyOn(usersRepository, 'findOneBy').mockResolvedValue(mockUser); + + const queryBuilder = { + leftJoinAndSelect: jest.fn().mockReturnThis(), + where: jest.fn().mockReturnThis(), + andWhere: jest.fn().mockReturnThis(), + orderBy: jest.fn().mockReturnThis(), + skip: jest.fn().mockReturnThis(), + take: jest.fn().mockReturnThis(), + getManyAndCount: jest.fn().mockResolvedValue([[], 0]), + }; + + jest + .spyOn(predictionsRepository, 'createQueryBuilder') + .mockReturnValue( + queryBuilder as unknown as ReturnType< + Repository['createQueryBuilder'] + >, + ); + + return request(app.getHttpServer()) + .get(`/users/${mockUser.stellar_address}/predictions`) + .expect(200) + .expect((res: { body: { data: { data: unknown[] } } }) => { + expect(queryBuilder.andWhere).toHaveBeenCalledWith( + 'market.is_resolved = true', + ); + expect(res.body.data.data).toEqual([]); + }); + }); + }); }); From da38ba96570801d5bcef590e245ec6a8bf704d8d Mon Sep 17 00:00:00 2001 From: chiscookeke11 Date: Sun, 29 Mar 2026 07:05:36 +0100 Subject: [PATCH 2/3] merged updates --- backend/package-lock.json | 217 ++++---------------------------------- 1 file changed, 18 insertions(+), 199 deletions(-) diff --git a/backend/package-lock.json b/backend/package-lock.json index 9f691ac3..c6b8af49 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -750,7 +750,7 @@ "version": "0.8.1", "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "@jridgewell/trace-mapping": "0.3.9" @@ -763,7 +763,7 @@ "version": "0.3.9", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "@jridgewell/resolve-uri": "^3.0.3", @@ -2014,7 +2014,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">=6.0.0" @@ -2035,7 +2035,7 @@ "version": "1.5.5", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { @@ -2937,28 +2937,28 @@ "version": "1.0.12", "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.12.tgz", "integrity": "sha512-UCYBaeFvM11aU2y3YPZ//O5Rhj+xKyzy7mvcIoAjASbigy8mHMryP5cK7dgjlz2hWxh1g5pLw084E0a/wlUSFQ==", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/@tsconfig/node12": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/@tsconfig/node14": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/@tsconfig/node16": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/@tybys/wasm-util": { @@ -4070,7 +4070,7 @@ "version": "8.16.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", - "devOptional": true, + "dev": true, "license": "MIT", "bin": { "acorn": "bin/acorn" @@ -4106,7 +4106,7 @@ "version": "8.3.5", "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.5.tgz", "integrity": "sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "acorn": "^8.11.0" @@ -4298,7 +4298,7 @@ "version": "4.1.3", "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/argparse": { @@ -5324,7 +5324,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/cron": { @@ -5495,7 +5495,7 @@ "version": "4.0.4", "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.4.tgz", "integrity": "sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ==", - "devOptional": true, + "dev": true, "license": "BSD-3-Clause", "engines": { "node": ">=0.3.1" @@ -8317,7 +8317,7 @@ "version": "1.3.6", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "devOptional": true, + "dev": true, "license": "ISC" }, "node_modules/makeerror": { @@ -10745,7 +10745,7 @@ "version": "10.9.2", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "@cspotcode/source-map-support": "^0.8.0", @@ -11087,7 +11087,7 @@ "version": "5.9.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "devOptional": true, + "dev": true, "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", @@ -11298,7 +11298,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/v8-to-istanbul": { @@ -11368,56 +11368,6 @@ "defaults": "^1.0.3" } }, - "node_modules/webpack": { - "version": "5.105.4", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.105.4.tgz", - "integrity": "sha512-jTywjboN9aHxFlToqb0K0Zs9SbBoW4zRUlGzI2tYNxVYcEi/IPpn+Xi4ye5jTLvX2YeLuic/IvxNot+Q1jMoOw==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@types/eslint-scope": "^3.7.7", - "@types/estree": "^1.0.8", - "@types/json-schema": "^7.0.15", - "@webassemblyjs/ast": "^1.14.1", - "@webassemblyjs/wasm-edit": "^1.14.1", - "@webassemblyjs/wasm-parser": "^1.14.1", - "acorn": "^8.16.0", - "acorn-import-phases": "^1.0.3", - "browserslist": "^4.28.1", - "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.20.0", - "es-module-lexer": "^2.0.0", - "eslint-scope": "5.1.1", - "events": "^3.2.0", - "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.2.11", - "json-parse-even-better-errors": "^2.3.1", - "loader-runner": "^4.3.1", - "mime-types": "^2.1.27", - "neo-async": "^2.6.2", - "schema-utils": "^4.3.3", - "tapable": "^2.3.0", - "terser-webpack-plugin": "^5.3.17", - "watchpack": "^2.5.1", - "webpack-sources": "^3.3.4" - }, - "bin": { - "webpack": "bin/webpack.js" - }, - "engines": { - "node": ">=10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependenciesMeta": { - "webpack-cli": { - "optional": true - } - } - }, "node_modules/webpack-node-externals": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/webpack-node-externals/-/webpack-node-externals-3.0.0.tgz", @@ -11438,137 +11388,6 @@ "node": ">=10.13.0" } }, - "node_modules/webpack/node_modules/ajv": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", - "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/webpack/node_modules/ajv-formats": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", - "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "ajv": "^8.0.0" - }, - "peerDependencies": { - "ajv": "^8.0.0" - }, - "peerDependenciesMeta": { - "ajv": { - "optional": true - } - } - }, - "node_modules/webpack/node_modules/ajv-keywords": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", - "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "fast-deep-equal": "^3.1.3" - }, - "peerDependencies": { - "ajv": "^8.8.2" - } - }, - "node_modules/webpack/node_modules/eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "dev": true, - "license": "BSD-2-Clause", - "peer": true, - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/webpack/node_modules/estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true, - "license": "BSD-2-Clause", - "peer": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/webpack/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/webpack/node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/webpack/node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/webpack/node_modules/schema-utils": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz", - "integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@types/json-schema": "^7.0.9", - "ajv": "^8.9.0", - "ajv-formats": "^2.1.1", - "ajv-keywords": "^5.1.0" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -11743,7 +11562,7 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">=6" From 6b4a806625e073758cfb68b6eaa00f9e46000bc7 Mon Sep 17 00:00:00 2001 From: chiscookeke11 Date: Sun, 29 Mar 2026 07:09:18 +0100 Subject: [PATCH 3/3] updated the mock user data --- backend/src/users/users.service.spec.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/backend/src/users/users.service.spec.ts b/backend/src/users/users.service.spec.ts index a3c79a21..da6a5e61 100644 --- a/backend/src/users/users.service.spec.ts +++ b/backend/src/users/users.service.spec.ts @@ -24,6 +24,10 @@ describe('UsersService', () => { reputation_score: 85, season_points: 100, role: 'user', + is_banned: false, + ban_reason: "", + banned_at: null, + banned_by: "", created_at: new Date('2024-01-01'), updated_at: new Date('2024-01-01'), };