From 36547092505b0a5a26dadee0ea5956b64ff726f2 Mon Sep 17 00:00:00 2001 From: styve Lioumba Date: Fri, 24 Oct 2025 16:18:27 +0200 Subject: [PATCH] project et profile => clean archi et test vert --- .actrc | 4 +- .gitea/workflows/ci.yaml | 2 +- jest.config.js | 1 - package-lock.json | 386 +++++++++++++++--- package.json | 1 + .../profile/detail/detail.resolver.spec.ts | 2 +- .../profile/detail/detail.resolver.ts | 2 +- .../resolvers/profile/list/list.resolver.ts | 2 +- .../services/profile/profile.service.spec.ts | 27 -- .../core/services/profile/profile.service.ts | 35 -- src/app/domain/profiles/profile.repository.ts | 4 +- src/app/domain/projects/project.repository.ts | 2 +- .../profiles/pb-profile.repository.ts | 2 +- .../projects/pb-project.repository.ts | 2 +- src/app/routes/home/home.component.spec.ts | 2 + .../my-profile/my-profile.component.html | 24 +- .../my-profile/my-profile.component.spec.ts | 17 +- .../routes/my-profile/my-profile.component.ts | 24 +- .../profile-detail.component.ts | 2 +- .../my-home-profile.component.ts | 8 +- ...y-profile-update-cv-form.component.spec.ts | 20 +- .../my-profile-update-cv-form.component.ts | 43 +- .../my-profile-update-form.component.spec.ts | 15 + .../my-profile-update-form.component.ts | 58 +-- ...y-profile-update-project-form.component.ts | 6 +- .../pdf-viewer/pdf-viewer.component.spec.ts | 6 +- .../pdf-viewer/pdf-viewer.component.ts | 4 +- .../register/register.component.spec.ts | 18 +- .../features/register/register.component.ts | 57 +-- src/app/shared/models/profile-dto.ts | 5 - src/app/shared/models/profile.ts | 14 - src/app/shared/models/project-dto.ts | 6 - src/app/shared/models/project.ts | 10 - .../profiles/fake-profile.repository.ts | 4 +- .../projects/fake-project.repository.ts | 2 +- .../ui/profiles/profile.facade.spec.ts | 51 +++ .../ui/profiles/profile.presenter.spec.ts | 4 + .../profiles/create-profile.usecase.spec.ts | 26 ++ .../profiles/get-profile.usecase.spec.ts | 16 + .../profiles/update-profile.usecase.spec.ts | 23 ++ src/app/ui/profiles/profile.facade.ts | 55 ++- .../ui/profiles/profile.presenter.model.ts | 4 + src/app/ui/profiles/profile.presenter.ts | 4 + .../profiles/create-profile.usecase.ts | 12 + .../usecase/profiles/get-profile.usecase.ts | 11 + .../profiles/update-profile.usecase.ts | 11 + src/setup-jest.ts | 30 ++ structure.md | 354 ---------------- 48 files changed, 762 insertions(+), 656 deletions(-) delete mode 100644 src/app/core/services/profile/profile.service.spec.ts delete mode 100644 src/app/core/services/profile/profile.service.ts delete mode 100644 src/app/shared/models/profile-dto.ts delete mode 100644 src/app/shared/models/profile.ts delete mode 100644 src/app/shared/models/project-dto.ts delete mode 100644 src/app/shared/models/project.ts create mode 100644 src/app/testing/usecase/profiles/create-profile.usecase.spec.ts create mode 100644 src/app/testing/usecase/profiles/get-profile.usecase.spec.ts create mode 100644 src/app/testing/usecase/profiles/update-profile.usecase.spec.ts create mode 100644 src/app/usecase/profiles/create-profile.usecase.ts create mode 100644 src/app/usecase/profiles/get-profile.usecase.ts create mode 100644 src/app/usecase/profiles/update-profile.usecase.ts delete mode 100644 structure.md diff --git a/.actrc b/.actrc index 4ce487a..b0f4a64 100644 --- a/.actrc +++ b/.actrc @@ -1,3 +1,3 @@ ---container-architecture linux/arm64 +--container-architecture linux/amd64 -W .gitea/workflows --P ubuntu-latest=node:20-bullseye +-P ubuntu-latest==ghcr.io/catthehacker/ubuntu:act-latest diff --git a/.gitea/workflows/ci.yaml b/.gitea/workflows/ci.yaml index 98910a8..3436c39 100644 --- a/.gitea/workflows/ci.yaml +++ b/.gitea/workflows/ci.yaml @@ -1,4 +1,4 @@ -name: Docker Build Check +name: Build Check # Déclencheur pour chaque pull request on: diff --git a/jest.config.js b/jest.config.js index 03c93e4..955d567 100644 --- a/jest.config.js +++ b/jest.config.js @@ -14,5 +14,4 @@ module.exports = { '^@app/(.*)$': '/src/app/$1', '^@env/(.*)$': '/src/environments/$1', }, - globalSetup: 'jest-preset-angular/global-setup', }; diff --git a/package-lock.json b/package-lock.json index 63588ec..fd18812 100644 --- a/package-lock.json +++ b/package-lock.json @@ -46,6 +46,7 @@ "@types/node-fetch": "^2.6.13", "angular-eslint": "20.4.0", "autoprefixer": "^10.4.20", + "canvas": "^2.11.2", "eslint": "^9.37.0", "eslint-config-prettier": "^10.1.8", "eslint-plugin-prettier": "^5.5.4", @@ -5268,6 +5269,103 @@ "node": ">= 0.4" } }, + "node_modules/@mapbox/node-pre-gyp": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz", + "integrity": "sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "detect-libc": "^2.0.0", + "https-proxy-agent": "^5.0.0", + "make-dir": "^3.1.0", + "node-fetch": "^2.6.7", + "nopt": "^5.0.0", + "npmlog": "^5.0.1", + "rimraf": "^3.0.2", + "semver": "^7.3.5", + "tar": "^6.1.11" + }, + "bin": { + "node-pre-gyp": "bin/node-pre-gyp" + } + }, + "node_modules/@mapbox/node-pre-gyp/node_modules/abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "dev": true, + "license": "ISC" + }, + "node_modules/@mapbox/node-pre-gyp/node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/@mapbox/node-pre-gyp/node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/@mapbox/node-pre-gyp/node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@mapbox/node-pre-gyp/node_modules/make-dir/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@mapbox/node-pre-gyp/node_modules/nopt": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", + "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "abbrev": "1" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/@ngneat/until-destroy": { "version": "10.0.0", "resolved": "https://registry.npmjs.org/@ngneat/until-destroy/-/until-destroy-10.0.0.tgz", @@ -6225,9 +6323,9 @@ "license": "MIT" }, "node_modules/@types/express": { - "version": "4.17.23", - "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.23.tgz", - "integrity": "sha512-Crp6WY9aTYP3qPi2wGDo9iUe/rceX01UMhnF1jmwDcKCFM6cx7YhGP/Mpr3y9AASpfHixIG0E6azCcL5OcDHsQ==", + "version": "4.17.24", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.24.tgz", + "integrity": "sha512-Mbrt4SRlXSTWryOnHAh2d4UQ/E7n9lZyGSi6KgX+4hkuL9soYbLOVXVhnk/ODp12YsGc95f4pOvqywJ6kngUwg==", "dev": true, "license": "MIT", "dependencies": { @@ -6268,9 +6366,9 @@ "license": "MIT" }, "node_modules/@types/http-proxy": { - "version": "1.17.16", - "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.16.tgz", - "integrity": "sha512-sdWoUajOB1cd0A8cRRQ1cfyWNbmFKLAqBB89Y8x5iYyG/mkJHc0YUH8pdWBy2omi9qtCpiIgGjuwO0dQST2l5w==", + "version": "1.17.17", + "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.17.tgz", + "integrity": "sha512-ED6LB+Z1AVylNTu7hdzuBqOgMnvG/ld6wGCG8wFnAzKX5uyW2K3WD52v0gnLCTK/VLpXtKckgWuyScYK6cSPaw==", "dev": true, "license": "MIT", "dependencies": { @@ -6394,9 +6492,9 @@ "license": "MIT" }, "node_modules/@types/send": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.0.tgz", - "integrity": "sha512-zBF6vZJn1IaMpg3xUF25VK3gd3l8zwE0ZLRX7dsQyQi+jp4E8mMDJNGDYnYse+bQhYwWERTxVwHpi3dMOq7RKQ==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz", + "integrity": "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==", "dev": true, "license": "MIT", "dependencies": { @@ -6414,9 +6512,9 @@ } }, "node_modules/@types/serve-static": { - "version": "1.15.9", - "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.9.tgz", - "integrity": "sha512-dOTIuqpWLyl3BBXU3maNQsS4A3zuuoYRNIvYSxxhebPfXg2mzWQEPne/nlJ37yOse6uGgR386uTpdsx4D0QZWA==", + "version": "1.15.10", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.10.tgz", + "integrity": "sha512-tRs1dB+g8Itk72rlSI2ZrW6vZg0YrLI81iQSTkMmOqnqCaNr/8Ek4VwWcN5vZgCYWbg/JJSGBlUaYGAOP73qBw==", "dev": true, "license": "MIT", "dependencies": { @@ -6426,9 +6524,9 @@ } }, "node_modules/@types/serve-static/node_modules/@types/send": { - "version": "0.17.5", - "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.5.tgz", - "integrity": "sha512-z6F2D3cOStZvuk2SaP6YrwkNO65iTZcwA2ZkSABegdkAh/lf+Aa/YQndZVfmEXT5vgAp6zv06VQ3ejSVjAny4w==", + "version": "0.17.6", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.6.tgz", + "integrity": "sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og==", "dev": true, "license": "MIT", "dependencies": { @@ -6471,9 +6569,9 @@ } }, "node_modules/@types/yargs": { - "version": "17.0.33", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", - "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", + "version": "17.0.34", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.34.tgz", + "integrity": "sha512-KExbHVa92aJpw9WDQvzBaGVE2/Pz+pLZQloT2hjL8IqsZnV62rlPOYvNnLmf/L2dyllfVUOVBj64M0z/46eR2A==", "dev": true, "license": "MIT", "dependencies": { @@ -8045,6 +8143,28 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/aproba": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.1.0.tgz", + "integrity": "sha512-tLIEcj5GuR2RSTnxNKdkK0dJ/GrC7P38sUkiDmDuHfsHmbagTFAxDVIBltoklXEVIQ/f14IL8IMJ5pn9Hez1Ew==", + "dev": true, + "license": "ISC" + }, + "node_modules/are-we-there-yet": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz", + "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==", + "deprecated": "This package is no longer supported.", + "dev": true, + "license": "ISC", + "dependencies": { + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/arg": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", @@ -8720,18 +8840,19 @@ "license": "CC-BY-4.0" }, "node_modules/canvas": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/canvas/-/canvas-3.2.0.tgz", - "integrity": "sha512-jk0GxrLtUEmW/TmFsk2WghvgHe8B0pxGilqCL21y8lHkPUGa6FTsnCNtHPOzT8O3y+N+m3espawV80bbBlgfTA==", + "version": "2.11.2", + "resolved": "https://registry.npmjs.org/canvas/-/canvas-2.11.2.tgz", + "integrity": "sha512-ItanGBMrmRV7Py2Z+Xhs7cT+FNt5K0vPL4p9EZ/UX/Mu7hFbkxSjKF2KVtPwX7UYWp7dRKnrTvReflgrItJbdw==", + "dev": true, "hasInstallScript": true, "license": "MIT", - "optional": true, "dependencies": { - "node-addon-api": "^7.0.0", - "prebuild-install": "^7.1.3" + "@mapbox/node-pre-gyp": "^1.0.0", + "nan": "^2.17.0", + "simple-get": "^3.0.3" }, "engines": { - "node": "^18.12.0 || >= 20.9.0" + "node": ">=6" } }, "node_modules/chalk": { @@ -8975,6 +9096,16 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "license": "MIT" }, + "node_modules/color-support": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "dev": true, + "license": "ISC", + "bin": { + "color-support": "bin.js" + } + }, "node_modules/colorette": { "version": "2.0.20", "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", @@ -9088,6 +9219,13 @@ "node": ">=0.8" } }, + "node_modules/console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==", + "dev": true, + "license": "ISC" + }, "node_modules/content-disposition": { "version": "0.5.4", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", @@ -9487,19 +9625,16 @@ "license": "MIT" }, "node_modules/decompress-response": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", - "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-4.2.1.tgz", + "integrity": "sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==", + "dev": true, "license": "MIT", - "optional": true, "dependencies": { - "mimic-response": "^3.1.0" + "mimic-response": "^2.0.0" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=8" } }, "node_modules/dedent": { @@ -9608,6 +9743,13 @@ "node": ">=0.4.0" } }, + "node_modules/delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", + "dev": true, + "license": "MIT" + }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -9631,8 +9773,8 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "devOptional": true, "license": "Apache-2.0", - "optional": true, "engines": { "node": ">=8" } @@ -9807,9 +9949,9 @@ "license": "MIT" }, "node_modules/electron-to-chromium": { - "version": "1.5.239", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.239.tgz", - "integrity": "sha512-1y5w0Zsq39MSPmEjHjbizvhYoTaulVtivpxkp5q5kaPmQtsK6/2nvAzGRxNMS9DoYySp9PkW0MAQDwU1m764mg==", + "version": "1.5.240", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.240.tgz", + "integrity": "sha512-OBwbZjWgrCOH+g6uJsA2/7Twpas2OlepS9uvByJjR2datRDuKGYeD+nP8lBBks2qnB7bGJNHDUx7c/YLaT3QMQ==", "dev": true, "license": "ISC" }, @@ -11057,6 +11199,28 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/gauge": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz", + "integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==", + "deprecated": "This package is no longer supported.", + "dev": true, + "license": "ISC", + "dependencies": { + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.2", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.1", + "object-assign": "^4.1.1", + "signal-exit": "^3.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.2" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -11380,6 +11544,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==", + "dev": true, + "license": "ISC" + }, "node_modules/hasown": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", @@ -14771,13 +14942,13 @@ } }, "node_modules/mimic-response": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", - "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-2.1.0.tgz", + "integrity": "sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==", + "dev": true, "license": "MIT", - "optional": true, "engines": { - "node": ">=10" + "node": ">=8" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -15114,6 +15285,13 @@ "thenify-all": "^1.0.0" } }, + "node_modules/nan": { + "version": "2.23.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.23.0.tgz", + "integrity": "sha512-1UxuyYGdoQHcGg87Lkqm3FzefucTa0NAiOcuRsDmysep3c1LVCRK2krrUDafMWtjSG04htvAmvg96+SDknOmgQ==", + "dev": true, + "license": "MIT" + }, "node_modules/nanoid": { "version": "3.3.11", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", @@ -15602,6 +15780,20 @@ "node": ">=8" } }, + "node_modules/npmlog": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz", + "integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==", + "deprecated": "This package is no longer supported.", + "dev": true, + "license": "ISC", + "dependencies": { + "are-we-there-yet": "^2.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^3.0.0", + "set-blocking": "^2.0.0" + } + }, "node_modules/nth-check": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", @@ -16121,6 +16313,21 @@ "path2d": "^0.2.1" } }, + "node_modules/pdfjs-dist/node_modules/canvas": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/canvas/-/canvas-3.2.0.tgz", + "integrity": "sha512-jk0GxrLtUEmW/TmFsk2WghvgHe8B0pxGilqCL21y8lHkPUGa6FTsnCNtHPOzT8O3y+N+m3espawV80bbBlgfTA==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "node-addon-api": "^7.0.0", + "prebuild-install": "^7.1.3" + }, + "engines": { + "node": "^18.12.0 || >= 20.9.0" + } + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -16593,6 +16800,61 @@ "node": ">=10" } }, + "node_modules/prebuild-install/node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/prebuild-install/node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/prebuild-install/node_modules/simple-get": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", + "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "optional": true, + "dependencies": { + "decompress-response": "^6.0.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -17978,6 +18240,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "devOptional": true, "funding": [ { "type": "github", @@ -17992,31 +18255,16 @@ "url": "https://feross.org/support" } ], - "license": "MIT", - "optional": true + "license": "MIT" }, "node_modules/simple-get": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", - "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-3.1.1.tgz", + "integrity": "sha512-CQ5LTKGfCpvE1K0n2us+kuMPbk/q0EKl82s4aheV9oXjFEz6W/Y7oQFVJuU6QG77hRT4Ghb5RURteF5vnWjupA==", + "dev": true, "license": "MIT", - "optional": true, "dependencies": { - "decompress-response": "^6.0.0", + "decompress-response": "^4.2.0", "once": "^1.3.1", "simple-concat": "^1.0.0" } @@ -20552,6 +20800,16 @@ "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==", "license": "ISC" }, + "node_modules/wide-align": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", + "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^1.0.2 || 2 || 3 || 4" + } + }, "node_modules/wildcard": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.1.tgz", diff --git a/package.json b/package.json index b7e0188..29e3f0a 100644 --- a/package.json +++ b/package.json @@ -58,6 +58,7 @@ "@types/node-fetch": "^2.6.13", "angular-eslint": "20.4.0", "autoprefixer": "^10.4.20", + "canvas": "^2.11.2", "eslint": "^9.37.0", "eslint-config-prettier": "^10.1.8", "eslint-plugin-prettier": "^5.5.4", diff --git a/src/app/core/resolvers/profile/detail/detail.resolver.spec.ts b/src/app/core/resolvers/profile/detail/detail.resolver.spec.ts index 5ec53ff..b0d5469 100644 --- a/src/app/core/resolvers/profile/detail/detail.resolver.spec.ts +++ b/src/app/core/resolvers/profile/detail/detail.resolver.spec.ts @@ -3,7 +3,7 @@ import { Router } from '@angular/router'; import { detailResolver } from './detail.resolver'; import { User } from '@app/shared/models/user'; -import { Profile } from '@app/shared/models/profile'; +import { Profile } from '@app/domain/profiles/profile.model'; describe('detailResolver', () => { let mockRoute: Partial; diff --git a/src/app/core/resolvers/profile/detail/detail.resolver.ts b/src/app/core/resolvers/profile/detail/detail.resolver.ts index f1a52f2..94b1721 100644 --- a/src/app/core/resolvers/profile/detail/detail.resolver.ts +++ b/src/app/core/resolvers/profile/detail/detail.resolver.ts @@ -1,7 +1,7 @@ import { ResolveFn, Router } from '@angular/router'; import { inject } from '@angular/core'; import { User } from '@app/shared/models/user'; -import { Profile } from '@app/shared/models/profile'; +import { Profile } from '@app/domain/profiles/profile.model'; export const detailResolver: ResolveFn<{ user: User; profile: Profile }> = (route, state) => { const paramValue = route.params['name']; diff --git a/src/app/core/resolvers/profile/list/list.resolver.ts b/src/app/core/resolvers/profile/list/list.resolver.ts index 962190c..8c59bb1 100644 --- a/src/app/core/resolvers/profile/list/list.resolver.ts +++ b/src/app/core/resolvers/profile/list/list.resolver.ts @@ -1,5 +1,5 @@ import { ResolveFn } from '@angular/router'; -import { Profile } from '@app/shared/models/profile'; +import { Profile } from '@app/domain/profiles/profile.model'; export const listResolver: ResolveFn = (route, state) => { const queryValue = route.queryParams['search']; diff --git a/src/app/core/services/profile/profile.service.spec.ts b/src/app/core/services/profile/profile.service.spec.ts deleted file mode 100644 index f1b3954..0000000 --- a/src/app/core/services/profile/profile.service.spec.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { TestBed } from '@angular/core/testing'; - -import { ProfileService } from './profile.service'; -import { Router } from '@angular/router'; - -describe('ProfileService', () => { - let service: ProfileService; - - const routerSpy = { - navigate: jest.fn(), - navigateByUrl: jest.fn(), - }; - - beforeEach(() => { - TestBed.configureTestingModule({ - providers: [ - { provide: Router, useValue: routerSpy }, // <<— spy: neutralise la navigation - ], - imports: [], - }); - service = TestBed.inject(ProfileService); - }); - - it('should be created', () => { - expect(service).toBeTruthy(); - }); -}); diff --git a/src/app/core/services/profile/profile.service.ts b/src/app/core/services/profile/profile.service.ts deleted file mode 100644 index 1fc4406..0000000 --- a/src/app/core/services/profile/profile.service.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { Injectable } from '@angular/core'; -import PocketBase from 'pocketbase'; -import { environment } from '@env/environment'; -import { Profile } from '@app/shared/models/profile'; -import { from } from 'rxjs'; -import { ProfileDto } from '@app/shared/models/profile-dto'; - -@Injectable({ - providedIn: 'root', -}) -export class ProfileService { - createProfile(profileDto: ProfileDto) { - const pb = new PocketBase(environment.baseUrl); - return from(pb.collection('profiles').create(profileDto)); - } - - get profiles() { - const pb = new PocketBase(environment.baseUrl); - return from( - pb.collection('profiles').getFullList({ - sort: 'profession', - }) - ); - } - - getProfileByUserId(userId: string) { - const pb = new PocketBase(environment.baseUrl); - return from(pb.collection('profiles').getFirstListItem(`utilisateur="${userId}"`)); - } - - updateProfile(id: string, data: Profile | any) { - const pb = new PocketBase(environment.baseUrl); - return from(pb.collection('profiles').update(id, data)); - } -} diff --git a/src/app/domain/profiles/profile.repository.ts b/src/app/domain/profiles/profile.repository.ts index 4421f74..8a0fad8 100644 --- a/src/app/domain/profiles/profile.repository.ts +++ b/src/app/domain/profiles/profile.repository.ts @@ -3,7 +3,7 @@ import { Observable } from 'rxjs'; export interface ProfileRepository { list(params?: { search?: string; page?: number; pageSize?: number }): Observable; - getByUserId(userId: string): Observable; + getByUserId(userId: string): Observable; create(profile: Profile): Observable; - update(id: string, profile: Partial): Observable; + update(profileId: string, profile: Partial): Observable; } diff --git a/src/app/domain/projects/project.repository.ts b/src/app/domain/projects/project.repository.ts index 7a80427..d70a65f 100644 --- a/src/app/domain/projects/project.repository.ts +++ b/src/app/domain/projects/project.repository.ts @@ -1,6 +1,6 @@ import { Observable } from 'rxjs'; -import { Project } from '@app/shared/models/project'; import { CreateProjectDto } from '@app/domain/projects/dto/create-project.dto'; +import { Project } from '@app/domain/projects/project.model'; export interface ProjectRepository { create(projectDto: CreateProjectDto): Observable; diff --git a/src/app/infrastructure/profiles/pb-profile.repository.ts b/src/app/infrastructure/profiles/pb-profile.repository.ts index a9b6467..eab4f5d 100644 --- a/src/app/infrastructure/profiles/pb-profile.repository.ts +++ b/src/app/infrastructure/profiles/pb-profile.repository.ts @@ -14,7 +14,7 @@ export class PbProfileRepository implements ProfileRepository { return from(this.pb.collection('profiles').getFullList({ sort: 'profession' })); } - getByUserId(userId: string): Observable { + getByUserId(userId: string): Observable { return from( this.pb.collection('profiles').getFirstListItem(`utilisateur="${userId}"`) ); diff --git a/src/app/infrastructure/projects/pb-project.repository.ts b/src/app/infrastructure/projects/pb-project.repository.ts index 03173d7..8252067 100644 --- a/src/app/infrastructure/projects/pb-project.repository.ts +++ b/src/app/infrastructure/projects/pb-project.repository.ts @@ -1,10 +1,10 @@ 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'; +import { Project } from '@app/domain/projects/project.model'; @Injectable({ providedIn: 'root', diff --git a/src/app/routes/home/home.component.spec.ts b/src/app/routes/home/home.component.spec.ts index 7737f1c..d6c16ef 100644 --- a/src/app/routes/home/home.component.spec.ts +++ b/src/app/routes/home/home.component.spec.ts @@ -1,6 +1,7 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { HomeComponent } from './home.component'; +import { provideRouter } from '@angular/router'; describe('HomeComponent', () => { let component: HomeComponent; @@ -9,6 +10,7 @@ describe('HomeComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ imports: [HomeComponent], + providers: [provideRouter([])], }).compileComponents(); fixture = TestBed.createComponent(HomeComponent); diff --git a/src/app/routes/my-profile/my-profile.component.html b/src/app/routes/my-profile/my-profile.component.html index 9a3411d..355200c 100644 --- a/src/app/routes/my-profile/my-profile.component.html +++ b/src/app/routes/my-profile/my-profile.component.html @@ -19,7 +19,7 @@ - @if (profile.estVerifier) { + @if (profile().estVerifier) { }
- @if (profile.bio) { -

{{ profile.bio }}

+ @if (profile().bio) { +

{{ profile().bio }}

} @else {

Je suis sur la plateforme Trouve Ton Profile pour partager mon expertise et mes @@ -110,17 +110,17 @@

} - @if (profile.secteur) { + @if (profile().secteur) {

Secteur

- +
} - @if (profile.reseaux) { + @if (profile().reseaux) {

Réseaux

- +
} @@ -240,23 +240,23 @@
@switch (menu().toLowerCase()) { @case ('home'.toLowerCase()) { - + } @case ('projects'.toLowerCase()) { } @case ('update'.toLowerCase()) { - + } @case ('cv'.toLowerCase()) { - + } @default { - + } }
diff --git a/src/app/routes/my-profile/my-profile.component.spec.ts b/src/app/routes/my-profile/my-profile.component.spec.ts index 400d8ec..4b8888c 100644 --- a/src/app/routes/my-profile/my-profile.component.spec.ts +++ b/src/app/routes/my-profile/my-profile.component.spec.ts @@ -2,15 +2,30 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { MyProfileComponent } from './my-profile.component'; import { provideRouter } from '@angular/router'; +import { ProfileRepository } from '@app/domain/profiles/profile.repository'; +import { PROFILE_REPOSITORY_TOKEN } from '@app/infrastructure/profiles/profile-repository.token'; +import { of } from 'rxjs'; +import { Profile } from '@app/domain/profiles/profile.model'; describe('MyProfileComponent', () => { let component: MyProfileComponent; let fixture: ComponentFixture; + let mockProfileRepo: ProfileRepository; + beforeEach(async () => { + mockProfileRepo = { + create: jest.fn(), + list: jest.fn(), + update: jest.fn(), + getByUserId: jest.fn().mockReturnValue(of({} as Profile)), + }; await TestBed.configureTestingModule({ imports: [MyProfileComponent], - providers: [provideRouter([])], + providers: [ + provideRouter([]), + { provide: PROFILE_REPOSITORY_TOKEN, useValue: mockProfileRepo }, + ], }).compileComponents(); fixture = TestBed.createComponent(MyProfileComponent); diff --git a/src/app/routes/my-profile/my-profile.component.ts b/src/app/routes/my-profile/my-profile.component.ts index 1f0a681..0527084 100644 --- a/src/app/routes/my-profile/my-profile.component.ts +++ b/src/app/routes/my-profile/my-profile.component.ts @@ -1,7 +1,7 @@ import { Component, computed, inject, OnInit, signal } from '@angular/core'; import { ActivatedRoute, RouterLink, RouterOutlet } from '@angular/router'; import { User } from '@app/shared/models/user'; -import { AsyncPipe, JsonPipe, Location, NgClass, UpperCasePipe } from '@angular/common'; +import { Location, NgClass } from '@angular/common'; import { UntilDestroy } from '@ngneat/until-destroy'; import { SafeUrl } from '@angular/platform-browser'; import { QRCodeModule } from 'angularx-qrcode'; @@ -12,22 +12,18 @@ import { UpdateUserComponent } from '@app/shared/features/update-user/update-use import { MyProfileProjectListComponent } from '@app/shared/components/my-profile-project-list/my-profile-project-list.component'; import { MyHomeProfileComponent } from '@app/shared/components/my-home-profile/my-home-profile.component'; import { MyProfileUpdateFormComponent } from '@app/shared/components/my-profile-update-form/my-profile-update-form.component'; -import { ProfileService } from '@app/core/services/profile/profile.service'; -import { Profile } from '@app/shared/models/profile'; import { PdfViewerComponent } from '@app/shared/features/pdf-viewer/pdf-viewer.component'; +import { ProfileFacade } from '@app/ui/profiles/profile.facade'; @Component({ selector: 'app-my-profile', standalone: true, imports: [ - JsonPipe, RouterLink, - AsyncPipe, QRCodeModule, ChipsComponent, ReseauxComponent, UpdateUserComponent, - UpperCasePipe, MyProfileProjectListComponent, RouterOutlet, MyHomeProfileComponent, @@ -40,8 +36,6 @@ import { PdfViewerComponent } from '@app/shared/features/pdf-viewer/pdf-viewer.c }) @UntilDestroy() export class MyProfileComponent implements OnInit { - private profileService = inject(ProfileService); - protected readonly environment = environment; protected menu = signal('home'); @@ -58,8 +52,6 @@ export class MyProfileComponent implements OnInit { return {} as User; }); - protected profile: Profile = {} as Profile; - protected isEditMode = signal(false); onChangeURL(url: SafeUrl) { @@ -70,13 +62,13 @@ export class MyProfileComponent implements OnInit { this.isEditMode.set(!$event); } + private readonly profileFacade = new ProfileFacade(); + protected profile = this.profileFacade.profile; + protected readonly loading = this.profileFacade.loading; + protected readonly error = this.profileFacade.error; + ngOnInit(): void { this.myProfileQrCode = `${this.myProfileQrCode}/profiles/${this.user().id}`; - - this.profileService.getProfileByUserId(this.user().id).subscribe({ - next: (value: Profile) => { - this.profile = value; - }, - }); + this.profileFacade.loadOne(this.user().id); } } diff --git a/src/app/routes/profile/profile-detail/profile-detail.component.ts b/src/app/routes/profile/profile-detail/profile-detail.component.ts index 306391c..1dc8caf 100644 --- a/src/app/routes/profile/profile-detail/profile-detail.component.ts +++ b/src/app/routes/profile/profile-detail/profile-detail.component.ts @@ -3,12 +3,12 @@ import { ActivatedRoute, RouterLink } from '@angular/router'; import { QRCodeModule } from 'angularx-qrcode'; import { UpperCasePipe } from '@angular/common'; import { User } from '@app/shared/models/user'; -import { Profile } from '@app/shared/models/profile'; import { ChipsComponent } from '@app/shared/components/chips/chips.component'; import { ReseauxComponent } from '@app/shared/components/reseaux/reseaux.component'; import { UntilDestroy } from '@ngneat/until-destroy'; import { ProjectListComponent } from '@app/shared/components/project-list/project-list.component'; import { environment } from '@env/environment'; +import { Profile } from '@app/domain/profiles/profile.model'; @Component({ selector: 'app-profile-detail', diff --git a/src/app/shared/components/my-home-profile/my-home-profile.component.ts b/src/app/shared/components/my-home-profile/my-home-profile.component.ts index 09db013..3829260 100644 --- a/src/app/shared/components/my-home-profile/my-home-profile.component.ts +++ b/src/app/shared/components/my-home-profile/my-home-profile.component.ts @@ -1,16 +1,16 @@ import { Component, Input } from '@angular/core'; -import { AsyncPipe, JsonPipe, UpperCasePipe } from '@angular/common'; -import { Profile } from '@app/shared/models/profile'; +import { UpperCasePipe } from '@angular/common'; import { UntilDestroy } from '@ngneat/until-destroy'; +import { ProfileViewModel } from '@app/ui/profiles/profile.presenter.model'; @Component({ selector: 'app-my-home-profile', standalone: true, - imports: [UpperCasePipe, AsyncPipe, JsonPipe], + imports: [UpperCasePipe], templateUrl: './my-home-profile.component.html', styleUrl: './my-home-profile.component.scss', }) @UntilDestroy() export class MyHomeProfileComponent { - @Input({ required: true }) profile: Profile | undefined = undefined; + @Input({ required: true }) profile: ProfileViewModel | undefined = undefined; } diff --git a/src/app/shared/components/my-profile-update-cv-form/my-profile-update-cv-form.component.spec.ts b/src/app/shared/components/my-profile-update-cv-form/my-profile-update-cv-form.component.spec.ts index 1b48808..b23f64f 100644 --- a/src/app/shared/components/my-profile-update-cv-form/my-profile-update-cv-form.component.spec.ts +++ b/src/app/shared/components/my-profile-update-cv-form/my-profile-update-cv-form.component.spec.ts @@ -2,19 +2,22 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { MyProfileUpdateCvFormComponent } from './my-profile-update-cv-form.component'; import { ToastrService } from 'ngx-toastr'; -import { ProfileService } from '@app/core/services/profile/profile.service'; import { AuthService } from '@app/core/services/authentication/auth.service'; import { signal } from '@angular/core'; import { Auth } from '@app/shared/models/auth'; import { provideRouter } from '@angular/router'; +import { ProfileRepository } from '@app/domain/profiles/profile.repository'; +import { of } from 'rxjs'; +import { Profile } from '@app/domain/profiles/profile.model'; +import { PROFILE_REPOSITORY_TOKEN } from '@app/infrastructure/profiles/profile-repository.token'; describe('MyProfileUpdateCvFormComponent', () => { let component: MyProfileUpdateCvFormComponent; let fixture: ComponentFixture; let mockToastrService: Partial; - let mockProfileService: Partial; let mockAuthService: Partial; + let mockProfileRepo: ProfileRepository; beforeEach(async () => { mockToastrService = { @@ -23,10 +26,11 @@ describe('MyProfileUpdateCvFormComponent', () => { warning: jest.fn(), }; - mockProfileService = { - updateProfile: jest.fn().mockReturnValue({ - subscribe: jest.fn(), - }), + mockProfileRepo = { + create: jest.fn(), + list: jest.fn(), + update: jest.fn().mockReturnValue(of({} as Profile)), + getByUserId: jest.fn().mockReturnValue(of({} as Profile)), }; mockAuthService = { @@ -38,8 +42,8 @@ describe('MyProfileUpdateCvFormComponent', () => { imports: [MyProfileUpdateCvFormComponent], providers: [ provideRouter([]), + { provide: PROFILE_REPOSITORY_TOKEN, useValue: mockProfileRepo }, { provide: ToastrService, useValue: mockToastrService }, - { provide: ProfileService, useValue: mockProfileService }, { provide: AuthService, useValue: mockAuthService }, ], }).compileComponents(); @@ -47,6 +51,8 @@ describe('MyProfileUpdateCvFormComponent', () => { fixture = TestBed.createComponent(MyProfileUpdateCvFormComponent); component = fixture.componentInstance; fixture.detectChanges(); + + await fixture.whenStable(); }); it('should create', () => { diff --git a/src/app/shared/components/my-profile-update-cv-form/my-profile-update-cv-form.component.ts b/src/app/shared/components/my-profile-update-cv-form/my-profile-update-cv-form.component.ts index 5eb76ec..b5c56de 100644 --- a/src/app/shared/components/my-profile-update-cv-form/my-profile-update-cv-form.component.ts +++ b/src/app/shared/components/my-profile-update-cv-form/my-profile-update-cv-form.component.ts @@ -1,9 +1,11 @@ -import { Component, inject, Input, output } from '@angular/core'; +import { Component, effect, inject, Input } from '@angular/core'; import { AuthService } from '@app/core/services/authentication/auth.service'; -import { Profile } from '@app/shared/models/profile'; -import { ProfileService } from '@app/core/services/profile/profile.service'; import { NgClass } from '@angular/common'; import { ToastrService } from 'ngx-toastr'; +import { ProfileViewModel } from '@app/ui/profiles/profile.presenter.model'; +import { ProfileFacade } from '@app/ui/profiles/profile.facade'; +import { ActionType } from '@app/domain/action-type.util'; +import { Profile } from '@app/domain/profiles/profile.model'; @Component({ selector: 'app-my-profile-update-cv-form', @@ -13,29 +15,40 @@ import { ToastrService } from 'ngx-toastr'; styleUrl: './my-profile-update-cv-form.component.scss', }) export class MyProfileUpdateCvFormComponent { - @Input({ required: true }) profile: Profile | undefined = undefined; + @Input({ required: true }) profile: ProfileViewModel | undefined = undefined; - private readonly profileService = inject(ProfileService); private readonly toastrService = inject(ToastrService); private readonly authService = inject(AuthService); file: File | null = null; // Variable to store file + private readonly profileFacade = new ProfileFacade(); + protected readonly loading = this.profileFacade.loading; + protected readonly error = this.profileFacade.error; + + constructor() { + effect(() => { + switch (this.loading().action) { + case ActionType.UPDATE: + if (!this.loading() && !this.error().hasError) { + this.authService.updateUser(); + + this.toastrService.success(` Votre CV a bien été modifier !`, `Mise à jour`, { + closeButton: true, + progressAnimation: 'decreasing', + progressBar: true, + }); + } + } + }); + } + onSubmit() { if (this.file != null) { const formData = new FormData(); formData.append('cv', this.file); // "avatar" est le nom du champ dans PocketBase - - this.profileService.updateProfile(this.profile?.id!, formData).subscribe((value) => { - this.authService.updateUser(); - - this.toastrService.success(` Votre CV a bien été modifier !`, `Mise à jour`, { - closeButton: true, - progressAnimation: 'decreasing', - progressBar: true, - }); - }); + this.profileFacade.update(this.profile?.id!, formData as Partial); } } diff --git a/src/app/shared/components/my-profile-update-form/my-profile-update-form.component.spec.ts b/src/app/shared/components/my-profile-update-form/my-profile-update-form.component.spec.ts index f3d18a1..a51312e 100644 --- a/src/app/shared/components/my-profile-update-form/my-profile-update-form.component.spec.ts +++ b/src/app/shared/components/my-profile-update-form/my-profile-update-form.component.spec.ts @@ -4,12 +4,17 @@ import { MyProfileUpdateFormComponent } from './my-profile-update-form.component import { provideRouter } from '@angular/router'; import { ToastrService } from 'ngx-toastr'; import { FormBuilder } from '@angular/forms'; +import { PROFILE_REPOSITORY_TOKEN } from '@app/infrastructure/profiles/profile-repository.token'; +import { ProfileRepository } from '@app/domain/profiles/profile.repository'; +import { of } from 'rxjs'; +import { Profile } from '@app/domain/profiles/profile.model'; describe('MyProfileUpdateFormComponent', () => { let component: MyProfileUpdateFormComponent; let fixture: ComponentFixture; let mockToastrService: Partial; + let mockProfileRepo: ProfileRepository; const mockProfileData = { profession: '', @@ -26,18 +31,28 @@ describe('MyProfileUpdateFormComponent', () => { error: jest.fn(), }; + mockProfileRepo = { + create: jest.fn(), + list: jest.fn(), + update: jest.fn().mockReturnValue(of({} as Profile)), + getByUserId: jest.fn().mockReturnValue(of({} as Profile)), + }; + await TestBed.configureTestingModule({ imports: [MyProfileUpdateFormComponent], providers: [ FormBuilder, provideRouter([]), { provide: ToastrService, useValue: mockToastrService }, + { provide: PROFILE_REPOSITORY_TOKEN, useValue: mockProfileRepo }, ], }).compileComponents(); fixture = TestBed.createComponent(MyProfileUpdateFormComponent); component = fixture.componentInstance; fixture.detectChanges(); + + await fixture.whenStable(); }); it('should create', () => { diff --git a/src/app/shared/components/my-profile-update-form/my-profile-update-form.component.ts b/src/app/shared/components/my-profile-update-form/my-profile-update-form.component.ts index 29ab3ff..79df41a 100644 --- a/src/app/shared/components/my-profile-update-form/my-profile-update-form.component.ts +++ b/src/app/shared/components/my-profile-update-form/my-profile-update-form.component.ts @@ -1,4 +1,4 @@ -import { Component, inject, Input, OnInit, signal } from '@angular/core'; +import { Component, effect, inject, Input, OnInit, signal } from '@angular/core'; import { FormBuilder, FormControl, @@ -7,14 +7,16 @@ import { Validators, } from '@angular/forms'; import { UntilDestroy } from '@ngneat/until-destroy'; -import { Profile } from '@app/shared/models/profile'; import { NgClass } from '@angular/common'; import { SectorService } from '@app/core/services/sector/sector.service'; import { Sector } from '@app/shared/models/sector'; -import { ProfileService } from '@app/core/services/profile/profile.service'; import { AuthService } from '@app/core/services/authentication/auth.service'; import { MyProfileUpdateCvFormComponent } from '@app/shared/components/my-profile-update-cv-form/my-profile-update-cv-form.component'; import { ToastrService } from 'ngx-toastr'; +import { ProfileViewModel } from '@app/ui/profiles/profile.presenter.model'; +import { ProfileFacade } from '@app/ui/profiles/profile.facade'; +import { ActionType } from '@app/domain/action-type.util'; +import { Profile } from '@app/domain/profiles/profile.model'; @Component({ selector: 'app-my-profile-update-form', @@ -27,14 +29,38 @@ import { ToastrService } from 'ngx-toastr'; export class MyProfileUpdateFormComponent implements OnInit { private readonly toastrService = inject(ToastrService); - @Input({ required: true }) profile: Profile = {} as Profile; + @Input({ required: true }) profile: ProfileViewModel = {} as ProfileViewModel; private readonly formBuilder = inject(FormBuilder); protected readonly sectorService = inject(SectorService); - protected readonly profileService = inject(ProfileService); protected readonly authService = inject(AuthService); profileForm!: FormGroup; protected sectors = signal([]); + private readonly profileFacade = new ProfileFacade(); + protected readonly loading = this.profileFacade.loading; + protected readonly error = this.profileFacade.error; + + constructor() { + effect(() => { + switch (this.loading().action) { + case ActionType.UPDATE: + if (!this.loading() && !this.error().hasError) { + this.authService.updateUser(); + + this.toastrService.success( + ` Vos informations personnelles ont bien été modifier !`, + `Mise à jour`, + { + closeButton: true, + progressAnimation: 'decreasing', + progressBar: true, + } + ); + } + } + }); + } + ngOnInit(): void { this.profileForm = this.formBuilder.group({ profession: new FormControl( @@ -90,26 +116,6 @@ export class MyProfileUpdateFormComponent implements OnInit { reseaux: this.profileForm.getRawValue().reseaux, } as Profile; - this.profileService.updateProfile(this.profile.id, data).subscribe({ - next: (value) => { - this.authService.updateUser(); - - this.toastrService.success( - ` Vos informations personnelles ont bien été modifier !`, - `Mise à jour`, - { - closeButton: true, - progressAnimation: 'decreasing', - progressBar: true, - } - ); - }, - error: (error) => { - this.toastrService.error( - 'Une erreur est survenue lors de la mise à jour de votre profil', - 'Erreur' - ); - }, - }); + this.profileFacade.update(this.profile.id, data); } } diff --git a/src/app/shared/components/my-profile-update-project-form/my-profile-update-project-form.component.ts b/src/app/shared/components/my-profile-update-project-form/my-profile-update-project-form.component.ts index f19bfdf..4acb6da 100644 --- a/src/app/shared/components/my-profile-update-project-form/my-profile-update-project-form.component.ts +++ b/src/app/shared/components/my-profile-update-project-form/my-profile-update-project-form.component.ts @@ -64,9 +64,9 @@ export class MyProfileUpdateProjectFormComponent implements OnInit, OnChanges { if (this.project() !== undefined) { this.projectForm.setValue({ - nom: this.project()!.nom, - description: this.project()!.description, - lien: this.project()!.lien, + nom: this.project().nom ?? '', + description: this.project().description ?? '', + lien: this.project().lien ?? '', }); } } diff --git a/src/app/shared/features/pdf-viewer/pdf-viewer.component.spec.ts b/src/app/shared/features/pdf-viewer/pdf-viewer.component.spec.ts index 34753b4..eb92555 100644 --- a/src/app/shared/features/pdf-viewer/pdf-viewer.component.spec.ts +++ b/src/app/shared/features/pdf-viewer/pdf-viewer.component.spec.ts @@ -1,16 +1,16 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { PdfViewerComponent } from './pdf-viewer.component'; -import { Profile } from '@app/shared/models/profile'; +import { ProfileViewModel } from '@app/ui/profiles/profile.presenter.model'; describe('PdfViewerComponent', () => { let component: PdfViewerComponent; let fixture: ComponentFixture; - const mockProfile: Profile = { + const mockProfile: ProfileViewModel = { id: '123', cv: 'cvfilename.pdf', - } as Profile; + } as ProfileViewModel; beforeEach(async () => { await TestBed.configureTestingModule({ diff --git a/src/app/shared/features/pdf-viewer/pdf-viewer.component.ts b/src/app/shared/features/pdf-viewer/pdf-viewer.component.ts index a2204ce..0181203 100644 --- a/src/app/shared/features/pdf-viewer/pdf-viewer.component.ts +++ b/src/app/shared/features/pdf-viewer/pdf-viewer.component.ts @@ -1,7 +1,7 @@ import { Component, computed, Input } from '@angular/core'; import { PdfViewerModule } from 'ng2-pdf-viewer'; -import { Profile } from '@app/shared/models/profile'; import { environment } from '@env/environment'; +import { ProfileViewModel } from '@app/ui/profiles/profile.presenter.model'; @Component({ selector: 'app-pdf-viewer', @@ -11,7 +11,7 @@ import { environment } from '@env/environment'; styleUrl: './pdf-viewer.component.scss', }) export class PdfViewerComponent { - @Input({ required: true }) profile: Profile | undefined = undefined; + @Input({ required: true }) profile: ProfileViewModel | undefined = undefined; protected readonly environment = environment; protected readonly cv_link = computed(() => { return `${environment.baseUrl}/api/files/profiles/${this.profile!.id}/${this.profile!.cv}`; diff --git a/src/app/shared/features/register/register.component.spec.ts b/src/app/shared/features/register/register.component.spec.ts index 1b8a144..b3ceab7 100644 --- a/src/app/shared/features/register/register.component.spec.ts +++ b/src/app/shared/features/register/register.component.spec.ts @@ -3,8 +3,9 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { RegisterComponent } from './register.component'; import { ToastrService } from 'ngx-toastr'; import { AuthService } from '@app/core/services/authentication/auth.service'; -import { ProfileService } from '@app/core/services/profile/profile.service'; import { provideRouter } from '@angular/router'; +import { ProfileRepository } from '@app/domain/profiles/profile.repository'; +import { PROFILE_REPOSITORY_TOKEN } from '@app/infrastructure/profiles/profile-repository.token'; describe('RegisterComponent', () => { let component: RegisterComponent; @@ -12,19 +13,22 @@ describe('RegisterComponent', () => { let mockToastrService: Partial; let mockAuthService: Partial; - let mockProfileService: Partial; + let mockProfileRepo: ProfileRepository; beforeEach(async () => { + mockProfileRepo = { + create: jest.fn(), + list: jest.fn(), + update: jest.fn(), + getByUserId: jest.fn(), + }; + mockToastrService = { success: jest.fn(), error: jest.fn(), warning: jest.fn(), }; - mockProfileService = { - createProfile: jest.fn().mockResolvedValue(true), - }; - mockAuthService = {}; await TestBed.configureTestingModule({ @@ -33,7 +37,7 @@ describe('RegisterComponent', () => { provideRouter([]), { provide: ToastrService, useValue: mockToastrService }, { provide: AuthService, useValue: mockAuthService }, - { provide: ProfileService, useValue: mockProfileService }, + { provide: PROFILE_REPOSITORY_TOKEN, useValue: mockProfileRepo }, ], }).compileComponents(); diff --git a/src/app/shared/features/register/register.component.ts b/src/app/shared/features/register/register.component.ts index 280d8a2..f0cef66 100644 --- a/src/app/shared/features/register/register.component.ts +++ b/src/app/shared/features/register/register.component.ts @@ -1,13 +1,14 @@ -import { ChangeDetectionStrategy, Component, inject, output, signal } from '@angular/core'; +import { ChangeDetectionStrategy, Component, effect, inject, output, signal } from '@angular/core'; import { Router, RouterLink } from '@angular/router'; import { FormBuilder, FormControl, ReactiveFormsModule, Validators } from '@angular/forms'; import { AuthService } from '@app/core/services/authentication/auth.service'; import { RegisterDto } from '@app/shared/models/register-dto'; import { UntilDestroy } from '@ngneat/until-destroy'; import { ToastrService } from 'ngx-toastr'; -import { ProfileService } from '@app/core/services/profile/profile.service'; -import { ProfileDto } from '@app/shared/models/profile-dto'; import { ProgressBarModule } from 'primeng/progressbar'; +import { ProfileFacade } from '@app/ui/profiles/profile.facade'; +import { ActionType } from '@app/domain/action-type.util'; +import { ProfileDTO } from '@app/domain/profiles/dto/create-profile.dto'; @Component({ selector: 'app-register', @@ -20,7 +21,6 @@ import { ProgressBarModule } from 'primeng/progressbar'; @UntilDestroy() export class RegisterComponent { private readonly authService = inject(AuthService); - private readonly profileService = inject(ProfileService); private readonly toastrService = inject(ToastrService); private readonly formBuilder = inject(FormBuilder); @@ -34,6 +34,26 @@ export class RegisterComponent { formSubmitted = output(); protected isLoading = signal(false); + private readonly profileFacade = new ProfileFacade(); + protected readonly loading = this.profileFacade.loading; + protected readonly error = this.profileFacade.error; + + constructor() { + effect(() => { + switch (this.loading().action) { + case ActionType.CREATE: + if (!this.loading().isLoading) { + if (!this.error().hasError) { + this.router.navigate(['/auth']).then(() => { + this.sendVerificationEmail(); + }); + } + } + break; + } + }); + } + onSubmit() { if (this.registerForm.invalid) { this.isLoading.set(false); @@ -63,13 +83,13 @@ export class RegisterComponent { register(registerDto: RegisterDto) { this.authService.register(registerDto).then((res) => { if (res) { - this.createProfile(res.id, registerDto.email); + this.createProfile(res.id); } }); } - createProfile(userId: string, email: string) { - const profileDto: ProfileDto = { + createProfile(userId: string) { + const profileDto: ProfileDTO = { profession: 'Profession non renseignée', utilisateur: userId, reseaux: { @@ -83,28 +103,11 @@ export class RegisterComponent { }, }; - this.profileService.createProfile(profileDto).subscribe({ - next: (profile) => { - if (profile) - this.router.navigate(['/auth']).then(() => { - this.sendVerificationEmail(email); - }); - }, - error: (err) => { - this.toastrService.error( - `Une erreur est survenue lors de la création de votre profil.`, - `Erreur`, - { - closeButton: true, - progressAnimation: 'decreasing', - progressBar: true, - } - ); - }, - }); + this.profileFacade.create(profileDto); } - sendVerificationEmail(email: string) { + sendVerificationEmail() { + const email = this.registerForm.getRawValue().email!; this.authService.verifyEmail(email).then((isVerified: boolean) => { this.isLoading.set(false); this.registerForm.enable(); diff --git a/src/app/shared/models/profile-dto.ts b/src/app/shared/models/profile-dto.ts deleted file mode 100644 index fe226e6..0000000 --- a/src/app/shared/models/profile-dto.ts +++ /dev/null @@ -1,5 +0,0 @@ -export interface ProfileDto { - profession: string; - utilisateur: string; - reseaux: any; -} diff --git a/src/app/shared/models/profile.ts b/src/app/shared/models/profile.ts deleted file mode 100644 index b8b4796..0000000 --- a/src/app/shared/models/profile.ts +++ /dev/null @@ -1,14 +0,0 @@ -export interface Profile { - id: string; - created: string; - updated: string; - profession: string; - utilisateur: string; - estVerifier: boolean; - secteur: string; - reseaux: JSON; - bio: string; - cv: string; - projets: string[]; - apropos: string; -} diff --git a/src/app/shared/models/project-dto.ts b/src/app/shared/models/project-dto.ts deleted file mode 100644 index db6b3f5..0000000 --- a/src/app/shared/models/project-dto.ts +++ /dev/null @@ -1,6 +0,0 @@ -export interface ProjectDto { - nom: string; - description: string; - lien: string; - utilisateur: string; -} diff --git a/src/app/shared/models/project.ts b/src/app/shared/models/project.ts deleted file mode 100644 index 854c576..0000000 --- a/src/app/shared/models/project.ts +++ /dev/null @@ -1,10 +0,0 @@ -export interface Project { - id: string; - created: string; - updated: string; - nom: string; - lien: string; - description: string; - fichier: string[]; - utilisateur: string; -} diff --git a/src/app/testing/domain/profiles/fake-profile.repository.ts b/src/app/testing/domain/profiles/fake-profile.repository.ts index 5c9380e..920a1f1 100644 --- a/src/app/testing/domain/profiles/fake-profile.repository.ts +++ b/src/app/testing/domain/profiles/fake-profile.repository.ts @@ -8,8 +8,8 @@ export class FakeProfileRepository implements ProfileRepository { return of(mockProfiles); } - getByUserId(userId: string): Observable { - const profile = mockProfiles.find((p) => p.utilisateur === userId) ?? null; + getByUserId(userId: string): Observable { + const profile = mockProfiles.find((p) => p.utilisateur === userId) ?? ({} as Profile); return of(profile); } diff --git a/src/app/testing/domain/projects/fake-project.repository.ts b/src/app/testing/domain/projects/fake-project.repository.ts index 62f902a..dfdf150 100644 --- a/src/app/testing/domain/projects/fake-project.repository.ts +++ b/src/app/testing/domain/projects/fake-project.repository.ts @@ -1,8 +1,8 @@ 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'; +import { Project } from '@app/domain/projects/project.model'; export class FakeProjectRepository implements ProjectRepository { private projects: Project[] = [...fakeProjects]; diff --git a/src/app/testing/ui/profiles/profile.facade.spec.ts b/src/app/testing/ui/profiles/profile.facade.spec.ts index dcbfa81..cc7a482 100644 --- a/src/app/testing/ui/profiles/profile.facade.spec.ts +++ b/src/app/testing/ui/profiles/profile.facade.spec.ts @@ -2,6 +2,9 @@ import { ProfileFacade } from '@app/ui/profiles/profile.facade'; import { TestBed } from '@angular/core/testing'; import { PROFILE_REPOSITORY_TOKEN } from '@app/infrastructure/profiles/profile-repository.token'; import { FakeProfileRepository } from '@app/testing/domain/profiles/fake-profile.repository'; +import { mockProfiles } from '@app/testing/profile.mock'; +import { Profile } from '@app/domain/profiles/profile.model'; +import { ProfileDTO } from '@app/domain/profiles/dto/create-profile.dto'; describe('ProfileFacade', () => { let facade: ProfileFacade; @@ -27,4 +30,52 @@ describe('ProfileFacade', () => { done(); }, 0); }); + + it("doit charger le profile d'un utilisateur ", () => { + facade.loadOne('1'); + const expectedProfile = mockProfiles[0]; + + // attendre un peu le .subscribe + setTimeout(() => { + expect(facade.profile()).toBe(expectedProfile); + expect(facade.loading().isLoading).toBe(false); + expect(facade.error().hasError).toBe(false); + }, 0); + }); + + it('doit mettre à jour un profile ', () => { + expect(mockProfiles[0].profession).toBe('Développeur Web'); + + const profileUpdated: Profile = { + ...mockProfiles[0], + profession: 'Devops', + }; + + facade.update('1', profileUpdated); + + // attendre un peu le .subscribe + setTimeout(() => { + expect(facade.profile().profession).toBe('Devops'); + expect(facade.loading().isLoading).toBe(false); + expect(facade.error().hasError).toBe(false); + }, 0); + }); + + it('doit creer un nouveau profile ', () => { + const profile: ProfileDTO = { + utilisateur: 'john doe', + reseaux: {}, + profession: 'Journaliste', + }; + + facade.create(profile); + + // attendre un peu le .subscribe + setTimeout(() => { + expect(mockProfiles.find((profile) => profile.id === facade.profile().id)).toBeDefined(); + expect(facade.profile().profession).toBe('Journaliste'); + expect(facade.loading().isLoading).toBe(false); + expect(facade.error().hasError).toBe(false); + }, 0); + }); }); diff --git a/src/app/testing/ui/profiles/profile.presenter.spec.ts b/src/app/testing/ui/profiles/profile.presenter.spec.ts index aea5a4a..6d4db35 100644 --- a/src/app/testing/ui/profiles/profile.presenter.spec.ts +++ b/src/app/testing/ui/profiles/profile.presenter.spec.ts @@ -19,6 +19,10 @@ describe('ProfilePresenter', () => { utilisateur: 'user_abc', createdAtFormatted: new Date(profile.created).toLocaleDateString(), avatarUrl: '', + apropos: 'Développeur Angular & Node.js', + bio: 'Passionné de code.', + cv: 'cv.pdf', + projets: ['p1', 'p2'], }); }); diff --git a/src/app/testing/usecase/profiles/create-profile.usecase.spec.ts b/src/app/testing/usecase/profiles/create-profile.usecase.spec.ts new file mode 100644 index 0000000..6aaff3a --- /dev/null +++ b/src/app/testing/usecase/profiles/create-profile.usecase.spec.ts @@ -0,0 +1,26 @@ +import { FakeProfileRepository } from '@app/testing/domain/profiles/fake-profile.repository'; +import { mockProfiles } from '@app/testing/profile.mock'; +import { CreateProfileUseCase } from '@app/usecase/profiles/create-profile.usecase'; +import { ProfileDTO } from '@app/domain/profiles/dto/create-profile.dto'; + +describe('CreateProfileUseCase', () => { + it('doit creer nouveau un profile', () => { + const repo = new FakeProfileRepository(); + const useCase = new CreateProfileUseCase(repo); + + const profile: ProfileDTO = { + utilisateur: 'john doe', + profession: 'Designer', + reseaux: {}, + }; + + useCase.execute(profile).subscribe({ + next: (profile) => { + expect(mockProfiles).toContain(profile); + expect(mockProfiles.find((current_profile) => profile.id === current_profile.id)).toBe( + profile.id + ); + }, + }); + }); +}); diff --git a/src/app/testing/usecase/profiles/get-profile.usecase.spec.ts b/src/app/testing/usecase/profiles/get-profile.usecase.spec.ts new file mode 100644 index 0000000..936e645 --- /dev/null +++ b/src/app/testing/usecase/profiles/get-profile.usecase.spec.ts @@ -0,0 +1,16 @@ +import { FakeProfileRepository } from '@app/testing/domain/profiles/fake-profile.repository'; +import { mockProfiles } from '@app/testing/profile.mock'; +import { GetProfileUseCase } from '@app/usecase/profiles/get-profile.usecase'; + +describe('GetProfileUseCase', () => { + it('doit retourner un profile', () => { + const repo = new FakeProfileRepository(); + const useCase = new GetProfileUseCase(repo); + + useCase.execute('1').subscribe({ + next: (profile) => { + expect(profile).toEqual(mockProfiles[0]); + }, + }); + }); +}); diff --git a/src/app/testing/usecase/profiles/update-profile.usecase.spec.ts b/src/app/testing/usecase/profiles/update-profile.usecase.spec.ts new file mode 100644 index 0000000..4f65f8d --- /dev/null +++ b/src/app/testing/usecase/profiles/update-profile.usecase.spec.ts @@ -0,0 +1,23 @@ +import { FakeProfileRepository } from '@app/testing/domain/profiles/fake-profile.repository'; +import { mockProfiles } from '@app/testing/profile.mock'; +import { UpdateProfileUseCase } from '@app/usecase/profiles/update-profile.usecase'; + +describe('UpdateProfileUseCase', () => { + it('doit retourner un profile modifier', () => { + const repo = new FakeProfileRepository(); + const useCase = new UpdateProfileUseCase(repo); + const profile = mockProfiles[0]; + + expect(profile.profession).toBe('Développeur Web'); + + const profileUpdate = { + ...profile, + profession: 'Développeur fullstack', + }; + useCase.execute('1', profileUpdate).subscribe({ + next: (profile) => { + expect(profile.profession).toEqual('Développeur fullstack'); + }, + }); + }); +}); diff --git a/src/app/ui/profiles/profile.facade.ts b/src/app/ui/profiles/profile.facade.ts index f4ce503..dc7bd2a 100644 --- a/src/app/ui/profiles/profile.facade.ts +++ b/src/app/ui/profiles/profile.facade.ts @@ -1,21 +1,30 @@ import { ListProfilesUseCase } from '@app/usecase/profiles/list-profiles.usecase'; -import { inject, signal } from '@angular/core'; +import { inject, Injectable, signal } from '@angular/core'; import { PROFILE_REPOSITORY_TOKEN } from '@app/infrastructure/profiles/profile-repository.token'; import { Profile } from '@app/domain/profiles/profile.model'; -import { Injectable } from '@angular/core'; import { ProfilePresenter } from '@app/ui/profiles/profile.presenter'; 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'; +import { CreateProfileUseCase } from '@app/usecase/profiles/create-profile.usecase'; +import { UpdateProfileUseCase } from '@app/usecase/profiles/update-profile.usecase'; +import { GetProfileUseCase } from '@app/usecase/profiles/get-profile.usecase'; +import { ProfileDTO } from '@app/domain/profiles/dto/create-profile.dto'; @Injectable({ providedIn: 'root', }) export class ProfileFacade { private profileRepository = inject(PROFILE_REPOSITORY_TOKEN); + private listUseCase = new ListProfilesUseCase(this.profileRepository); + private createUseCase = new CreateProfileUseCase(this.profileRepository); + private updateUseCase = new UpdateProfileUseCase(this.profileRepository); + private getUseCase = new GetProfileUseCase(this.profileRepository); + readonly profiles = signal([]); + readonly profile = signal({} as ProfileViewModel); readonly loading = signal({ isLoading: false, action: ActionType.NONE }); readonly error = signal({ action: ActionType.NONE, @@ -37,6 +46,48 @@ export class ProfileFacade { }); } + loadOne(userId: string) { + this.handleError(ActionType.READ, false, null, true); + + this.getUseCase.execute(userId).subscribe({ + next: (profile: Profile) => { + this.profile.set(ProfilePresenter.toViewModel(profile)); + this.handleError(ActionType.READ, false, null, false); + }, + error: (err) => { + this.handleError(ActionType.READ, false, err, false); + }, + }); + } + + create(profileDto: ProfileDTO) { + this.handleError(ActionType.CREATE, false, null, true); + + this.createUseCase.execute(profileDto).subscribe({ + next: (profile: Profile) => { + this.profile.set(ProfilePresenter.toViewModel(profile)); + this.handleError(ActionType.CREATE, false, null, false); + }, + error: (err) => { + this.handleError(ActionType.CREATE, false, err, false); + }, + }); + } + + update(profileId: string, profile: Partial) { + this.handleError(ActionType.UPDATE, false, null, true); + + this.updateUseCase.execute(profileId, profile).subscribe({ + next: (profile: Profile) => { + this.profile.set(ProfilePresenter.toViewModel(profile)); + this.handleError(ActionType.UPDATE, false, null, false); + }, + error: (err) => { + this.handleError(ActionType.UPDATE, false, err, false); + }, + }); + } + private handleError( action: ActionType = ActionType.NONE, hasError: boolean, diff --git a/src/app/ui/profiles/profile.presenter.model.ts b/src/app/ui/profiles/profile.presenter.model.ts index 91a384e..d3ff22f 100644 --- a/src/app/ui/profiles/profile.presenter.model.ts +++ b/src/app/ui/profiles/profile.presenter.model.ts @@ -9,4 +9,8 @@ export interface ProfileViewModel { profession: string; secteur: string; reseaux: any; + apropos: string; + bio: string; + cv: string; + projets: string[]; } diff --git a/src/app/ui/profiles/profile.presenter.ts b/src/app/ui/profiles/profile.presenter.ts index 7f2b003..d2ec86b 100644 --- a/src/app/ui/profiles/profile.presenter.ts +++ b/src/app/ui/profiles/profile.presenter.ts @@ -14,6 +14,10 @@ export class ProfilePresenter { profession: profile.profession, secteur: profile.secteur, reseaux: profile.reseaux, + apropos: profile.apropos, + projets: profile.projets, + cv: profile.cv, + bio: profile.bio, }; } diff --git a/src/app/usecase/profiles/create-profile.usecase.ts b/src/app/usecase/profiles/create-profile.usecase.ts new file mode 100644 index 0000000..a584915 --- /dev/null +++ b/src/app/usecase/profiles/create-profile.usecase.ts @@ -0,0 +1,12 @@ +import { ProfileRepository } from '@app/domain/profiles/profile.repository'; +import { Profile } from '@app/domain/profiles/profile.model'; +import { Observable } from 'rxjs'; +import { ProfileDTO } from '@app/domain/profiles/dto/create-profile.dto'; + +export class CreateProfileUseCase { + constructor(private readonly repo: ProfileRepository) {} + + execute(profileDto: ProfileDTO): Observable { + return this.repo.create(profileDto as Profile); + } +} diff --git a/src/app/usecase/profiles/get-profile.usecase.ts b/src/app/usecase/profiles/get-profile.usecase.ts new file mode 100644 index 0000000..7841dd2 --- /dev/null +++ b/src/app/usecase/profiles/get-profile.usecase.ts @@ -0,0 +1,11 @@ +import { ProfileRepository } from '@app/domain/profiles/profile.repository'; +import { Observable } from 'rxjs'; +import { Profile } from '@app/domain/profiles/profile.model'; + +export class GetProfileUseCase { + constructor(private readonly repo: ProfileRepository) {} + + execute(userId: string): Observable { + return this.repo.getByUserId(userId); + } +} diff --git a/src/app/usecase/profiles/update-profile.usecase.ts b/src/app/usecase/profiles/update-profile.usecase.ts new file mode 100644 index 0000000..057545e --- /dev/null +++ b/src/app/usecase/profiles/update-profile.usecase.ts @@ -0,0 +1,11 @@ +import { ProfileRepository } from '@app/domain/profiles/profile.repository'; +import { Profile } from '@app/domain/profiles/profile.model'; +import { Observable } from 'rxjs'; + +export class UpdateProfileUseCase { + constructor(private readonly repo: ProfileRepository) {} + + execute(profileId: string, profile: Partial): Observable { + return this.repo.update(profileId, profile as Profile); + } +} diff --git a/src/setup-jest.ts b/src/setup-jest.ts index 7c3ae46..6e92006 100644 --- a/src/setup-jest.ts +++ b/src/setup-jest.ts @@ -22,3 +22,33 @@ Object.defineProperty(document.body.style, 'transform', { }; }, }); + +// Simule la fonction fetch pour éviter les erreurs dans les tests Jest +global.fetch = jest.fn(() => + Promise.resolve({ + ok: true, + json: () => Promise.resolve({}), + blob: () => Promise.resolve(new Blob()), + }) +) as jest.Mock; + +// 🟡 Ignore le warning pdfjs-dist +const originalWarn = console.warn; +console.warn = (...args) => { + if ( + typeof args[0] === 'string' && + args[0].includes('ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING_FLAG') + ) { + return; + } + originalWarn(...args); +}; + +// 🔴 Ignore le log angularx-qrcode après les tests +const originalError = console.error; +console.error = (...args) => { + if (typeof args[0] === 'string' && args[0].includes('[angularx-qrcode]')) { + return; + } + originalError(...args); +}; diff --git a/structure.md b/structure.md deleted file mode 100644 index a72fcb0..0000000 --- a/structure.md +++ /dev/null @@ -1,354 +0,0 @@ -. -├── .actrc -├── .angular -│   └── cache -│   └── 17.3.17 -│   ├── TrouveTonProfile -│   │   └── .tsbuildinfo -│   └── vite -│   └── deps -│   ├── @angular_common.js -│   ├── @angular_common.js.map -│   ├── @angular_common_http.js -│   ├── @angular_common_http.js.map -│   ├── @angular_core.js -│   ├── @angular_core.js.map -│   ├── @angular_forms.js -│   ├── @angular_forms.js.map -│   ├── @angular_platform-browser.js -│   ├── @angular_platform-browser.js.map -│   ├── @angular_platform-browser_animations.js -│   ├── @angular_platform-browser_animations.js.map -│   ├── @angular_router.js -│   ├── @angular_router.js.map -│   ├── @fortawesome_angular-fontawesome.js -│   ├── @fortawesome_angular-fontawesome.js.map -│   ├── @ngneat_until-destroy.js -│   ├── @ngneat_until-destroy.js.map -│   ├── _metadata.json -│   ├── angularx-qrcode.js -│   ├── angularx-qrcode.js.map -│   ├── chunk-24ZYNOED.js -│   ├── chunk-24ZYNOED.js.map -│   ├── chunk-3EFO7QKA.js -│   ├── chunk-3EFO7QKA.js.map -│   ├── chunk-3VPQ5C6Q.js -│   ├── chunk-3VPQ5C6Q.js.map -│   ├── chunk-5HONZSNN.js -│   ├── chunk-5HONZSNN.js.map -│   ├── chunk-5S2WJ7I2.js -│   ├── chunk-5S2WJ7I2.js.map -│   ├── chunk-ARYSD6E7.js -│   ├── chunk-ARYSD6E7.js.map -│   ├── chunk-CMCTAO2J.js -│   ├── chunk-CMCTAO2J.js.map -│   ├── chunk-DMKDLVH4.js -│   ├── chunk-DMKDLVH4.js.map -│   ├── chunk-HW7FRNZO.js -│   ├── chunk-HW7FRNZO.js.map -│   ├── chunk-QJER22WX.js -│   ├── chunk-QJER22WX.js.map -│   ├── fs-BSX2J5PF.js -│   ├── fs-BSX2J5PF.js.map -│   ├── http-3CT2HGWN.js -│   ├── http-3CT2HGWN.js.map -│   ├── https-HEKUO7DA.js -│   ├── https-HEKUO7DA.js.map -│   ├── ng2-pdf-viewer.js -│   ├── ng2-pdf-viewer.js.map -│   ├── ngx-toastr.js -│   ├── ngx-toastr.js.map -│   ├── package.json -│   ├── pocketbase.js -│   ├── pocketbase.js.map -│   ├── primeng_paginator.js -│   ├── primeng_paginator.js.map -│   ├── primeng_progressbar.js -│   ├── primeng_progressbar.js.map -│   ├── rxjs.js -│   ├── rxjs.js.map -│   ├── tslib.js -│   ├── tslib.js.map -│   ├── url-JDV7XCKY.js -│   └── url-JDV7XCKY.js.map -├── .dockerignore -├── .editorconfig -├── .gitea -│   └── workflows -│   └── ci.yaml -├── .gitignore -├── .idea -│   ├── .gitignore -│   ├── TrouveTonProfile.iml -│   ├── misc.xml -│   ├── modules.xml -│   ├── vcs.xml -│   └── workspace.xml -├── CMD.md -├── Dockerfile -├── LICENSE -├── README.md -├── angular.json -├── jest.config.js -├── nginx.conf -├── package-lock.json -├── package.json -├── server.ts -├── src -│   ├── app -│   │   ├── app.component.html -│   │   ├── app.component.scss -│   │   ├── app.component.spec.ts -│   │   ├── app.component.ts -│   │   ├── app.config.server.ts -│   │   ├── app.config.ts -│   │   ├── app.routes.ts -│   │   ├── core -│   │   │   ├── guard -│   │   │   │   └── authentication -│   │   │   │   ├── auth.guard.spec.ts -│   │   │   │   └── auth.guard.ts -│   │   │   ├── resolvers -│   │   │   │   ├── my-profile -│   │   │   │   │   ├── my-profile.resolver.spec.ts -│   │   │   │   │   └── my-profile.resolver.ts -│   │   │   │   └── profile -│   │   │   │   ├── detail -│   │   │   │   │   ├── detail.resolver.spec.ts -│   │   │   │   │   └── detail.resolver.ts -│   │   │   │   └── list -│   │   │   │   ├── list.resolver.spec.ts -│   │   │   │   └── list.resolver.ts -│   │   │   └── services -│   │   │   ├── authentication -│   │   │   │   ├── auth.service.spec.ts -│   │   │   │   └── auth.service.ts -│   │   │   ├── profile -│   │   │   │   ├── profile.service.spec.ts -│   │   │   │   └── profile.service.ts -│   │   │   ├── project -│   │   │   │   ├── project.service.spec.ts -│   │   │   │   └── project.service.ts -│   │   │   ├── sector -│   │   │   │   ├── sector.service.spec.ts -│   │   │   │   └── sector.service.ts -│   │   │   ├── theme -│   │   │   │   ├── theme.service.spec.ts -│   │   │   │   └── theme.service.ts -│   │   │   └── user -│   │   │   ├── user.service.spec.ts -│   │   │   └── user.service.ts -│   │   ├── routes -│   │   │   ├── authentification -│   │   │   │   ├── auth -│   │   │   │   │   ├── auth.component.html -│   │   │   │   │   ├── auth.component.scss -│   │   │   │   │   ├── auth.component.spec.ts -│   │   │   │   │   └── auth.component.ts -│   │   │   │   ├── authentification-routing.module.ts -│   │   │   │   └── authentification.module.ts -│   │   │   ├── home -│   │   │   │   ├── home-routing.module.ts -│   │   │   │   ├── home.component.html -│   │   │   │   ├── home.component.scss -│   │   │   │   ├── home.component.spec.ts -│   │   │   │   ├── home.component.ts -│   │   │   │   └── home.module.ts -│   │   │   ├── my-profile -│   │   │   │   ├── my-profile-routing.module.ts -│   │   │   │   ├── my-profile.component.html -│   │   │   │   ├── my-profile.component.scss -│   │   │   │   ├── my-profile.component.spec.ts -│   │   │   │   ├── my-profile.component.ts -│   │   │   │   └── my-profile.module.ts -│   │   │   ├── not-found -│   │   │   │   ├── not-found-routing.module.ts -│   │   │   │   ├── not-found.component.html -│   │   │   │   ├── not-found.component.scss -│   │   │   │   ├── not-found.component.spec.ts -│   │   │   │   ├── not-found.component.ts -│   │   │   │   └── not-found.module.ts -│   │   │   └── profile -│   │   │   ├── profile-detail -│   │   │   │   ├── profile-detail.component.html -│   │   │   │   ├── profile-detail.component.scss -│   │   │   │   ├── profile-detail.component.spec.ts -│   │   │   │   └── profile-detail.component.ts -│   │   │   ├── profile-list -│   │   │   │   ├── profile-list.component.html -│   │   │   │   ├── profile-list.component.scss -│   │   │   │   ├── profile-list.component.spec.ts -│   │   │   │   └── profile-list.component.ts -│   │   │   ├── profile-routing.module.ts -│   │   │   └── profile.module.ts -│   │   └── shared -│   │   ├── components -│   │   │   ├── chips -│   │   │   │   ├── chips.component.html -│   │   │   │   ├── chips.component.scss -│   │   │   │   ├── chips.component.spec.ts -│   │   │   │   └── chips.component.ts -│   │   │   ├── footer -│   │   │   │   ├── footer.component.html -│   │   │   │   ├── footer.component.scss -│   │   │   │   ├── footer.component.spec.ts -│   │   │   │   └── footer.component.ts -│   │   │   ├── horizental-profile-item -│   │   │   │   ├── horizental-profile-item.component.html -│   │   │   │   ├── horizental-profile-item.component.scss -│   │   │   │   ├── horizental-profile-item.component.spec.ts -│   │   │   │   └── horizental-profile-item.component.ts -│   │   │   ├── horizental-profile-list -│   │   │   │   ├── horizental-profile-list.component.html -│   │   │   │   ├── horizental-profile-list.component.scss -│   │   │   │   ├── horizental-profile-list.component.spec.ts -│   │   │   │   └── horizental-profile-list.component.ts -│   │   │   ├── my-home-profile -│   │   │   │   ├── my-home-profile.component.html -│   │   │   │   ├── my-home-profile.component.scss -│   │   │   │   ├── my-home-profile.component.spec.ts -│   │   │   │   └── my-home-profile.component.ts -│   │   │   ├── my-profile-project-item -│   │   │   │   ├── my-profile-project-item.component.html -│   │   │   │   ├── my-profile-project-item.component.scss -│   │   │   │   ├── my-profile-project-item.component.spec.ts -│   │   │   │   └── my-profile-project-item.component.ts -│   │   │   ├── my-profile-project-list -│   │   │   │   ├── my-profile-project-list.component.html -│   │   │   │   ├── my-profile-project-list.component.scss -│   │   │   │   ├── my-profile-project-list.component.spec.ts -│   │   │   │   └── my-profile-project-list.component.ts -│   │   │   ├── my-profile-update-cv-form -│   │   │   │   ├── my-profile-update-cv-form.component.html -│   │   │   │   ├── my-profile-update-cv-form.component.scss -│   │   │   │   ├── my-profile-update-cv-form.component.spec.ts -│   │   │   │   └── my-profile-update-cv-form.component.ts -│   │   │   ├── my-profile-update-form -│   │   │   │   ├── my-profile-update-form.component.html -│   │   │   │   ├── my-profile-update-form.component.scss -│   │   │   │   ├── my-profile-update-form.component.spec.ts -│   │   │   │   └── my-profile-update-form.component.ts -│   │   │   ├── my-profile-update-project-form -│   │   │   │   ├── my-profile-update-project-form.component.html -│   │   │   │   ├── my-profile-update-project-form.component.scss -│   │   │   │   ├── my-profile-update-project-form.component.spec.ts -│   │   │   │   └── my-profile-update-project-form.component.ts -│   │   │   ├── nav-bar -│   │   │   │   ├── nav-bar.component.html -│   │   │   │   ├── nav-bar.component.scss -│   │   │   │   ├── nav-bar.component.spec.ts -│   │   │   │   └── nav-bar.component.ts -│   │   │   ├── project-item -│   │   │   │   ├── project-item.component.html -│   │   │   │   ├── project-item.component.scss -│   │   │   │   ├── project-item.component.spec.ts -│   │   │   │   └── project-item.component.ts -│   │   │   ├── project-list -│   │   │   │   ├── project-list.component.html -│   │   │   │   ├── project-list.component.scss -│   │   │   │   ├── project-list.component.spec.ts -│   │   │   │   └── project-list.component.ts -│   │   │   ├── project-picture-form -│   │   │   │   ├── project-picture-form.component.html -│   │   │   │   ├── project-picture-form.component.scss -│   │   │   │   ├── project-picture-form.component.spec.ts -│   │   │   │   └── project-picture-form.component.ts -│   │   │   ├── reseaux -│   │   │   │   ├── reseaux.component.html -│   │   │   │   ├── reseaux.component.scss -│   │   │   │   ├── reseaux.component.spec.ts -│   │   │   │   └── reseaux.component.ts -│   │   │   ├── user-avatar-form -│   │   │   │   ├── user-avatar-form.component.html -│   │   │   │   ├── user-avatar-form.component.scss -│   │   │   │   ├── user-avatar-form.component.spec.ts -│   │   │   │   └── user-avatar-form.component.ts -│   │   │   ├── user-form -│   │   │   │   ├── user-form.component.html -│   │   │   │   ├── user-form.component.scss -│   │   │   │   ├── user-form.component.spec.ts -│   │   │   │   └── user-form.component.ts -│   │   │   ├── user-password-form -│   │   │   │   ├── user-password-form.component.html -│   │   │   │   ├── user-password-form.component.scss -│   │   │   │   ├── user-password-form.component.spec.ts -│   │   │   │   └── user-password-form.component.ts -│   │   │   ├── vertical-profile-item -│   │   │   │   ├── vertical-profile-item.component.html -│   │   │   │   ├── vertical-profile-item.component.scss -│   │   │   │   ├── vertical-profile-item.component.spec.ts -│   │   │   │   └── vertical-profile-item.component.ts -│   │   │   └── vertical-profile-list -│   │   │   ├── vertical-profile-list.component.html -│   │   │   ├── vertical-profile-list.component.scss -│   │   │   ├── vertical-profile-list.component.spec.ts -│   │   │   └── vertical-profile-list.component.ts -│   │   ├── features -│   │   │   ├── display-profile-card -│   │   │   │   ├── display-profile-card.component.html -│   │   │   │   ├── display-profile-card.component.scss -│   │   │   │   ├── display-profile-card.component.spec.ts -│   │   │   │   └── display-profile-card.component.ts -│   │   │   ├── login -│   │   │   │   ├── login.component.html -│   │   │   │   ├── login.component.scss -│   │   │   │   ├── login.component.spec.ts -│   │   │   │   └── login.component.ts -│   │   │   ├── pdf-viewer -│   │   │   │   ├── pdf-viewer.component.html -│   │   │   │   ├── pdf-viewer.component.scss -│   │   │   │   ├── pdf-viewer.component.spec.ts -│   │   │   │   └── pdf-viewer.component.ts -│   │   │   ├── register -│   │   │   │   ├── register.component.html -│   │   │   │   ├── register.component.scss -│   │   │   │   ├── register.component.spec.ts -│   │   │   │   └── register.component.ts -│   │   │   ├── search -│   │   │   │   ├── search.component.html -│   │   │   │   ├── search.component.scss -│   │   │   │   ├── search.component.spec.ts -│   │   │   │   └── search.component.ts -│   │   │   └── update-user -│   │   │   ├── update-user.component.html -│   │   │   ├── update-user.component.scss -│   │   │   ├── update-user.component.spec.ts -│   │   │   └── update-user.component.ts -│   │   ├── models -│   │   │   ├── auth.ts -│   │   │   ├── login-dto.ts -│   │   │   ├── profile-dto.ts -│   │   │   ├── profile.ts -│   │   │   ├── project-dto.ts -│   │   │   ├── project.ts -│   │   │   ├── register-dto.ts -│   │   │   ├── sector.ts -│   │   │   └── user.ts -│   │   └── utils -│   ├── assets -│   │   ├── .gitkeep -│   │   ├── fonts -│   │   │   └── ubuntu -│   │   │   └── Ubuntu-Regular.ttf -│   │   └── images -│   │   ├── bg.avif -│   │   ├── pdf.svg -│   │   └── ttp.jpg -│   ├── environments -│   │   ├── environment.development.ts -│   │   └── environment.ts -│   ├── favicon.ico -│   ├── index.html -│   ├── main.server.ts -│   ├── main.ts -│   ├── setiterator-fix.d.ts -│   ├── setup-jest.ts -│   └── styles.scss -├── start.sh -├── structure.md -├── tailwind.config.js -├── tsconfig.app.json -├── tsconfig.json -└── tsconfig.spec.json - -72 directories, 280 files