project refactoring en clean archi
This commit is contained in:
13
angular.json
13
angular.json
@@ -17,6 +17,9 @@
|
|||||||
"build": {
|
"build": {
|
||||||
"builder": "@angular-devkit/build-angular:application",
|
"builder": "@angular-devkit/build-angular:application",
|
||||||
"options": {
|
"options": {
|
||||||
|
"allowedCommonJsDependencies": [
|
||||||
|
"pocketbase"
|
||||||
|
],
|
||||||
"outputPath": "dist/",
|
"outputPath": "dist/",
|
||||||
"index": "src/index.html",
|
"index": "src/index.html",
|
||||||
"browser": "src/main.ts",
|
"browser": "src/main.ts",
|
||||||
@@ -88,6 +91,15 @@
|
|||||||
"options": {
|
"options": {
|
||||||
"buildTarget": "TrouveTonProfile:build"
|
"buildTarget": "TrouveTonProfile:build"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"lint": {
|
||||||
|
"builder": "@angular-eslint/builder:lint",
|
||||||
|
"options": {
|
||||||
|
"lintFilePatterns": [
|
||||||
|
"src/**/*.ts",
|
||||||
|
"src/**/*.html"
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -95,6 +107,7 @@
|
|||||||
"cli": {
|
"cli": {
|
||||||
"analytics": "dda3ec82-e13e-4042-ae63-71d138479518",
|
"analytics": "dda3ec82-e13e-4042-ae63-71d138479518",
|
||||||
"schematicCollections": [
|
"schematicCollections": [
|
||||||
|
"angular-eslint",
|
||||||
"angular-eslint"
|
"angular-eslint"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
43
eslint.config.js
Normal file
43
eslint.config.js
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
// @ts-check
|
||||||
|
const eslint = require("@eslint/js");
|
||||||
|
const tseslint = require("typescript-eslint");
|
||||||
|
const angular = require("angular-eslint");
|
||||||
|
|
||||||
|
module.exports = tseslint.config(
|
||||||
|
{
|
||||||
|
files: ["**/*.ts"],
|
||||||
|
extends: [
|
||||||
|
eslint.configs.recommended,
|
||||||
|
...tseslint.configs.recommended,
|
||||||
|
...tseslint.configs.stylistic,
|
||||||
|
...angular.configs.tsRecommended,
|
||||||
|
],
|
||||||
|
processor: angular.processInlineTemplates,
|
||||||
|
rules: {
|
||||||
|
"@angular-eslint/directive-selector": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
type: "attribute",
|
||||||
|
prefix: "app",
|
||||||
|
style: "camelCase",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"@angular-eslint/component-selector": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
type: "element",
|
||||||
|
prefix: "app",
|
||||||
|
style: "kebab-case",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files: ["**/*.html"],
|
||||||
|
extends: [
|
||||||
|
...angular.configs.templateRecommended,
|
||||||
|
...angular.configs.templateAccessibility,
|
||||||
|
],
|
||||||
|
rules: {},
|
||||||
|
}
|
||||||
|
);
|
||||||
@@ -1,17 +1,21 @@
|
|||||||
|
/** @type {import('jest').Config} */
|
||||||
module.exports = {
|
module.exports = {
|
||||||
preset: 'jest-preset-angular',
|
preset: 'jest-preset-angular',
|
||||||
rootDir: '.',
|
rootDir: '.',
|
||||||
setupFilesAfterEnv: ['<rootDir>/src/setup-jest.ts'],
|
setupFilesAfterEnv: ['<rootDir>/src/setup-jest.ts'],
|
||||||
testPathIgnorePatterns: ['<rootDir>/node_modules/', '<rootDir>/dist/'],
|
testPathIgnorePatterns: ['<rootDir>/node_modules/', '<rootDir>/dist/'],
|
||||||
|
testEnvironment: 'jsdom',
|
||||||
transform: {
|
transform: {
|
||||||
'^.+\\.ts$': 'ts-jest', // Only transform .ts files
|
'^.+\\.(ts|mjs|js|html)$': 'jest-preset-angular'
|
||||||
},
|
},
|
||||||
transformIgnorePatterns: [
|
transformIgnorePatterns: [
|
||||||
'/node_modules/(?!flat)/', // Exclude modules except 'flat' from transformation
|
'node_modules/(?!(@angular|rxjs|pocketbase|flat)/)', // 👈 simplifié et plus robuste
|
||||||
],
|
],
|
||||||
moduleNameMapper: {
|
moduleNameMapper: {
|
||||||
'^@app/(.*)$': '<rootDir>/src/app/$1',
|
'^@app/(.*)$': '<rootDir>/src/app/$1',
|
||||||
'^@env/(.*)$': '<rootDir>/src/environments/$1',
|
'^@env/(.*)$': '<rootDir>/src/environments/$1',
|
||||||
},
|
},
|
||||||
|
testMatch: ['**/*.spec.ts'],
|
||||||
testTimeout: 30000,
|
testTimeout: 30000,
|
||||||
|
verbose: true,
|
||||||
};
|
};
|
||||||
|
|||||||
3220
package-lock.json
generated
3220
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
13
package.json
13
package.json
@@ -11,11 +11,12 @@
|
|||||||
"tsc:watch": "tsc --noEmit --watch",
|
"tsc:watch": "tsc --noEmit --watch",
|
||||||
"format": "prettier --write \"src/**/*.{ts,html,scss,css,md,json}\"",
|
"format": "prettier --write \"src/**/*.{ts,html,scss,css,md,json}\"",
|
||||||
"check:all": "npm run format && npm run tsc && npm run lint && npm run test",
|
"check:all": "npm run format && npm run tsc && npm run lint && npm run test",
|
||||||
"test": "jest",
|
"test": "jest --no-warnings node_modules/jest/bin/jest.js",
|
||||||
"test:watch": "jest --watch",
|
"test:watch": "jest --watch",
|
||||||
"test:coverage": "jest --coverage",
|
"test:coverage": "jest --coverage",
|
||||||
"test:ci": "jest --runInBand",
|
"test:ci": "jest --runInBand",
|
||||||
"serve:ssr:TrouveTonProfile": "node dist/trouve-ton-profile/server/server.mjs"
|
"serve:ssr:TrouveTonProfile": "node dist/trouve-ton-profile/server/server.mjs",
|
||||||
|
"lint": "ng lint"
|
||||||
},
|
},
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -55,16 +56,20 @@
|
|||||||
"@types/jest": "^30.0.0",
|
"@types/jest": "^30.0.0",
|
||||||
"@types/node": "^18.18.0",
|
"@types/node": "^18.18.0",
|
||||||
"@types/node-fetch": "^2.6.13",
|
"@types/node-fetch": "^2.6.13",
|
||||||
|
"angular-eslint": "20.4.0",
|
||||||
"autoprefixer": "^10.4.20",
|
"autoprefixer": "^10.4.20",
|
||||||
|
"eslint": "^9.37.0",
|
||||||
"eslint-config-prettier": "^10.1.8",
|
"eslint-config-prettier": "^10.1.8",
|
||||||
"eslint-plugin-prettier": "^5.5.4",
|
"eslint-plugin-prettier": "^5.5.4",
|
||||||
"jest": "^29.7.0",
|
"jest": "^29.7.0",
|
||||||
"jest-environment-jsdom": "^29.7.0",
|
"jest-environment-jsdom": "^29.7.0",
|
||||||
"jest-preset-angular": "^14.6.1",
|
"jest-preset-angular": "^14.6.2",
|
||||||
"node-fetch": "^2.7.0",
|
"node-fetch": "^2.7.0",
|
||||||
"postcss": "^8.4.47",
|
"postcss": "^8.4.47",
|
||||||
"prettier": "^3.6.2",
|
"prettier": "^3.6.2",
|
||||||
"tailwindcss": "^3.4.12",
|
"tailwindcss": "^3.4.12",
|
||||||
"typescript": "~5.2.2"
|
"ts-jest": "^29.4.5",
|
||||||
|
"typescript": "~5.2.2",
|
||||||
|
"typescript-eslint": "8.46.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -13,6 +13,8 @@ import { provideAnimations } from '@angular/platform-browser/animations';
|
|||||||
import { provideToastr } from 'ngx-toastr';
|
import { provideToastr } from 'ngx-toastr';
|
||||||
import { PROFILE_REPOSITORY_TOKEN } from '@app/infrastructure/profiles/profile-repository.token';
|
import { PROFILE_REPOSITORY_TOKEN } from '@app/infrastructure/profiles/profile-repository.token';
|
||||||
import { PbProfileRepository } from '@app/infrastructure/profiles/pb-profile.repository';
|
import { PbProfileRepository } from '@app/infrastructure/profiles/pb-profile.repository';
|
||||||
|
import { PROJECT_REPOSITORY_TOKEN } from '@app/infrastructure/projects/project-repository.token';
|
||||||
|
import { PbProjectRepository } from '@app/infrastructure/projects/pb-project.repository';
|
||||||
|
|
||||||
export const appConfig: ApplicationConfig = {
|
export const appConfig: ApplicationConfig = {
|
||||||
providers: [
|
providers: [
|
||||||
@@ -28,6 +30,7 @@ export const appConfig: ApplicationConfig = {
|
|||||||
provideAnimations(),
|
provideAnimations(),
|
||||||
provideHttpClient(withFetch()),
|
provideHttpClient(withFetch()),
|
||||||
{ provide: PROFILE_REPOSITORY_TOKEN, useExisting: PbProfileRepository },
|
{ provide: PROFILE_REPOSITORY_TOKEN, useExisting: PbProfileRepository },
|
||||||
|
{ provide: PROJECT_REPOSITORY_TOKEN, useExisting: PbProjectRepository },
|
||||||
provideToastr({
|
provideToastr({
|
||||||
timeOut: 10000,
|
timeOut: 10000,
|
||||||
positionClass: 'toast-top-right',
|
positionClass: 'toast-top-right',
|
||||||
|
|||||||
@@ -1,27 +0,0 @@
|
|||||||
import { TestBed } from '@angular/core/testing';
|
|
||||||
|
|
||||||
import { ProjectService } from './project.service';
|
|
||||||
import { Router } from '@angular/router';
|
|
||||||
|
|
||||||
describe('ProjectService', () => {
|
|
||||||
let service: ProjectService;
|
|
||||||
|
|
||||||
const routerSpy = {
|
|
||||||
navigate: jest.fn(),
|
|
||||||
navigateByUrl: jest.fn(),
|
|
||||||
};
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
TestBed.configureTestingModule({
|
|
||||||
providers: [
|
|
||||||
{ provide: Router, useValue: routerSpy }, // <<— spy: neutralise la navigation
|
|
||||||
],
|
|
||||||
imports: [],
|
|
||||||
});
|
|
||||||
service = TestBed.inject(ProjectService);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should be created', () => {
|
|
||||||
expect(service).toBeTruthy();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
import { Injectable } from '@angular/core';
|
|
||||||
import PocketBase from 'pocketbase';
|
|
||||||
import { environment } from '@env/environment.development';
|
|
||||||
import { from } from 'rxjs';
|
|
||||||
import { Project } from '@app/shared/models/project';
|
|
||||||
import { ProjectDto } from '@app/shared/models/project-dto';
|
|
||||||
|
|
||||||
@Injectable({
|
|
||||||
providedIn: 'root',
|
|
||||||
})
|
|
||||||
export class ProjectService {
|
|
||||||
createProject(projectDto: ProjectDto) {
|
|
||||||
const pb = new PocketBase(environment.baseUrl);
|
|
||||||
return from(pb.collection('projets').create<Project>(projectDto));
|
|
||||||
}
|
|
||||||
|
|
||||||
getProjectByUserId(userId: string) {
|
|
||||||
const pb = new PocketBase(environment.baseUrl);
|
|
||||||
return from(
|
|
||||||
pb.collection<Project>('projets').getFullList({ filter: `utilisateur='${userId}'` })
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
getProjectById(id: string) {
|
|
||||||
const pb = new PocketBase(environment.baseUrl);
|
|
||||||
return from(pb.collection<Project>('projets').getOne<Project>(id));
|
|
||||||
}
|
|
||||||
|
|
||||||
updateProject(id: string, data: Project | any) {
|
|
||||||
const pb = new PocketBase(environment.baseUrl);
|
|
||||||
return from(pb.collection('projets').update<Project>(id, data));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
7
src/app/domain/action-type.util.ts
Normal file
7
src/app/domain/action-type.util.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
export enum ActionType {
|
||||||
|
CREATE = 'CREATE',
|
||||||
|
READ = 'READ',
|
||||||
|
UPDATE = 'UPDATE',
|
||||||
|
DELETE = 'DELETE',
|
||||||
|
NONE = 'NONE',
|
||||||
|
}
|
||||||
7
src/app/domain/error-response.util.ts
Normal file
7
src/app/domain/error-response.util.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import { ActionType } from '@app/domain/action-type.util';
|
||||||
|
|
||||||
|
export interface ErrorResponse {
|
||||||
|
action: ActionType;
|
||||||
|
hasError: boolean;
|
||||||
|
message?: string | null;
|
||||||
|
}
|
||||||
6
src/app/domain/loader-action.util.ts
Normal file
6
src/app/domain/loader-action.util.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
import { ActionType } from '@app/domain/action-type.util';
|
||||||
|
|
||||||
|
export interface LoaderAction {
|
||||||
|
action: ActionType;
|
||||||
|
isLoading: boolean;
|
||||||
|
}
|
||||||
5
src/app/domain/profiles/dto/create-profile.dto.ts
Normal file
5
src/app/domain/profiles/dto/create-profile.dto.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
export interface ProfileDTO {
|
||||||
|
profession: string;
|
||||||
|
utilisateur: string;
|
||||||
|
reseaux: any;
|
||||||
|
}
|
||||||
6
src/app/domain/projects/dto/create-project.dto.ts
Normal file
6
src/app/domain/projects/dto/create-project.dto.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
export interface CreateProjectDto {
|
||||||
|
nom: string;
|
||||||
|
description: string;
|
||||||
|
lien: string;
|
||||||
|
utilisateur: string;
|
||||||
|
}
|
||||||
10
src/app/domain/projects/project.model.ts
Normal file
10
src/app/domain/projects/project.model.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
export interface Project {
|
||||||
|
id: string;
|
||||||
|
created: string;
|
||||||
|
updated: string;
|
||||||
|
nom: string;
|
||||||
|
lien: string;
|
||||||
|
description: string;
|
||||||
|
fichier: string[];
|
||||||
|
utilisateur: string;
|
||||||
|
}
|
||||||
10
src/app/domain/projects/project.repository.ts
Normal file
10
src/app/domain/projects/project.repository.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import { Observable } from 'rxjs';
|
||||||
|
import { Project } from '@app/shared/models/project';
|
||||||
|
import { CreateProjectDto } from '@app/domain/projects/dto/create-project.dto';
|
||||||
|
|
||||||
|
export interface ProjectRepository {
|
||||||
|
create(projectDto: CreateProjectDto): Observable<Project>;
|
||||||
|
list(userId: string): Observable<Project[]>;
|
||||||
|
get(projectId: string): Observable<Project>;
|
||||||
|
update(id: string, data: Project | any): Observable<Project>;
|
||||||
|
}
|
||||||
@@ -4,12 +4,7 @@ import { Profile } from '@app/domain/profiles/profile.model';
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import PocketBase from 'pocketbase';
|
import PocketBase from 'pocketbase';
|
||||||
import { environment } from '@env/environment';
|
import { environment } from '@env/environment';
|
||||||
|
import { ProfileDTO } from '@app/domain/profiles/dto/create-profile.dto';
|
||||||
interface ProfileDTO {
|
|
||||||
profession: string;
|
|
||||||
utilisateur: string;
|
|
||||||
reseaux: any;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Injectable({ providedIn: 'root' })
|
@Injectable({ providedIn: 'root' })
|
||||||
export class PbProfileRepository implements ProfileRepository {
|
export class PbProfileRepository implements ProfileRepository {
|
||||||
|
|||||||
29
src/app/infrastructure/projects/pb-project.repository.ts
Normal file
29
src/app/infrastructure/projects/pb-project.repository.ts
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { environment } from '@env/environment';
|
||||||
|
import { ProjectRepository } from '@app/domain/projects/project.repository';
|
||||||
|
import { Project } from '@app/shared/models/project';
|
||||||
|
import { from, Observable } from 'rxjs';
|
||||||
|
import PocketBase from 'pocketbase';
|
||||||
|
import { CreateProjectDto } from '@app/domain/projects/dto/create-project.dto';
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root',
|
||||||
|
})
|
||||||
|
export class PbProjectRepository implements ProjectRepository {
|
||||||
|
private pb = new PocketBase(environment.baseUrl);
|
||||||
|
|
||||||
|
create(project: CreateProjectDto): Observable<Project> {
|
||||||
|
return from(this.pb.collection('projets').create<Project>(project));
|
||||||
|
}
|
||||||
|
list(userId: string): Observable<Project[]> {
|
||||||
|
return from(
|
||||||
|
this.pb.collection<Project>('projets').getFullList({ filter: `utilisateur='${userId}'` })
|
||||||
|
);
|
||||||
|
}
|
||||||
|
get(projectId: string): Observable<Project> {
|
||||||
|
return from(this.pb.collection<Project>('projets').getOne<Project>(projectId));
|
||||||
|
}
|
||||||
|
update(id: string, data: any): Observable<Project> {
|
||||||
|
return from(this.pb.collection('projets').update<Project>(id, data));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
import { InjectionToken } from '@angular/core';
|
||||||
|
import { ProjectRepository } from '@app/domain/projects/project.repository';
|
||||||
|
|
||||||
|
export const PROJECT_REPOSITORY_TOKEN = new InjectionToken<ProjectRepository>('ProjectRepository');
|
||||||
@@ -8,7 +8,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="max-w-6xl mx-auto px-4">
|
<div class="max-w-6xl mx-auto px-4">
|
||||||
@if (loading()) {
|
@if (loading().isLoading) {
|
||||||
<div class="flex justify-center items-center h-96">
|
<div class="flex justify-center items-center h-96">
|
||||||
<p>Chargement...</p>
|
<p>Chargement...</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { Component, inject, OnInit } from '@angular/core';
|
import { Component, inject, OnInit } from '@angular/core';
|
||||||
import { SearchComponent } from '@app/shared/features/search/search.component';
|
import { SearchComponent } from '@app/shared/features/search/search.component';
|
||||||
import { DisplayProfileCardComponent } from '@app/shared/features/display-profile-card/display-profile-card.component';
|
|
||||||
import { VerticalProfileListComponent } from '@app/shared/components/vertical-profile-list/vertical-profile-list.component';
|
import { VerticalProfileListComponent } from '@app/shared/components/vertical-profile-list/vertical-profile-list.component';
|
||||||
import { UntilDestroy } from '@ngneat/until-destroy';
|
import { UntilDestroy } from '@ngneat/until-destroy';
|
||||||
import { ProfileFacade } from '@app/ui/profiles/profile.facade';
|
import { ProfileFacade } from '@app/ui/profiles/profile.facade';
|
||||||
@@ -8,7 +7,7 @@ import { ProfileFacade } from '@app/ui/profiles/profile.facade';
|
|||||||
@Component({
|
@Component({
|
||||||
selector: 'app-profile-list',
|
selector: 'app-profile-list',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [SearchComponent, DisplayProfileCardComponent, VerticalProfileListComponent],
|
imports: [SearchComponent, VerticalProfileListComponent],
|
||||||
templateUrl: './profile-list.component.html',
|
templateUrl: './profile-list.component.html',
|
||||||
styleUrl: './profile-list.component.scss',
|
styleUrl: './profile-list.component.scss',
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
@if (project) {
|
@if (project()) {
|
||||||
<div class="bg-white rounded-2xl border p-6 max-w-sm">
|
<div class="bg-white rounded-2xl border p-6 max-w-sm">
|
||||||
<div class="">
|
<div class="">
|
||||||
<h3 class="text-lg font-bold text-gray-800 mb-3">{{ project.nom }}</h3>
|
<h3 class="text-lg font-bold text-gray-800 mb-3">{{ project().nom }}</h3>
|
||||||
<p class="text-gray-800 text-sm">{{ project.description }}</p>
|
<p class="text-gray-800 text-sm">{{ project().description }}</p>
|
||||||
<div class="mt-6">
|
<div class="mt-6">
|
||||||
<a
|
<a
|
||||||
[routerLink]="[]"
|
[routerLink]="[]"
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
import { Component, inject, Input, OnInit } from '@angular/core';
|
import { Component, inject, Input, OnInit } from '@angular/core';
|
||||||
import { AuthService } from '@app/core/services/authentication/auth.service';
|
import { AuthService } from '@app/core/services/authentication/auth.service';
|
||||||
import { ProjectService } from '@app/core/services/project/project.service';
|
|
||||||
import { Project } from '@app/shared/models/project';
|
|
||||||
import { environment } from '@env/environment';
|
import { environment } from '@env/environment';
|
||||||
import { RouterLink } from '@angular/router';
|
import { RouterLink } from '@angular/router';
|
||||||
|
import { ProjectFacade } from '@app/ui/projects/project.facade';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-my-profile-project-item',
|
selector: 'app-my-profile-project-item',
|
||||||
@@ -17,11 +16,10 @@ export class MyProfileProjectItemComponent implements OnInit {
|
|||||||
@Input({ required: true }) projectId = '';
|
@Input({ required: true }) projectId = '';
|
||||||
protected authService = inject(AuthService);
|
protected authService = inject(AuthService);
|
||||||
|
|
||||||
protected projectService = inject(ProjectService);
|
private readonly projectFacade = new ProjectFacade();
|
||||||
|
protected project = this.projectFacade.project;
|
||||||
protected project: Project | undefined = undefined;
|
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.projectService.getProjectById(this.projectId).subscribe((value) => (this.project = value));
|
this.projectFacade.loadOne(this.projectId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
<div class="max-w-4xl max-lg:max-w-2xl max-sm:max-w-sm mx-auto">
|
<div class="max-w-4xl max-lg:max-w-2xl max-sm:max-w-sm mx-auto">
|
||||||
<h2 class="text-2xl font-bold text-gray-800 mb-8">Mes projets</h2>
|
<h2 class="text-2xl font-bold text-gray-800 mb-8">Mes projets</h2>
|
||||||
|
|
||||||
@if (projects) {
|
@if (projects()) {
|
||||||
<div class="relative flex items-center">
|
<div class="relative flex items-center">
|
||||||
<select
|
<select
|
||||||
[(ngModel)]="projectIdSelected"
|
[(ngModel)]="projectIdSelected"
|
||||||
@@ -12,7 +12,7 @@
|
|||||||
|
|
||||||
<option [value]="'add'.toLowerCase()">Ajouter un nouveau projet</option>
|
<option [value]="'add'.toLowerCase()">Ajouter un nouveau projet</option>
|
||||||
|
|
||||||
@for (project of projects; track project.id) {
|
@for (project of projects(); track project.id) {
|
||||||
<option [value]="project.id">
|
<option [value]="project.id">
|
||||||
{{ project.nom }}
|
{{ project.nom }}
|
||||||
</option>
|
</option>
|
||||||
|
|||||||
@@ -1,24 +1,14 @@
|
|||||||
import { Component, inject, Input, OnInit, signal } from '@angular/core';
|
import { Component, Input, OnInit, signal } from '@angular/core';
|
||||||
import { MyProfileProjectItemComponent } from '@app/shared/components/my-profile-project-item/my-profile-project-item.component';
|
|
||||||
import { PaginatorModule } from 'primeng/paginator';
|
import { PaginatorModule } from 'primeng/paginator';
|
||||||
import { ReactiveFormsModule } from '@angular/forms';
|
import { ReactiveFormsModule } from '@angular/forms';
|
||||||
import { ProjectService } from '@app/core/services/project/project.service';
|
|
||||||
import { AsyncPipe, JsonPipe } from '@angular/common';
|
|
||||||
import { Project } from '@app/shared/models/project';
|
|
||||||
import { UntilDestroy } from '@ngneat/until-destroy';
|
import { UntilDestroy } from '@ngneat/until-destroy';
|
||||||
import { MyProfileUpdateProjectFormComponent } from '@app/shared/components/my-profile-update-project-form/my-profile-update-project-form.component';
|
import { MyProfileUpdateProjectFormComponent } from '@app/shared/components/my-profile-update-project-form/my-profile-update-project-form.component';
|
||||||
|
import { ProjectFacade } from '@app/ui/projects/project.facade';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-my-profile-project-list',
|
selector: 'app-my-profile-project-list',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [
|
imports: [PaginatorModule, ReactiveFormsModule, MyProfileUpdateProjectFormComponent],
|
||||||
MyProfileProjectItemComponent,
|
|
||||||
PaginatorModule,
|
|
||||||
ReactiveFormsModule,
|
|
||||||
AsyncPipe,
|
|
||||||
JsonPipe,
|
|
||||||
MyProfileUpdateProjectFormComponent,
|
|
||||||
],
|
|
||||||
templateUrl: './my-profile-project-list.component.html',
|
templateUrl: './my-profile-project-list.component.html',
|
||||||
styleUrl: './my-profile-project-list.component.scss',
|
styleUrl: './my-profile-project-list.component.scss',
|
||||||
})
|
})
|
||||||
@@ -26,15 +16,14 @@ import { MyProfileUpdateProjectFormComponent } from '@app/shared/components/my-p
|
|||||||
export class MyProfileProjectListComponent implements OnInit {
|
export class MyProfileProjectListComponent implements OnInit {
|
||||||
@Input({ required: true }) projectIds: string[] = [];
|
@Input({ required: true }) projectIds: string[] = [];
|
||||||
@Input({ required: true }) userId = '';
|
@Input({ required: true }) userId = '';
|
||||||
protected projectService = inject(ProjectService);
|
|
||||||
protected projectIdSelected = signal<string | null>(null);
|
protected projectIdSelected = signal<string | null>(null);
|
||||||
|
|
||||||
protected projects: Project[] = [];
|
private readonly projectFacade = new ProjectFacade();
|
||||||
|
protected projects = this.projectFacade.projects;
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.projectService
|
this.projectFacade.load(this.userId);
|
||||||
.getProjectByUserId(this.userId)
|
|
||||||
.subscribe((value) => (this.projects = value));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onProjectFormSubmitted($event: string | null) {
|
onProjectFormSubmitted($event: string | null) {
|
||||||
|
|||||||
@@ -2,13 +2,21 @@
|
|||||||
@if (projectId == 'add'.toLowerCase()) {
|
@if (projectId == 'add'.toLowerCase()) {
|
||||||
<app-project-picture-form [project]="undefined" />
|
<app-project-picture-form [project]="undefined" />
|
||||||
} @else {
|
} @else {
|
||||||
<app-project-picture-form [project]="project" />
|
<app-project-picture-form [project]="project()" />
|
||||||
}
|
}
|
||||||
|
|
||||||
<h3 class="font-ubuntu w-full text-start font-bold text-xl uppercase dark:text-white my-5">
|
<h3 class="font-ubuntu w-full text-start font-bold text-xl uppercase dark:text-white my-5">
|
||||||
Information du projet
|
Information du projet
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
|
@if (loading().isLoading) {
|
||||||
|
@switch (loading().action) {
|
||||||
|
@case (ActionType.NONE || ActionType.CREATE || ActionType.DELETE) {}
|
||||||
|
@default {
|
||||||
|
<p>Chargement...</p>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} @else {
|
||||||
<form class="mx-8" [formGroup]="projectForm" (ngSubmit)="onSubmit()">
|
<form class="mx-8" [formGroup]="projectForm" (ngSubmit)="onSubmit()">
|
||||||
<label class="mb-2 text-sm text-black block dark:text-white">Nom</label>
|
<label class="mb-2 text-sm text-black block dark:text-white">Nom</label>
|
||||||
<div class="relative flex items-center">
|
<div class="relative flex items-center">
|
||||||
@@ -86,4 +94,5 @@
|
|||||||
Annuler
|
Annuler
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import { ComponentFixture, TestBed } from '@angular/core/testing';
|
|||||||
import { MyProfileUpdateProjectFormComponent } from './my-profile-update-project-form.component';
|
import { MyProfileUpdateProjectFormComponent } from './my-profile-update-project-form.component';
|
||||||
import { AuthService } from '@app/core/services/authentication/auth.service';
|
import { AuthService } from '@app/core/services/authentication/auth.service';
|
||||||
import { ToastrService } from 'ngx-toastr';
|
import { ToastrService } from 'ngx-toastr';
|
||||||
import { ProjectService } from '@app/core/services/project/project.service';
|
|
||||||
import { provideRouter } from '@angular/router';
|
import { provideRouter } from '@angular/router';
|
||||||
import { signal } from '@angular/core';
|
import { signal } from '@angular/core';
|
||||||
import { Auth } from '@app/shared/models/auth';
|
import { Auth } from '@app/shared/models/auth';
|
||||||
@@ -12,7 +11,6 @@ describe('MyProfileUpdateProjectFormComponent', () => {
|
|||||||
let component: MyProfileUpdateProjectFormComponent;
|
let component: MyProfileUpdateProjectFormComponent;
|
||||||
let fixture: ComponentFixture<MyProfileUpdateProjectFormComponent>;
|
let fixture: ComponentFixture<MyProfileUpdateProjectFormComponent>;
|
||||||
|
|
||||||
let mockProjectService: Partial<ProjectService>;
|
|
||||||
let mockAuthService: Partial<AuthService>;
|
let mockAuthService: Partial<AuthService>;
|
||||||
let mockToastrService: Partial<ToastrService>;
|
let mockToastrService: Partial<ToastrService>;
|
||||||
|
|
||||||
@@ -27,15 +25,12 @@ describe('MyProfileUpdateProjectFormComponent', () => {
|
|||||||
user: signal<Auth | undefined>(undefined),
|
user: signal<Auth | undefined>(undefined),
|
||||||
};
|
};
|
||||||
|
|
||||||
mockProjectService = {};
|
|
||||||
|
|
||||||
await TestBed.configureTestingModule({
|
await TestBed.configureTestingModule({
|
||||||
imports: [MyProfileUpdateProjectFormComponent],
|
imports: [MyProfileUpdateProjectFormComponent],
|
||||||
providers: [
|
providers: [
|
||||||
provideRouter([]),
|
provideRouter([]),
|
||||||
{ provide: AuthService, useValue: mockAuthService },
|
{ provide: AuthService, useValue: mockAuthService },
|
||||||
{ provide: ToastrService, useValue: mockToastrService },
|
{ provide: ToastrService, useValue: mockToastrService },
|
||||||
{ provide: ProjectService, useValue: mockProjectService },
|
|
||||||
],
|
],
|
||||||
}).compileComponents();
|
}).compileComponents();
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,22 @@
|
|||||||
import { Component, inject, Input, OnChanges, OnInit, output, SimpleChanges } from '@angular/core';
|
import {
|
||||||
|
Component,
|
||||||
|
effect,
|
||||||
|
inject,
|
||||||
|
Input,
|
||||||
|
OnChanges,
|
||||||
|
OnInit,
|
||||||
|
output,
|
||||||
|
SimpleChanges,
|
||||||
|
} from '@angular/core';
|
||||||
import { FormBuilder, FormControl, ReactiveFormsModule, Validators } from '@angular/forms';
|
import { FormBuilder, FormControl, ReactiveFormsModule, Validators } from '@angular/forms';
|
||||||
import { ProjectService } from '@app/core/services/project/project.service';
|
|
||||||
import { Project } from '@app/shared/models/project';
|
|
||||||
import { NgClass } from '@angular/common';
|
import { NgClass } from '@angular/common';
|
||||||
import { PaginatorModule } from 'primeng/paginator';
|
import { PaginatorModule } from 'primeng/paginator';
|
||||||
import { ProjectPictureFormComponent } from '@app/shared/components/project-picture-form/project-picture-form.component';
|
import { ProjectPictureFormComponent } from '@app/shared/components/project-picture-form/project-picture-form.component';
|
||||||
import { ToastrService } from 'ngx-toastr';
|
import { ToastrService } from 'ngx-toastr';
|
||||||
import { AuthService } from '@app/core/services/authentication/auth.service';
|
import { AuthService } from '@app/core/services/authentication/auth.service';
|
||||||
import { ProjectDto } from '@app/shared/models/project-dto';
|
import { ProjectFacade } from '@app/ui/projects/project.facade';
|
||||||
|
import { CreateProjectDto } from '@app/domain/projects/dto/create-project.dto';
|
||||||
|
import { ActionType } from '@app/domain/action-type.util';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-my-profile-update-project-form',
|
selector: 'app-my-profile-update-project-form',
|
||||||
@@ -19,11 +28,15 @@ import { ProjectDto } from '@app/shared/models/project-dto';
|
|||||||
export class MyProfileUpdateProjectFormComponent implements OnInit, OnChanges {
|
export class MyProfileUpdateProjectFormComponent implements OnInit, OnChanges {
|
||||||
@Input({ required: true }) projectId: string | null = null;
|
@Input({ required: true }) projectId: string | null = null;
|
||||||
|
|
||||||
protected project: Project | undefined = undefined;
|
|
||||||
private readonly toastrService = inject(ToastrService);
|
private readonly toastrService = inject(ToastrService);
|
||||||
private readonly authService = inject(AuthService);
|
private readonly authService = inject(AuthService);
|
||||||
|
|
||||||
protected readonly projectService = inject(ProjectService);
|
private readonly projectFacade = new ProjectFacade();
|
||||||
|
protected readonly ActionType = ActionType;
|
||||||
|
protected readonly project = this.projectFacade.project;
|
||||||
|
protected readonly loading = this.projectFacade.loading;
|
||||||
|
protected readonly error = this.projectFacade.error;
|
||||||
|
|
||||||
private readonly formBuilder = inject(FormBuilder);
|
private readonly formBuilder = inject(FormBuilder);
|
||||||
|
|
||||||
protected projectForm = this.formBuilder.group({
|
protected projectForm = this.formBuilder.group({
|
||||||
@@ -34,6 +47,32 @@ export class MyProfileUpdateProjectFormComponent implements OnInit, OnChanges {
|
|||||||
|
|
||||||
formIsUpdated = output<string | null>();
|
formIsUpdated = output<string | null>();
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
let message = '';
|
||||||
|
effect(() => {
|
||||||
|
if (!this.loading().isLoading) {
|
||||||
|
switch (this.loading().action) {
|
||||||
|
case ActionType.CREATE:
|
||||||
|
message = `Le projet ${this.projectForm.getRawValue().nom} a bien été créer !`;
|
||||||
|
this.customToast(ActionType.CREATE, message);
|
||||||
|
break;
|
||||||
|
case ActionType.UPDATE:
|
||||||
|
message = `Les informations du projet ${this.projectForm.getRawValue().nom} ont bien été modifier !`;
|
||||||
|
this.customToast(ActionType.UPDATE, message);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.project() !== undefined) {
|
||||||
|
this.projectForm.setValue({
|
||||||
|
nom: this.project()!.nom,
|
||||||
|
description: this.project()!.description,
|
||||||
|
lien: this.project()!.lien,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
if (this.projectId == 'add'.toLowerCase()) {
|
if (this.projectId == 'add'.toLowerCase()) {
|
||||||
this.projectForm.setValue({
|
this.projectForm.setValue({
|
||||||
@@ -44,14 +83,7 @@ export class MyProfileUpdateProjectFormComponent implements OnInit, OnChanges {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (this.projectId != null && this.projectId != 'add'.toLowerCase()) {
|
if (this.projectId != null && this.projectId != 'add'.toLowerCase()) {
|
||||||
this.projectService.getProjectById(this.projectId).subscribe((value) => {
|
this.projectFacade.loadOne(this.projectId);
|
||||||
this.project = value;
|
|
||||||
this.projectForm.setValue({
|
|
||||||
nom: value.nom,
|
|
||||||
description: value.description,
|
|
||||||
lien: value.lien,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,39 +94,15 @@ export class MyProfileUpdateProjectFormComponent implements OnInit, OnChanges {
|
|||||||
|
|
||||||
if (this.projectId != null && this.projectId != 'add'.toLowerCase()) {
|
if (this.projectId != null && this.projectId != 'add'.toLowerCase()) {
|
||||||
// Update
|
// Update
|
||||||
|
this.projectFacade.update(this.project()!.id, this.projectForm.getRawValue());
|
||||||
this.projectService
|
|
||||||
.updateProject(this.project!.id, this.projectForm.getRawValue())
|
|
||||||
.subscribe((value) => {
|
|
||||||
this.formIsUpdated.emit(value.id);
|
|
||||||
|
|
||||||
this.toastrService.success(
|
|
||||||
`Les informations du projet ${value.nom} ont bien été modifier !`,
|
|
||||||
`Mise à jour`,
|
|
||||||
{
|
|
||||||
closeButton: true,
|
|
||||||
progressAnimation: 'decreasing',
|
|
||||||
progressBar: true,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
// Create
|
// Create
|
||||||
|
const projectDto: CreateProjectDto = {
|
||||||
const projectDto: ProjectDto = {
|
|
||||||
...this.projectForm.getRawValue(),
|
...this.projectForm.getRawValue(),
|
||||||
utilisateur: this.authService.user()!.record!.id,
|
utilisateur: this.authService.user()!.record!.id,
|
||||||
} as ProjectDto;
|
} as CreateProjectDto;
|
||||||
|
|
||||||
this.projectService.createProject(projectDto).subscribe((value) => {
|
this.projectFacade.create(projectDto);
|
||||||
this.formIsUpdated.emit(value.id);
|
|
||||||
|
|
||||||
this.toastrService.success(`Le projet ${value.nom} a bien été créer !`, `Nouveau projet`, {
|
|
||||||
closeButton: true,
|
|
||||||
progressAnimation: 'decreasing',
|
|
||||||
progressBar: true,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -102,4 +110,30 @@ export class MyProfileUpdateProjectFormComponent implements OnInit, OnChanges {
|
|||||||
this.projectId = changes['projectId'].currentValue;
|
this.projectId = changes['projectId'].currentValue;
|
||||||
this.ngOnInit();
|
this.ngOnInit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private customToast(action: ActionType, message: string): void {
|
||||||
|
if (this.error().hasError) {
|
||||||
|
this.toastrService.error(
|
||||||
|
`Une erreur s'est produite, veuillez réessayer ulterieurement`,
|
||||||
|
`Erreur`,
|
||||||
|
{
|
||||||
|
closeButton: true,
|
||||||
|
progressAnimation: 'decreasing',
|
||||||
|
progressBar: true,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.formIsUpdated.emit(this.project()!.id);
|
||||||
|
this.toastrService.success(
|
||||||
|
`${message}`,
|
||||||
|
`${action === ActionType.UPDATE ? 'Mise à jour' : 'Nouveau projet'}`,
|
||||||
|
{
|
||||||
|
closeButton: true,
|
||||||
|
progressAnimation: 'decreasing',
|
||||||
|
progressBar: true,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +1,16 @@
|
|||||||
import { Component, Input } from '@angular/core';
|
import { Component, Input } from '@angular/core';
|
||||||
import { Project } from '@app/shared/models/project';
|
|
||||||
import { JsonPipe } from '@angular/common';
|
|
||||||
import { environment } from '@env/environment';
|
import { environment } from '@env/environment';
|
||||||
import { RouterLink } from '@angular/router';
|
import { ProjectViewModel } from '@app/ui/projects/project.presenter.model';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-project-item',
|
selector: 'app-project-item',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [JsonPipe, RouterLink],
|
imports: [],
|
||||||
templateUrl: './project-item.component.html',
|
templateUrl: './project-item.component.html',
|
||||||
styleUrl: './project-item.component.scss',
|
styleUrl: './project-item.component.scss',
|
||||||
})
|
})
|
||||||
export class ProjectItemComponent {
|
export class ProjectItemComponent {
|
||||||
protected readonly environment = environment;
|
protected readonly environment = environment;
|
||||||
|
|
||||||
@Input({ required: true }) project: Project | undefined = undefined;
|
@Input({ required: true }) project: ProjectViewModel | undefined = undefined;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
<div class="max-w-4xl max-lg:max-w-2xl max-sm:max-w-sm mx-auto">
|
<div class="max-w-4xl max-lg:max-w-2xl max-sm:max-w-sm mx-auto">
|
||||||
<h2 class="text-2xl font-bold text-gray-800 mb-8">Explorer les projets</h2>
|
<h2 class="text-2xl font-bold text-gray-800 mb-8">Explorer les projets</h2>
|
||||||
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
|
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||||
@for (project of projects; track project) {
|
@for (project of projects(); track project) {
|
||||||
<app-project-item [project]="project" />
|
<app-project-item [project]="project" />
|
||||||
} @empty {
|
} @empty {
|
||||||
<p>Aucun projet</p>
|
<p>Aucun projet</p>
|
||||||
|
|||||||
@@ -1,14 +1,12 @@
|
|||||||
import { Component, inject, Input, OnInit } from '@angular/core';
|
import { Component, Input, OnInit } from '@angular/core';
|
||||||
import { UntilDestroy } from '@ngneat/until-destroy';
|
import { UntilDestroy } from '@ngneat/until-destroy';
|
||||||
import { ProjectItemComponent } from '@app/shared/components/project-item/project-item.component';
|
import { ProjectItemComponent } from '@app/shared/components/project-item/project-item.component';
|
||||||
import { JsonPipe } from '@angular/common';
|
import { ProjectFacade } from '@app/ui/projects/project.facade';
|
||||||
import { ProjectService } from '@app/core/services/project/project.service';
|
|
||||||
import { Project } from '@app/shared/models/project';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-project-list',
|
selector: 'app-project-list',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [ProjectItemComponent, JsonPipe],
|
imports: [ProjectItemComponent],
|
||||||
templateUrl: './project-list.component.html',
|
templateUrl: './project-list.component.html',
|
||||||
styleUrl: './project-list.component.scss',
|
styleUrl: './project-list.component.scss',
|
||||||
})
|
})
|
||||||
@@ -16,13 +14,11 @@ import { Project } from '@app/shared/models/project';
|
|||||||
export class ProjectListComponent implements OnInit {
|
export class ProjectListComponent implements OnInit {
|
||||||
@Input({ required: true }) userProjectId = '';
|
@Input({ required: true }) userProjectId = '';
|
||||||
|
|
||||||
protected readonly projectService = inject(ProjectService);
|
private readonly projectFacade = new ProjectFacade();
|
||||||
|
|
||||||
protected projects: Project[] = [];
|
protected projects = this.projectFacade.projects;
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.projectService
|
this.projectFacade.load(this.userProjectId);
|
||||||
.getProjectByUserId(this.userProjectId)
|
|
||||||
.subscribe((value) => (this.projects = value));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import { ComponentFixture, TestBed } from '@angular/core/testing';
|
|||||||
|
|
||||||
import { ProjectPictureFormComponent } from './project-picture-form.component';
|
import { ProjectPictureFormComponent } from './project-picture-form.component';
|
||||||
import { provideRouter } from '@angular/router';
|
import { provideRouter } from '@angular/router';
|
||||||
import { ProjectService } from '@app/core/services/project/project.service';
|
|
||||||
import { ToastrService } from 'ngx-toastr';
|
import { ToastrService } from 'ngx-toastr';
|
||||||
import { AuthService } from '@app/core/services/authentication/auth.service';
|
import { AuthService } from '@app/core/services/authentication/auth.service';
|
||||||
|
|
||||||
@@ -10,16 +9,10 @@ describe('ProjectPictureFormComponent', () => {
|
|||||||
let component: ProjectPictureFormComponent;
|
let component: ProjectPictureFormComponent;
|
||||||
let fixture: ComponentFixture<ProjectPictureFormComponent>;
|
let fixture: ComponentFixture<ProjectPictureFormComponent>;
|
||||||
|
|
||||||
let mockProjectService: Partial<ProjectService>;
|
|
||||||
let mockToastrService: Partial<ToastrService>;
|
let mockToastrService: Partial<ToastrService>;
|
||||||
let mockAuthService: Partial<AuthService>;
|
let mockAuthService: Partial<AuthService>;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
mockProjectService = {
|
|
||||||
updateProject: jest.fn().mockReturnValue({
|
|
||||||
subscribe: jest.fn(),
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
mockToastrService = {
|
mockToastrService = {
|
||||||
success: jest.fn(),
|
success: jest.fn(),
|
||||||
error: jest.fn(),
|
error: jest.fn(),
|
||||||
@@ -33,7 +26,6 @@ describe('ProjectPictureFormComponent', () => {
|
|||||||
imports: [ProjectPictureFormComponent],
|
imports: [ProjectPictureFormComponent],
|
||||||
providers: [
|
providers: [
|
||||||
provideRouter([]),
|
provideRouter([]),
|
||||||
{ provide: ProjectService, useValue: mockProjectService },
|
|
||||||
{ provide: ToastrService, useValue: mockToastrService },
|
{ provide: ToastrService, useValue: mockToastrService },
|
||||||
{ provide: AuthService, useValue: mockAuthService },
|
{ provide: AuthService, useValue: mockAuthService },
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
import { Component, inject, Input, output } from '@angular/core';
|
import { Component, effect, inject, Input, output } from '@angular/core';
|
||||||
import { AuthService } from '@app/core/services/authentication/auth.service';
|
import { AuthService } from '@app/core/services/authentication/auth.service';
|
||||||
import { Project } from '@app/shared/models/project';
|
|
||||||
import { ProjectService } from '@app/core/services/project/project.service';
|
|
||||||
import { NgClass } from '@angular/common';
|
import { NgClass } from '@angular/common';
|
||||||
import { environment } from '@env/environment';
|
import { environment } from '@env/environment';
|
||||||
import { ToastrService } from 'ngx-toastr';
|
import { ToastrService } from 'ngx-toastr';
|
||||||
|
import { ProjectViewModel } from '@app/ui/projects/project.presenter.model';
|
||||||
|
import { ProjectFacade } from '@app/ui/projects/project.facade';
|
||||||
|
import { ActionType } from '@app/domain/action-type.util';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-project-picture-form',
|
selector: 'app-project-picture-form',
|
||||||
@@ -14,27 +15,29 @@ import { ToastrService } from 'ngx-toastr';
|
|||||||
styleUrl: './project-picture-form.component.scss',
|
styleUrl: './project-picture-form.component.scss',
|
||||||
})
|
})
|
||||||
export class ProjectPictureFormComponent {
|
export class ProjectPictureFormComponent {
|
||||||
@Input({ required: true }) project: Project | undefined = undefined;
|
@Input({ required: true }) project: ProjectViewModel | undefined = undefined;
|
||||||
|
|
||||||
onFormSubmitted = output<any>();
|
onFormSubmitted = output<any>();
|
||||||
private readonly projectService = inject(ProjectService);
|
|
||||||
private readonly toastrService = inject(ToastrService);
|
private readonly toastrService = inject(ToastrService);
|
||||||
|
|
||||||
|
private readonly projectFacade = new ProjectFacade();
|
||||||
|
protected readonly loading = this.projectFacade.loading;
|
||||||
|
protected readonly error = this.projectFacade.error;
|
||||||
|
|
||||||
private readonly authService = inject(AuthService);
|
private readonly authService = inject(AuthService);
|
||||||
|
|
||||||
file: File | null = null; // Variable to store file
|
file: File | null = null; // Variable to store file
|
||||||
imagePreviewUrl: string | null = null; // URL for image preview
|
imagePreviewUrl: string | null = null; // URL for image preview
|
||||||
|
|
||||||
onSubmit() {
|
constructor() {
|
||||||
if (this.file != null) {
|
effect(() => {
|
||||||
const formData = new FormData();
|
if (!this.loading().isLoading) {
|
||||||
formData.append('fichier', this.file); // "fichier" est le nom du champ dans PocketBase
|
switch (this.loading().action) {
|
||||||
|
case ActionType.UPDATE:
|
||||||
this.projectService.updateProject(this.project?.id!, formData).subscribe((value) => {
|
|
||||||
this.authService.updateUser();
|
this.authService.updateUser();
|
||||||
|
|
||||||
this.toastrService.success(
|
this.toastrService.success(
|
||||||
`L'aperçu du projet ${value.nom} ont bien été modifier !`,
|
`L'aperçu du projet ${this.project!.nom} ont bien été modifier !`,
|
||||||
`Mise à jour`,
|
`Mise à jour`,
|
||||||
{
|
{
|
||||||
closeButton: true,
|
closeButton: true,
|
||||||
@@ -42,9 +45,20 @@ export class ProjectPictureFormComponent {
|
|||||||
progressBar: true,
|
progressBar: true,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
});
|
|
||||||
|
|
||||||
this.onFormSubmitted.emit('');
|
this.onFormSubmitted.emit('');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onSubmit() {
|
||||||
|
if (this.file != null) {
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('fichier', this.file); // "fichier" est le nom du champ dans PocketBase
|
||||||
|
|
||||||
|
this.projectFacade.update(this.project?.id!, formData);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
45
src/app/testing/domain/projects/fake-project.repository.ts
Normal file
45
src/app/testing/domain/projects/fake-project.repository.ts
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
import { ProjectRepository } from '@app/domain/projects/project.repository';
|
||||||
|
import { Project } from '@app/shared/models/project';
|
||||||
|
import { Observable, of } from 'rxjs';
|
||||||
|
import { CreateProjectDto } from '@app/domain/projects/dto/create-project.dto';
|
||||||
|
import { fakeProjects } from '@app/testing/project.mock';
|
||||||
|
|
||||||
|
export class FakeProjectRepository implements ProjectRepository {
|
||||||
|
private projects: Project[] = [...fakeProjects];
|
||||||
|
|
||||||
|
create(projectDto: CreateProjectDto): Observable<Project> {
|
||||||
|
const newProject: Project = {
|
||||||
|
...projectDto,
|
||||||
|
fichier: [],
|
||||||
|
id: (this.projects.length + 1).toString(),
|
||||||
|
created: new Date().toISOString(),
|
||||||
|
updated: new Date().toISOString(),
|
||||||
|
};
|
||||||
|
this.projects.push(newProject);
|
||||||
|
return of(newProject);
|
||||||
|
}
|
||||||
|
|
||||||
|
list(userId: string): Observable<Project[]> {
|
||||||
|
const filtered = this.projects.filter((p) => p.utilisateur === userId);
|
||||||
|
return of(filtered);
|
||||||
|
}
|
||||||
|
|
||||||
|
get(projectId: string): Observable<Project> {
|
||||||
|
const found = this.projects.find((p) => p.id === projectId) ?? ({} as Project);
|
||||||
|
return of(found);
|
||||||
|
}
|
||||||
|
|
||||||
|
update(id: string, data: Partial<Project>): Observable<Project> {
|
||||||
|
const index = this.projects.findIndex((p) => p.id === id);
|
||||||
|
if (index === -1) {
|
||||||
|
return of({} as Project);
|
||||||
|
}
|
||||||
|
const updated: Project = {
|
||||||
|
...this.projects[index],
|
||||||
|
...data,
|
||||||
|
updated: new Date().toISOString(),
|
||||||
|
};
|
||||||
|
this.projects[index] = updated;
|
||||||
|
return of(updated);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,156 @@
|
|||||||
|
import { PbProfileRepository } from '@app/infrastructure/profiles/pb-profile.repository';
|
||||||
|
import { Profile } from '@app/domain/profiles/profile.model';
|
||||||
|
import { ProfileDTO } from '@app/domain/profiles/dto/create-profile.dto';
|
||||||
|
import PocketBase from 'pocketbase';
|
||||||
|
|
||||||
|
jest.mock('pocketbase'); // on mock le module PocketBase
|
||||||
|
|
||||||
|
describe('PbProfileRepository', () => {
|
||||||
|
let repo: PbProfileRepository;
|
||||||
|
let mockPocketBase: any;
|
||||||
|
let mockCollection: any;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
// Création d’un faux client PocketBase avec les méthodes dont on a besoin
|
||||||
|
mockCollection = {
|
||||||
|
getFullList: jest.fn(),
|
||||||
|
getFirstListItem: jest.fn(),
|
||||||
|
create: jest.fn(),
|
||||||
|
update: jest.fn(),
|
||||||
|
};
|
||||||
|
|
||||||
|
mockPocketBase = {
|
||||||
|
collection: jest.fn().mockReturnValue(mockCollection),
|
||||||
|
};
|
||||||
|
|
||||||
|
// @ts-ignore : on remplace l’instance réelle de PocketBase par notre mock
|
||||||
|
(PocketBase as jest.Mock).mockImplementation(() => mockPocketBase);
|
||||||
|
|
||||||
|
// on récupère une "collection" simulée
|
||||||
|
mockCollection = mockPocketBase.collection('profiles');
|
||||||
|
|
||||||
|
repo = new PbProfileRepository();
|
||||||
|
});
|
||||||
|
|
||||||
|
// ------------------------------------------
|
||||||
|
// 🔹 TEST : list()
|
||||||
|
// ------------------------------------------
|
||||||
|
it('devrait appeler pb.collection("profiles").getFullList() avec un tri par profession', (done) => {
|
||||||
|
const fakeProfiles: Profile[] = [
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
created: '',
|
||||||
|
updated: '',
|
||||||
|
profession: 'Développeur',
|
||||||
|
utilisateur: 'u001',
|
||||||
|
estVerifier: false,
|
||||||
|
secteur: 'Informatique',
|
||||||
|
reseaux: {} as JSON,
|
||||||
|
bio: 'Bio test',
|
||||||
|
cv: '',
|
||||||
|
projets: [],
|
||||||
|
apropos: 'À propos...',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
mockCollection.getFullList.mockResolvedValue(fakeProfiles);
|
||||||
|
|
||||||
|
repo.list().subscribe((result) => {
|
||||||
|
expect(mockPocketBase.collection).toHaveBeenCalledWith('profiles');
|
||||||
|
expect(mockCollection.getFullList).toHaveBeenCalledWith({ sort: 'profession' });
|
||||||
|
expect(result).toEqual(fakeProfiles);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// ------------------------------------------
|
||||||
|
// 🔹 TEST : getByUserId()
|
||||||
|
// ------------------------------------------
|
||||||
|
it('devrait appeler pb.collection("profiles").getFirstListItem() avec le bon filtre utilisateur', (done) => {
|
||||||
|
const userId = 'user_001';
|
||||||
|
const fakeProfile: Profile = {
|
||||||
|
id: 'p001',
|
||||||
|
created: '',
|
||||||
|
updated: '',
|
||||||
|
profession: 'Designer',
|
||||||
|
utilisateur: userId,
|
||||||
|
estVerifier: true,
|
||||||
|
secteur: 'Création',
|
||||||
|
reseaux: {} as JSON,
|
||||||
|
bio: 'Bio',
|
||||||
|
cv: '',
|
||||||
|
projets: [],
|
||||||
|
apropos: 'À propos...',
|
||||||
|
};
|
||||||
|
|
||||||
|
mockCollection.getFirstListItem.mockResolvedValue(fakeProfile);
|
||||||
|
|
||||||
|
repo.getByUserId(userId).subscribe((result) => {
|
||||||
|
expect(mockCollection.getFirstListItem).toHaveBeenCalledWith(`utilisateur="${userId}"`);
|
||||||
|
expect(result).toEqual(fakeProfile);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// ------------------------------------------
|
||||||
|
// 🔹 TEST : create()
|
||||||
|
// ------------------------------------------
|
||||||
|
it('devrait appeler pb.collection("profiles").create() avec le DTO du profil', (done) => {
|
||||||
|
const dto: ProfileDTO = {
|
||||||
|
utilisateur: 'user',
|
||||||
|
profession: 'DevOps',
|
||||||
|
reseaux: {} as JSON,
|
||||||
|
};
|
||||||
|
|
||||||
|
const fakeResponse: Profile = {
|
||||||
|
id: '123',
|
||||||
|
created: '2025-01-01T00:00:00Z',
|
||||||
|
updated: '2025-01-01T00:00:00Z',
|
||||||
|
estVerifier: true,
|
||||||
|
secteur: 'Création',
|
||||||
|
bio: 'Bio',
|
||||||
|
cv: '',
|
||||||
|
projets: [],
|
||||||
|
apropos: 'À propos...',
|
||||||
|
...dto,
|
||||||
|
};
|
||||||
|
|
||||||
|
mockCollection.create.mockResolvedValue(fakeResponse);
|
||||||
|
|
||||||
|
repo.create(dto).subscribe((result) => {
|
||||||
|
expect(mockCollection.create).toHaveBeenCalledWith(dto);
|
||||||
|
expect(result).toEqual(fakeResponse);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// ------------------------------------------
|
||||||
|
// 🔹 TEST : update()
|
||||||
|
// ------------------------------------------
|
||||||
|
it('devrait appeler pb.collection("profiles").update() avec ID et données partielle', (done) => {
|
||||||
|
const id = 'p002';
|
||||||
|
const data = { bio: 'Bio mise à jour' };
|
||||||
|
const updatedProfile: Profile = {
|
||||||
|
id,
|
||||||
|
created: '',
|
||||||
|
updated: '',
|
||||||
|
profession: 'Dev',
|
||||||
|
utilisateur: 'user_001',
|
||||||
|
estVerifier: false,
|
||||||
|
secteur: 'Tech',
|
||||||
|
reseaux: {} as JSON,
|
||||||
|
bio: data.bio!,
|
||||||
|
cv: '',
|
||||||
|
projets: [],
|
||||||
|
apropos: '',
|
||||||
|
};
|
||||||
|
|
||||||
|
mockCollection.update.mockResolvedValue(updatedProfile);
|
||||||
|
|
||||||
|
repo.update(id, data).subscribe((result) => {
|
||||||
|
expect(mockCollection.update).toHaveBeenCalledWith(id, data);
|
||||||
|
expect(result.bio).toBe('Bio mise à jour');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,141 @@
|
|||||||
|
import { PbProjectRepository } from '@app/infrastructure/projects/pb-project.repository';
|
||||||
|
import { CreateProjectDto } from '@app/domain/projects/dto/create-project.dto';
|
||||||
|
import { Project } from '@app/domain/projects/project.model';
|
||||||
|
import PocketBase from 'pocketbase';
|
||||||
|
|
||||||
|
jest.mock('pocketbase'); // on mock le module entier
|
||||||
|
|
||||||
|
describe('PbProjectRepository', () => {
|
||||||
|
let repo: PbProjectRepository;
|
||||||
|
let mockCollection: any;
|
||||||
|
let mockPocketBase: any;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
// Création d’un faux client PocketBase avec des méthodes mockées
|
||||||
|
mockPocketBase = {
|
||||||
|
collection: jest.fn().mockReturnValue({
|
||||||
|
create: jest.fn(),
|
||||||
|
getFullList: jest.fn(),
|
||||||
|
getOne: jest.fn(),
|
||||||
|
update: jest.fn(),
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
// 👇 On remplace la classe importée par notre version mockée
|
||||||
|
(PocketBase as jest.Mock).mockImplementation(() => mockPocketBase);
|
||||||
|
|
||||||
|
// on récupère une "collection" simulée
|
||||||
|
mockCollection = mockPocketBase.collection('projets');
|
||||||
|
|
||||||
|
// on instancie le repository à tester
|
||||||
|
repo = new PbProjectRepository();
|
||||||
|
});
|
||||||
|
|
||||||
|
// ------------------------------------------
|
||||||
|
// 🔹 TEST : create()
|
||||||
|
// ------------------------------------------
|
||||||
|
it('devrait appeler pb.collection("projets").create() avec le DTO', (done) => {
|
||||||
|
const dto: CreateProjectDto = {
|
||||||
|
nom: 'Projet test',
|
||||||
|
lien: 'https://exemple.com',
|
||||||
|
description: 'Test création',
|
||||||
|
utilisateur: 'user_001',
|
||||||
|
};
|
||||||
|
|
||||||
|
const fakeResponse: Project = {
|
||||||
|
id: '123',
|
||||||
|
created: '2025-01-01T00:00:00Z',
|
||||||
|
updated: '2025-01-01T00:00:00Z',
|
||||||
|
fichier: [],
|
||||||
|
...dto,
|
||||||
|
};
|
||||||
|
|
||||||
|
mockCollection.create.mockResolvedValue(fakeResponse);
|
||||||
|
|
||||||
|
repo.create(dto).subscribe((result) => {
|
||||||
|
expect(mockCollection.create).toHaveBeenCalledWith(dto);
|
||||||
|
expect(result).toEqual(fakeResponse);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// ------------------------------------------
|
||||||
|
// 🔹 TEST : list()
|
||||||
|
// ------------------------------------------
|
||||||
|
it('devrait appeler pb.collection("projets").getFullList() avec le filtre utilisateur', (done) => {
|
||||||
|
const userId = 'user_001';
|
||||||
|
const fakeProjects: Project[] = [
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
created: '',
|
||||||
|
updated: '',
|
||||||
|
nom: 'P1',
|
||||||
|
lien: '',
|
||||||
|
description: '',
|
||||||
|
fichier: [],
|
||||||
|
utilisateur: userId,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
mockCollection.getFullList.mockResolvedValue(fakeProjects);
|
||||||
|
|
||||||
|
repo.list(userId).subscribe((result) => {
|
||||||
|
expect(mockCollection.getFullList).toHaveBeenCalledWith({
|
||||||
|
filter: `utilisateur='${userId}'`,
|
||||||
|
});
|
||||||
|
expect(result).toEqual(fakeProjects);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// ------------------------------------------
|
||||||
|
// 🔹 TEST : get()
|
||||||
|
// ------------------------------------------
|
||||||
|
it('devrait appeler pb.collection("projets").getOne() avec le bon ID', (done) => {
|
||||||
|
const id = 'p123';
|
||||||
|
const fakeProject: Project = {
|
||||||
|
id,
|
||||||
|
created: '',
|
||||||
|
updated: '',
|
||||||
|
nom: 'Test',
|
||||||
|
lien: '',
|
||||||
|
description: '',
|
||||||
|
fichier: [],
|
||||||
|
utilisateur: 'user_001',
|
||||||
|
};
|
||||||
|
|
||||||
|
mockCollection.getOne.mockResolvedValue(fakeProject);
|
||||||
|
|
||||||
|
repo.get(id).subscribe((result) => {
|
||||||
|
expect(mockCollection.getOne).toHaveBeenCalledWith(id);
|
||||||
|
expect(result).toEqual(fakeProject);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// ------------------------------------------
|
||||||
|
// 🔹 TEST : update()
|
||||||
|
// ------------------------------------------
|
||||||
|
it('devrait appeler pb.collection("projets").update() avec ID et data', (done) => {
|
||||||
|
const id = 'p001';
|
||||||
|
const data = { nom: 'Projet modifié' };
|
||||||
|
const updated: Project = {
|
||||||
|
id,
|
||||||
|
created: '',
|
||||||
|
updated: '',
|
||||||
|
nom: data.nom,
|
||||||
|
lien: '',
|
||||||
|
description: '',
|
||||||
|
fichier: [],
|
||||||
|
utilisateur: 'user_001',
|
||||||
|
};
|
||||||
|
|
||||||
|
mockCollection.update.mockResolvedValue(updated);
|
||||||
|
|
||||||
|
repo.update(id, data).subscribe((result) => {
|
||||||
|
expect(mockCollection.update).toHaveBeenCalledWith(id, data);
|
||||||
|
expect(result.nom).toBe('Projet modifié');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
59
src/app/testing/project.mock.ts
Normal file
59
src/app/testing/project.mock.ts
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
import { Project } from '@app/domain/projects/project.model';
|
||||||
|
|
||||||
|
export const fakeProjects: Project[] = [
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
created: '2025-01-12T09:32:00Z',
|
||||||
|
updated: '2025-01-15T11:45:00Z',
|
||||||
|
nom: 'Portfolio Web 3D',
|
||||||
|
lien: 'https://portfolio-3d.example.com',
|
||||||
|
description:
|
||||||
|
'Un site web interactif utilisant Three.js et Angular 17 pour présenter un portfolio 3D.',
|
||||||
|
fichier: ['portfolio-preview.png', 'scene-setup.glb'],
|
||||||
|
utilisateur: 'user_001',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '2',
|
||||||
|
created: '2025-02-03T08:22:00Z',
|
||||||
|
updated: '2025-02-10T10:05:00Z',
|
||||||
|
nom: 'Application Mobile de Gestion de Budget',
|
||||||
|
lien: 'https://budget-app.example.com',
|
||||||
|
description:
|
||||||
|
'Une application mobile multiplateforme développée avec Angular et Capacitor pour suivre les dépenses quotidiennes.',
|
||||||
|
fichier: ['budget-app-icon.png', 'screenshot-dashboard.png'],
|
||||||
|
utilisateur: 'user_002',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '3',
|
||||||
|
created: '2025-03-01T13:10:00Z',
|
||||||
|
updated: '2025-03-05T16:50:00Z',
|
||||||
|
nom: 'Système de Suivi des Présences',
|
||||||
|
lien: 'https://presence-system.example.com',
|
||||||
|
description:
|
||||||
|
'Un projet SaaS développé avec Angular et Spring Boot pour gérer la présence des étudiants dans les écoles.',
|
||||||
|
fichier: ['attendance-report.pdf'],
|
||||||
|
utilisateur: 'user_001',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '4',
|
||||||
|
created: '2025-04-20T12:15:00Z',
|
||||||
|
updated: '2025-04-22T09:40:00Z',
|
||||||
|
nom: 'Dashboard d’Analyse Crypto',
|
||||||
|
lien: 'https://crypto-dashboard.example.com',
|
||||||
|
description:
|
||||||
|
'Un tableau de bord en temps réel affichant les cours et tendances des crypto-monnaies avec RxJS et WebSocket.',
|
||||||
|
fichier: ['crypto-dashboard-chart.png'],
|
||||||
|
utilisateur: 'user_003',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '5',
|
||||||
|
created: '2025-05-11T14:00:00Z',
|
||||||
|
updated: '2025-05-12T10:20:00Z',
|
||||||
|
nom: 'API de Gestion de Projets',
|
||||||
|
lien: 'https://api-project-manager.example.com',
|
||||||
|
description:
|
||||||
|
'Backend RESTful pour la gestion des projets, conçu avec Node.js, Express et PostgreSQL.',
|
||||||
|
fichier: ['openapi-schema.yaml'],
|
||||||
|
utilisateur: 'user_001',
|
||||||
|
},
|
||||||
|
];
|
||||||
@@ -23,7 +23,7 @@ describe('ProfileFacade', () => {
|
|||||||
// attendre un peu le .subscribe
|
// attendre un peu le .subscribe
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
expect(facade.profiles().length).toBe(2);
|
expect(facade.profiles().length).toBe(2);
|
||||||
expect(facade.loading()).toBe(false);
|
expect(facade.loading().isLoading).toBe(false);
|
||||||
done();
|
done();
|
||||||
}, 0);
|
}, 0);
|
||||||
});
|
});
|
||||||
|
|||||||
92
src/app/testing/ui/projects/project.facade.spec.ts
Normal file
92
src/app/testing/ui/projects/project.facade.spec.ts
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
import { TestBed } from '@angular/core/testing';
|
||||||
|
import { ProjectFacade } from '@app/ui/projects/project.facade';
|
||||||
|
import { FakeProjectRepository } from '@app/testing/domain/projects/fake-project.repository';
|
||||||
|
import { PROJECT_REPOSITORY_TOKEN } from '@app/infrastructure/projects/project-repository.token';
|
||||||
|
import { fakeProjects } from '@app/testing/project.mock';
|
||||||
|
import { CreateProjectDto } from '@app/domain/projects/dto/create-project.dto';
|
||||||
|
|
||||||
|
describe('ProjectFacade', () => {
|
||||||
|
let facade: ProjectFacade;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
providers: [
|
||||||
|
ProjectFacade,
|
||||||
|
{ provide: PROJECT_REPOSITORY_TOKEN, useClass: FakeProjectRepository },
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
facade = TestBed.inject(ProjectFacade);
|
||||||
|
});
|
||||||
|
|
||||||
|
// ------------------------------------------
|
||||||
|
// 🔹 TEST : Chargement des projets (load)
|
||||||
|
// ------------------------------------------
|
||||||
|
it('devrait charger les projets pour un utilisateur', (done) => {
|
||||||
|
const userId = 'user_001';
|
||||||
|
facade.load(userId);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
const projets = facade.projects();
|
||||||
|
expect(projets.length).toBe(fakeProjects.filter((p) => p.utilisateur === userId).length);
|
||||||
|
expect(facade.loading().isLoading).toBe(false);
|
||||||
|
expect(facade.error().hasError).toBe(false);
|
||||||
|
done();
|
||||||
|
}, 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
// ------------------------------------------
|
||||||
|
// 🔹 TEST : Chargement d’un projet unique
|
||||||
|
// ------------------------------------------
|
||||||
|
it('devrait charger un projet par son ID', (done) => {
|
||||||
|
const projectId = '1';
|
||||||
|
facade.loadOne(projectId);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
const project = facade.project();
|
||||||
|
expect(project?.id).toBe(projectId);
|
||||||
|
expect(facade.error().hasError).toBe(false);
|
||||||
|
done();
|
||||||
|
}, 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
// ------------------------------------------
|
||||||
|
// 🔹 TEST : Création d’un nouveau projet
|
||||||
|
// ------------------------------------------
|
||||||
|
it('devrait créer un nouveau projet', (done) => {
|
||||||
|
const dto: CreateProjectDto = {
|
||||||
|
nom: 'Projet test création',
|
||||||
|
lien: 'https://test-create.example.com',
|
||||||
|
description: 'Projet ajouté via façade',
|
||||||
|
utilisateur: 'user_010',
|
||||||
|
};
|
||||||
|
|
||||||
|
facade.create(dto);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
const dernier = facade.projects().find((p) => p.nom === dto.nom);
|
||||||
|
expect(dernier).toBeDefined();
|
||||||
|
expect(facade.project()?.nom).toBe(dto.nom);
|
||||||
|
expect(facade.loading().isLoading).toBe(false);
|
||||||
|
expect(facade.error().hasError).toBe(false);
|
||||||
|
done();
|
||||||
|
}, 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
// ------------------------------------------
|
||||||
|
// 🔹 TEST : Mise à jour d’un projet
|
||||||
|
// ------------------------------------------
|
||||||
|
it('devrait mettre à jour un projet existant', (done) => {
|
||||||
|
const projectId = '1';
|
||||||
|
const data = { nom: 'Projet mis à jour' };
|
||||||
|
|
||||||
|
facade.update(projectId, data);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
const updated = facade.project();
|
||||||
|
expect(updated?.nom).toBe('Projet mis à jour');
|
||||||
|
expect(facade.error().hasError).toBe(false);
|
||||||
|
done();
|
||||||
|
}, 0);
|
||||||
|
});
|
||||||
|
});
|
||||||
66
src/app/testing/ui/projects/project.presenter.spec.ts
Normal file
66
src/app/testing/ui/projects/project.presenter.spec.ts
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
import { Project } from '@app/domain/projects/project.model';
|
||||||
|
import { ProjectPresenter } from '@app/ui/projects/project.presenter';
|
||||||
|
import { fakeProjects } from '@app/testing/project.mock';
|
||||||
|
import { ProjectViewModel } from '@app/ui/projects/project.presenter.model';
|
||||||
|
|
||||||
|
describe('ProjectPresenter', () => {
|
||||||
|
let presenter: ProjectPresenter;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
presenter = new ProjectPresenter();
|
||||||
|
});
|
||||||
|
|
||||||
|
// ------------------------------------------
|
||||||
|
// 🔹 TEST 1 : Conversion d’un seul projet
|
||||||
|
// ------------------------------------------
|
||||||
|
it('devrait convertir un projet en ProjectViewModel', () => {
|
||||||
|
const projet: Project = fakeProjects[0];
|
||||||
|
|
||||||
|
const viewModel: ProjectViewModel = presenter.toViewModel(projet);
|
||||||
|
|
||||||
|
expect(viewModel.id).toBe(projet.id);
|
||||||
|
expect(viewModel.nom).toBe(projet.nom);
|
||||||
|
expect(viewModel.lien).toBe(projet.lien);
|
||||||
|
expect(viewModel.utilisateur).toBe(projet.utilisateur);
|
||||||
|
expect(viewModel.description).toContain(projet.description);
|
||||||
|
});
|
||||||
|
|
||||||
|
// ------------------------------------------
|
||||||
|
// 🔹 TEST 2 : Conversion d’une liste de projets
|
||||||
|
// ------------------------------------------
|
||||||
|
it('devrait convertir une liste de projets en liste de ViewModels', () => {
|
||||||
|
const viewModels = presenter.toViewModels(fakeProjects);
|
||||||
|
|
||||||
|
expect(Array.isArray(viewModels)).toBe(true);
|
||||||
|
expect(viewModels.length).toBe(fakeProjects.length);
|
||||||
|
|
||||||
|
// Vérifie que le premier élément est bien mappé
|
||||||
|
expect(viewModels[0].nom).toBe(fakeProjects[0].nom);
|
||||||
|
expect(viewModels[0].lien).toBe(fakeProjects[0].lien);
|
||||||
|
});
|
||||||
|
|
||||||
|
// ------------------------------------------
|
||||||
|
// 🔹 TEST 3 : Vérification d’une conversion cohérente
|
||||||
|
// ------------------------------------------
|
||||||
|
it('devrait garder les mêmes données essentielles entre domaine et vue', () => {
|
||||||
|
const projet = fakeProjects[1];
|
||||||
|
const vm = presenter.toViewModel(projet);
|
||||||
|
|
||||||
|
expect(vm).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
id: projet.id,
|
||||||
|
nom: projet.nom,
|
||||||
|
utilisateur: projet.utilisateur,
|
||||||
|
description: projet.description,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// ------------------------------------------
|
||||||
|
// 🔹 TEST 4 : Conversion d’une liste vide
|
||||||
|
// ------------------------------------------
|
||||||
|
it('devrait renvoyer un tableau vide si aucun projet n’est fourni', () => {
|
||||||
|
const result = presenter.toViewModels([]);
|
||||||
|
expect(result).toEqual([]);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
import { CreateProjectUseCase } from '@app/usecase/projects/create-project.usecase';
|
||||||
|
import { CreateProjectDto } from '@app/domain/projects/dto/create-project.dto';
|
||||||
|
import { FakeProjectRepository } from '@app/testing/domain/projects/fake-project.repository';
|
||||||
|
|
||||||
|
describe('CreateProjectUseCase', () => {
|
||||||
|
let useCase: CreateProjectUseCase;
|
||||||
|
let repo: FakeProjectRepository;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
repo = new FakeProjectRepository();
|
||||||
|
useCase = new CreateProjectUseCase(repo);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('doit creer un nouveau projet', (done) => {
|
||||||
|
const dto: CreateProjectDto = {
|
||||||
|
nom: 'New Project',
|
||||||
|
lien: 'https://example.com',
|
||||||
|
description: 'Un nouveau projet de test',
|
||||||
|
utilisateur: 'user_005',
|
||||||
|
};
|
||||||
|
|
||||||
|
useCase.execute(dto).subscribe((project) => {
|
||||||
|
expect(project.id).toBeDefined();
|
||||||
|
expect(project.nom).toBe(dto.nom);
|
||||||
|
expect(project.utilisateur).toBe(dto.utilisateur);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
20
src/app/testing/usecase/projects/get-project.usecase.spec.ts
Normal file
20
src/app/testing/usecase/projects/get-project.usecase.spec.ts
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import { FakeProjectRepository } from '@app/testing/domain/projects/fake-project.repository';
|
||||||
|
import { GetProjectUseCase } from '@app/usecase/projects/get-project.usecase';
|
||||||
|
|
||||||
|
describe('GetProjectUseCase', () => {
|
||||||
|
let useCase: GetProjectUseCase;
|
||||||
|
let repo: FakeProjectRepository;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
repo = new FakeProjectRepository();
|
||||||
|
useCase = new GetProjectUseCase(repo);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("doit retourné le projet en fonction de l'id projet", (done) => {
|
||||||
|
const projectId = '1';
|
||||||
|
useCase.execute(projectId).subscribe((project) => {
|
||||||
|
expect(project.id).toBe(projectId);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import { FakeProjectRepository } from '@app/testing/domain/projects/fake-project.repository';
|
||||||
|
import { ListProjectUseCase } from '@app/usecase/projects/list-project.usecase';
|
||||||
|
import { fakeProjects } from '@app/testing/project.mock';
|
||||||
|
|
||||||
|
describe('ListProjectUseCase', () => {
|
||||||
|
let useCase: ListProjectUseCase;
|
||||||
|
let repo: FakeProjectRepository;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
repo = new FakeProjectRepository();
|
||||||
|
useCase = new ListProjectUseCase(repo);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("doit retourne la liste des projets d'un utilisateur en fonction de l'id", (done) => {
|
||||||
|
const userId = 'user_001';
|
||||||
|
useCase.execute(userId).subscribe((projects) => {
|
||||||
|
expect(projects.length).toBe(fakeProjects.filter((p) => p.utilisateur === userId).length);
|
||||||
|
expect(projects.every((p) => p.utilisateur === userId)).toBeTruthy();
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import { FakeProjectRepository } from '@app/testing/domain/projects/fake-project.repository';
|
||||||
|
import { UpdateProjectUseCase } from '@app/usecase/projects/update-project.usecase';
|
||||||
|
|
||||||
|
describe('UpdateProjectUseCase', () => {
|
||||||
|
let useCase: UpdateProjectUseCase;
|
||||||
|
let repo: FakeProjectRepository;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
repo = new FakeProjectRepository();
|
||||||
|
useCase = new UpdateProjectUseCase(repo);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("doit modifier un projet en fonction de l'id projet", (done) => {
|
||||||
|
const projectId = '1';
|
||||||
|
const newData = { nom: 'Projet modifié' };
|
||||||
|
|
||||||
|
useCase.execute(projectId, newData).subscribe((updated) => {
|
||||||
|
expect(updated.nom).toBe('Projet modifié');
|
||||||
|
expect(updated.updated).toBeDefined();
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -5,30 +5,45 @@ import { Profile } from '@app/domain/profiles/profile.model';
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { ProfilePresenter } from '@app/ui/profiles/profile.presenter';
|
import { ProfilePresenter } from '@app/ui/profiles/profile.presenter';
|
||||||
import { ProfileViewModel } from '@app/ui/profiles/profile.presenter.model';
|
import { ProfileViewModel } from '@app/ui/profiles/profile.presenter.model';
|
||||||
|
import { LoaderAction } from '@app/domain/loader-action.util';
|
||||||
|
import { ActionType } from '@app/domain/action-type.util';
|
||||||
|
import { ErrorResponse } from '@app/domain/error-response.util';
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root',
|
providedIn: 'root',
|
||||||
})
|
})
|
||||||
export class ProfileFacade {
|
export class ProfileFacade {
|
||||||
private profileRepository = inject(PROFILE_REPOSITORY_TOKEN);
|
private profileRepository = inject(PROFILE_REPOSITORY_TOKEN);
|
||||||
private useCase = new ListProfilesUseCase(this.profileRepository);
|
private listUseCase = new ListProfilesUseCase(this.profileRepository);
|
||||||
readonly loading = signal(false);
|
|
||||||
readonly profiles = signal<ProfileViewModel[]>([]);
|
readonly profiles = signal<ProfileViewModel[]>([]);
|
||||||
readonly error = signal<string | null>(null);
|
readonly loading = signal<LoaderAction>({ isLoading: false, action: ActionType.NONE });
|
||||||
|
readonly error = signal<ErrorResponse>({
|
||||||
|
action: ActionType.NONE,
|
||||||
|
hasError: false,
|
||||||
|
message: null,
|
||||||
|
});
|
||||||
|
|
||||||
load(search?: string) {
|
load(search?: string) {
|
||||||
this.loading.set(true);
|
this.handleError(ActionType.READ, false, null, true);
|
||||||
this.error.set(null);
|
|
||||||
|
|
||||||
this.useCase.execute({ search }).subscribe({
|
this.listUseCase.execute({ search }).subscribe({
|
||||||
next: (profiles) => {
|
next: (profiles) => {
|
||||||
this.profiles.set(ProfilePresenter.toViewModels(profiles));
|
this.profiles.set(ProfilePresenter.toViewModels(profiles));
|
||||||
this.loading.set(false);
|
this.handleError(ActionType.READ, false, null, false);
|
||||||
},
|
},
|
||||||
error: (err) => {
|
error: (err) => {
|
||||||
this.error.set('Failed to load profiles');
|
this.handleError(ActionType.READ, false, err, false);
|
||||||
this.loading.set(false);
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private handleError(
|
||||||
|
action: ActionType = ActionType.NONE,
|
||||||
|
hasError: boolean,
|
||||||
|
message: string | null = null,
|
||||||
|
isLoading = false
|
||||||
|
) {
|
||||||
|
this.error.set({ action, hasError, message });
|
||||||
|
this.loading.set({ action, isLoading });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
116
src/app/ui/projects/project.facade.ts
Normal file
116
src/app/ui/projects/project.facade.ts
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
import { inject, Injectable, signal } from '@angular/core';
|
||||||
|
import { PROJECT_REPOSITORY_TOKEN } from '@app/infrastructure/projects/project-repository.token';
|
||||||
|
import { CreateProjectUseCase } from '@app/usecase/projects/create-project.usecase';
|
||||||
|
import { ListProjectUseCase } from '@app/usecase/projects/list-project.usecase';
|
||||||
|
import { GetProjectUseCase } from '@app/usecase/projects/get-project.usecase';
|
||||||
|
import { UpdateProjectUseCase } from '@app/usecase/projects/update-project.usecase';
|
||||||
|
import { Project } from '@app/domain/projects/project.model';
|
||||||
|
import { ProjectViewModel } from '@app/ui/projects/project.presenter.model';
|
||||||
|
import { ProjectPresenter } from '@app/ui/projects/project.presenter';
|
||||||
|
import { CreateProjectDto } from '@app/domain/projects/dto/create-project.dto';
|
||||||
|
import { ErrorResponse } from '@app/domain/error-response.util';
|
||||||
|
import { ActionType } from '@app/domain/action-type.util';
|
||||||
|
import { LoaderAction } from '@app/domain/loader-action.util';
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root',
|
||||||
|
})
|
||||||
|
export class ProjectFacade {
|
||||||
|
private readonly projectRepo = inject(PROJECT_REPOSITORY_TOKEN);
|
||||||
|
|
||||||
|
private readonly createUseCase = new CreateProjectUseCase(this.projectRepo);
|
||||||
|
private readonly listUseCase = new ListProjectUseCase(this.projectRepo);
|
||||||
|
private readonly getUseCase = new GetProjectUseCase(this.projectRepo);
|
||||||
|
private readonly UpdateUseCase = new UpdateProjectUseCase(this.projectRepo);
|
||||||
|
|
||||||
|
readonly projects = signal<ProjectViewModel[]>([]);
|
||||||
|
readonly project = signal<ProjectViewModel>({} as ProjectViewModel);
|
||||||
|
readonly loading = signal<LoaderAction>({ isLoading: false, action: ActionType.NONE });
|
||||||
|
readonly error = signal<ErrorResponse>({
|
||||||
|
action: ActionType.NONE,
|
||||||
|
hasError: false,
|
||||||
|
message: null,
|
||||||
|
});
|
||||||
|
|
||||||
|
private readonly projectPresenter = new ProjectPresenter();
|
||||||
|
|
||||||
|
load(userId: string) {
|
||||||
|
this.loading.set({
|
||||||
|
action: ActionType.READ,
|
||||||
|
isLoading: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.listUseCase.execute(userId).subscribe({
|
||||||
|
next: (projects: Project[]) => {
|
||||||
|
this.projects.set(this.projectPresenter.toViewModels(projects));
|
||||||
|
this.handleError(ActionType.READ, false, null, false);
|
||||||
|
},
|
||||||
|
error: (err) => {
|
||||||
|
this.handleError(ActionType.READ, false, err, false);
|
||||||
|
},
|
||||||
|
complete: () => {},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
loadOne(projectId: string) {
|
||||||
|
this.loading.set({
|
||||||
|
action: ActionType.READ,
|
||||||
|
isLoading: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.getUseCase.execute(projectId).subscribe({
|
||||||
|
next: (project: Project) => {
|
||||||
|
this.project.set(this.projectPresenter.toViewModel(project));
|
||||||
|
this.handleError(ActionType.READ, false, null, false);
|
||||||
|
},
|
||||||
|
error: (err) => {
|
||||||
|
this.handleError(ActionType.READ, false, err, false);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
create(projectDto: CreateProjectDto) {
|
||||||
|
this.loading.set({
|
||||||
|
action: ActionType.CREATE,
|
||||||
|
isLoading: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.createUseCase.execute(projectDto).subscribe({
|
||||||
|
next: (project: Project) => {
|
||||||
|
this.project.set(this.projectPresenter.toViewModel(project));
|
||||||
|
this.projects.update((prev) => [...prev, this.projectPresenter.toViewModel(project)]);
|
||||||
|
this.handleError(ActionType.CREATE, false, null, false);
|
||||||
|
},
|
||||||
|
error: (err) => {
|
||||||
|
this.handleError(ActionType.CREATE, false, err, false);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
update(userId: string, data: any) {
|
||||||
|
this.loading.set({
|
||||||
|
action: ActionType.UPDATE,
|
||||||
|
isLoading: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.UpdateUseCase.execute(userId, data).subscribe({
|
||||||
|
next: (project: Project) => {
|
||||||
|
this.project.set(this.projectPresenter.toViewModel(project));
|
||||||
|
this.handleError(ActionType.UPDATE, false, null, false);
|
||||||
|
},
|
||||||
|
error: (err) => {
|
||||||
|
this.handleError(ActionType.UPDATE, false, err, false);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleError(
|
||||||
|
action: ActionType = ActionType.NONE,
|
||||||
|
hasError: boolean,
|
||||||
|
message: string | null = null,
|
||||||
|
isLoading = false
|
||||||
|
) {
|
||||||
|
this.error.set({ action, hasError, message });
|
||||||
|
this.loading.set({ action, isLoading });
|
||||||
|
}
|
||||||
|
}
|
||||||
10
src/app/ui/projects/project.presenter.model.ts
Normal file
10
src/app/ui/projects/project.presenter.model.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
export interface ProjectViewModel {
|
||||||
|
id: string;
|
||||||
|
created: string;
|
||||||
|
updated: string;
|
||||||
|
nom: string;
|
||||||
|
lien: string;
|
||||||
|
description: string;
|
||||||
|
fichier: string[];
|
||||||
|
utilisateur: string;
|
||||||
|
}
|
||||||
23
src/app/ui/projects/project.presenter.ts
Normal file
23
src/app/ui/projects/project.presenter.ts
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import { ProjectViewModel } from '@app/ui/projects/project.presenter.model';
|
||||||
|
import { Project } from '@app/domain/projects/project.model';
|
||||||
|
|
||||||
|
export class ProjectPresenter {
|
||||||
|
constructor() {}
|
||||||
|
|
||||||
|
toViewModel(project: Project): ProjectViewModel {
|
||||||
|
return {
|
||||||
|
id: project.id,
|
||||||
|
created: project.created,
|
||||||
|
updated: project.updated,
|
||||||
|
nom: project.nom,
|
||||||
|
lien: project.lien,
|
||||||
|
description: project.description,
|
||||||
|
fichier: project.fichier,
|
||||||
|
utilisateur: project.utilisateur,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
toViewModels(projects: Project[]): ProjectViewModel[] {
|
||||||
|
return projects.map(this.toViewModel);
|
||||||
|
}
|
||||||
|
}
|
||||||
10
src/app/usecase/projects/create-project.usecase.ts
Normal file
10
src/app/usecase/projects/create-project.usecase.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import { ProjectRepository } from '@app/domain/projects/project.repository';
|
||||||
|
import { CreateProjectDto } from '@app/domain/projects/dto/create-project.dto';
|
||||||
|
|
||||||
|
export class CreateProjectUseCase {
|
||||||
|
constructor(private readonly repo: ProjectRepository) {}
|
||||||
|
|
||||||
|
execute(projectDto: CreateProjectDto) {
|
||||||
|
return this.repo.create(projectDto);
|
||||||
|
}
|
||||||
|
}
|
||||||
11
src/app/usecase/projects/get-project.usecase.ts
Normal file
11
src/app/usecase/projects/get-project.usecase.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import { ProjectRepository } from '@app/domain/projects/project.repository';
|
||||||
|
import { Project } from '@app/domain/projects/project.model';
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
|
||||||
|
export class GetProjectUseCase {
|
||||||
|
constructor(private readonly repo: ProjectRepository) {}
|
||||||
|
|
||||||
|
execute(projectId: string): Observable<Project> {
|
||||||
|
return this.repo.get(projectId);
|
||||||
|
}
|
||||||
|
}
|
||||||
11
src/app/usecase/projects/list-project.usecase.ts
Normal file
11
src/app/usecase/projects/list-project.usecase.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import { ProjectRepository } from '@app/domain/projects/project.repository';
|
||||||
|
import { Project } from '@app/domain/projects/project.model';
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
|
||||||
|
export class ListProjectUseCase {
|
||||||
|
constructor(private readonly repo: ProjectRepository) {}
|
||||||
|
|
||||||
|
execute(userId: string): Observable<Project[]> {
|
||||||
|
return this.repo.list(userId);
|
||||||
|
}
|
||||||
|
}
|
||||||
11
src/app/usecase/projects/update-project.usecase.ts
Normal file
11
src/app/usecase/projects/update-project.usecase.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import { ProjectRepository } from '@app/domain/projects/project.repository';
|
||||||
|
import { Project } from '@app/domain/projects/project.model';
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
|
||||||
|
export class UpdateProjectUseCase {
|
||||||
|
constructor(private readonly repo: ProjectRepository) {}
|
||||||
|
|
||||||
|
execute(userId: string, data: any): Observable<Project> {
|
||||||
|
return this.repo.update(userId, data);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,6 +2,11 @@
|
|||||||
{
|
{
|
||||||
"extends": "./tsconfig.json",
|
"extends": "./tsconfig.json",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
|
"module": "ESNext",
|
||||||
|
"target": "ES2022",
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"allowJs": true,
|
||||||
"outDir": "./out-tsc/spec",
|
"outDir": "./out-tsc/spec",
|
||||||
"types": [
|
"types": [
|
||||||
"jest","node"
|
"jest","node"
|
||||||
|
|||||||
Reference in New Issue
Block a user