Compare commits

...

6 Commits

Author SHA1 Message Date
raveline
a3802454d5 Personne en cours 2026-04-16 20:51:25 +02:00
raveline
ff4d4cf892 Ajout config cross-pc 2026-04-16 20:45:08 +02:00
raveline
68f8ca0ca5 Ajout du user dans Shared 2026-04-14 14:11:22 +02:00
raveline
6e254930b9 Ajout de la configuration pour le package Shared 2026-04-14 14:10:58 +02:00
raveline
5088e0bb2e Ajout du module Users 2026-04-13 21:39:33 +02:00
raveline
79a8db94f3 Configs TS, Docker, EsLint 2026-04-13 21:39:11 +02:00
30 changed files with 592 additions and 25 deletions

5
.gitignore vendored
View File

@@ -127,6 +127,11 @@ dist
# Stores VSCode versions used for testing VSCode extensions # Stores VSCode versions used for testing VSCode extensions
.vscode-test .vscode-test
.vscode/*
!.vscode/extensions.json
!.vscode/settings.json
# yarn v2 # yarn v2
.yarn/cache .yarn/cache
.yarn/unplugged .yarn/unplugged

16
.vscode/extensions.json vendored Normal file
View File

@@ -0,0 +1,16 @@
{
// See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations.
// Extension identifier format: ${publisher}.${name}. Example: vscode.csharp
// List of extensions which should be recommended for users of this workspace.
"recommendations": [
],
// List of extensions recommended by VS Code that should not be recommended for users of this workspace.
"unwantedRecommendations": [
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode",
"Vue.volar",
"atommaterial.a-file-icon-vscode"
]
}

14
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,14 @@
{
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit"
},
"js/ts.tsdk.path": "node_modules/typescript/lib",
"[typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[vue]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
}
}

View File

@@ -1,5 +1,4 @@
services: services:
# 1. Base de données : PostgreSQL
db: db:
image: postgres:18-alpine image: postgres:18-alpine
container_name: gim_db container_name: gim_db
@@ -11,12 +10,10 @@ services:
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-gim_db_password} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-gim_db_password}
POSTGRES_DB: ${POSTGRES_DB:-gim_db} POSTGRES_DB: ${POSTGRES_DB:-gim_db}
volumes: volumes:
# Persistance des données : crucial pour ne rien perdre au redémarrage
- db_data:/var/lib/postgresql/data - db_data:/var/lib/postgresql/data
networks: networks:
- gim_network - gim_network
# 2. Back-end : API NestJS
api: api:
build: build:
context: ./packages/backend context: ./packages/backend
@@ -26,20 +23,17 @@ services:
depends_on: depends_on:
- db - db
environment: environment:
# Connexion à la BDD via le nom du conteneur ("db")
- DB_HOST=db - DB_HOST=db
- DB_PORT=5432 - DB_PORT=5432
- DB_USER=${POSTGRES_USER:-gim_db_admin} - DB_USER=${POSTGRES_USER:-gim_db_admin}
- DB_PASSWORD=${POSTGRES_PASSWORD:-gim_db_password} - DB_PASSWORD=${POSTGRES_PASSWORD:-gim_db_password}
- DB_NAME=${POSTGRES_DB:-gim_db} - DB_NAME=${POSTGRES_DB:-gim_db}
# Clé de chiffrement applicatif
- ENCRYPTION_MASTER_KEY=${ENCRYPTION_MASTER_KEY} - ENCRYPTION_MASTER_KEY=${ENCRYPTION_MASTER_KEY}
ports: ports:
- "3000:3000" - "3000:3000"
networks: networks:
- gim_network - gim_network
# 3. Front-end : Vue.js servi par Nginx
web: web:
build: build:
context: ./packages/frontend context: ./packages/frontend
@@ -54,11 +48,9 @@ services:
networks: networks:
- gim_network - gim_network
# Définition du réseau virtuel privé pour isoler les conteneurs
networks: networks:
gim_network: gim_network:
driver: bridge driver: bridge
# Définition du volume physique sur le serveur pour la base de données
volumes: volumes:
db_data: db_data:

111
package-lock.json generated
View File

@@ -2715,26 +2715,20 @@
} }
}, },
"node_modules/@nestjs/config": { "node_modules/@nestjs/config": {
"version": "4.0.3", "version": "4.0.4",
"resolved": "https://registry.npmjs.org/@nestjs/config/-/config-4.0.3.tgz", "resolved": "https://registry.npmjs.org/@nestjs/config/-/config-4.0.4.tgz",
"integrity": "sha512-FQ3M3Ohqfl+nHAn5tp7++wUQw0f2nAk+SFKe8EpNRnIifPqvfJP6JQxPKtFLMOHbyer4X646prFG4zSRYEssQQ==", "integrity": "sha512-CJPjNitr0bAufSEnRe2N+JbnVmMmDoo6hvKCPzXgZoGwJSmp/dZPk9f/RMbuD/+Q1ZJPjwsRpq0vxna++Knwow==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"dotenv": "17.2.3", "dotenv": "17.4.1",
"dotenv-expand": "12.0.3", "dotenv-expand": "12.0.3",
"lodash": "4.17.23" "lodash": "4.18.1"
}, },
"peerDependencies": { "peerDependencies": {
"@nestjs/common": "^10.0.0 || ^11.0.0", "@nestjs/common": "^10.0.0 || ^11.0.0",
"rxjs": "^7.1.0" "rxjs": "^7.1.0"
} }
}, },
"node_modules/@nestjs/config/node_modules/lodash": {
"version": "4.17.23",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz",
"integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==",
"license": "MIT"
},
"node_modules/@nestjs/core": { "node_modules/@nestjs/core": {
"version": "11.1.18", "version": "11.1.18",
"resolved": "https://registry.npmjs.org/@nestjs/core/-/core-11.1.18.tgz", "resolved": "https://registry.npmjs.org/@nestjs/core/-/core-11.1.18.tgz",
@@ -4235,6 +4229,16 @@
"@babel/types": "^7.28.2" "@babel/types": "^7.28.2"
} }
}, },
"node_modules/@types/bcrypt": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/@types/bcrypt/-/bcrypt-6.0.0.tgz",
"integrity": "sha512-/oJGukuH3D2+D+3H4JWLaAsJ/ji86dhRidzZ/Od7H/i8g+aCmvkeCc6Ni/f9uxGLSQVCRZkX2/lqEFG2BvWtlQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@types/body-parser": { "node_modules/@types/body-parser": {
"version": "1.19.6", "version": "1.19.6",
"resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz",
@@ -4459,6 +4463,12 @@
"@types/superagent": "^8.1.0" "@types/superagent": "^8.1.0"
} }
}, },
"node_modules/@types/validator": {
"version": "13.15.10",
"resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.15.10.tgz",
"integrity": "sha512-T8L6i7wCuyoK8A/ZeLYt1+q0ty3Zb9+qbSSvrIVitzT3YjZqkTZ40IbRsPanlB4h1QB3JVL1SYCdR6ngtFYcuA==",
"license": "MIT"
},
"node_modules/@types/yargs": { "node_modules/@types/yargs": {
"version": "17.0.35", "version": "17.0.35",
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.35.tgz", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.35.tgz",
@@ -6065,6 +6075,20 @@
"node": ">=6.0.0" "node": ">=6.0.0"
} }
}, },
"node_modules/bcrypt": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-6.0.0.tgz",
"integrity": "sha512-cU8v/EGSrnH+HnxV2z0J7/blxH8gq7Xh2JFT6Aroax7UohdmiJJlxApMxtKfuI7z68NvvVcmR78k2LbT6efhRg==",
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
"node-addon-api": "^8.3.0",
"node-gyp-build": "^4.8.4"
},
"engines": {
"node": ">= 18"
}
},
"node_modules/birpc": { "node_modules/birpc": {
"version": "2.9.0", "version": "2.9.0",
"resolved": "https://registry.npmjs.org/birpc/-/birpc-2.9.0.tgz", "resolved": "https://registry.npmjs.org/birpc/-/birpc-2.9.0.tgz",
@@ -6436,6 +6460,23 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/class-transformer": {
"version": "0.5.1",
"resolved": "https://registry.npmjs.org/class-transformer/-/class-transformer-0.5.1.tgz",
"integrity": "sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==",
"license": "MIT"
},
"node_modules/class-validator": {
"version": "0.15.1",
"resolved": "https://registry.npmjs.org/class-validator/-/class-validator-0.15.1.tgz",
"integrity": "sha512-LqoS80HBBSCVhz/3KloUly0ovokxpdOLR++Al3J3+dHXWt9sTKlKd4eYtoxhxyUjoe5+UcIM+5k9MIxyBWnRTw==",
"license": "MIT",
"dependencies": {
"@types/validator": "^13.15.3",
"libphonenumber-js": "^1.11.1",
"validator": "^13.15.22"
}
},
"node_modules/cli-cursor": { "node_modules/cli-cursor": {
"version": "3.1.0", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz",
@@ -6990,9 +7031,9 @@
} }
}, },
"node_modules/dotenv": { "node_modules/dotenv": {
"version": "17.2.3", "version": "17.4.1",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.3.tgz", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.4.1.tgz",
"integrity": "sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==", "integrity": "sha512-k8DaKGP6r1G30Lx8V4+pCsLzKr8vLmV2paqEj1Y55GdAgJuIqpRp5FfajGF8KtwMxCz9qJc6wUIJnm053d/WCw==",
"license": "BSD-2-Clause", "license": "BSD-2-Clause",
"engines": { "engines": {
"node": ">=12" "node": ">=12"
@@ -9694,6 +9735,12 @@
"node": ">= 0.8.0" "node": ">= 0.8.0"
} }
}, },
"node_modules/libphonenumber-js": {
"version": "1.12.41",
"resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.12.41.tgz",
"integrity": "sha512-lsmMmGXBxXIK/VMLEj0kL6MtUs1kBGj1nTCzi6zgQoG1DEwqwt2DQyHxcLykceIxAnfE3hya7NuIh6PpC6S3fA==",
"license": "MIT"
},
"node_modules/lightningcss": { "node_modules/lightningcss": {
"version": "1.32.0", "version": "1.32.0",
"resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz",
@@ -10044,7 +10091,6 @@
"version": "4.18.1", "version": "4.18.1",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz",
"integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/lodash.memoize": { "node_modules/lodash.memoize": {
@@ -10534,6 +10580,15 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/node-addon-api": {
"version": "8.7.0",
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.7.0.tgz",
"integrity": "sha512-9MdFxmkKaOYVTV+XVRG8ArDwwQ77XIgIPyKASB1k3JPq3M8fGQQQE3YpMOrKm6g//Ktx8ivZr8xo1Qmtqub+GA==",
"license": "MIT",
"engines": {
"node": "^18 || ^20 || >= 21"
}
},
"node_modules/node-emoji": { "node_modules/node-emoji": {
"version": "1.11.0", "version": "1.11.0",
"resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-1.11.0.tgz", "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-1.11.0.tgz",
@@ -10544,6 +10599,17 @@
"lodash": "^4.17.21" "lodash": "^4.17.21"
} }
}, },
"node_modules/node-gyp-build": {
"version": "4.8.4",
"resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz",
"integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==",
"license": "MIT",
"bin": {
"node-gyp-build": "bin.js",
"node-gyp-build-optional": "optional.js",
"node-gyp-build-test": "build-test.js"
}
},
"node_modules/node-int64": { "node_modules/node-int64": {
"version": "0.4.0", "version": "0.4.0",
"resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz",
@@ -13497,6 +13563,15 @@
"node": ">=10.12.0" "node": ">=10.12.0"
} }
}, },
"node_modules/validator": {
"version": "13.15.35",
"resolved": "https://registry.npmjs.org/validator/-/validator-13.15.35.tgz",
"integrity": "sha512-TQ5pAGhd5whStmqWvYF4OjQROlmv9SMFVt37qoCBdqRffuuklWYQlCNnEs2ZaIBD1kZRNnikiZOS1eqgkar0iw==",
"license": "MIT",
"engines": {
"node": ">= 0.10"
}
},
"node_modules/vary": { "node_modules/vary": {
"version": "1.1.2", "version": "1.1.2",
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
@@ -14374,11 +14449,16 @@
"version": "0.0.1", "version": "0.0.1",
"license": "UNLICENSED", "license": "UNLICENSED",
"dependencies": { "dependencies": {
"@gim/shared": "*",
"@nestjs/common": "^11.0.1", "@nestjs/common": "^11.0.1",
"@nestjs/config": "^4.0.3", "@nestjs/config": "^4.0.3",
"@nestjs/core": "^11.0.1", "@nestjs/core": "^11.0.1",
"@nestjs/mapped-types": "*",
"@nestjs/platform-express": "^11.0.1", "@nestjs/platform-express": "^11.0.1",
"@nestjs/typeorm": "^11.0.1", "@nestjs/typeorm": "^11.0.1",
"bcrypt": "^6.0.0",
"class-transformer": "^0.5.1",
"class-validator": "^0.15.1",
"pg": "^8.20.0", "pg": "^8.20.0",
"reflect-metadata": "^0.2.2", "reflect-metadata": "^0.2.2",
"rxjs": "^7.8.1", "rxjs": "^7.8.1",
@@ -14390,6 +14470,7 @@
"@nestjs/cli": "^11.0.0", "@nestjs/cli": "^11.0.0",
"@nestjs/schematics": "^11.0.0", "@nestjs/schematics": "^11.0.0",
"@nestjs/testing": "^11.0.1", "@nestjs/testing": "^11.0.1",
"@types/bcrypt": "^6.0.0",
"@types/express": "^5.0.0", "@types/express": "^5.0.0",
"@types/jest": "^30.0.0", "@types/jest": "^30.0.0",
"@types/node": "^24.0.0", "@types/node": "^24.0.0",

View File

@@ -30,6 +30,10 @@ export default tseslint.config(
'@typescript-eslint/no-floating-promises': 'warn', '@typescript-eslint/no-floating-promises': 'warn',
'@typescript-eslint/no-unsafe-argument': 'warn', '@typescript-eslint/no-unsafe-argument': 'warn',
"prettier/prettier": ["error", { endOfLine: "auto" }], "prettier/prettier": ["error", { endOfLine: "auto" }],
'@typescript-eslint/no-unsafe-call': 'off',
'@typescript-eslint/no-unsafe-assignment': 'off',
'@typescript-eslint/no-unsafe-member-access': 'off',
'@typescript-eslint/no-unsafe-return': 'off',
}, },
}, },
); );

View File

@@ -20,11 +20,16 @@
"test:e2e": "jest --config ./test/jest-e2e.json" "test:e2e": "jest --config ./test/jest-e2e.json"
}, },
"dependencies": { "dependencies": {
"@gim/shared": "*",
"@nestjs/common": "^11.0.1", "@nestjs/common": "^11.0.1",
"@nestjs/config": "^4.0.3", "@nestjs/config": "^4.0.3",
"@nestjs/core": "^11.0.1", "@nestjs/core": "^11.0.1",
"@nestjs/mapped-types": "*",
"@nestjs/platform-express": "^11.0.1", "@nestjs/platform-express": "^11.0.1",
"@nestjs/typeorm": "^11.0.1", "@nestjs/typeorm": "^11.0.1",
"bcrypt": "^6.0.0",
"class-transformer": "^0.5.1",
"class-validator": "^0.15.1",
"pg": "^8.20.0", "pg": "^8.20.0",
"reflect-metadata": "^0.2.2", "reflect-metadata": "^0.2.2",
"rxjs": "^7.8.1", "rxjs": "^7.8.1",
@@ -36,6 +41,7 @@
"@nestjs/cli": "^11.0.0", "@nestjs/cli": "^11.0.0",
"@nestjs/schematics": "^11.0.0", "@nestjs/schematics": "^11.0.0",
"@nestjs/testing": "^11.0.1", "@nestjs/testing": "^11.0.1",
"@types/bcrypt": "^6.0.0",
"@types/express": "^5.0.0", "@types/express": "^5.0.0",
"@types/jest": "^30.0.0", "@types/jest": "^30.0.0",
"@types/node": "^24.0.0", "@types/node": "^24.0.0",

View File

@@ -3,6 +3,8 @@ import { ConfigModule, ConfigService } from '@nestjs/config';
import { TypeOrmModule } from '@nestjs/typeorm'; import { TypeOrmModule } from '@nestjs/typeorm';
import { AppController } from './app.controller'; import { AppController } from './app.controller';
import { AppService } from './app.service'; import { AppService } from './app.service';
import { UsersModule } from './users/users.module';
import { PersonsModule } from './persons/persons.module';
@Module({ @Module({
imports: [ imports: [
@@ -31,6 +33,10 @@ import { AppService } from './app.service';
synchronize: true, synchronize: true,
}), }),
}), }),
UsersModule,
PersonsModule,
], ],
controllers: [AppController], controllers: [AppController],
providers: [AppService], providers: [AppService],

View File

@@ -1,8 +1,16 @@
import { NestFactory } from '@nestjs/core'; import { NestFactory } from '@nestjs/core';
import { ValidationPipe } from '@nestjs/common';
import { AppModule } from './app.module'; import { AppModule } from './app.module';
async function bootstrap() { async function bootstrap() {
const app = await NestFactory.create(AppModule); const app = await NestFactory.create(AppModule);
app.useGlobalPipes(
new ValidationPipe({
whitelist: true,
forbidNonWhitelisted: true,
transform: true,
}),
);
await app.listen(process.env.PORT ?? 3000); await app.listen(process.env.PORT ?? 3000);
} }
bootstrap(); bootstrap();

View File

@@ -0,0 +1,33 @@
import {
IsString,
IsNotEmpty,
IsDateString,
IsBoolean,
IsOptional,
} from 'class-validator';
import { ICreatePersonneDTO } from '@gim/shared';
export class CreatePersonDto implements ICreatePersonneDTO {
@IsString()
@IsNotEmpty()
numeroLicence!: string;
@IsString()
@IsNotEmpty()
nom!: string;
@IsString()
@IsNotEmpty()
prenom!: string;
@IsDateString()
dateNaissance!: string;
@IsBoolean()
@IsOptional()
surclasse!: boolean;
@IsBoolean()
@IsOptional()
coach!: boolean;
}

View File

@@ -0,0 +1,4 @@
import { PartialType } from '@nestjs/mapped-types';
import { CreatePersonDto } from './create-person.dto';
export class UpdatePersonDto extends PartialType(CreatePersonDto) {}

View File

@@ -0,0 +1,26 @@
import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';
import { IPersonne } from '@gim/shared';
@Entity('personnes')
export class Person implements IPersonne {
@PrimaryGeneratedColumn('uuid')
id!: string;
@Column({ unique: true })
numeroLicence!: string;
@Column()
nom!: string;
@Column()
prenom!: string;
@Column({ type: 'date' })
dateNaissance!: string;
@Column({ default: false })
surclasse!: boolean;
@Column({ default: false })
coach!: boolean;
}

View File

@@ -0,0 +1,20 @@
import { Test, TestingModule } from '@nestjs/testing';
import { PersonsController } from './persons.controller';
import { PersonsService } from './persons.service';
describe('PersonsController', () => {
let controller: PersonsController;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [PersonsController],
providers: [PersonsService],
}).compile();
controller = module.get<PersonsController>(PersonsController);
});
it('should be defined', () => {
expect(controller).toBeDefined();
});
});

View File

@@ -0,0 +1,34 @@
import { Controller, Get, Post, Body, Patch, Param, Delete } from '@nestjs/common';
import { PersonsService } from './persons.service';
import { CreatePersonDto } from './dto/create-person.dto';
import { UpdatePersonDto } from './dto/update-person.dto';
@Controller('persons')
export class PersonsController {
constructor(private readonly personsService: PersonsService) {}
@Post()
create(@Body() createPersonDto: CreatePersonDto) {
return this.personsService.create(createPersonDto);
}
@Get()
findAll() {
return this.personsService.findAll();
}
@Get(':id')
findOne(@Param('id') id: string) {
return this.personsService.findOne(+id);
}
@Patch(':id')
update(@Param('id') id: string, @Body() updatePersonDto: UpdatePersonDto) {
return this.personsService.update(+id, updatePersonDto);
}
@Delete(':id')
remove(@Param('id') id: string) {
return this.personsService.remove(+id);
}
}

View File

@@ -0,0 +1,12 @@
import { Module } from '@nestjs/common';
import { PersonsService } from './persons.service';
import { PersonsController } from './persons.controller';
import { Person } from './entities/person.entity';
import { TypeOrmModule } from '@nestjs/typeorm';
@Module({
imports: [TypeOrmModule.forFeature([Person])],
controllers: [PersonsController],
providers: [PersonsService],
})
export class PersonsModule {}

View File

@@ -0,0 +1,18 @@
import { Test, TestingModule } from '@nestjs/testing';
import { PersonsService } from './persons.service';
describe('PersonsService', () => {
let service: PersonsService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [PersonsService],
}).compile();
service = module.get<PersonsService>(PersonsService);
});
it('should be defined', () => {
expect(service).toBeDefined();
});
});

View File

@@ -0,0 +1,26 @@
import { Injectable } from '@nestjs/common';
import { CreatePersonDto } from './dto/create-person.dto';
import { UpdatePersonDto } from './dto/update-person.dto';
@Injectable()
export class PersonsService {
create(createPersonDto: CreatePersonDto) {
return 'This action adds a new person';
}
findAll() {
return `This action returns all persons`;
}
findOne(id: number) {
return `This action returns a #${id} person`;
}
update(id: number, updatePersonDto: UpdatePersonDto) {
return `This action updates a #${id} person`;
}
remove(id: number) {
return `This action removes a #${id} person`;
}
}

View File

@@ -0,0 +1,17 @@
import { IsEmail, IsNotEmpty, MinLength } from 'class-validator';
import { ICreateUserDTO } from '@gim/shared';
export class CreateUserDto implements ICreateUserDTO {
@IsEmail({}, { message: "L'email doit être valide" })
email!: string;
@IsNotEmpty()
@MinLength(8, { message: 'Le mot de passe doit faire au moins 8 caractères' })
motDePasse!: string;
@IsNotEmpty()
nom!: string;
@IsNotEmpty()
prenom!: string;
}

View File

@@ -0,0 +1,4 @@
import { PartialType } from '@nestjs/mapped-types';
import { CreateUserDto } from './create-user.dto';
export class UpdateUserDto extends PartialType(CreateUserDto) {}

View File

@@ -0,0 +1,35 @@
import { IUser } from '@gim/shared';
import {
Entity,
PrimaryGeneratedColumn,
Column,
CreateDateColumn,
ManyToMany,
JoinTable,
} from 'typeorm';
import { Person } from '../../persons/entities/person.entity';
@Entity('utilisateurs')
export class User implements IUser {
@PrimaryGeneratedColumn('uuid')
id!: string;
@Column({ unique: true })
email!: string;
@Column()
motDePasse!: string;
@Column()
nom!: string;
@Column()
prenom!: string;
@CreateDateColumn()
dateCreation!: Date;
@ManyToMany(() => Person)
@JoinTable({ name: 'utilisateurs_personnes' })
personnesRattachees!: Person[];
}

View File

@@ -0,0 +1,20 @@
import { Test, TestingModule } from '@nestjs/testing';
import { UsersController } from './users.controller';
import { UsersService } from './users.service';
describe('UsersController', () => {
let controller: UsersController;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [UsersController],
providers: [UsersService],
}).compile();
controller = module.get<UsersController>(UsersController);
});
it('should be defined', () => {
expect(controller).toBeDefined();
});
});

View File

@@ -0,0 +1,42 @@
import {
Controller,
Get,
Post,
Body,
Patch,
Param,
Delete,
} from '@nestjs/common';
import { UsersService } from './users.service';
import { CreateUserDto } from './dto/create-user.dto';
import { UpdateUserDto } from './dto/update-user.dto';
@Controller('users')
export class UsersController {
constructor(private readonly usersService: UsersService) {}
@Post()
create(@Body() createUserDto: CreateUserDto) {
return this.usersService.create(createUserDto);
}
@Get()
findAll() {
return this.usersService.findAll();
}
@Get(':id')
findOne(@Param('id') id: string) {
return this.usersService.findOne(id);
}
@Patch(':id')
update(@Param('id') id: string, @Body() updateUserDto: UpdateUserDto) {
return this.usersService.update(id, updateUserDto);
}
@Delete(':id')
remove(@Param('id') id: string) {
return this.usersService.remove(id);
}
}

View File

@@ -0,0 +1,12 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { UsersService } from './users.service';
import { UsersController } from './users.controller';
import { User } from './entities/user.entity';
@Module({
imports: [TypeOrmModule.forFeature([User])],
controllers: [UsersController],
providers: [UsersService],
})
export class UsersModule {}

View File

@@ -0,0 +1,18 @@
import { Test, TestingModule } from '@nestjs/testing';
import { UsersService } from './users.service';
describe('UsersService', () => {
let service: UsersService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [UsersService],
}).compile();
service = module.get<UsersService>(UsersService);
});
it('should be defined', () => {
expect(service).toBeDefined();
});
});

View File

@@ -0,0 +1,70 @@
import {
Injectable,
ConflictException,
NotFoundException,
} from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import * as bcrypt from 'bcrypt';
import { CreateUserDto } from './dto/create-user.dto';
import { User } from './entities/user.entity';
import { UpdateUserDto } from './dto/update-user.dto';
@Injectable()
export class UsersService {
constructor(
@InjectRepository(User)
private readonly userRepository: Repository<User>,
) {}
async create(createUserDto: CreateUserDto): Promise<User> {
const utilisateurExistant = await this.userRepository.findOne({
where: { email: createUserDto.email },
});
if (utilisateurExistant) {
throw new ConflictException('Cet email est déjà utilisé.');
}
const motDePasseHache = await bcrypt.hash(createUserDto.motDePasse, 10);
const nouvelUtilisateur = this.userRepository.create({
...createUserDto,
motDePasse: motDePasseHache,
});
return this.userRepository.save(nouvelUtilisateur);
}
findAll(): Promise<User[]> {
return this.userRepository.find();
}
async findOne(id: string): Promise<User> {
const user = await this.userRepository.findOne({ where: { id } });
if (!user) {
throw new NotFoundException(`Utilisateur avec l'ID ${id} introuvable.`);
}
return user;
}
async update(id: string, updateUserDto: UpdateUserDto): Promise<User> {
const user = await this.findOne(id);
if (updateUserDto.motDePasse) {
updateUserDto.motDePasse = await bcrypt.hash(
updateUserDto.motDePasse,
10,
);
}
Object.assign(user, updateUserDto);
return this.userRepository.save(user);
}
async remove(id: string): Promise<void> {
const user = await this.findOne(id);
await this.userRepository.remove(user);
}
}

View File

@@ -6,6 +6,7 @@
"esModuleInterop": true, "esModuleInterop": true,
"isolatedModules": true, "isolatedModules": true,
"declaration": true, "declaration": true,
"types": ["jest", "node"],
"removeComments": true, "removeComments": true,
"emitDecoratorMetadata": true, "emitDecoratorMetadata": true,
"experimentalDecorators": true, "experimentalDecorators": true,

View File

@@ -1,5 +1,7 @@
{ {
"name": "@gim/shared", "name": "@gim/shared",
"version": "1.0.0", "version": "1.0.0",
"main": "index.js" "private": true,
} "main": "src/index.ts",
"types": "src/index.ts"
}

View File

@@ -0,0 +1,2 @@
export * from './users/user.interface';
export * from './persons/person.interface';

View File

@@ -0,0 +1,19 @@
/**
* Contrat pour les données envoyées lors de la création d'une personne
*/
export interface ICreatePersonneDTO {
numeroLicence: string;
nom: string;
prenom: string;
dateNaissance: string; // Format ISO: "YYYY-MM-DD"
surclasse?: boolean;
coach?: boolean;
}
/**
* Contrat pour les données renvoyées par l'API (profil public)
*/
export interface IPersonne extends ICreatePersonneDTO {
id: string;
}

View File

@@ -0,0 +1,20 @@
/**
* Contrat pour les données envoyées lors de l'inscription
*/
export interface ICreateUserDTO {
email: string;
motDePasse: string;
nom: string;
prenom: string;
}
/**
* Contrat pour les données renvoyées par l'API (profil public)
*/
export interface IUser {
id: string;
email: string;
nom: string;
prenom: string;
dateCreation: Date;
}