From 08042d723b325898111c1d789658430844e4a8fc Mon Sep 17 00:00:00 2001 From: Depo-Lonedev Date: Wed, 28 Jan 2026 11:41:59 +0100 Subject: [PATCH 01/17] feat: add onboarding fields to user entity and update flow --- backend/package.json | 2 +- .../src/auth/providers/sign-in.provider.ts | 1 - backend/src/redis/redis.constants.ts | 1 - backend/src/redis/redis.module.ts | 1 - backend/src/redis/redis.provider.ts | 1 - backend/src/users/dtos/editUserDto.dto.ts | 62 ++++++++++++- .../users/providers/update-user.service.ts | 6 ++ backend/src/users/user.entity.ts | 42 +++++++++ package-lock.json | 91 ++++++++++++++----- 9 files changed, 178 insertions(+), 29 deletions(-) diff --git a/backend/package.json b/backend/package.json index bedf73b..77072ea 100644 --- a/backend/package.json +++ b/backend/package.json @@ -30,7 +30,7 @@ "@nestjs/passport": "^11.0.5", "@nestjs/platform-express": "^11.0.1", "@nestjs/schedule": "^6.0.0", - "@nestjs/swagger": "^11.0.7", + "@nestjs/swagger": "^11.2.5", "@nestjs/throttler": "^6.4.0", "@nestjs/typeorm": "^11.0.0", "@types/passport-google-oauth20": "^2.0.16", diff --git a/backend/src/auth/providers/sign-in.provider.ts b/backend/src/auth/providers/sign-in.provider.ts index 703b5ac..e76ed4c 100644 --- a/backend/src/auth/providers/sign-in.provider.ts +++ b/backend/src/auth/providers/sign-in.provider.ts @@ -1,4 +1,3 @@ -/* eslint-disable prettier/prettier */ import { forwardRef, Inject, diff --git a/backend/src/redis/redis.constants.ts b/backend/src/redis/redis.constants.ts index 0905d76..6fddc65 100644 --- a/backend/src/redis/redis.constants.ts +++ b/backend/src/redis/redis.constants.ts @@ -1,2 +1 @@ -/* eslint-disable prettier/prettier */ export const REDIS_CLIENT = 'REDIS_CLIENT'; diff --git a/backend/src/redis/redis.module.ts b/backend/src/redis/redis.module.ts index bc3d021..d6e8f2e 100644 --- a/backend/src/redis/redis.module.ts +++ b/backend/src/redis/redis.module.ts @@ -1,4 +1,3 @@ -/* eslint-disable prettier/prettier */ import { Global, Module, OnModuleDestroy, Inject } from '@nestjs/common'; import { ConfigModule } from '@nestjs/config'; import { redisProvider } from './redis.provider'; diff --git a/backend/src/redis/redis.provider.ts b/backend/src/redis/redis.provider.ts index 1857e13..91fb8ed 100644 --- a/backend/src/redis/redis.provider.ts +++ b/backend/src/redis/redis.provider.ts @@ -1,4 +1,3 @@ -/* eslint-disable prettier/prettier */ import { Provider } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import Redis from 'ioredis'; diff --git a/backend/src/users/dtos/editUserDto.dto.ts b/backend/src/users/dtos/editUserDto.dto.ts index 0707856..3c73961 100644 --- a/backend/src/users/dtos/editUserDto.dto.ts +++ b/backend/src/users/dtos/editUserDto.dto.ts @@ -1,5 +1,11 @@ import { ApiProperty } from '@nestjs/swagger'; -import { IsOptional, IsString, IsEmail, MinLength } from 'class-validator'; +import { + IsOptional, + IsString, + IsEmail, + MinLength, + IsArray, +} from 'class-validator'; export class EditUserDto { @ApiProperty({ @@ -27,4 +33,58 @@ export class EditUserDto { @IsString() @MinLength(6) password?: string; + + @ApiProperty({ + description: 'Country of the user', + required: false, + }) + @IsOptional() + @IsString() + country?: string; + + @ApiProperty({ + description: 'Interests of the user', + required: false, + type: [String], + }) + @IsOptional() + @IsArray() + @IsString({ each: true }) + interests?: string[]; + + @ApiProperty({ + description: 'Occupation of the user', + required: false, + }) + @IsOptional() + @IsString() + occupation?: string; + + @ApiProperty({ + description: 'Goals of the user', + required: false, + type: [String], + }) + @IsOptional() + @IsArray() + @IsString({ each: true }) + goals?: string[]; + + @ApiProperty({ + description: 'Available hours for the user', + required: false, + type: [String], + }) + @IsOptional() + @IsArray() + @IsString({ each: true }) + availableHours?: string[]; + + @ApiProperty({ + description: 'Bio of the user', + required: false, + }) + @IsOptional() + @IsString() + bio?: string; } diff --git a/backend/src/users/providers/update-user.service.ts b/backend/src/users/providers/update-user.service.ts index c964334..7347ab7 100644 --- a/backend/src/users/providers/update-user.service.ts +++ b/backend/src/users/providers/update-user.service.ts @@ -26,6 +26,12 @@ export class UpdateUserService { user.username = editUserDto.username ?? user.username; user.email = editUserDto.email ?? user.email; user.password = editUserDto.password ?? user.password; + user.country = editUserDto.country ?? user.country; + user.interests = editUserDto.interests ?? user.interests; + user.occupation = editUserDto.occupation ?? user.occupation; + user.goals = editUserDto.goals ?? user.goals; + user.availableHours = editUserDto.availableHours ?? user.availableHours; + user.bio = editUserDto.bio ?? user.bio; try { return await this.userRepository.save(user); diff --git a/backend/src/users/user.entity.ts b/backend/src/users/user.entity.ts index 38f3f1c..306a056 100644 --- a/backend/src/users/user.entity.ts +++ b/backend/src/users/user.entity.ts @@ -101,6 +101,48 @@ export class User { @Column('varchar', { length: 50, nullable: true }) ageGroup?: string; + /** + * Country of the user + */ + @ApiProperty({ example: 'United States', required: false }) + @Column('varchar', { length: 100, nullable: true }) + country?: string; + + /** + * User interests + */ + @ApiProperty({ example: ['Coding', 'Design'], required: false }) + @Column('simple-array', { nullable: true }) + interests?: string[]; + + /** + * User occupation + */ + @ApiProperty({ example: 'Software Engineer', required: false }) + @Column('varchar', { length: 150, nullable: true }) + occupation?: string; + + /** + * User goals + */ + @ApiProperty({ example: ['Learn NestJS', 'Build an app'], required: false }) + @Column('simple-array', { nullable: true }) + goals?: string[]; + + /** + * Available hours for learning + */ + @ApiProperty({ example: ['09:00', '10:00'], required: false }) + @Column('simple-array', { nullable: true }) + availableHours?: string[]; + + /** + * User bio + */ + @ApiProperty({ example: 'I am a passionate developer...', required: false }) + @Column('text', { nullable: true }) + bio?: string; + @Column({ nullable: true }) passwordResetToken?: string; diff --git a/package-lock.json b/package-lock.json index 8d99953..6180a83 100644 --- a/package-lock.json +++ b/package-lock.json @@ -34,7 +34,7 @@ "@nestjs/passport": "^11.0.5", "@nestjs/platform-express": "^11.0.1", "@nestjs/schedule": "^6.0.0", - "@nestjs/swagger": "^11.0.7", + "@nestjs/swagger": "^11.2.5", "@nestjs/throttler": "^6.4.0", "@nestjs/typeorm": "^11.0.0", "@types/passport-google-oauth20": "^2.0.16", @@ -154,6 +154,7 @@ "integrity": "sha512-Q5FsI3Cw0fGMXhmsg7c08i4EmXCrcl+WnAxb6LYOLHw4JFFC3yzmx9LaXZ7QMbA+JZXbigU2TirI7RAfO0Qlnw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@swc/counter": "^0.1.3", "@xhmikosr/bin-wrapper": "^13.0.5", @@ -200,6 +201,7 @@ "dev": true, "hasInstallScript": true, "license": "Apache-2.0", + "peer": true, "dependencies": { "@swc/counter": "^0.1.3", "@swc/types": "^0.1.24" @@ -238,6 +240,7 @@ "integrity": "sha512-m5ObIqwsUp6BZzyiy4RdZpzWGub9bqLJMvZDD0QMXhxjqMHMENlj+SqF5QxoUwaQNFe+8kz8XM8ZQhqkQPTgMQ==", "devOptional": true, "license": "MIT", + "peer": true, "dependencies": { "undici-types": "~6.21.0" } @@ -346,6 +349,7 @@ "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", "devOptional": true, "license": "MIT", + "peer": true, "dependencies": { "@cspotcode/source-map-support": "^0.8.0", "@tsconfig/node10": "^1.0.7", @@ -389,6 +393,7 @@ "resolved": "https://registry.npmjs.org/typeorm/-/typeorm-0.3.26.tgz", "integrity": "sha512-o2RrBNn3lczx1qv4j+JliVMmtkPSqEGpG0UuZkt9tCfWkoXKu8MZnjvp2GjWPll1SehwemQw6xrbVRhmOglj8Q==", "license": "MIT", + "peer": true, "dependencies": { "@sqltools/formatter": "^1.2.5", "ansis": "^3.17.0", @@ -521,6 +526,7 @@ "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", "devOptional": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -835,6 +841,7 @@ "integrity": "sha512-yDBHV9kQNcr2/sUr9jghVyz9C3Y5G2zUM2H2lo+9mKv4sFgbA8s8Z9t8D1jiTkGoO/NoIfKMyKWr4s6CN23ZwQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.27.1", @@ -3165,9 +3172,9 @@ } }, "node_modules/@microsoft/tsdoc": { - "version": "0.15.1", - "resolved": "https://registry.npmjs.org/@microsoft/tsdoc/-/tsdoc-0.15.1.tgz", - "integrity": "sha512-4aErSrCR/On/e5G2hDP0wjooqDdauzEbIq8hIkIe5pXV0rtWJZvdCEKL0ykZxex+IxIwBp0eGeV48hQN07dXtw==", + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/@microsoft/tsdoc/-/tsdoc-0.16.0.tgz", + "integrity": "sha512-xgAyonlVVS+q7Vc7qLW0UrJU7rSFcETRWsqdXZtjzRU8dF+6CkozTK4V4y1LwOX7j8r/vHphjDeMeGI4tNGeGA==", "license": "MIT" }, "node_modules/@napi-rs/nice": { @@ -3522,6 +3529,7 @@ "resolved": "https://registry.npmjs.org/@nestjs/common/-/common-11.1.6.tgz", "integrity": "sha512-krKwLLcFmeuKDqngG2N/RuZHCs2ycsKcxWIDgcm7i1lf3sQ0iG03ci+DsP/r3FcT/eJDFsIHnKtNta2LIi7PzQ==", "license": "MIT", + "peer": true, "dependencies": { "file-type": "21.0.0", "iterare": "1.2.1", @@ -3581,6 +3589,7 @@ "integrity": "sha512-siWX7UDgErisW18VTeJA+x+/tpNZrJewjTBsRPF3JVxuWRuAB1kRoiJcxHgln8Lb5UY9NdvklITR84DUEXD0Cg==", "hasInstallScript": true, "license": "MIT", + "peer": true, "dependencies": { "@nuxt/opencollective": "0.4.1", "fast-safe-stringify": "2.1.1", @@ -3677,6 +3686,7 @@ "resolved": "https://registry.npmjs.org/@nestjs/platform-express/-/platform-express-11.1.6.tgz", "integrity": "sha512-HErwPmKnk+loTq8qzu1up+k7FC6Kqa8x6lJ4cDw77KnTxLzsCaPt+jBvOq6UfICmfqcqCCf3dKXg+aObQp+kIQ==", "license": "MIT", + "peer": true, "dependencies": { "cors": "2.8.5", "express": "5.1.0", @@ -3724,20 +3734,20 @@ } }, "node_modules/@nestjs/swagger": { - "version": "11.2.0", - "resolved": "https://registry.npmjs.org/@nestjs/swagger/-/swagger-11.2.0.tgz", - "integrity": "sha512-5wolt8GmpNcrQv34tIPUtPoV1EeFbCetm40Ij3+M0FNNnf2RJ3FyWfuQvI8SBlcJyfaounYVTKzKHreFXsUyOg==", + "version": "11.2.5", + "resolved": "https://registry.npmjs.org/@nestjs/swagger/-/swagger-11.2.5.tgz", + "integrity": "sha512-wCykbEybMqiYcvkyzPW4SbXKcwra9AGdajm0MvFgKR3W+gd1hfeKlo67g/s9QCRc/mqUU4KOE5Qtk7asMeFuiA==", "license": "MIT", "dependencies": { - "@microsoft/tsdoc": "0.15.1", + "@microsoft/tsdoc": "0.16.0", "@nestjs/mapped-types": "2.1.0", - "js-yaml": "4.1.0", + "js-yaml": "4.1.1", "lodash": "4.17.21", - "path-to-regexp": "8.2.0", - "swagger-ui-dist": "5.21.0" + "path-to-regexp": "8.3.0", + "swagger-ui-dist": "5.31.0" }, "peerDependencies": { - "@fastify/static": "^8.0.0", + "@fastify/static": "^8.0.0 || ^9.0.0", "@nestjs/common": "^11.0.1", "@nestjs/core": "^11.0.1", "class-transformer": "*", @@ -3756,6 +3766,16 @@ } } }, + "node_modules/@nestjs/swagger/node_modules/path-to-regexp": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz", + "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/@nestjs/testing": { "version": "11.1.6", "resolved": "https://registry.npmjs.org/@nestjs/testing/-/testing-11.1.6.tgz", @@ -4861,6 +4881,7 @@ "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@types/estree": "*", "@types/json-schema": "*" @@ -5118,6 +5139,7 @@ "integrity": "sha512-lr3jdBw/BGj49Eps7EvqlUaoeA0xpj3pc0RoJkHpYaCHkVK7i28dKyImLQb3JVlqs3aYSXf7qYuWOW/fgZnTXQ==", "devOptional": true, "license": "MIT", + "peer": true, "dependencies": { "csstype": "^3.0.2" } @@ -5259,6 +5281,7 @@ "integrity": "sha512-r1XG74QgShUgXph1BYseJ+KZd17bKQib/yF3SR+demvytiRXrwd12Blnz5eYGm8tXaeRdd4x88MlfwldHoudGg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.42.0", "@typescript-eslint/types": "8.42.0", @@ -6254,6 +6277,7 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "devOptional": true, "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -6339,6 +6363,7 @@ "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -6787,6 +6812,7 @@ "resolved": "https://registry.npmjs.org/axios/-/axios-1.11.0.tgz", "integrity": "sha512-1Lx3WLFQWm3ooKDYZD1eXmoGO9fxYQjrycfHFC8P0sCfQVXyROp0p9PFWBehewBOdCwHc+f/b8I0fMto5eSfwA==", "license": "MIT", + "peer": true, "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.4", @@ -7211,6 +7237,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "caniuse-lite": "^1.0.30001737", "electron-to-chromium": "^1.5.211", @@ -7676,13 +7703,15 @@ "version": "0.5.1", "resolved": "https://registry.npmjs.org/class-transformer/-/class-transformer-0.5.1.tgz", "integrity": "sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/class-validator": { "version": "0.14.2", "resolved": "https://registry.npmjs.org/class-validator/-/class-validator-0.14.2.tgz", "integrity": "sha512-3kMVRF2io8N8pY1IFIXlho9r8IPUUIfHe2hYVtiebvAzU2XeQFXTv+XI4WX+TnXmtwXMDcjngcpkiPM0O9PvLw==", "license": "MIT", + "peer": true, "dependencies": { "@types/validator": "^13.11.8", "libphonenumber-js": "^1.11.1", @@ -8878,6 +8907,7 @@ "integrity": "sha512-RNCHRX5EwdrESy3Jc9o8ie8Bog+PeYvvSR8sDGoZxNFTvZ4dlxUB3WzQ3bQMztFrSRODGrLLj8g6OFuGY/aiQg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", @@ -8967,6 +8997,7 @@ "integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==", "dev": true, "license": "MIT", + "peer": true, "bin": { "eslint-config-prettier": "bin/cli.js" }, @@ -9068,6 +9099,7 @@ "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@rtsao/scc": "^1.1.0", "array-includes": "^3.1.9", @@ -9490,6 +9522,7 @@ "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz", "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==", "license": "MIT", + "peer": true, "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.0", @@ -10984,6 +11017,7 @@ "resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.7.0.tgz", "integrity": "sha512-NUcA93i1lukyXU+riqEyPtSEkyFq8tX90uL659J+qpCZ3rEdViB/APC58oAhIh3+bJln2hzdlZbBZsGNrlsR8g==", "license": "MIT", + "peer": true, "dependencies": { "@ioredis/commands": "^1.3.0", "cluster-key-slot": "^1.1.0", @@ -11652,6 +11686,7 @@ "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@jest/core": "^29.7.0", "@jest/types": "^29.6.3", @@ -12328,9 +12363,9 @@ "license": "MIT" }, "node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", "license": "MIT", "dependencies": { "argparse": "^2.0.1" @@ -14488,6 +14523,7 @@ "resolved": "https://registry.npmjs.org/passport/-/passport-0.7.0.tgz", "integrity": "sha512-cPLl+qZpSc+ireUvt+IzqbED1cHHkDoVYMo30jbJIdOOjQ1MQYZBPiNvmi8UM6lJuOpTPXJGZQk0DtC4y61MYQ==", "license": "MIT", + "peer": true, "dependencies": { "passport-strategy": "1.x.x", "pause": "0.0.1", @@ -14663,6 +14699,7 @@ "resolved": "https://registry.npmjs.org/pg/-/pg-8.16.3.tgz", "integrity": "sha512-enxc1h0jA/aq5oSDMvqyW3q89ra6XIIDZgCX9vkMrnz5DFTw/Ny3Li2lFQ+pt3L6MCgm/5o2o8HW9hiJji+xvw==", "license": "MIT", + "peer": true, "dependencies": { "pg-connection-string": "^2.9.1", "pg-pool": "^3.10.1", @@ -14989,6 +15026,7 @@ "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==", "dev": true, "license": "MIT", + "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -15261,6 +15299,7 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz", "integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -15270,6 +15309,7 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz", "integrity": "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==", "license": "MIT", + "peer": true, "dependencies": { "scheduler": "^0.26.0" }, @@ -15289,6 +15329,7 @@ "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz", "integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==", "license": "MIT", + "peer": true, "dependencies": { "@types/use-sync-external-store": "^0.0.6", "use-sync-external-store": "^1.4.0" @@ -15360,7 +15401,8 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/redux-thunk": { "version": "3.1.0", @@ -15375,7 +15417,8 @@ "version": "0.2.2", "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz", "integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==", - "license": "Apache-2.0" + "license": "Apache-2.0", + "peer": true }, "node_modules/reflect.getprototypeof": { "version": "1.0.10", @@ -15699,6 +15742,7 @@ "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", "license": "Apache-2.0", + "peer": true, "dependencies": { "tslib": "^2.1.0" } @@ -15816,6 +15860,7 @@ "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -17103,9 +17148,9 @@ } }, "node_modules/swagger-ui-dist": { - "version": "5.21.0", - "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.21.0.tgz", - "integrity": "sha512-E0K3AB6HvQd8yQNSMR7eE5bk+323AUxjtCz/4ZNKiahOlPhPJxqn3UPIGs00cyY/dhrTDJ61L7C/a8u6zhGrZg==", + "version": "5.31.0", + "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.31.0.tgz", + "integrity": "sha512-zSUTIck02fSga6rc0RZP3b7J7wgHXwLea8ZjgLA3Vgnb8QeOl3Wou2/j5QkzSGeoz6HusP/coYuJl33aQxQZpg==", "license": "Apache-2.0", "dependencies": { "@scarf/scarf": "=1.4.0" @@ -17430,6 +17475,7 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -17825,6 +17871,7 @@ "integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==", "dev": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -18325,7 +18372,6 @@ "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", "dev": true, "license": "BSD-2-Clause", - "peer": true, "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^4.1.1" @@ -18340,7 +18386,6 @@ "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", "dev": true, "license": "BSD-2-Clause", - "peer": true, "engines": { "node": ">=4.0" } From a2b9f3699b789fe993d840f6644944e3390ed387 Mon Sep 17 00:00:00 2001 From: Depo-Lonedev Date: Wed, 28 Jan 2026 12:05:24 +0100 Subject: [PATCH 02/17] scoped change --- backend/src/users/user.entity.ts | Bin 3983 -> 4031 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/backend/src/users/user.entity.ts b/backend/src/users/user.entity.ts index 306a05698ceec71090169de9133db022f7845e2c..8819997846f337e14879403bd8ac1f54fbc2f26c 100644 GIT binary patch delta 56 zcmeB|-!H$RpWi^AL7#z_fs3J-A( Date: Wed, 28 Jan 2026 12:09:23 +0100 Subject: [PATCH 03/17] chore: implement scope-lock enforcement system --- .scope-lock.json | 14 +++ package-lock.json | 243 +++++++++++++++++++++++++++++++++---- package.json | 1 + scripts/git-scope-check.js | 60 +++++++++ 4 files changed, 293 insertions(+), 25 deletions(-) create mode 100644 .scope-lock.json create mode 100644 scripts/git-scope-check.js diff --git a/.scope-lock.json b/.scope-lock.json new file mode 100644 index 0000000..9e06150 --- /dev/null +++ b/.scope-lock.json @@ -0,0 +1,14 @@ +{ + "task": "USER-ONBOARDING-ENTITY", + "owner": "deon", + "enforce": true, + "allowedPatterns": [ + "backend/src/users/user.entity.ts", + "backend/src/users/dtos/editUserDto.dto.ts", + "backend/src/users/providers/update-user.service.ts", + ".scope-lock.json", + "package.json", + "package-lock.json", + "scripts/git-scope-check.js" + ] +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 6180a83..a04b709 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,6 +14,7 @@ "contracts" ], "dependencies": { + "minimatch": "^10.1.1", "tsconfig-paths": "^4.2.0" }, "devDependencies": { @@ -1462,6 +1463,19 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, + "node_modules/@eslint/config-array/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/@eslint/config-helpers": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.3.1.tgz", @@ -1509,6 +1523,19 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/@eslint/js": { "version": "9.34.0", "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.34.0.tgz", @@ -2461,7 +2488,6 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz", "integrity": "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==", - "dev": true, "license": "MIT", "engines": { "node": "20 || >=22" @@ -2471,7 +2497,6 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.0.tgz", "integrity": "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==", - "dev": true, "license": "MIT", "dependencies": { "@isaacs/balanced-match": "^4.0.1" @@ -2817,6 +2842,19 @@ } } }, + "node_modules/@jest/core/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/@jest/core/node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -2983,6 +3021,19 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/@jest/reporters/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/@jest/reporters/node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -7415,6 +7466,19 @@ "node": ">=10" } }, + "node_modules/cacache/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "license": "ISC", + "optional": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/cacache/node_modules/minipass": { "version": "3.3.6", "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", @@ -8180,6 +8244,19 @@ } } }, + "node_modules/create-jest/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/create-require": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", @@ -9151,6 +9228,19 @@ "json5": "lib/cli.js" } }, + "node_modules/eslint-plugin-import/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/eslint-plugin-import/node_modules/semver": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", @@ -9204,6 +9294,19 @@ "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9" } }, + "node_modules/eslint-plugin-jsx-a11y/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/eslint-plugin-prettier": { "version": "5.5.4", "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.5.4.tgz", @@ -9281,6 +9384,19 @@ "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" } }, + "node_modules/eslint-plugin-react/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/eslint-plugin-react/node_modules/resolve": { "version": "2.0.0-next.5", "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.5.tgz", @@ -9339,6 +9455,19 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/eslint/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/espree": { "version": "10.4.0", "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", @@ -9998,6 +10127,19 @@ "webpack": "^5.11.0" } }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/fork-ts-checker-webpack-plugin/node_modules/schema-utils": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", @@ -10457,22 +10599,6 @@ "dev": true, "license": "BSD-2-Clause" }, - "node_modules/glob/node_modules/minimatch": { - "version": "10.0.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.3.tgz", - "integrity": "sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw==", - "dev": true, - "license": "ISC", - "dependencies": { - "@isaacs/brace-expansion": "^5.0.0" - }, - "engines": { - "node": "20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/globals": { "version": "14.0.0", "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", @@ -11857,6 +11983,19 @@ } } }, + "node_modules/jest-cli/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/jest-diff": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", @@ -12196,6 +12335,19 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/jest-runtime/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/jest-runtime/node_modules/strip-bom": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", @@ -13410,16 +13562,18 @@ "link": true }, "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "devOptional": true, - "license": "ISC", + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.1.1.tgz", + "integrity": "sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ==", + "license": "BlueOak-1.0.0", "dependencies": { - "brace-expansion": "^1.1.7" + "@isaacs/brace-expansion": "^5.0.0" }, "engines": { - "node": "*" + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/minimist": { @@ -13983,6 +14137,19 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/node-gyp/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "license": "ISC", + "optional": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/node-gyp/node_modules/minipass": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", @@ -15697,6 +15864,19 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/rimraf/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "license": "ISC", + "optional": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/router": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", @@ -17411,6 +17591,19 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/test-exclude/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/text-decoder": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.3.tgz", diff --git a/package.json b/package.json index 3c22fdb..8c0d66c 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,7 @@ ], "homepage": "https://github.com/MindBlockLabs/mindBlock_Backend#readme", "dependencies": { + "minimatch": "^10.1.1", "tsconfig-paths": "^4.2.0" }, "devDependencies": { diff --git a/scripts/git-scope-check.js b/scripts/git-scope-check.js new file mode 100644 index 0000000..e6a1420 --- /dev/null +++ b/scripts/git-scope-check.js @@ -0,0 +1,60 @@ +#!/usr/bin/env node +const fs = require('fs'); +const { execSync } = require('child_process'); +const path = require('path'); +const minimatch = require('minimatch').minimatch || require('minimatch'); + +const SCOPE_FILE = '.scope-lock.json'; +const RED = '\x1b[31m'; +const GREEN = '\x1b[32m'; +const CYAN = '\x1b[36m'; +const RESET = '\x1b[0m'; + +// 1. Check if scope lock exists +if (!fs.existsSync(SCOPE_FILE)) { + process.exit(0); +} + +const scopeConfig = JSON.parse(fs.readFileSync(SCOPE_FILE, 'utf8')); + +if (!scopeConfig.enforce) { + console.log(`${CYAN}â„šī¸ Scope enforcement disabled in config.${RESET}`); + process.exit(0); +} + +// 2. Get Staged Files +try { + // Run git command from the root of the repo + // We assume the script is run from project root or .git/hooks which executes in project root usually + const stagedFilesOutput = execSync('git diff --cached --name-only', { encoding: 'utf8' }); + const stagedFiles = stagedFilesOutput.split('\n').filter(line => line.trim() !== ''); + + if (stagedFiles.length === 0) process.exit(0); + + // 3. Validate + const violations = []; + const allowedPatterns = scopeConfig.allowedPatterns || []; + + stagedFiles.forEach(file => { + const isAllowed = allowedPatterns.some(pattern => minimatch(file, pattern)); + if (!isAllowed) { + violations.push(file); + } + }); + + // 4. Result + if (violations.length > 0) { + console.error(`\n${RED}🛑 SCOPE VIOLATION DETECTED 🛑${RESET}`); + console.error(`The following files are not in the allowed scope for task "${scopeConfig.task}":\n`); + violations.forEach(v => console.error(` - ${v}`)); + console.error(`\n${CYAN}Action Required:${RESET} Unstage these files or update ${SCOPE_FILE}.\n`); + process.exit(1); + } else { + console.log(`${GREEN}✅ Scope Check Passed: All files within defined boundaries.${RESET}`); + process.exit(0); + } + +} catch (error) { + console.error('Failed to run scope check:', error); + process.exit(1); +} From 901a853c6b0dc66c28d9b5264c69b861f63db2b7 Mon Sep 17 00:00:00 2001 From: Depo-Lonedev Date: Wed, 28 Jan 2026 12:12:26 +0100 Subject: [PATCH 04/17] Test: clean commit with scope enforcement From d34d838cdee9cbe5e09a3faecb8b56107b16536a Mon Sep 17 00:00:00 2001 From: Depo-Lonedev Date: Wed, 28 Jan 2026 12:17:13 +0100 Subject: [PATCH 05/17] chore: cleanup test artifacts from entity --- backend/src/users/user.entity.ts | Bin 4031 -> 3982 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/backend/src/users/user.entity.ts b/backend/src/users/user.entity.ts index 8819997846f337e14879403bd8ac1f54fbc2f26c..e18ff24cffcd811c3c9c9edc76e5361aabaa96a6 100644 GIT binary patch delta 7 Ocmdll-zUGJj~@UGcLLb} delta 57 zcmeB^-!H$RkKd3>pFy92mw}6+m?4=VpP_&u70gR!$N;jE8M48=5+J*np#;nZ07je% ArT_o{ From 9b88512e94edba4aad5fb4c9d76f921b26f9d5db Mon Sep 17 00:00:00 2001 From: Depo-Lonedev Date: Wed, 28 Jan 2026 13:45:53 +0100 Subject: [PATCH 06/17] chore: update scope-lock for onboarding UI files --- .scope-lock.json | 5 +++++ frontend/public/tile.svg | 3 +++ 2 files changed, 8 insertions(+) create mode 100644 frontend/public/tile.svg diff --git a/.scope-lock.json b/.scope-lock.json index 9e06150..fc45828 100644 --- a/.scope-lock.json +++ b/.scope-lock.json @@ -6,6 +6,11 @@ "backend/src/users/user.entity.ts", "backend/src/users/dtos/editUserDto.dto.ts", "backend/src/users/providers/update-user.service.ts", + "frontend/app/onboarding/**", + "frontend/components/onboarding/**", + "frontend/components/ui/Input.tsx", + "frontend/public/icon-*.svg", + "frontend/public/tile.svg", ".scope-lock.json", "package.json", "package-lock.json", diff --git a/frontend/public/tile.svg b/frontend/public/tile.svg new file mode 100644 index 0000000..c0ab2b7 --- /dev/null +++ b/frontend/public/tile.svg @@ -0,0 +1,3 @@ + + + From 711de87d15bf2b37ce4849c7581b23064ea52450 Mon Sep 17 00:00:00 2001 From: Depo-Lonedev Date: Wed, 28 Jan 2026 13:47:04 +0100 Subject: [PATCH 07/17] feat(onboarding): add challenge level icons --- frontend/public/icon-level-advanced.svg | 5 +++++ frontend/public/icon-level-beginner.svg | 5 +++++ frontend/public/icon-level-expert.svg | 5 +++++ frontend/public/icon-level-intermediate.svg | 5 +++++ 4 files changed, 20 insertions(+) create mode 100644 frontend/public/icon-level-advanced.svg create mode 100644 frontend/public/icon-level-beginner.svg create mode 100644 frontend/public/icon-level-expert.svg create mode 100644 frontend/public/icon-level-intermediate.svg diff --git a/frontend/public/icon-level-advanced.svg b/frontend/public/icon-level-advanced.svg new file mode 100644 index 0000000..3c51688 --- /dev/null +++ b/frontend/public/icon-level-advanced.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/frontend/public/icon-level-beginner.svg b/frontend/public/icon-level-beginner.svg new file mode 100644 index 0000000..c838b72 --- /dev/null +++ b/frontend/public/icon-level-beginner.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/frontend/public/icon-level-expert.svg b/frontend/public/icon-level-expert.svg new file mode 100644 index 0000000..66356bf --- /dev/null +++ b/frontend/public/icon-level-expert.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/frontend/public/icon-level-intermediate.svg b/frontend/public/icon-level-intermediate.svg new file mode 100644 index 0000000..041439a --- /dev/null +++ b/frontend/public/icon-level-intermediate.svg @@ -0,0 +1,5 @@ + + + + + From 82e7e7178240cdda0c1d8d627640ef2cf817d2bb Mon Sep 17 00:00:00 2001 From: Depo-Lonedev Date: Wed, 28 Jan 2026 13:47:46 +0100 Subject: [PATCH 08/17] feat(onboarding): add challenge type icons --- frontend/public/icon-blockchain.svg | 9 +++++++++ frontend/public/icon-code.svg | 9 +++++++++ frontend/public/icon-puzzle.svg | 9 +++++++++ 3 files changed, 27 insertions(+) create mode 100644 frontend/public/icon-blockchain.svg create mode 100644 frontend/public/icon-code.svg create mode 100644 frontend/public/icon-puzzle.svg diff --git a/frontend/public/icon-blockchain.svg b/frontend/public/icon-blockchain.svg new file mode 100644 index 0000000..3ebe6f0 --- /dev/null +++ b/frontend/public/icon-blockchain.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/frontend/public/icon-code.svg b/frontend/public/icon-code.svg new file mode 100644 index 0000000..a8b76ba --- /dev/null +++ b/frontend/public/icon-code.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/frontend/public/icon-puzzle.svg b/frontend/public/icon-puzzle.svg new file mode 100644 index 0000000..673efc0 --- /dev/null +++ b/frontend/public/icon-puzzle.svg @@ -0,0 +1,9 @@ + + + + + + + + + From 0574a3164f775cf1ff6e02d225c86d14bd72f676 Mon Sep 17 00:00:00 2001 From: Depo-Lonedev Date: Wed, 28 Jan 2026 13:48:55 +0100 Subject: [PATCH 09/17] feat(onboarding): add OnboardingLayout component with progress bar --- .../onboarding/OnboardingLayout.tsx | 87 +++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 frontend/components/onboarding/OnboardingLayout.tsx diff --git a/frontend/components/onboarding/OnboardingLayout.tsx b/frontend/components/onboarding/OnboardingLayout.tsx new file mode 100644 index 0000000..2478006 --- /dev/null +++ b/frontend/components/onboarding/OnboardingLayout.tsx @@ -0,0 +1,87 @@ +'use client'; + +import React from 'react'; +import { useRouter } from 'next/navigation'; +import { ArrowLeft } from 'lucide-react'; +import Image from 'next/image'; + +interface OnboardingLayoutProps { + children: React.ReactNode; + currentStep: number; + totalSteps?: number; + title?: string; + onBack?: () => void; +} + +const OnboardingLayout: React.FC = ({ + children, + currentStep, + totalSteps = 4, + onBack, +}) => { + const router = useRouter(); + + const handleBack = () => { + if (onBack) { + onBack(); + } else { + router.back(); + } + }; + + const progressPercentage = (currentStep / totalSteps) * 100; + + return ( +
+ {/* Top Navigation Bar */} +
+ + + {/* Progress Bar */} +
+
+
+ + {/* Placeholder for symmetry or explicit step count if needed */} +
+
+ + {/* Main Content Area */} +
+ + {/* Puzzle Icon Header */} +
+ {/* Using tile.svg as the main logo/puzzle piece */} +
+ MindBlock +
+
+ + {children} +
+ + {/* Background Ambience (Optional) */} +
+
+
+
+
+ ); +}; + +export default OnboardingLayout; From 6fa356d21be6ff1c7071a590941013f61534327c Mon Sep 17 00:00:00 2001 From: Depo-Lonedev Date: Wed, 28 Jan 2026 13:50:00 +0100 Subject: [PATCH 10/17] feat(onboarding): add OnboardingContext for state management --- frontend/app/onboarding/OnboardingContext.tsx | 88 +++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 frontend/app/onboarding/OnboardingContext.tsx diff --git a/frontend/app/onboarding/OnboardingContext.tsx b/frontend/app/onboarding/OnboardingContext.tsx new file mode 100644 index 0000000..1a06089 --- /dev/null +++ b/frontend/app/onboarding/OnboardingContext.tsx @@ -0,0 +1,88 @@ +'use client'; + +import React, { createContext, useContext, useState, ReactNode } from 'react'; + +interface OnboardingData { + challengeLevel: string; + challengeTypes: string[]; + additionalInfo: { + country: string; + occupation: string; + interests: string[]; + goals: string[]; + }; + availability: { + availableHours: string[]; + bio: string; + }; +} + +interface OnboardingContextType { + data: OnboardingData; + updateData: (section: keyof OnboardingData, payload: any) => void; + // Specialized updaters for deep nesting + updateAdditionalInfo: (field: keyof OnboardingData['additionalInfo'], value: any) => void; + updateAvailability: (field: keyof OnboardingData['availability'], value: any) => void; +} + +const defaultData: OnboardingData = { + challengeLevel: '', + challengeTypes: [], + additionalInfo: { + country: '', + occupation: '', + interests: [], + goals: [] + }, + availability: { + availableHours: [], + bio: '' + } +}; + +const OnboardingContext = createContext(undefined); + +export const OnboardingProvider = ({ children }: { children: ReactNode }) => { + const [data, setData] = useState(defaultData); + + const updateData = (section: keyof OnboardingData, payload: any) => { + setData((prev) => ({ + ...prev, + [section]: payload, + })); + }; + + const updateAdditionalInfo = (field: keyof OnboardingData['additionalInfo'], value: any) => { + setData((prev) => ({ + ...prev, + additionalInfo: { + ...prev.additionalInfo, + [field]: value + } + })); + }; + + const updateAvailability = (field: keyof OnboardingData['availability'], value: any) => { + setData((prev) => ({ + ...prev, + availability: { + ...prev.availability, + [field]: value + } + })); + }; + + return ( + + {children} + + ); +}; + +export const useOnboarding = () => { + const context = useContext(OnboardingContext); + if (!context) { + throw new Error('useOnboarding must be used within an OnboardingProvider'); + } + return context; +}; From c37eac942f4acdd977bf58a352c3e52bba5c2225 Mon Sep 17 00:00:00 2001 From: Depo-Lonedev Date: Wed, 28 Jan 2026 13:50:47 +0100 Subject: [PATCH 11/17] feat(onboarding): add onboarding layout wrapper --- frontend/app/onboarding/layout.tsx | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 frontend/app/onboarding/layout.tsx diff --git a/frontend/app/onboarding/layout.tsx b/frontend/app/onboarding/layout.tsx new file mode 100644 index 0000000..2f2bca1 --- /dev/null +++ b/frontend/app/onboarding/layout.tsx @@ -0,0 +1,15 @@ +'use client'; + +import { OnboardingProvider } from './OnboardingContext'; + +export default function OnboardingRootLayout({ + children, +}: { + children: React.ReactNode; +}) { + return ( + + {children} + + ); +} From 876cb696e3c41d338201d4e407950083a5a13e75 Mon Sep 17 00:00:00 2001 From: Depo-Lonedev Date: Wed, 28 Jan 2026 13:51:02 +0100 Subject: [PATCH 12/17] feat(onboarding): add Step 1 - challenge level selection --- .../app/onboarding/challenge-level/page.tsx | 114 ++++++++++++++++++ 1 file changed, 114 insertions(+) create mode 100644 frontend/app/onboarding/challenge-level/page.tsx diff --git a/frontend/app/onboarding/challenge-level/page.tsx b/frontend/app/onboarding/challenge-level/page.tsx new file mode 100644 index 0000000..55a82a8 --- /dev/null +++ b/frontend/app/onboarding/challenge-level/page.tsx @@ -0,0 +1,114 @@ +'use client'; + +import React from 'react'; +import { useRouter } from 'next/navigation'; +import Button from '@/components/Button'; +import { useOnboarding } from '../OnboardingContext'; +import Image from 'next/image'; + +const levels = [ + { id: 'BEGINNER', label: 'I am a total beginner', icon: '/icon-level-beginner.svg' }, + { id: 'INTERMEDIATE', label: 'I am intermediate', icon: '/icon-level-intermediate.svg' }, + { id: 'ADVANCED', label: 'I am advanced', icon: '/icon-level-advanced.svg' }, + { id: 'EXPERT', label: 'I am an expert', icon: '/icon-level-expert.svg' }, +]; + +export default function ChallengeLevelPage() { + const router = useRouter(); + const { data, updateData } = useOnboarding(); + + const handleSelect = (levelId: string) => { + updateData('challengeLevel', levelId); + }; + + const handleContinue = () => { + if (data.challengeLevel) { + router.push('/onboarding/challenge-types'); + } + }; + + return ( +
+ {/* Top Navigation Bar */} +
+ + + {/* Progress Bar - White track */} +
+
+
+ +
+
+ + {/* Header with Puzzle Icon INLINE with Title */} +
+ MindBlock +
+ Choose Challenge level that matches your skills +
+
+ + {/* Selection Cards - Increased gap */} +
+ {levels.map((level) => ( + + ))} +
+ +
+ +
+ + {/* Background Ambience */} +
+
+
+
+
+ ); +} From 813086767740a550ea598b1a242aae8f527883b3 Mon Sep 17 00:00:00 2001 From: Depo-Lonedev Date: Wed, 28 Jan 2026 13:51:35 +0100 Subject: [PATCH 13/17] feat(onboarding): add Step 2 - challenge types selection --- .../app/onboarding/challenge-types/page.tsx | 121 ++++++++++++++++++ 1 file changed, 121 insertions(+) create mode 100644 frontend/app/onboarding/challenge-types/page.tsx diff --git a/frontend/app/onboarding/challenge-types/page.tsx b/frontend/app/onboarding/challenge-types/page.tsx new file mode 100644 index 0000000..0b32133 --- /dev/null +++ b/frontend/app/onboarding/challenge-types/page.tsx @@ -0,0 +1,121 @@ +'use client'; + +import React from 'react'; +import { useRouter } from 'next/navigation'; +import Button from '@/components/Button'; +import Image from 'next/image'; +import { useOnboarding } from '../OnboardingContext'; + +const types = [ + { id: 'CODING', label: 'Coding Challenges', icon: '/icon-code.svg' }, + { id: 'LOGIC', label: 'Logic Puzzle', icon: '/icon-puzzle.svg' }, + { id: 'BLOCKCHAIN', label: 'Blockchain', icon: '/icon-blockchain.svg' }, +]; + +export default function ChallengeTypesPage() { + const router = useRouter(); + const { data, updateData } = useOnboarding(); + + const handleToggle = (typeId: string) => { + const current = data.challengeTypes; + if (current.includes(typeId)) { + updateData('challengeTypes', current.filter(id => id !== typeId)); + } else { + updateData('challengeTypes', [...current, typeId]); + } + }; + + const handleContinue = () => { + if (data.challengeTypes.length > 0) { + router.push('/onboarding/additional-info'); + } + }; + + return ( +
+ {/* Top Navigation Bar */} +
+ + + {/* Progress Bar - White track */} +
+
+
+ +
+
+ + {/* Header with Puzzle Icon INLINE with Title */} +
+ MindBlock +
+ Choose the Challenge types (select at least one) +
+
+ + {/* Selection Cards - Increased gap */} +
+ {types.map((type) => { + const isSelected = data.challengeTypes.includes(type.id); + return ( + + ); + })} +
+ +
+ +
+ + {/* Background Ambience */} +
+
+
+
+
+ ); +} From 1d009f370e93496dc0a2f0898ff6db785b15f2dd Mon Sep 17 00:00:00 2001 From: Depo-Lonedev Date: Wed, 28 Jan 2026 13:52:34 +0100 Subject: [PATCH 14/17] feat(onboarding): add Step 3 - referral source and age selection with loading screen --- .../app/onboarding/additional-info/page.tsx | 280 ++++++++++++++++++ 1 file changed, 280 insertions(+) create mode 100644 frontend/app/onboarding/additional-info/page.tsx diff --git a/frontend/app/onboarding/additional-info/page.tsx b/frontend/app/onboarding/additional-info/page.tsx new file mode 100644 index 0000000..9e29b1d --- /dev/null +++ b/frontend/app/onboarding/additional-info/page.tsx @@ -0,0 +1,280 @@ +'use client'; + +import React, { useState } from 'react'; +import { useRouter } from 'next/navigation'; +import Button from '@/components/Button'; +import { useOnboarding } from '../OnboardingContext'; +import Image from 'next/image'; + +// Step 3a: How did you hear about Block Mind? (Selection) +const referralSources = [ + 'Google Search', + 'X (formerly called Twitter)', + 'Facebook / Instagram', + 'Friends / family', + 'Play Store', + 'App Store', + 'News / article / blog', + 'Youtube', + 'Others' +]; + +// Step 3b: How old are you? (Age Range Selection) +const ageRanges = [ + 'From 10 to 17 years old', + '18 to 24 years old', + '25 to 34 years old', + '35 to 44 years old', + '45 to 54 years old', + '55 to 64 years old', + '65+' +]; + +export default function AdditionalInfoPage() { + const router = useRouter(); + const { updateAdditionalInfo } = useOnboarding(); + const [step, setStep] = useState<'referral' | 'age'>('referral'); + const [selectedSource, setSelectedSource] = useState(null); + const [selectedAge, setSelectedAge] = useState(null); + const [isSubmitting, setIsSubmitting] = useState(false); + + const handleSourceSelect = (source: string) => { + setSelectedSource(source); + }; + + const handleAgeSelect = (age: string) => { + setSelectedAge(age); + }; + + const handleContinueFromReferral = () => { + if (selectedSource) { + updateAdditionalInfo('country', selectedSource); + setStep('age'); + } + }; + + const handleContinueFromAge = () => { + if (selectedAge) { + updateAdditionalInfo('occupation', selectedAge); // Storing age + setIsSubmitting(true); + } + }; + + const [loadingProgress, setLoadingProgress] = useState(0); + + // Handle loading animation when submitting + React.useEffect(() => { + if (!isSubmitting) return; + + const interval = setInterval(() => { + setLoadingProgress(prev => { + if (prev >= 100) { + clearInterval(interval); + return 100; + } + return prev + 2; + }); + }, 50); + + const timeout = setTimeout(() => { + router.push('/dashboard'); + }, 2700); + + return () => { + clearInterval(interval); + clearTimeout(timeout); + }; + }, [isSubmitting, router]); + + // Loading screen with animated progress + if (isSubmitting) { + return ( +
+ {/* Puzzle Icon */} +
+ Loading +
+ + {/* Message Card */} +
+ We're setting up your personalized challenges +
+ + {/* Animated Progress Bar */} +
+
+
+
+ {loadingProgress}% +
+
+ ); + } + + // Step 3a: Referral Source Selection Screen + if (step === 'referral') { + return ( +
+ {/* Top Navigation Bar */} +
+ + + {/* Progress Bar - White track */} +
+
+
+ +
+
+ + {/* Header with Puzzle Icon INLINE with Title */} +
+ MindBlock +
+ How do you hear about Block Mind? +
+
+ + {/* Selection Cards - Increased gap */} +
+ {referralSources.map((source) => ( + + ))} +
+ +
+ +
+ + {/* Background Ambience */} +
+
+
+
+
+ ); + } + + // Step 3b: Age Range Selection Screen + return ( +
+ {/* Top Navigation Bar */} +
+ + + {/* Progress Bar - White track */} +
+
+
+ +
+
+ + {/* Header with Puzzle Icon INLINE with Title */} +
+ MindBlock +
+ How old are you? +
+
+ + {/* Age Range Selection Cards - Increased gap */} +
+ {ageRanges.map((age) => ( + + ))} +
+ +
+ +
+ + {/* Background Ambience */} +
+
+
+
+
+ ); +} From 25fb2cc7063c1883616b54175d333f856bd359df Mon Sep 17 00:00:00 2001 From: Depo-Lonedev Date: Wed, 28 Jan 2026 13:54:10 +0100 Subject: [PATCH 15/17] style(ui): update Input component styling for dark theme --- frontend/components/ui/Input.tsx | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/frontend/components/ui/Input.tsx b/frontend/components/ui/Input.tsx index 676e0e8..00b1efb 100644 --- a/frontend/components/ui/Input.tsx +++ b/frontend/components/ui/Input.tsx @@ -3,7 +3,7 @@ import React, { useState } from 'react'; import { Eye, EyeOff } from 'lucide-react'; -interface InputProps { +interface InputProps extends React.InputHTMLAttributes { type: 'text' | 'email' | 'password'; placeholder: string; value: string; @@ -18,7 +18,8 @@ const Input = ({ value, onChange, label, - className = '' + className = '', + ...props }: InputProps) => { const [showPassword, setShowPassword] = useState(false); const [isFocused, setIsFocused] = useState(false); @@ -32,7 +33,7 @@ const Input = ({ return (
{label && ( -