Compare commits
	
		
			204 Commits
		
	
	
		
			3dc8131c33
			...
			front_foun
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | 47be7d340b | ||
|  | 232d10b164 | ||
| bc7ce888ad | |||
| ed67a3734a | |||
| 95eb154556 | |||
| 19fef63b0e | |||
| 1fd95265ea | |||
|  | 3ef2d8a198 | ||
|  | 6b49bbbe57 | ||
|  | 4c15cab607 | ||
|  | abfe92bc87 | ||
|  | 85b4fe6a4c | ||
| f2448a029f | |||
|  | cef4daef15 | ||
|  | f5aba70017 | ||
|  | 27adc81ddc | ||
|  | 48f14e8a04 | ||
| d4533ea725 | |||
|  | 7fc06035c7 | ||
|  | 1b559f29b7 | ||
| 63327bc312 | |||
|  | f0cef41e2b | ||
| 7f16cdc86f | |||
| 72d6f49995 | |||
| 695ec5d9b8 | |||
| 0abafb4f7f | |||
| 3cd63e78e9 | |||
| 255af7ee7f | |||
| 3b308cfa6d | |||
| d039105f0a | |||
| 0a15dbbf2d | |||
| d1fce63ac5 | |||
| d9aaa225aa | |||
| d31bf259dd | |||
| 43b40c9432 | |||
|  | e84f69c21a | ||
| cc1fc9b45b | |||
|  | c76e83f2bf | ||
| 0d0ec255a5 | |||
| e0c43a5c95 | |||
|  | 1f0f9196c4 | ||
| 40e577ef07 | |||
| 60302c44d2 | |||
| a6e4f80a01 | |||
| 02bff19de0 | |||
| ae36549de9 | |||
| b1a4c874ec | |||
| 829baac85e | |||
| 2e9d841709 | |||
| 25235f418a | |||
| 13845394e3 | |||
|  | 73aac1875a | ||
|  | c3ad092512 | ||
|  | f4589c6306 | ||
|  | 6004bce4e8 | ||
|  | 0730275e75 | ||
| 4356a01e4a | |||
| 592331236e | |||
| 49e52e1826 | |||
| efcea7f680 | |||
| 8154814805 | |||
|  | 7d41da97de | ||
|  | 35f314498f | ||
|  | f46c3756f0 | ||
| 92696c3e16 | |||
| 5130c00796 | |||
| 5183a088e7 | |||
| 8ec569e6ff | |||
| b503cae235 | |||
| fcf4e1c01d | |||
|  | 4f90da69f3 | ||
|  | 0fe64fd844 | ||
| eb302268ba | |||
| 3f18304028 | |||
| bbb4debcd8 | |||
| 6f7fc70c4c | |||
|  | 4044a95dd1 | ||
| aee8e8797c | |||
| 3d57ecb01a | |||
| 1f91ab72d8 | |||
| a4e13b0f0a | |||
| b116771375 | |||
| cbef042e97 | |||
| 63f6f16d83 | |||
| 864bbbb9fd | |||
| e890a03a48 | |||
| 351727f3d5 | |||
| b5637e4552 | |||
|  | 8024b8070b | ||
|  | 008e003207 | ||
| 820c979c94 | |||
| 2ebea35e5d | |||
|  | c3dded1e05 | ||
|  | 8b48a55bf0 | ||
|  | 32de0d6ca8 | ||
| 7534d0d9c6 | |||
| a0cbd5e324 | |||
|  | 09eeaacfa6 | ||
|  | de09ab2891 | ||
| 6ae50f9cf7 | |||
| 0199e9d0dc | |||
| 05a56dc022 | |||
| 9f4596d0ba | |||
|  | be73815861 | ||
| 6d84b3ff93 | |||
|  | 549028b1d0 | ||
|  | 3de1ec71ff | ||
|  | a895b0afda | ||
|  | bb55209946 | ||
|  | a8e22de4a3 | ||
| f9ee12ab6f | |||
| 4d3aae1249 | |||
|  | fa96bd2b0d | ||
| 3673aa379c | |||
| b672b2e9f9 | |||
| 193876e51c | |||
| b8c7c6f587 | |||
|  | bee47473d5 | ||
|  | e7739af80b | ||
| 32557f8f87 | |||
| 8ee06b93a6 | |||
| fcc20d1f42 | |||
| 28b0e69da1 | |||
|  | 6861d07dfc | ||
| 6226c9f632 | |||
| 7fce831c75 | |||
| 8403bc0592 | |||
| 5edcf9ffc8 | |||
| 5615b0fb11 | |||
| 8a13993d8a | |||
| 561f6d16b3 | |||
|  | 7199e7ebbf | ||
|  | 9762ca27fb | ||
| ca8c5d9209 | |||
| 4b6d501adc | |||
|  | 08706af6c2 | ||
|  | 01f062211a | ||
| bca88a7b20 | |||
|  | c60fb8945b | ||
|  | e20556ed0f | ||
|  | d9c5f7bacf | ||
|  | fdae3e4c04 | ||
| 36db3c2968 | |||
| a0eeb6715e | |||
|  | 6d875d9df1 | ||
|  | 7c8f3ba36a | ||
| 832539f43b | |||
| dfea20b9c4 | |||
|  | 8b863ee4b1 | ||
|  | f96872fb6b | ||
|  | 0140672812 | ||
|  | 7df2c768c8 | ||
| 6b3cb2610d | |||
|  | 6029457735 | ||
| ba99b3c2b0 | |||
| 31d82f6271 | |||
| 37e631a096 | |||
| 6306a00eca | |||
| 55112c8508 | |||
| 676f1204cb | |||
|  | c4ba7646d5 | ||
|  | 4e1908d528 | ||
|  | 03bbc77e8a | ||
| ad1fd45bed | |||
| f0c4a3a10d | |||
|  | 70658e4fb9 | ||
|  | 2b31753265 | ||
| f8991e90ab | |||
|  | 66be0baca6 | ||
|  | 60290956ec | ||
|  | b9647ce36e | ||
| 8c4b9ceb9d | |||
|  | 84d8d4523b | ||
| 647812576e | |||
|  | 2dfee66958 | ||
| 7e1271cfe2 | |||
| 801ecb3817 | |||
|  | cc89d4c79f | ||
| adf9a93e2e | |||
|  | 37d8bcc719 | ||
|  | 385c5cd8d0 | ||
|  | b672dd200c | ||
|  | 9e1f568ea4 | ||
|  | aaa6e46d0c | ||
|  | 2b1666c949 | ||
|  | 0c724cae7f | ||
|  | 6de45801d2 | ||
| 03897e1139 | |||
|  | 9b9cfbdb2e | ||
| 00a733c03b | |||
|  | 5b6b647697 | ||
|  | 81ce4fdb4c | ||
|  | ebd76a30ee | ||
|  | 6ff6ce5052 | ||
| 60ec920cff | |||
| 900a4c5bdc | |||
| d0b615c59d | |||
| eccf116f49 | |||
| 8491c9b3cf | |||
| b1df7421dc | |||
| 7a03146bf8 | |||
| f0a371dc52 | |||
|  | ac19d33bdb | ||
|  | 3d4d5b90d1 | 
| @@ -27,7 +27,6 @@ jobs: | ||||
|       uses: gradle/actions/setup-gradle@v4 | ||||
|       with:  | ||||
|         cache-disabled: true # Once the code has been pushed once in main, this should be reenabled.  | ||||
|  | ||||
|     - name: init gradle | ||||
|       working-directory: ./MyINPulse-back/ | ||||
|       run: ./gradlew build # todo: run test, currently fail because no database is present  | ||||
|   | ||||
							
								
								
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -1,5 +1,8 @@ | ||||
| .env | ||||
| .idea | ||||
| keycloak/CAS/target | ||||
| keycloak/.installed | ||||
| docker-compose.yaml | ||||
| node_modules | ||||
| .vscode | ||||
| postgres/data | ||||
| @@ -1,741 +0,0 @@ | ||||
| openapi: 3.0.3 | ||||
| info: | ||||
|   title: MyInpulse Backend Api | ||||
|   description: this document servers as a documentation for the backend api.   | ||||
|   version: 0.0.0 | ||||
|  | ||||
| tags: | ||||
|   - name: Entrepreneurs API | ||||
|     description: La partie de l'api dédiée aux entrepreneurs | ||||
|   - name: Admin API | ||||
|     description: La partie de l'api dédiée aux entrepreneurs | ||||
|   - name: Shared API | ||||
|     description: La partie de l'api dédiée aux entrepreneurs et admins | ||||
|  | ||||
|  | ||||
| components: | ||||
|   schemas: | ||||
|     user: | ||||
|       type: object | ||||
|       properties: | ||||
|         nom: | ||||
|           type: string | ||||
|         prenom: | ||||
|           type: string | ||||
|         email: | ||||
|           type: string | ||||
|           example: "example@exmaple.com" | ||||
|         secondaryEmail: | ||||
|           type: string | ||||
|           example: "example@exmaple.com" | ||||
|         tel: | ||||
|           type: string | ||||
|           example: "0612345678" | ||||
|     user-entrepreneur: | ||||
|       type: object | ||||
|       properties: | ||||
|         user: | ||||
|           $ref: "#/components/schemas/user" | ||||
|         entrepreneur: | ||||
|           type: object | ||||
|           properties: | ||||
|             ecole:  | ||||
|               type: string | ||||
|               example: "enseirb" | ||||
|             filiere: | ||||
|               type: string | ||||
|               example: "info" | ||||
|             status: | ||||
|               type: boolean | ||||
|               example: false | ||||
|     user-admin: | ||||
|       type: object | ||||
|       properties: | ||||
|         admin: | ||||
|           $ref: "#/components/schemas/user" | ||||
|  | ||||
|   securitySchemes: | ||||
|       MyINPulse: | ||||
|         type: oauth2 | ||||
|         flows: | ||||
|           implicit: | ||||
|             authorizationUrl: https://petstore3.swagger.io/oauth/authorize | ||||
|             scopes: | ||||
|               MyINPulse-admin: Administrateur | ||||
|               MyINPulse-entrepreneur: Utilisateur | ||||
|  | ||||
| paths: | ||||
|  | ||||
| #          _    ____  __  __ ___ _   _      _    ____ ___  | ||||
| #         / \  |  _ \|  \/  |_ _| \ | |    / \  |  _ \_ _| | ||||
| #        / _ \ | | | | |\/| || ||  \| |   / _ \ | |_) | |  | ||||
| #       / ___ \| |_| | |  | || || |\  |  / ___ \|  __/| |  | ||||
| #      /_/   \_\____/|_|  |_|___|_| \_| /_/   \_\_|  |___| | ||||
| # | ||||
|  | ||||
|   /admin/projects: | ||||
|     get: | ||||
|       summary: Retourne la liste of projets associés à l'admin | ||||
|       tags:  | ||||
|         - Admin API  | ||||
|       security: | ||||
|         - MyINPulse: | ||||
|             - MyINPulse-admin | ||||
|       description: | ||||
|         JSON array of who's elements are objects containing necessary information for the view  | ||||
|         (project name, entrepreneur names, etc..) | ||||
|         of the projects an admin is watching over. | ||||
|       responses: | ||||
|         "200": | ||||
|           description: OK | ||||
|           content: | ||||
|             application/json: | ||||
|               schema: | ||||
|                 type: array | ||||
|                 items: | ||||
|                   type: object | ||||
|                   properties: | ||||
|                     name: | ||||
|                       type: string | ||||
|                     E_names: | ||||
|                       type: string | ||||
|                       description: entrepreneur names | ||||
|         "400": | ||||
|           description: Bad request | ||||
|         "401": | ||||
|           description: Authorization information is missing or invalid | ||||
|   /admin/projects/pending/decision: | ||||
|     post: | ||||
|       summary: valider un projet en attente de validation | ||||
|       tags: | ||||
|         - Admin API | ||||
|       description: | ||||
|         if the request is accepted the status of the | ||||
|         project is changed to ongoing, entrepreneur | ||||
|         account is confirmed and the project is linked | ||||
|         to the admin accepting the request and the | ||||
|         entrepreneur requesting it. Else the pending | ||||
|         project and user info are deleted. | ||||
|       security: | ||||
|           - MyINPulse: | ||||
|               - MyINPulse-admin | ||||
|       requestBody: | ||||
|         required: true | ||||
|         content: | ||||
|           application/json: | ||||
|             schema: | ||||
|               type: object | ||||
|               properties: | ||||
|                 pedingProjectId:  | ||||
|                   type: integer | ||||
|                 decision: | ||||
|                   type: boolean | ||||
|                  | ||||
|       responses: | ||||
|         "200":  | ||||
|           description: OK | ||||
|         "400": | ||||
|           description: Bad request | ||||
|         "401": | ||||
|           description: Authorization information is | ||||
|             missing or invalid | ||||
|              | ||||
|   /admin/projects/add: | ||||
|     post: | ||||
|       summary: Ajout manuel d'un projet | ||||
|       description: | ||||
|         Adds a project with the | ||||
|         inputed details | ||||
|       tags: | ||||
|         - Admin API | ||||
|       security: | ||||
|           - MyINPulse: | ||||
|               - MyINPulse-admin | ||||
|       requestBody: | ||||
|         required: true | ||||
|         content: | ||||
|           application/json: | ||||
|             schema: | ||||
|               type: object | ||||
|               properties: | ||||
|                 name: | ||||
|                   type: string | ||||
|                 founder: | ||||
|                   $ref: "#/components/schemas/user-entrepreneur" | ||||
|                  | ||||
|       responses: | ||||
|         "200":  | ||||
|           description: OK | ||||
|         "400": | ||||
|           description: Bad request | ||||
|         "401": | ||||
|           description: Authorization information is | ||||
|             missing or invalid | ||||
|    | ||||
|   /admin/appointments/report/{appointmentId}: | ||||
|     put: | ||||
|       summary: enregistrer un rapport du rendez-vous | ||||
|       description: | ||||
|         Generate a PDF file formatted  | ||||
|         from input text and links it  | ||||
|         to the appointement. | ||||
|       tags: | ||||
|         - Admin API | ||||
|       security: | ||||
|         - MyINPulse: | ||||
|           - MyINPulse-admin | ||||
|       parameters: | ||||
|         - in: path | ||||
|           name: appointmentId | ||||
|           required: true | ||||
|           schema: | ||||
|             type: integer | ||||
|       requestBody: | ||||
|         required: true | ||||
|         content: | ||||
|           application/json: | ||||
|             schema: | ||||
|               type: object | ||||
|               properties: | ||||
|                 title: | ||||
|                   type: string | ||||
|                 body: | ||||
|                   type: string | ||||
|                 conclusion: | ||||
|                   type: string | ||||
|          | ||||
|       responses: | ||||
|         "200":  | ||||
|           description: OK | ||||
|         "400": | ||||
|           description: Bad request | ||||
|         "401": | ||||
|           description: Authorization information is | ||||
|                     missing or invalid | ||||
|     post: | ||||
|       summary: modifier un rapport déja éxistant du rendez-vous | ||||
|       description: | ||||
|         Modifies the report file to input  | ||||
|         text and links it to the appointement. | ||||
|       tags: | ||||
|         - Admin API | ||||
|       security: | ||||
|         - MyINPulse: | ||||
|           - MyINPulse-admin | ||||
|       parameters: | ||||
|         - in: path | ||||
|           name: appointmentId | ||||
|           required: true | ||||
|           schema: | ||||
|             type: integer | ||||
|       requestBody: | ||||
|         required: true | ||||
|         content: | ||||
|           application/json: | ||||
|             schema: | ||||
|               type: object | ||||
|               properties: | ||||
|                 title: | ||||
|                   type: string | ||||
|                 body: | ||||
|                   type: string | ||||
|                 conclusion: | ||||
|                   type: string | ||||
|          | ||||
|       responses: | ||||
|         "200":  | ||||
|           description: OK | ||||
|         "400": | ||||
|           description: Bad request | ||||
|         "401": | ||||
|           description: Authorization information is | ||||
|                     missing or invalid | ||||
|  | ||||
|  | ||||
|   /admin/projects/remove/{projectId}: | ||||
|     delete: | ||||
|       summary: supression d'un project | ||||
|       description: | ||||
|         Removes the project | ||||
|         with the inputed id projectId | ||||
|       tags: | ||||
|         - Admin API | ||||
|       security: | ||||
|           - MyINPulse: | ||||
|               - MyINPulse-admin | ||||
|       parameters: | ||||
|         - in: path | ||||
|           name: projectId | ||||
|           required: true | ||||
|           schema: | ||||
|             type: integer | ||||
|            | ||||
|       responses: | ||||
|         "200":  | ||||
|           description: OK | ||||
|         "400": | ||||
|           description: Bad request | ||||
|         "401": | ||||
|           description: Authorization information is | ||||
|             missing or invalid | ||||
|       | ||||
|  | ||||
|   /admin/projects/pending: | ||||
|     get: | ||||
|       summary: Retourne la liste des projets en attente de validation | ||||
|       tags:  | ||||
|         - Admin API | ||||
|       security: | ||||
|         - MyINPulse: | ||||
|             - MyINPulse-admin | ||||
|       description: | ||||
|         JSON array of who's elements are objects containing | ||||
|         necessary information for the view (project name, | ||||
|         entrepreneur names, etc..) of all pending projects. | ||||
|       responses: | ||||
|         "200": | ||||
|           description: OK | ||||
|           content: | ||||
|             application/json: | ||||
|               schema: | ||||
|                 type: array | ||||
|                 items: | ||||
|                   type: object | ||||
|                   properties: | ||||
|                     name: | ||||
|                       type: string | ||||
|                     founder: | ||||
|                       $ref: "#/components/schemas/user-entrepreneur" | ||||
|         "400": | ||||
|           description: Bad request | ||||
|         "401": | ||||
|           description: Authorization information is missing or invalid | ||||
|  | ||||
| # | ||||
| #       ____  _                        _      _    ____ ___  | ||||
| #      / ___|| |__   __ _ _ __ ___  __| |    / \  |  _ \_ _| | ||||
| #      \___ \| '_ \ / _` | '__/ _ \/ _` |   / _ \ | |_) | |  | ||||
| #       ___) | | | | (_| | | |  __/ (_| |  / ___ \|  __/| |  | ||||
| #      |____/|_| |_|\__,_|_|  \___|\__,_| /_/   \_\_|  |___| | ||||
| #                    | ||||
|  | ||||
|   /shared/appointments/upcoming: | ||||
|     get: | ||||
|       summary: Retourne la list des prochains rendez-vous de l'utilisateur | ||||
|       tags:  | ||||
|         - Shared API | ||||
|       security: | ||||
|         - MyINPulse: | ||||
|             - MyINPulse-admin | ||||
|             - MyINPulse-entrepreneur | ||||
|       description: | ||||
|         JSON array of upcoming appointment data (name, date, time etc..) for a user. | ||||
|       responses: | ||||
|         "200": | ||||
|           description: OK | ||||
|           content: | ||||
|             application/json: | ||||
|               schema: | ||||
|                 type: array | ||||
|                 items: | ||||
|                   type: object | ||||
|                   properties: | ||||
|                     name: | ||||
|                       type: string | ||||
|                     date: | ||||
|                       type: string | ||||
|                     time: | ||||
|                       type: string | ||||
|         "400": | ||||
|           description: Bad request | ||||
|         "401": | ||||
|           description: Authorization information is missing or invalid         | ||||
|  | ||||
|   /shared/projects/lcsection/{projectId}/{title}/{date}: | ||||
|       get: | ||||
|         summary: Retourne la liste de sections de LC avec un titre donné | ||||
|         tags:  | ||||
|           - Shared API | ||||
|         security: | ||||
|         - MyINPulse: | ||||
|             - MyINPulse-admin | ||||
|             - MyINPulse-entrepreneur | ||||
|         description: | ||||
|           JSON array containing Lean Canvas  | ||||
|           section data with a title for the | ||||
|           current date (or given date if the | ||||
|           date parameter is passed) | ||||
|         parameters: | ||||
|           - in: path | ||||
|             required: true | ||||
|             name: projectId | ||||
|             schema: | ||||
|               type: integer | ||||
|           - in: path | ||||
|             required: true | ||||
|             description: this number can be 1, 2,...,8. It is associated with the title of the lcsection | ||||
|             name: title | ||||
|             schema: | ||||
|               type: integer | ||||
|               enum: [1, 2, 3, 4, 5, 6, 7, 8]  | ||||
|              | ||||
|           - in: path | ||||
|             required: true | ||||
|             name: date | ||||
|             description: the date corresponding to the wanted version of lc section. "Nan" for the latest version | ||||
|             example: "NaN" | ||||
|             schema: | ||||
|               type: string | ||||
|                | ||||
|         responses: | ||||
|           "200": | ||||
|             description: OK | ||||
|             content: | ||||
|               application/json: | ||||
|                 schema: | ||||
|                   type: array | ||||
|                   items: | ||||
|                     type: object | ||||
|                     properties: | ||||
|                       section: | ||||
|                         type: string | ||||
|                       txt: | ||||
|                         type: string | ||||
|                       time: | ||||
|                         type: string | ||||
|           "400": | ||||
|             description: Bad request | ||||
|           "401": | ||||
|             description: Authorization information is missing or invalid | ||||
|    | ||||
|   /shared/projects/entrepreneurs/{projectId}: | ||||
|     get: | ||||
|       summary: Retourne la liste d'entrepreneurs associée à un projet donné | ||||
|       tags:  | ||||
|           - Shared API | ||||
|       security: | ||||
|         - MyINPulse: | ||||
|             - MyINPulse-admin | ||||
|             - MyINPulse-entrepreneur | ||||
|       description: | ||||
|         JSON array of entrepreneur | ||||
|         names associated with a project | ||||
|       parameters: | ||||
|           - in: path | ||||
|             name: projectId | ||||
|             schema: | ||||
|               type: integer | ||||
|             required: true | ||||
|       responses: | ||||
|         "200": | ||||
|           description: OK | ||||
|           content: | ||||
|             application/json: | ||||
|               schema: | ||||
|                 type: array | ||||
|                 items: | ||||
|                   $ref: "#/components/schemas/user-entrepreneur" | ||||
|         "400": | ||||
|           description: Bad request | ||||
|         "401": | ||||
|           description: Authorization information is missing or invalid | ||||
|    | ||||
|   /shared/projects/admin/{projectId}: | ||||
|     get: | ||||
|       summary: Retourne les informations de l'admin qui accompagne le projet | ||||
|       tags:  | ||||
|           - Shared API | ||||
|       security: | ||||
|         - MyINPulse: | ||||
|             - MyINPulse-admin | ||||
|             - MyINPulse-entrepreneur | ||||
|       description:  | ||||
|         JSON object containing information (name, gmail, tel, etc..)  | ||||
|         the admin supervising the project with id projectID. | ||||
|       parameters: | ||||
|             - in: path | ||||
|               name: projectId | ||||
|               schema: | ||||
|                 type: integer | ||||
|               required: true | ||||
|       responses: | ||||
|         "200": | ||||
|           description: OK | ||||
|           content: | ||||
|             application/json: | ||||
|               schema: | ||||
|                 $ref: "#/components/schemas/user-admin" | ||||
|         "400": | ||||
|           description: Bad request | ||||
|         "401": | ||||
|           description: Authorization information is | ||||
|             missing or invalid | ||||
|    | ||||
|   /shared/projects/appointments/{projectId}: | ||||
|     get: | ||||
|       summary: Retourne les rendez-vous du projet  | ||||
|       tags:  | ||||
|           - Shared API | ||||
|       security: | ||||
|         - MyINPulse: | ||||
|             - MyINPulse-admin | ||||
|             - MyINPulse-entrepreneur | ||||
|       description:  | ||||
|         JSON array of upcoming and past appointment  | ||||
|         data for the project with id projectID. | ||||
|       parameters: | ||||
|         - in: path | ||||
|           name: projectId | ||||
|           schema: | ||||
|             type: integer | ||||
|           required: true | ||||
|       responses: | ||||
|         "200": | ||||
|           description: OK | ||||
|           content: | ||||
|             application/json: | ||||
|               schema: | ||||
|                 type: array | ||||
|                 items: | ||||
|                   type: object | ||||
|                   properties: | ||||
|                     appointementId: | ||||
|                       type: integer | ||||
|                     name:  | ||||
|                       type: string | ||||
|                     date:  | ||||
|                       type: string | ||||
|                     time:  | ||||
|                       type: string | ||||
|         "400": | ||||
|           description: Bad request | ||||
|         "401": | ||||
|           description: Authorization information is | ||||
|             missing or invalid | ||||
|   /shared/projects/appointments/report/{apointementId}: | ||||
|     get: | ||||
|       summary: Retourne le rapport pdf du rendez-vous | ||||
|       tags:  | ||||
|           - Shared API | ||||
|       security: | ||||
|         - MyINPulse: | ||||
|             - MyINPulse-admin | ||||
|             - MyINPulse-entrepreneur | ||||
|       description: | ||||
|         PDF file containing the ap- | ||||
|         pointment report | ||||
|       parameters: | ||||
|         - in: path | ||||
|           name: apointementId | ||||
|           schema: | ||||
|             type: integer | ||||
|           required: true | ||||
|       responses: | ||||
|         "200": | ||||
|           description: OK | ||||
|           content: | ||||
|             application/pdf: | ||||
|               schema: | ||||
|                 type: string | ||||
|                 format: binary | ||||
|         "400": | ||||
|           description: Bad request | ||||
|         "401": | ||||
|           description: Authorization information is | ||||
|             missing or invalid | ||||
|    | ||||
|   /shared/appointments/request: | ||||
|     post: | ||||
|       summary: demander un rendez-vous | ||||
|       description: | ||||
|         will add an appointement request request by the applicant  | ||||
|         to have an appointment to be confirmed or denied by the | ||||
|         specified participants of the appointement. | ||||
|       tags: | ||||
|         - Shared API | ||||
|       security: | ||||
|         - MyINPulse: | ||||
|           - MyINPulse-entrepreneur | ||||
|           - MyINPulse-admin | ||||
|       requestBody: | ||||
|         description: \"participants\" property is an array containing userids of the participants in the appointement | ||||
|         required: true | ||||
|         content: | ||||
|           application/json: | ||||
|             schema: | ||||
|               type: object | ||||
|               properties: | ||||
|                 title: | ||||
|                   type: string | ||||
|                 start_time: | ||||
|                   type: string | ||||
|                 end_time: | ||||
|                   type: string | ||||
|                 place: | ||||
|                   type: string | ||||
|                 applicantId: | ||||
|                   type: integer | ||||
|                 participants: | ||||
|                   #/* */ | ||||
|                   type: array | ||||
|                   items: | ||||
|                     type: integer | ||||
|                      | ||||
|       responses: | ||||
|         "200":  | ||||
|           description: OK | ||||
|         "400": | ||||
|           description: Bad request | ||||
|         "401": | ||||
|           description: Authorization information is | ||||
|                     missing or invalid         | ||||
|    | ||||
|  | ||||
|  | ||||
| #      _____ _   _ _____ ____  _____ ____  ____  _____ _   _ _____ _   _ ____   | ||||
| #     | ____| \ | |_   _|  _ \| ____|  _ \|  _ \| ____| \ | | ____| | | |  _ \  | ||||
| #     |  _| |  \| | | | | |_) |  _| | |_) | |_) |  _| |  \| |  _| | | | | |_) | | ||||
| #     | |___| |\  | | | |  _ <| |___|  __/|  _ <| |___| |\  | |___| |_| |  _ <  | ||||
| #     |_____|_|_\_| |_| |_| \_\_____|_|   |_| \_\_____|_| \_|_____|\___/|_| \_\ | ||||
| #        / \  |  _ \_ _|                                                        | ||||
| #       / _ \ | |_) | |                                                         | ||||
| #      / ___ \|  __/| |                                                         | ||||
| #     /_/   \_\_|  |___|                                                        | ||||
| # | ||||
|  | ||||
|                      | ||||
|   /entrepreneur/projects/request: | ||||
|     post: | ||||
|       summary: demander la création et validation d'un projet | ||||
|       tags: | ||||
|         - Entrepreneurs API | ||||
|       description:  | ||||
|         Adds project to pending projects  | ||||
|         to then be accepted or rejected by | ||||
|         an admin | ||||
|       security: | ||||
|         - MyINPulse: | ||||
|           - MyINPulse-entrepreneur | ||||
|       requestBody: | ||||
|         required: true | ||||
|         content: | ||||
|           application/json: | ||||
|             schema: | ||||
|               type: object | ||||
|               properties: | ||||
|                 name: | ||||
|                   type: string | ||||
|                 founder: | ||||
|                   $ref: "#/components/schemas/user-entrepreneur" | ||||
|       responses: | ||||
|         "200":  | ||||
|           description: OK | ||||
|         "400": | ||||
|           description: Bad request | ||||
|         "401": | ||||
|           description: Authorization information is | ||||
|             missing or invalid | ||||
|              | ||||
|             | ||||
|   /entrepreneur/lcsection/add/{projectId}: | ||||
|     post: | ||||
|       summary: ajouter une sections au LC | ||||
|       description:  | ||||
|         Adds input data to the user's LC  | ||||
|         with a specified title. | ||||
|       tags: | ||||
|        - Entrepreneurs API | ||||
|       security: | ||||
|         - MyINPulse: | ||||
|           - MyINPulse-entrepreneur | ||||
|       parameters: | ||||
|         - in: path | ||||
|           name: projectId | ||||
|           schema: | ||||
|             type: integer | ||||
|           required: true | ||||
|       requestBody: | ||||
|         required: true | ||||
|         content: | ||||
|           application/json: | ||||
|             schema: | ||||
|               type: object | ||||
|               properties: | ||||
|                 title: | ||||
|                   type: string | ||||
|                 txt: | ||||
|                   type: string | ||||
|       responses: | ||||
|         "200":  | ||||
|           description: OK | ||||
|         "400": | ||||
|           description: Bad request | ||||
|         "401": | ||||
|           description: Authorization information is | ||||
|             missing or invalid | ||||
|   /entrepreneur/lcsection/modify/{sectionId}: | ||||
|     put: | ||||
|       summary: modifier les données d'une section LC | ||||
|       description:  | ||||
|         Modifies input Lean Canvas section by changing it to  | ||||
|         the information in the request body and changes the | ||||
|         time stamp. | ||||
|       tags: | ||||
|        - Entrepreneurs API | ||||
|       security: | ||||
|         - MyINPulse: | ||||
|           - MyINPulse-entrepreneur | ||||
|       parameters: | ||||
|         - in: path | ||||
|           name: sectionId | ||||
|           schema: | ||||
|             type: integer | ||||
|           required: true | ||||
|       requestBody: | ||||
|         required: true | ||||
|         content: | ||||
|           application/json: | ||||
|             schema: | ||||
|               type: object | ||||
|               properties: | ||||
|                 title: | ||||
|                   type: string | ||||
|                 txt: | ||||
|                   type: string | ||||
|       responses: | ||||
|         "200":  | ||||
|           description: OK | ||||
|         "400": | ||||
|           description: Bad request | ||||
|         "401": | ||||
|           description: Authorization information is | ||||
|             missing or invalid | ||||
|              | ||||
|   /entrepreneur/lcsection/remove/{sectionId}: | ||||
|     delete: | ||||
|       summary: supprimer une section LC. | ||||
|       description: | ||||
|         Deletes section from Lean Canvas | ||||
|       tags: | ||||
|         - Entrepreneurs API | ||||
|       security: | ||||
|         - MyINPulse: | ||||
|           - MyINPulse-entrepreneur | ||||
|       parameters: | ||||
|         - in: path | ||||
|           name: sectionId | ||||
|           schema: | ||||
|             type: integer | ||||
|           required: true | ||||
|       responses: | ||||
|         "200":  | ||||
|           description: OK | ||||
|         "400": | ||||
|           description: Bad request | ||||
|         "401": | ||||
|           description: Authorization information is | ||||
|                       missing or invalid | ||||
|    | ||||
|    | ||||
|    | ||||
|    | ||||
| @@ -29,6 +29,8 @@ dependencies { | ||||
|     implementation 'org.postgresql:postgresql' | ||||
|     implementation group: 'com.itextpdf', name: 'itextpdf', version: '5.5.13.3' | ||||
|  | ||||
|     runtimeOnly 'com.h2database:h2' | ||||
|  | ||||
|     testImplementation 'org.springframework.boot:spring-boot-starter-test' | ||||
|     testImplementation 'com.h2database:h2' | ||||
|  | ||||
|   | ||||
| @@ -31,7 +31,7 @@ public class WebSecurityCustomConfiguration { | ||||
|     public CorsConfigurationSource corsConfigurationSource() { | ||||
|         CorsConfiguration configuration = new CorsConfiguration(); | ||||
|         configuration.setAllowedOrigins(List.of(frontendUrl)); | ||||
|         configuration.setAllowedMethods(Arrays.asList("GET", "OPTIONS")); | ||||
|         configuration.setAllowedMethods(Arrays.asList("GET", "OPTIONS", "POST", "PUT", "DELETE")); | ||||
|         configuration.setAllowedHeaders( | ||||
|                 Arrays.asList("authorization", "content-type", "x-auth-token")); | ||||
|         UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); | ||||
| @@ -56,14 +56,18 @@ public class WebSecurityCustomConfiguration { | ||||
|         http.authorizeHttpRequests( | ||||
|                         authorize -> | ||||
|                                 authorize | ||||
|                                         .requestMatchers("/entrepreneur/**", "/shared/**") | ||||
|                                         .requestMatchers("/entrepreneur/**") | ||||
|                                         .access(hasRole("REALM_MyINPulse-entrepreneur")) | ||||
|                                         .requestMatchers("/admin/**", "/shared/**") | ||||
|                                         .requestMatchers("/admin/**") | ||||
|                                         .access(hasRole("REALM_MyINPulse-admin")) | ||||
|                                         .requestMatchers("/shared/**") | ||||
|                                         .hasAnyRole( | ||||
|                                                 "REALM_MyINPulse-admin", | ||||
|                                                 "REALM_MyINPulse-entrepreneur") | ||||
|                                         .requestMatchers("/unauth/**") | ||||
|                                         .permitAll() | ||||
|                                         .authenticated() | ||||
|                                         .anyRequest() | ||||
|                                         .authenticated()) | ||||
|                                         .denyAll()) | ||||
|                 .oauth2ResourceServer( | ||||
|                         oauth2 -> | ||||
|                                 oauth2.jwt( | ||||
|   | ||||
| @@ -57,7 +57,7 @@ public class AdminApi { | ||||
|      * | ||||
|      * @return the status code of the request | ||||
|      */ | ||||
|     @PostMapping("/admin/projects/decision") | ||||
|     @PostMapping("/admin/projects/pending/decision") | ||||
|     public void validateProject(@RequestBody ProjectDecision decision) { | ||||
|         adminApiService.validateProject(decision); | ||||
|     } | ||||
| @@ -67,7 +67,7 @@ public class AdminApi { | ||||
|      * | ||||
|      * @return the status code of the request | ||||
|      */ | ||||
|     @PostMapping("/admin/project/add") | ||||
|     @PostMapping("/admin/project") | ||||
|     public void addNewProject(@RequestBody Project project) { | ||||
|         adminApiService.addNewProject(project); | ||||
|     } | ||||
| @@ -79,7 +79,7 @@ public class AdminApi { | ||||
|      * | ||||
|      * @return the status code of the request | ||||
|      */ | ||||
|     @PostMapping("/admin/appoitements/report/{appointmentId}") | ||||
|     @PostMapping("/admin/appointments/report/{appointmentId}") | ||||
|     public void createAppointmentReport( | ||||
|             @PathVariable long appointmentId, | ||||
|             @RequestBody Report report, | ||||
| @@ -95,8 +95,35 @@ public class AdminApi { | ||||
|      * | ||||
|      * @return the status code of the request | ||||
|      */ | ||||
|     @DeleteMapping("/admin/projects/remove/{projectId}") | ||||
|     @DeleteMapping("/admin/projects/{projectId}") | ||||
|     public void deleteProject(@PathVariable long projectId) { | ||||
|         adminApiService.deleteProject(projectId); | ||||
|     } | ||||
|  | ||||
|     @PostMapping("/admin/make-admin/{userId}") | ||||
|     public void setAdmin(@PathVariable long userId, @AuthenticationPrincipal Jwt principal) { | ||||
|         this.adminApiService.setAdmin(userId, principal.getTokenValue()); | ||||
|     } | ||||
|  | ||||
|     @PostMapping("/admin/accounts/validate/{userId}") | ||||
|     public void validateEntrepreneurAcc( | ||||
|             @PathVariable long userId, @AuthenticationPrincipal Jwt principal) { | ||||
|         this.adminApiService.validateEntrepreneurAccount(userId, principal.getTokenValue()); | ||||
|     } | ||||
|  | ||||
|     @GetMapping("/admin/pending-accounts") | ||||
|     public Iterable<User> validateEntrepreneurAcc() { | ||||
|         return this.adminApiService.getPendingUsers(); | ||||
|     } | ||||
|  | ||||
|     @PostMapping("/admin/create-account") | ||||
|     public void createAccount(@AuthenticationPrincipal Jwt principal) { | ||||
|         String userSurname = principal.getClaimAsString("userSurname"); | ||||
|         String username = principal.getClaimAsString("preferred_username"); | ||||
|         String primaryMail = principal.getClaimAsString("email"); | ||||
|         String secondaryMail = principal.getClaimAsString("secondaryMail"); | ||||
|         String phoneNumber = principal.getClaimAsString("phoneNumber"); | ||||
|         this.adminApiService.createAccount( | ||||
|                 userSurname, username, primaryMail, secondaryMail, phoneNumber); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -1,14 +1,20 @@ | ||||
| package enseirb.myinpulse.controller; | ||||
|  | ||||
| import enseirb.myinpulse.model.SectionCell; | ||||
| import enseirb.myinpulse.model.Project; | ||||
| import enseirb.myinpulse.service.EntrepreneurApiService; | ||||
|  | ||||
| import org.springframework.beans.factory.annotation.Autowired; | ||||
| import org.springframework.boot.autoconfigure.SpringBootApplication; | ||||
| import org.springframework.security.core.annotation.AuthenticationPrincipal; | ||||
| import org.springframework.security.oauth2.jwt.Jwt; | ||||
| import org.springframework.web.bind.annotation.*; | ||||
| import org.springframework.web.bind.annotation.DeleteMapping; | ||||
| import org.springframework.web.bind.annotation.GetMapping; | ||||
| import org.springframework.web.bind.annotation.PathVariable; | ||||
| import org.springframework.web.bind.annotation.PostMapping; | ||||
| import org.springframework.web.bind.annotation.PutMapping; | ||||
| import org.springframework.web.bind.annotation.RequestBody; | ||||
| import org.springframework.web.bind.annotation.RestController; | ||||
|  | ||||
| import enseirb.myinpulse.model.Project; | ||||
| import enseirb.myinpulse.model.SectionCell; | ||||
| import enseirb.myinpulse.service.EntrepreneurApiService; | ||||
|  | ||||
| @SpringBootApplication | ||||
| @RestController | ||||
| @@ -28,13 +34,26 @@ public class EntrepreneurApi { | ||||
|      * | ||||
|      * @return status code | ||||
|      */ | ||||
|     @PutMapping("/entrepreneur/lcsection/modify/{sectionId}") | ||||
|     @PutMapping("/entrepreneur/sectionCells/{sectionCellId}") | ||||
|     public void editSectionCell( | ||||
|             @PathVariable Long sectionId, | ||||
|             @RequestBody SectionCell sectionCell, | ||||
|             @PathVariable Long sectionCellId, | ||||
|             @RequestBody String content, | ||||
|             @AuthenticationPrincipal Jwt principal) { | ||||
|         entrepreneurApiService.editSectionCell( | ||||
|                 sectionId, sectionCell, principal.getClaimAsString("email")); | ||||
|                 sectionCellId, content, principal.getClaimAsString("email")); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Endpoint used to update a LC section. | ||||
|      * | ||||
|      * @return status code | ||||
|      */ | ||||
|     @GetMapping("/entrepreneur/projects") | ||||
|     public Iterable<Project> getEntrepreneurProjectId( | ||||
|             @PathVariable Long sectionCellId, | ||||
|             @RequestBody String content, | ||||
|             @AuthenticationPrincipal Jwt principal) { | ||||
|         return entrepreneurApiService.getProjectIdViaClaim(principal.getClaimAsString("email")); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
| @@ -44,10 +63,11 @@ public class EntrepreneurApi { | ||||
|      * | ||||
|      * @return status code | ||||
|      */ | ||||
|     @DeleteMapping("/entrepreneur/lcsection/remove/{sectionId}") | ||||
|     @DeleteMapping("/entrepreneur/sectionCells/{sectionCellId}") | ||||
|     public void removeSectionCell( | ||||
|             @PathVariable Long sectionId, @AuthenticationPrincipal Jwt principal) { | ||||
|         entrepreneurApiService.removeSectionCell(sectionId, principal.getClaimAsString("email")); | ||||
|             @PathVariable Long sectionCellId, @AuthenticationPrincipal Jwt principal) { | ||||
|         entrepreneurApiService.removeSectionCell( | ||||
|                 sectionCellId, principal.getClaimAsString("email")); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
| @@ -57,7 +77,7 @@ public class EntrepreneurApi { | ||||
|      * | ||||
|      * @return status code | ||||
|      */ | ||||
|     @PostMapping("/entrepreneur/lcsection/add") // remove id from doc aswell | ||||
|     @PostMapping("/entrepreneur/sectionCells") | ||||
|     public void addLCSection( | ||||
|             @RequestBody SectionCell sectionCell, @AuthenticationPrincipal Jwt principal) { | ||||
|         entrepreneurApiService.addSectionCell(sectionCell, principal.getClaimAsString("email")); | ||||
| @@ -70,9 +90,27 @@ public class EntrepreneurApi { | ||||
|      * | ||||
|      * @return status code | ||||
|      */ | ||||
|     @PostMapping("/entrepreneur/project/request") | ||||
|     @PostMapping("/entrepreneur/projects/request") | ||||
|     public void requestNewProject( | ||||
|             @RequestBody Project project, @AuthenticationPrincipal Jwt principal) { | ||||
|         entrepreneurApiService.requestNewProject(project, principal.getClaimAsString("email")); | ||||
|     } | ||||
|  | ||||
|     /* | ||||
|      * <p>Endpoint to check if project is has already been validated by an admin | ||||
|      */ | ||||
|     @GetMapping("/entrepreneur/projects/project-is-active") | ||||
|     public Boolean checkIfProjectValidated(@AuthenticationPrincipal Jwt principal) { | ||||
|         return entrepreneurApiService.checkIfEntrepreneurProjectActive( | ||||
|                 principal.getClaimAsString("email")); | ||||
|     } | ||||
|  | ||||
|     /* | ||||
|      * <p>Endpoint to check if a user requested a project (used when project is pending) | ||||
|      */ | ||||
|     @GetMapping("/entrepreneur/projects/has-pending-request") | ||||
|     public Boolean checkIfHasRequested(@AuthenticationPrincipal Jwt principal) { | ||||
|         return entrepreneurApiService.entrepreneurHasPendingRequestedProject( | ||||
|                 principal.getClaimAsString("email")); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -30,7 +30,7 @@ public class SharedApi { | ||||
|      * | ||||
|      * @return a list of lean canvas sections | ||||
|      */ | ||||
|     @GetMapping("/shared/project/lcsection/{projectId}/{sectionId}/{date}") | ||||
|     @GetMapping("/shared/projects/sectionCells/{projectId}/{sectionId}/{date}") | ||||
|     public Iterable<SectionCell> getLCSection( | ||||
|             @PathVariable("projectId") Long projectId, | ||||
|             @PathVariable("sectionId") Long sectionId, | ||||
| @@ -45,7 +45,7 @@ public class SharedApi { | ||||
|      * | ||||
|      * @return a list of all entrepreneurs in a project | ||||
|      */ | ||||
|     @GetMapping("/shared/entrepreneurs/{projectId}") | ||||
|     @GetMapping("/shared/projects/entrepreneurs/{projectId}") | ||||
|     public Iterable<Entrepreneur> getEntrepreneursByProjectId( | ||||
|             @PathVariable int projectId, @AuthenticationPrincipal Jwt principal) { | ||||
|         return sharedApiService.getEntrepreneursByProjectId( | ||||
| @@ -80,7 +80,7 @@ public class SharedApi { | ||||
|      * | ||||
|      * @return a PDF file? TODO: how does that works ? | ||||
|      */ | ||||
|     @GetMapping("/shared/projects/appointments/report/{appointmentId}") | ||||
|     @GetMapping("/shared/appointments/report/{appointmentId}") | ||||
|     public void getPDFReport( | ||||
|             @PathVariable int appointmentId, @AuthenticationPrincipal Jwt principal) { | ||||
|         try { | ||||
| @@ -97,7 +97,7 @@ public class SharedApi { | ||||
|     /** | ||||
|      * @return TODO | ||||
|      */ | ||||
|     @PostMapping("/shared/appointment/request") | ||||
|     @PostMapping("/shared/appointments/request") | ||||
|     public void createAppointmentRequest( | ||||
|             @RequestBody Appointment appointment, @AuthenticationPrincipal Jwt principal) { | ||||
|         sharedApiService.createAppointmentRequest(appointment, principal.getClaimAsString("email")); | ||||
|   | ||||
| @@ -0,0 +1,61 @@ | ||||
| package enseirb.myinpulse.controller; | ||||
|  | ||||
| import enseirb.myinpulse.model.Entrepreneur; | ||||
| import enseirb.myinpulse.service.EntrepreneurApiService; | ||||
| import enseirb.myinpulse.service.UtilsService; | ||||
|  | ||||
| import org.springframework.beans.factory.annotation.Autowired; | ||||
| import org.springframework.boot.autoconfigure.SpringBootApplication; | ||||
| import org.springframework.security.core.annotation.AuthenticationPrincipal; | ||||
| import org.springframework.security.oauth2.jwt.Jwt; | ||||
| import org.springframework.web.bind.annotation.*; | ||||
|  | ||||
| @SpringBootApplication | ||||
| @RestController | ||||
| public class UnauthApi { | ||||
|  | ||||
|     private final EntrepreneurApiService entrepreneurApiService; | ||||
|     private final UtilsService utilsService; | ||||
|  | ||||
|     @Autowired | ||||
|     UnauthApi(EntrepreneurApiService entrepreneurApiService, UtilsService utilsService) { | ||||
|         this.entrepreneurApiService = entrepreneurApiService; | ||||
|         this.utilsService = utilsService; | ||||
|     } | ||||
|  | ||||
|     @PostMapping("/unauth/finalize") | ||||
|     public void createAccount(@AuthenticationPrincipal Jwt principal) { | ||||
|         boolean sneeStatus; | ||||
|         if (principal.getClaimAsString("sneeStatus") != null) { | ||||
|             sneeStatus = principal.getClaimAsString("sneeStatus").equals("true"); | ||||
|         } else { | ||||
|             sneeStatus = false; | ||||
|         } | ||||
|         String userSurname = principal.getClaimAsString("userSurname"); | ||||
|         String username = principal.getClaimAsString("preferred_username"); | ||||
|         String primaryMail = principal.getClaimAsString("email"); | ||||
|         String secondaryMail = principal.getClaimAsString("secondaryMail"); | ||||
|         String phoneNumber = principal.getClaimAsString("phoneNumber"); | ||||
|         String school = principal.getClaimAsString("school"); | ||||
|         String course = principal.getClaimAsString("course"); | ||||
|         Entrepreneur e = | ||||
|                 new Entrepreneur( | ||||
|                         userSurname, | ||||
|                         username, | ||||
|                         primaryMail, | ||||
|                         secondaryMail, | ||||
|                         phoneNumber, | ||||
|                         school, | ||||
|                         course, | ||||
|                         sneeStatus, | ||||
|                         true); | ||||
|  | ||||
|         entrepreneurApiService.createAccount(e); | ||||
|     } | ||||
|  | ||||
|     @GetMapping("/unauth/check-if-not-pending") | ||||
|     public Boolean checkAccountStatus(@AuthenticationPrincipal Jwt principal) { | ||||
|         // Throws 404 if user not found | ||||
|         return utilsService.checkEntrepreneurNotPending(principal.getClaimAsString("email")); | ||||
|     } | ||||
| } | ||||
| @@ -37,7 +37,7 @@ public class Administrator extends User { | ||||
|             String primaryMail, | ||||
|             String secondaryMail, | ||||
|             String phoneNumber) { | ||||
|         super(null, userSurname, username, primaryMail, secondaryMail, phoneNumber); | ||||
|         super(userSurname, username, primaryMail, secondaryMail, phoneNumber, false); | ||||
|     } | ||||
|  | ||||
|     public List<Project> getListProject() { | ||||
|   | ||||
| @@ -44,15 +44,30 @@ public class Entrepreneur extends User { | ||||
|             String phoneNumber, | ||||
|             String school, | ||||
|             String course, | ||||
|             boolean sneeStatus) { | ||||
|         super(userSurname, username, primaryMail, secondaryMail, phoneNumber); | ||||
|             boolean sneeStatus, | ||||
|             boolean pending) { | ||||
|         super(userSurname, username, primaryMail, secondaryMail, phoneNumber, pending); | ||||
|         this.school = school; | ||||
|         this.course = course; | ||||
|         this.sneeStatus = sneeStatus; | ||||
|     } | ||||
|  | ||||
|     public Entrepreneur( | ||||
|             String userSurname, | ||||
|             String username, | ||||
|             String primaryMail, | ||||
|             String secondaryMail, | ||||
|             String phoneNumber, | ||||
|             String school, | ||||
|             String course, | ||||
|             boolean sneeStatus) { | ||||
|         super(userSurname, username, primaryMail, secondaryMail, phoneNumber, true); | ||||
|         this.school = school; | ||||
|         this.course = course; | ||||
|         this.sneeStatus = sneeStatus; | ||||
|     } | ||||
|  | ||||
|     public Entrepreneur( | ||||
|             Long idUser, | ||||
|             String userSurname, | ||||
|             String userName, | ||||
|             String primaryMail, | ||||
| @@ -63,8 +78,9 @@ public class Entrepreneur extends User { | ||||
|             boolean sneeStatus, | ||||
|             Project projectParticipation, | ||||
|             Project projectProposed, | ||||
|             MakeAppointment makeAppointment) { | ||||
|         super(idUser, userSurname, userName, primaryMail, secondaryMail, phoneNumber); | ||||
|             MakeAppointment makeAppointment, | ||||
|             boolean pending) { | ||||
|         super(userSurname, userName, primaryMail, secondaryMail, phoneNumber, pending); | ||||
|         this.school = school; | ||||
|         this.course = course; | ||||
|         this.sneeStatus = sneeStatus; | ||||
|   | ||||
| @@ -66,6 +66,15 @@ public class Project { | ||||
|         this.entrepreneurProposed = entrepreneurProposed; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public boolean equals(Object o) { | ||||
|         if (o == this) { | ||||
|             return true; | ||||
|         } | ||||
|         Project project = (Project) o; | ||||
|         return this.idProject == project.idProject; | ||||
|     } | ||||
|  | ||||
|     public Long getIdProject() { | ||||
|         return idProject; | ||||
|     } | ||||
|   | ||||
| @@ -2,6 +2,9 @@ package enseirb.myinpulse.model; | ||||
|  | ||||
| import jakarta.persistence.*; | ||||
|  | ||||
| import org.hibernate.annotations.Generated; | ||||
| import org.hibernate.generator.EventType; | ||||
|  | ||||
| import java.time.LocalDateTime; | ||||
| import java.util.ArrayList; | ||||
| import java.util.List; | ||||
| @@ -20,6 +23,10 @@ public class SectionCell { | ||||
|     @GeneratedValue(strategy = GenerationType.IDENTITY) | ||||
|     private Long idSectionCell; | ||||
|  | ||||
|     @Column(columnDefinition = "serial") | ||||
|     @Generated(event = EventType.INSERT) | ||||
|     private Long idReference; | ||||
|  | ||||
|     @Column() private long sectionId; | ||||
|     private String contentSectionCell; | ||||
|  | ||||
| @@ -56,6 +63,14 @@ public class SectionCell { | ||||
|         this.idSectionCell = idSectionCell; | ||||
|     } | ||||
|  | ||||
|     public Long getIdReference() { | ||||
|         return idReference; | ||||
|     } | ||||
|  | ||||
|     public void setIdReference(Long idReference) { | ||||
|         this.idReference = idReference; | ||||
|     } | ||||
|  | ||||
|     public Long getSectionId() { | ||||
|         return sectionId; | ||||
|     } | ||||
|   | ||||
| @@ -26,36 +26,23 @@ public class User { | ||||
|     @Column(length = 20) | ||||
|     private String phoneNumber; | ||||
|  | ||||
|     @Column private boolean pending; | ||||
|  | ||||
|     public User() {} | ||||
|  | ||||
|     // TODO: this should be removed as we shouldn't be able to chose the ID. Leaving it for | ||||
|     // compatibility purposes, as soon as it's not used anymore, delete it | ||||
|     public User( | ||||
|             Long idUser, | ||||
|             String userSurname, | ||||
|             String userName, | ||||
|             String primaryMail, | ||||
|             String secondaryMail, | ||||
|             String phoneNumber) { | ||||
|         this.idUser = idUser; | ||||
|         this.userSurname = userSurname; | ||||
|         this.userName = userName; | ||||
|         this.primaryMail = primaryMail; | ||||
|         this.secondaryMail = secondaryMail; | ||||
|         this.phoneNumber = phoneNumber; | ||||
|     } | ||||
|  | ||||
|     public User( | ||||
|             String userSurname, | ||||
|             String userName, | ||||
|             String primaryMail, | ||||
|             String secondaryMail, | ||||
|             String phoneNumber) { | ||||
|             String phoneNumber, | ||||
|             boolean pending) { | ||||
|         this.userSurname = userSurname; | ||||
|         this.userName = userName; | ||||
|         this.primaryMail = primaryMail; | ||||
|         this.secondaryMail = secondaryMail; | ||||
|         this.phoneNumber = phoneNumber; | ||||
|         this.pending = pending; | ||||
|     } | ||||
|  | ||||
|     public Long getIdUser() { | ||||
| @@ -105,4 +92,12 @@ public class User { | ||||
|     public void setPhoneNumber(String phoneNumber) { | ||||
|         phoneNumber = phoneNumber; | ||||
|     } | ||||
|  | ||||
|     public boolean isPending() { | ||||
|         return pending; | ||||
|     } | ||||
|  | ||||
|     public void setPending(boolean pending) { | ||||
|         this.pending = pending; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -15,4 +15,6 @@ public interface SectionCellRepository extends JpaRepository<SectionCell, Long> | ||||
|  | ||||
|     Iterable<SectionCell> findByProjectSectionCellAndSectionIdAndModificationDateBefore( | ||||
|             Project project, long sectionId, LocalDateTime date); | ||||
|  | ||||
|     Iterable<SectionCell> findByProjectSectionCell(Project project); | ||||
| } | ||||
|   | ||||
| @@ -9,8 +9,9 @@ import java.util.Optional; | ||||
|  | ||||
| @RepositoryRestResource | ||||
| public interface UserRepository extends JpaRepository<User, Long> { | ||||
|     Optional<User> findByPrimaryMail(String email); | ||||
|     Optional<User> findByPrimaryMail(String primaryMail); | ||||
|  | ||||
|     Iterable<User> findAllByPendingEquals(boolean pending); | ||||
|     /* @Query("SELECT u from User u") | ||||
|     User findAllUser(); */ | ||||
|  | ||||
|   | ||||
| @@ -24,6 +24,7 @@ public class AdminApiService { | ||||
|     private final ProjectService projectService; | ||||
|     private final UserService userService; | ||||
|     private final AdministratorService administratorService; | ||||
|     private final EntrepreneurService entrepreneurService; | ||||
|     private final UtilsService utilsService; | ||||
|     private final AppointmentService appointmentService; | ||||
|     private final ReportService reportService; | ||||
| @@ -35,6 +36,7 @@ public class AdminApiService { | ||||
|             UserService userService, | ||||
|             AdministratorService administratorService, | ||||
|             UtilsService utilsService, | ||||
|             EntrepreneurService entrepreneurService, | ||||
|             AppointmentService appointmentService, | ||||
|             ReportService reportService, | ||||
|             SectionCellService sectionCellService) { | ||||
| @@ -45,6 +47,7 @@ public class AdminApiService { | ||||
|         this.appointmentService = appointmentService; | ||||
|         this.reportService = reportService; | ||||
|         this.sectionCellService = sectionCellService; | ||||
|         this.entrepreneurService = entrepreneurService; | ||||
|     } | ||||
|  | ||||
|     // TODO: check if tests are sufficient - peer verification required | ||||
| @@ -75,6 +78,12 @@ public class AdminApiService { | ||||
|         } | ||||
|         if (user instanceof Entrepreneur) { | ||||
|             Project project = ((Entrepreneur) user).getProjectParticipation(); | ||||
|             if (project == null) { | ||||
|                 throw new ResponseStatusException( | ||||
|                         HttpStatus.NOT_FOUND, | ||||
|                         "The user has no project, thus no appointments. No users should have no project"); | ||||
|             } | ||||
|  | ||||
|             project.getListSectionCell() | ||||
|                     .forEach( | ||||
|                             sectionCell -> { | ||||
| @@ -97,13 +106,14 @@ public class AdminApiService { | ||||
|                 decision.projectId, | ||||
|                 null, | ||||
|                 null, | ||||
|                 null, | ||||
|                 (decision.isAccepted == 1) ? ACTIVE : REJECTED, | ||||
|                 null, | ||||
|                 null, | ||||
|                 this.administratorService.getAdministratorById(decision.adminId)); | ||||
|     } | ||||
|  | ||||
|     // TODO: check if tests are sufficient - peer verification required | ||||
|     public void addNewProject(Project project) { | ||||
|     public Project addNewProject(Project project) { | ||||
|         project.setIdProject(null); | ||||
|         // We remove the ID from the request to be sure that it will be auto generated | ||||
|         try { | ||||
| @@ -135,6 +145,7 @@ public class AdminApiService { | ||||
|                         sectionCell -> { | ||||
|                             sectionCell.setProjectSectionCell(newProject); | ||||
|                         }); | ||||
|         return newProject; | ||||
|     } | ||||
|  | ||||
|     public void createAppointmentReport(long appointmentId, Report report, String mail) { | ||||
| @@ -163,4 +174,51 @@ public class AdminApiService { | ||||
|     public void deleteProject(long projectId) { | ||||
|         this.projectService.deleteProjectById(projectId); | ||||
|     } | ||||
|  | ||||
|     public void setAdmin(long userId, String token) { | ||||
|         Entrepreneur e = this.entrepreneurService.getEntrepreneurById(userId); | ||||
|         Administrator a = | ||||
|                 new Administrator( | ||||
|                         e.getUserSurname(), | ||||
|                         e.getUserName(), | ||||
|                         e.getPrimaryMail(), | ||||
|                         e.getSecondaryMail(), | ||||
|                         e.getPhoneNumber()); | ||||
|         this.entrepreneurService.deleteEntrepreneur(e); | ||||
|         this.administratorService.addAdministrator(a); | ||||
|         try { | ||||
|             KeycloakApi.setRoleToUser(a.getUserName(), "MyINPulse-admin", token); | ||||
|         } catch (Exception err) { | ||||
|             logger.error(err); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public void validateEntrepreneurAccount(long userId, String token) { | ||||
|         Entrepreneur e = this.entrepreneurService.getEntrepreneurById(userId); | ||||
|         try { | ||||
|             KeycloakApi.setRoleToUser(e.getUserName(), "MyINPulse-entrepreneur", token); | ||||
|         } catch (Exception err) { | ||||
|             logger.error(err); | ||||
|         } | ||||
|         this.entrepreneurService.validateEntrepreneurById(userId); | ||||
|     } | ||||
|  | ||||
|     public Iterable<User> getPendingUsers() { | ||||
|         return this.userService.getPendingAccounts(); | ||||
|     } | ||||
|  | ||||
|     public void createAccount( | ||||
|             String username, | ||||
|             String userSurname, | ||||
|             String primaryMail, | ||||
|             String secondaryMail, | ||||
|             String phoneNumber) { | ||||
|         Administrator a = | ||||
|                 new Administrator(username, userSurname, primaryMail, secondaryMail, phoneNumber); | ||||
|         this.administratorService.addAdministrator(a); | ||||
|     } | ||||
|  | ||||
|     public Iterable<Administrator> getAllAdmins() { | ||||
|         return this.administratorService.allAdministrators(); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -1,11 +1,13 @@ | ||||
| package enseirb.myinpulse.service; | ||||
|  | ||||
| import static enseirb.myinpulse.model.ProjectDecisionValue.PENDING; | ||||
| import static enseirb.myinpulse.model.ProjectDecisionValue.ACTIVE; | ||||
|  | ||||
| import enseirb.myinpulse.model.Entrepreneur; | ||||
| import enseirb.myinpulse.model.Project; | ||||
| import enseirb.myinpulse.model.SectionCell; | ||||
| import enseirb.myinpulse.service.database.ProjectService; | ||||
| import enseirb.myinpulse.service.database.SectionCellService; | ||||
| import enseirb.myinpulse.model.User; | ||||
| import enseirb.myinpulse.service.database.*; | ||||
|  | ||||
| import org.apache.logging.log4j.LogManager; | ||||
| import org.apache.logging.log4j.Logger; | ||||
| @@ -14,6 +16,10 @@ import org.springframework.http.HttpStatus; | ||||
| import org.springframework.stereotype.Service; | ||||
| import org.springframework.web.server.ResponseStatusException; | ||||
|  | ||||
| import java.time.LocalDateTime; | ||||
| import java.util.ArrayList; | ||||
| import java.util.List; | ||||
|  | ||||
| @Service | ||||
| public class EntrepreneurApiService { | ||||
|  | ||||
| @@ -22,24 +28,39 @@ public class EntrepreneurApiService { | ||||
|     private final SectionCellService sectionCellService; | ||||
|     private final ProjectService projectService; | ||||
|     private final UtilsService utilsService; | ||||
|     private final UserService userService; | ||||
|     private final EntrepreneurService entrepreneurService; | ||||
|     private final AdministratorService administratorService; | ||||
|     private final AppointmentService appointmentService; | ||||
|     private final AnnotationService annotationService; | ||||
|  | ||||
|     @Autowired | ||||
|     EntrepreneurApiService( | ||||
|             SectionCellService sectionCellService, | ||||
|             ProjectService projectService, | ||||
|             UtilsService utilsService) { | ||||
|             UtilsService utilsService, | ||||
|             UserService userService, | ||||
|             EntrepreneurService entrepreneurService, | ||||
|             AdministratorService administratorService, | ||||
|             AppointmentService appointmentService, | ||||
|             AnnotationService annotationService) { | ||||
|         this.sectionCellService = sectionCellService; | ||||
|         this.projectService = projectService; | ||||
|         this.utilsService = utilsService; | ||||
|         this.userService = userService; | ||||
|         this.entrepreneurService = entrepreneurService; | ||||
|         this.administratorService = administratorService; | ||||
|         this.appointmentService = appointmentService; | ||||
|         this.annotationService = annotationService; | ||||
|     } | ||||
|  | ||||
|     public void editSectionCell(Long sectionCellId, SectionCell sectionCell, String mail) { | ||||
|         SectionCell editSectionCell = sectionCellService.getSectionCellById(sectionCellId); | ||||
|         if (editSectionCell == null) { | ||||
|             System.err.println("Trying to edit unknown section cell"); | ||||
|     public void editSectionCell(Long sectionCellId, String content, String mail) { | ||||
|         if (sectionCellId == null) { | ||||
|             logger.warn("Trying to edit unknown section cell"); | ||||
|             throw new ResponseStatusException( | ||||
|                     HttpStatus.NOT_FOUND, "Cette cellule de section n'existe pas"); | ||||
|         } | ||||
|         SectionCell sectionCell = sectionCellService.getSectionCellById(sectionCellId); | ||||
|         if (!utilsService.isAllowedToCheckProject( | ||||
|                 mail, this.sectionCellService.getProjectId(sectionCellId))) { | ||||
|             logger.warn( | ||||
| @@ -55,27 +76,45 @@ public class EntrepreneurApiService { | ||||
|                 mail, | ||||
|                 sectionCellId, | ||||
|                 this.sectionCellService.getProjectId(sectionCellId)); | ||||
|         sectionCellService.updateSectionCell( | ||||
|                 sectionCellId, | ||||
|                 sectionCell.getSectionId(), | ||||
|                 sectionCell.getContentSectionCell(), | ||||
|                 sectionCell.getModificationDate()); | ||||
|         SectionCell newSectionCell = | ||||
|                 new SectionCell( | ||||
|                         null, | ||||
|                         sectionCell.getSectionId(), | ||||
|                         content, | ||||
|                         LocalDateTime.now(), | ||||
|                         sectionCell.getProjectSectionCell()); | ||||
|         newSectionCell.setIdReference(sectionCell.getIdReference()); | ||||
|         this.addSectionCell(newSectionCell, mail); | ||||
|         sectionCell | ||||
|                 .getAppointmentSectionCell() | ||||
|                 .forEach( | ||||
|                         appointment -> { | ||||
|                             this.appointmentService.updateAppointmentListSectionCell( | ||||
|                                     appointment.getIdAppointment(), newSectionCell); | ||||
|                         }); | ||||
|         sectionCell | ||||
|                 .getListAnnotation() | ||||
|                 .forEach( | ||||
|                         annotation -> { | ||||
|                             this.annotationService.updateAnnotationSectionCell( | ||||
|                                     annotation.getIdAnnotation(), newSectionCell); | ||||
|                         }); | ||||
|     } | ||||
|  | ||||
|     public void removeSectionCell(Long sectionCellId, String mail) { | ||||
|         SectionCell editSectionCell = sectionCellService.getSectionCellById(sectionCellId); | ||||
|         if (editSectionCell == null) { | ||||
|             System.err.println("Trying to remove unknown section cell"); | ||||
|         if (sectionCellId == null) { | ||||
|             logger.warn("Trying to remove unknown section cell"); | ||||
|             throw new ResponseStatusException( | ||||
|                     HttpStatus.NOT_FOUND, "Cette cellule de section n'existe pas"); | ||||
|         } | ||||
|         SectionCell editSectionCell = sectionCellService.getSectionCellById(sectionCellId); | ||||
|         if (!utilsService.isAllowedToCheckProject( | ||||
|                 mail, this.sectionCellService.getProjectId(sectionCellId))) { | ||||
|             logger.warn( | ||||
|                     "User {} tried to remove section cells {} of the project {} but is not allowed to.", | ||||
|                     mail, | ||||
|                     sectionCellId, | ||||
|                     this.sectionCellService.getSectionCellById(sectionCellId)); | ||||
|                     this.sectionCellService.getProjectId(sectionCellId)); | ||||
|             throw new ResponseStatusException( | ||||
|                     HttpStatus.UNAUTHORIZED, "You're not allowed to check this project"); | ||||
|         } | ||||
| @@ -84,52 +123,162 @@ public class EntrepreneurApiService { | ||||
|                 mail, | ||||
|                 sectionCellId, | ||||
|                 this.sectionCellService.getProjectId(sectionCellId)); | ||||
|         sectionCellService.removeSectionCellById(sectionCellId); | ||||
|         SectionCell removedSectionCell = | ||||
|                 new SectionCell( | ||||
|                         null, | ||||
|                         -1L, | ||||
|                         "", | ||||
|                         LocalDateTime.now(), | ||||
|                         this.projectService.getProjectById( | ||||
|                                 editSectionCell.getProjectSectionCell().getIdProject())); | ||||
|         sectionCellService.addNewSectionCell(removedSectionCell); | ||||
|         this.sectionCellService.updateSectionCellReferenceId( | ||||
|                 removedSectionCell.getIdSectionCell(), editSectionCell.getIdReference()); | ||||
|         projectService.updateProjectListSectionCell( | ||||
|                 sectionCellService.getProjectId(sectionCellId), removedSectionCell); | ||||
|         // sectionCellService.removeSectionCellById(sectionCellId); | ||||
|     } | ||||
|  | ||||
|     public void addSectionCell(SectionCell sectionCell, String mail) { | ||||
|         if (sectionCell == null) { | ||||
|             System.err.println("Trying to create an empty section cell"); | ||||
|             logger.warn("Trying to create an empty section cell"); | ||||
|             throw new ResponseStatusException( | ||||
|                     HttpStatus.BAD_REQUEST, "La cellule de section fournie est vide"); | ||||
|         } | ||||
|         if (sectionCell.getSectionId() == -1) { | ||||
|             logger.warn("Trying to create an illegal section cell"); | ||||
|             throw new ResponseStatusException( | ||||
|                     HttpStatus.BAD_REQUEST, "La cellule de section fournie n'est pas valide"); | ||||
|         } | ||||
|         if (!utilsService.isAllowedToCheckProject( | ||||
|                 mail, this.sectionCellService.getProjectId(sectionCell.getIdSectionCell()))) { | ||||
|                 mail, sectionCell.getProjectSectionCell().getIdProject())) { | ||||
|             logger.warn( | ||||
|                     "User {} tried to add a section cell to the project {} but is not allowed to.", | ||||
|                     mail, | ||||
|                     this.sectionCellService.getProjectId(sectionCell.getIdSectionCell())); | ||||
|                     sectionCell.getProjectSectionCell().getIdProject()); | ||||
|             throw new ResponseStatusException( | ||||
|                     HttpStatus.UNAUTHORIZED, "You're not allowed to check this project"); | ||||
|         } | ||||
|         logger.info( | ||||
|                 "User {} added a new section cell {} to the project with id {}", | ||||
|                 "User {} added a new section cell {} to the project {}", | ||||
|                 mail, | ||||
|                 sectionCell.getIdSectionCell(), | ||||
|                 this.sectionCellService.getProjectId(sectionCell.getIdSectionCell())); | ||||
|         SectionCell newSectionCell = sectionCellService.addNewSectionCell(sectionCell); | ||||
|                 sectionCell.getProjectSectionCell().getIdProject()); | ||||
|         SectionCell newSectionCell = | ||||
|                 sectionCellService.addNewSectionCell( | ||||
|                         sectionCell); // if here, logger fails cause id is null (not added yet) | ||||
|         newSectionCell.getProjectSectionCell().updateListSectionCell(newSectionCell); | ||||
|         newSectionCell | ||||
|                 .getAppointmentSectionCell() | ||||
|                 .forEach( | ||||
|                         appointment -> { | ||||
|                             appointment.updateListSectionCell(newSectionCell); | ||||
|                             this.appointmentService.updateAppointmentListSectionCell( | ||||
|                                     appointment.getIdAppointment(), newSectionCell); | ||||
|                         }); | ||||
|         newSectionCell | ||||
|                 .getListAnnotation() | ||||
|                 .forEach( | ||||
|                         annotation -> { | ||||
|                             annotation.setSectionCellAnnotation(newSectionCell); | ||||
|                             this.annotationService.updateAnnotationSectionCell( | ||||
|                                     annotation.getIdAnnotation(), newSectionCell); | ||||
|                         }); | ||||
|     } | ||||
|  | ||||
|     public void requestNewProject(Project project, String mail) { | ||||
|         if (project == null) { | ||||
|             logger.error("Trying to request the creation of a null project"); | ||||
|             logger.warn("Trying to request the creation of a null project"); | ||||
|             throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Le projet fourni est vide"); | ||||
|         } | ||||
|         logger.info("User {} created a new project with id {}", mail, project.getIdProject()); | ||||
|         project.setProjectStatus(PENDING); | ||||
|         logger.info("User {} created a new project named {}", mail, project.getProjectName()); | ||||
|         project.setEntrepreneurProposed((Entrepreneur) this.userService.getUserByEmail(mail)); | ||||
|         projectService.addNewProject(project); | ||||
|         this.projectService.updateProjectStatus(project.getIdProject(), PENDING); | ||||
|         if (project.getProjectAdministrator() != null) { | ||||
|             this.administratorService.updateAdministratorListProject( | ||||
|                     project.getProjectAdministrator().getIdUser(), project); | ||||
|         } | ||||
|         this.entrepreneurService.updateEntrepreneurProjectProposed( | ||||
|                 this.userService.getUserByEmail(mail).getIdUser(), project); | ||||
|         this.entrepreneurService.updateEntrepreneurProjectParticipation( | ||||
|                 this.userService.getUserByEmail(mail).getIdUser(), project); | ||||
|         project.getListEntrepreneurParticipation() | ||||
|                 .forEach( | ||||
|                         entrepreneur -> | ||||
|                                 this.entrepreneurService.updateEntrepreneurProjectParticipation( | ||||
|                                         entrepreneur.getIdUser(), project)); | ||||
|         project.getListSectionCell() | ||||
|                 .forEach( | ||||
|                         sectionCell -> | ||||
|                                 this.sectionCellService.updateSectionCellProject( | ||||
|                                         sectionCell.getIdSectionCell(), project)); | ||||
|     } | ||||
|  | ||||
|     public void createAccount(Entrepreneur e) { | ||||
|         try { | ||||
|             userService.getUserByEmail(e.getPrimaryMail()); | ||||
|             logger.error("The user {} already exists in the system", e.getPrimaryMail()); | ||||
|         } catch (ResponseStatusException err) { | ||||
|             this.entrepreneurService.addEntrepreneur(e); | ||||
|             return; | ||||
|         } | ||||
|         throw new ResponseStatusException(HttpStatus.CONFLICT, "User already exists in the system"); | ||||
|     } | ||||
|  | ||||
|     public Iterable<Project> getProjectIdViaClaim(String email) { | ||||
|         Long UserId = this.userService.getUserByEmail(email).getIdUser(); | ||||
|         Entrepreneur entrepreneur = this.entrepreneurService.getEntrepreneurById(UserId); | ||||
|         List<Project> Project_List = new ArrayList<>(); | ||||
|  | ||||
|         Project_List.add(entrepreneur.getProjectParticipation()); | ||||
|         return Project_List; | ||||
|     } | ||||
|  | ||||
|     public Iterable<Entrepreneur> getAllEntrepreneurs() { | ||||
|         return entrepreneurService.getAllEntrepreneurs(); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Checks if an entrepreneur with the given email has a project that is ACTIVE. | ||||
|      * | ||||
|      * @param email The email of the entrepreneur. | ||||
|      * @return true if the entrepreneur has an active project, false otherwise. | ||||
|      */ | ||||
|     public Boolean checkIfEntrepreneurProjectActive(String email) { | ||||
|         User user = this.userService.getUserByEmail(email); | ||||
|         if (user == null) { | ||||
|             return false; | ||||
|         } | ||||
|         Long userId = user.getIdUser(); | ||||
|  | ||||
|         Entrepreneur entrepreneur = this.entrepreneurService.getEntrepreneurById(userId); | ||||
|         if (entrepreneur == null) { | ||||
|             return false; | ||||
|         } | ||||
|         Project proposedProject = entrepreneur.getProjectProposed(); | ||||
|         return proposedProject != null && proposedProject.getProjectStatus() == ACTIVE; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Checks if an entrepreneur with the given email has proposed a project. | ||||
|      * | ||||
|      * @param email The email of the entrepreneur. | ||||
|      * @return true if the entrepreneur has a proposed project, false otherwise. | ||||
|      */ | ||||
|     public Boolean entrepreneurHasPendingRequestedProject(String email) { | ||||
|         User user = this.userService.getUserByEmail(email); | ||||
|         if (user == null) { | ||||
|             return false; | ||||
|         } | ||||
|         Long userId = user.getIdUser(); | ||||
|  | ||||
|         Entrepreneur entrepreneur = this.entrepreneurService.getEntrepreneurById(userId); | ||||
|         if (entrepreneur == null) { | ||||
|             return false; | ||||
|         } | ||||
|         Project proposedProject = entrepreneur.getProjectProposed(); | ||||
|         if (entrepreneur.getProjectProposed() == null) { | ||||
|             return false; | ||||
|         } | ||||
|         return proposedProject.getProjectStatus() == PENDING; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -6,12 +6,17 @@ import enseirb.myinpulse.exception.UserNotFoundException; | ||||
| import enseirb.myinpulse.model.RoleRepresentation; | ||||
| import enseirb.myinpulse.model.UserRepresentation; | ||||
|  | ||||
| import org.apache.logging.log4j.LogManager; | ||||
| import org.apache.logging.log4j.Logger; | ||||
| import org.springframework.web.client.RestClient; | ||||
|  | ||||
| import java.util.List; | ||||
|  | ||||
| import javax.management.relation.RoleNotFoundException; | ||||
|  | ||||
| public class KeycloakApi { | ||||
|  | ||||
|     protected static final Logger logger = LogManager.getLogger(); | ||||
|     static final String keycloakUrl; | ||||
|     static final String realmName; | ||||
|  | ||||
| @@ -29,44 +34,48 @@ public class KeycloakApi { | ||||
|         realmName = System.getenv("VITE_KEYCLOAK_REALM"); | ||||
|     } | ||||
|  | ||||
|     static String toBearer(String b) { | ||||
|         return "Bearer " + b; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Uses Keycloak API to retrieve a role representation of a role by its name | ||||
|      * | ||||
|      * @param roleName name of the role | ||||
|      * @param bearer authorization header used by the client to authenticate to keycloak | ||||
|      * @param token authorization header used by the client to authenticate to keycloak | ||||
|      */ | ||||
|     public static RoleRepresentation getRoleRepresentationByName(String roleName, String bearer) | ||||
|     public static RoleRepresentation getRoleRepresentationByName(String roleName, String token) | ||||
|             throws RoleNotFoundException { | ||||
|         RoleRepresentation[] response = | ||||
|         RoleRepresentation response = | ||||
|                 RestClient.builder() | ||||
|                         .baseUrl(keycloakUrl) | ||||
|                         .defaultHeader("Authorization", bearer) | ||||
|                         .defaultHeader("Authorization", toBearer(token)) | ||||
|                         .build() | ||||
|                         .get() | ||||
|                         .uri("/admin/realms/{realmName}/roles/{roleName}", realmName, roleName) | ||||
|                         .retrieve() | ||||
|                         .body(RoleRepresentation[].class); | ||||
|  | ||||
|         if (response == null || response.length == 0) { | ||||
|             throw new RoleNotFoundException("Role not found"); | ||||
|         } | ||||
|         return response[0]; | ||||
|                         .body(RoleRepresentation.class); | ||||
|         /* | ||||
|         {"id":"7a845f2e-c832-4465-8cd8-894d72bc13f1","name":"MyINPulse-entrepreneur","description":"Role for entrepreneur","composite":false,"clientRole":false,"containerId":"0d6f691b-e328-471a-b89e-c30bd7e5b6b0","attributes":{}} | ||||
|          */ | ||||
|         // TODO: check what happens when role does not exist | ||||
|         return response; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Use keycloak API to to retreive a userID via his name or email. | ||||
|      * | ||||
|      * @param username username or mail of the user | ||||
|      * @param bearer bearer of the user, allowing access to database | ||||
|      * @param token bearer of the user, allowing access to database | ||||
|      * @return the userid, as a String | ||||
|      * @throws UserNotFoundException | ||||
|      */ | ||||
|     public static String getUserIdByName(String username, String bearer) | ||||
|     public static String getUserIdByName(String username, String token) | ||||
|             throws UserNotFoundException { | ||||
|         UserRepresentation[] response = | ||||
|                 RestClient.builder() | ||||
|                         .baseUrl(keycloakUrl) | ||||
|                         .defaultHeader("Authorization", bearer) | ||||
|                         .defaultHeader("Authorization", toBearer(token)) | ||||
|                         .build() | ||||
|                         .get() | ||||
|                         .uri( | ||||
| @@ -91,27 +100,26 @@ public class KeycloakApi { | ||||
|      * | ||||
|      * @param username | ||||
|      * @param roleName | ||||
|      * @param bearer | ||||
|      * @param token | ||||
|      * @throws RoleNotFoundException | ||||
|      * @throws UserNotFoundException | ||||
|      */ | ||||
|     public static void setRoleToUser(String username, String roleName, String bearer) | ||||
|     public static void setRoleToUser(String username, String roleName, String token) | ||||
|             throws RoleNotFoundException, UserNotFoundException { | ||||
|         RoleRepresentation roleRepresentation = getRoleRepresentationByName(roleName, bearer); | ||||
|         String userId = getUserIdByName(username, bearer); | ||||
|  | ||||
|         RoleRepresentation roleRepresentation = getRoleRepresentationByName(roleName, token); | ||||
|         String userId = getUserIdByName(username, token); | ||||
|         List<RoleRepresentation> rolesToAdd = List.of(roleRepresentation); | ||||
|         logger.debug("Adding role {} to user {}", roleRepresentation.id, userId); | ||||
|         RestClient.builder() | ||||
|                 .baseUrl(keycloakUrl) | ||||
|                 .defaultHeader("Authorization", bearer) | ||||
|                 .defaultHeader("Authorization", toBearer(token)) | ||||
|                 .build() | ||||
|                 .post() | ||||
|                 .uri( | ||||
|                         "/admin/realms/${realmName}/users/${userId}/role-mappings/realm", | ||||
|                         realmName, | ||||
|                         userId) | ||||
|                 .body(roleRepresentation) | ||||
|                 .uri("/admin/realms/" + realmName + "/users/" + userId + "/role-mappings/realm") | ||||
|                 .body(rolesToAdd) | ||||
|                 .contentType(APPLICATION_JSON) | ||||
|                 .retrieve(); | ||||
|                 .retrieve() | ||||
|                 .toBodilessEntity(); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|   | ||||
| @@ -2,7 +2,8 @@ package enseirb.myinpulse.service; | ||||
|  | ||||
| import com.itextpdf.text.*; | ||||
| import com.itextpdf.text.pdf.PdfWriter; | ||||
|  | ||||
| import enseirb.myinpulse.controller.AdminApi; | ||||
| import enseirb.myinpulse.controller.EntrepreneurApi; | ||||
| import enseirb.myinpulse.model.*; | ||||
| import enseirb.myinpulse.service.database.*; | ||||
|  | ||||
| @@ -24,11 +25,19 @@ import java.nio.file.StandardCopyOption; | ||||
| import java.time.LocalDateTime; | ||||
| import java.time.format.DateTimeFormatter; | ||||
| import java.util.ArrayList; | ||||
| import java.util.HashMap; | ||||
| import java.util.HashSet; | ||||
| import java.util.List; | ||||
| import java.util.Map; | ||||
| import java.util.Set; | ||||
|  | ||||
| @Service | ||||
| public class SharedApiService { | ||||
|  | ||||
|     private final AdminApi adminApi; | ||||
|  | ||||
|     private final EntrepreneurApi entrepreneurApi; | ||||
|  | ||||
|     protected static final Logger logger = LogManager.getLogger(); | ||||
|  | ||||
|     private final ProjectService projectService; | ||||
| @@ -44,12 +53,16 @@ public class SharedApiService { | ||||
|             EntrepreneurService entrepreneurService, | ||||
|             SectionCellService sectionCellService, | ||||
|             AppointmentService appointmentService, | ||||
|             UtilsService utilsService) { | ||||
|             UtilsService utilsService, | ||||
|             EntrepreneurApi entrepreneurApi, | ||||
|             AdminApi adminApi) { | ||||
|         this.projectService = projectService; | ||||
|         this.entrepreneurService = entrepreneurService; | ||||
|         this.sectionCellService = sectionCellService; | ||||
|         this.appointmentService = appointmentService; | ||||
|         this.utilsService = utilsService; | ||||
|         this.entrepreneurApi = entrepreneurApi; | ||||
|         this.adminApi = adminApi; | ||||
|     } | ||||
|  | ||||
|     // TODO filter this with date | ||||
| @@ -69,10 +82,54 @@ public class SharedApiService { | ||||
|         LocalDateTime dateTime = LocalDateTime.parse(date, formatter); | ||||
|  | ||||
|         Project project = this.projectService.getProjectById(projectId); | ||||
|         return this.sectionCellService.getSectionCellsByProjectAndSectionIdBeforeDate( | ||||
|         return this.sectionCellService.getLatestSectionCellsByIdReferenceBeforeDate( | ||||
|                 project, sectionId, dateTime); | ||||
|     } | ||||
|  | ||||
|     // Retrieve all up to date (for every sectionId) sectionCells of a project | ||||
|     public Iterable<SectionCell> getAllSectionCells(long projectId, String mail) { | ||||
|         if (!utilsService.isAllowedToCheckProject(mail, projectId)) { | ||||
|             logger.warn( | ||||
|                     "User {} tried to check section cells of the project {} but is not allowed to.", | ||||
|                     mail, | ||||
|                     projectId); | ||||
|             throw new ResponseStatusException( | ||||
|                     HttpStatus.UNAUTHORIZED, "You're not allowed to check this project"); | ||||
|         } | ||||
|  | ||||
|         Project project = this.projectService.getProjectById(projectId); | ||||
|  | ||||
|         Map<Long, SectionCell> latestSectionCellsMap = | ||||
|                 new HashMap<>(); // List for the intermediate result | ||||
|  | ||||
|         // Iterate through all SectionCells associated with the project | ||||
|         // This loop iterates over project.getListSectionCell() but does NOT modify it which causes | ||||
|         // ConcurrentModificationException. | ||||
|         // Modifications are done only on the latestSectionCellsMap (which is safe). | ||||
|         project.getListSectionCell() // <-- Iterating over the original list (read-only) | ||||
|                 .forEach( | ||||
|                         projectCell -> { | ||||
|                             Long idReference = projectCell.getIdReference(); | ||||
|                             // Check if we have already seen a SectionCell with this idReference in | ||||
|                             // our map | ||||
|                             if (latestSectionCellsMap.containsKey(idReference)) { | ||||
|                                 SectionCell existingCell = latestSectionCellsMap.get(idReference); | ||||
|                                 // Compare modification dates. If the current cell is newer, replace | ||||
|                                 // the one in the map. | ||||
|                                 if (projectCell | ||||
|                                         .getModificationDate() | ||||
|                                         .isAfter(existingCell.getModificationDate())) { | ||||
|                                     latestSectionCellsMap.put(idReference, projectCell); | ||||
|                                 } | ||||
|                             } else { | ||||
|                                 // If this is the first time we encounter this idReference, add the | ||||
|                                 // cell to the map. | ||||
|                                 latestSectionCellsMap.put(idReference, projectCell); | ||||
|                             } | ||||
|                         }); | ||||
|         return new ArrayList<>(latestSectionCellsMap.values()); | ||||
|     } | ||||
|  | ||||
|     // TODO: test | ||||
|     public Iterable<Entrepreneur> getEntrepreneursByProjectId(long projectId, String mail) { | ||||
|         if (!utilsService.isAllowedToCheckProject(mail, projectId)) { | ||||
| @@ -114,18 +171,26 @@ public class SharedApiService { | ||||
|                 "User {} tried to check the appointments related to the project {}", | ||||
|                 mail, | ||||
|                 projectId); | ||||
|         Iterable<SectionCell> sectionCells = | ||||
|                 this.sectionCellService.getSectionCellsByProject( | ||||
|                         projectService.getProjectById(projectId), | ||||
|                         2L); // sectionId useless in this function ? | ||||
|         List<Appointment> appointments = new ArrayList<Appointment>(); | ||||
|         sectionCells.forEach( | ||||
|  | ||||
|         Project project = projectService.getProjectById(projectId); | ||||
|  | ||||
|         Iterable<SectionCell> sectionCellsIterable = | ||||
|                 this.sectionCellService.getSectionCellsByProject(project); | ||||
|  | ||||
|         // Use a Set to collect unique appointments | ||||
|         Set<Appointment> uniqueAppointments = new HashSet<>(); | ||||
|  | ||||
|         sectionCellsIterable.forEach( | ||||
|                 sectionCell -> { | ||||
|                     appointments.addAll( | ||||
|                     List<Appointment> sectionAppointments = | ||||
|                             this.sectionCellService.getAppointmentsBySectionCellId( | ||||
|                                     sectionCell.getIdSectionCell())); | ||||
|                                     sectionCell.getIdSectionCell()); | ||||
|                     // Add all appointments from this section cell to the Set | ||||
|                     uniqueAppointments.addAll(sectionAppointments); | ||||
|                 }); | ||||
|         return appointments; | ||||
|  | ||||
|         // Convert the Set back to a List for the return value | ||||
|         return new ArrayList<>(uniqueAppointments); | ||||
|     } | ||||
|  | ||||
|     public void getPDFReport(long appointmentId, String mail) | ||||
| @@ -247,6 +312,13 @@ public class SharedApiService { | ||||
|                         sectionCell -> { | ||||
|                             sectionCell.updateAppointmentSectionCell(newAppointment); | ||||
|                         }); | ||||
|         newAppointment.getAppointmentReport().setAppointmentReport(newAppointment); | ||||
|  | ||||
|         /* | ||||
|          * On initial insertion, the resport value is null unless that report does already exist in the db somewhere | ||||
|          * If a non null value is passed and it does not exist in db it will throw an exception | ||||
|          */ | ||||
|         if (newAppointment.getAppointmentReport() != null) { | ||||
|             newAppointment.getAppointmentReport().setAppointmentReport(newAppointment); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -15,6 +15,8 @@ import org.springframework.beans.factory.annotation.Autowired; | ||||
| import org.springframework.stereotype.Service; | ||||
| import org.springframework.web.server.ResponseStatusException; | ||||
|  | ||||
| import java.util.Objects; | ||||
|  | ||||
| @Service | ||||
| public class UtilsService { | ||||
|  | ||||
| @@ -44,19 +46,36 @@ public class UtilsService { | ||||
|         } | ||||
|         User user = this.userService.getUserByEmail(mail); | ||||
|         Entrepreneur entrepreneur = this.entrepreneurService.getEntrepreneurById(user.getIdUser()); | ||||
|         if (entrepreneur == null) { | ||||
|             logger.debug("testing access with an unknown Entrepreneur"); | ||||
|             return false; | ||||
|         } | ||||
|         if (entrepreneur.getProjectParticipation() == null) { | ||||
|             logger.debug("testing access with an user with no project participation"); | ||||
|             return false; | ||||
|         } | ||||
|         Project project = this.projectService.getProjectById(projectId); | ||||
|         return entrepreneur.getProjectParticipation() == project; | ||||
|         // We compare the ID instead of the project themselves | ||||
|         return Objects.equals( | ||||
|                 entrepreneur.getProjectParticipation().getIdProject(), project.getIdProject()); | ||||
|     } | ||||
|  | ||||
|     // TODO: test | ||||
|     Boolean isAnAdmin(String mail) { | ||||
|     public Boolean isAnAdmin(String mail) { | ||||
|         try { | ||||
|             long userId = this.userService.getUserByEmail(mail).getIdUser(); | ||||
|             Administrator a = this.administratorService.getAdministratorById(userId); | ||||
|             return true; | ||||
|         } catch (ResponseStatusException e) { | ||||
|             logger.info(e); | ||||
|  | ||||
|             return false; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public Boolean checkEntrepreneurNotPending(String email) { | ||||
|         // Throws 404 if user not found | ||||
|         User user = userService.getUserByEmail(email); | ||||
|         return !user.isPending(); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -1,6 +1,9 @@ | ||||
| package enseirb.myinpulse.service.database; | ||||
|  | ||||
| import enseirb.myinpulse.model.Administrator; | ||||
| import enseirb.myinpulse.model.Annotation; | ||||
| import enseirb.myinpulse.model.MakeAppointment; | ||||
| import enseirb.myinpulse.model.Project; | ||||
| import enseirb.myinpulse.repository.AdministratorRepository; | ||||
|  | ||||
| import org.apache.logging.log4j.LogManager; | ||||
| @@ -52,6 +55,49 @@ public class AdministratorService { | ||||
|         return this.administratorRepository.save(administrator); | ||||
|     } | ||||
|  | ||||
|     public void updateAdministratorListProject(long idAdministrator, Project project) { | ||||
|         Administrator administrator = getAdministratorById(idAdministrator); | ||||
|         administrator.updateListProject(project); | ||||
|         this.administratorRepository.save(administrator); | ||||
|     } | ||||
|  | ||||
|     public void updateAdministratorListAnnotation(long idAdministrator, Annotation annotation) { | ||||
|         Administrator administrator = getAdministratorById(idAdministrator); | ||||
|         administrator.updateListAnnotation(annotation); | ||||
|         this.administratorRepository.save(administrator); | ||||
|     } | ||||
|  | ||||
|     public void updateAdministratorMakeAppointment( | ||||
|             long idAdministrator, MakeAppointment makeAppointment) { | ||||
|         Administrator administrator = getAdministratorById(idAdministrator); | ||||
|         administrator.setMakeAppointment(makeAppointment); | ||||
|         this.administratorRepository.save(administrator); | ||||
|     } | ||||
|  | ||||
|     public Administrator updateAdministrator( | ||||
|             Long idAdministrator, | ||||
|             Project project, | ||||
|             Annotation annotation, | ||||
|             MakeAppointment makeAppointment) { | ||||
|         Optional<Administrator> administrator = administratorRepository.findById(idAdministrator); | ||||
|         if (administrator.isEmpty()) { | ||||
|             logger.error( | ||||
|                     "updateAdministrator : No administrator found with id {}", idAdministrator); | ||||
|             throw new ResponseStatusException( | ||||
|                     HttpStatus.NOT_FOUND, "Cet administrateur n'existe pas"); | ||||
|         } | ||||
|         if (project != null) { | ||||
|             administrator.get().updateListProject(project); | ||||
|         } | ||||
|         if (annotation != null) { | ||||
|             administrator.get().updateListAnnotation(annotation); | ||||
|         } | ||||
|         if (makeAppointment != null) { | ||||
|             administrator.get().setMakeAppointment(makeAppointment); | ||||
|         } | ||||
|         return this.administratorRepository.save(administrator.get()); | ||||
|     } | ||||
|  | ||||
|     /* | ||||
|     public Administrator getAdministratorByProject(Project project) { | ||||
|         r | ||||
|   | ||||
| @@ -1,6 +1,8 @@ | ||||
| package enseirb.myinpulse.service.database; | ||||
|  | ||||
| import enseirb.myinpulse.model.Administrator; | ||||
| import enseirb.myinpulse.model.Annotation; | ||||
| import enseirb.myinpulse.model.SectionCell; | ||||
| import enseirb.myinpulse.repository.AnnotationRepository; | ||||
|  | ||||
| import org.apache.logging.log4j.LogManager; | ||||
| @@ -46,6 +48,12 @@ public class AnnotationService { | ||||
|         this.annotationRepository.deleteById(id); | ||||
|     } | ||||
|  | ||||
|     public void updateAnnotationComment(long idAnnotation, String comment) { | ||||
|         Annotation annotation = getAnnotationById(idAnnotation); | ||||
|         annotation.setComment(comment); | ||||
|         this.annotationRepository.save(annotation); | ||||
|     } | ||||
|  | ||||
|     public Annotation updateAnnotation(Long id, String comment) { | ||||
|         Optional<Annotation> annotation = annotationRepository.findById(id); | ||||
|         if (annotation.isEmpty()) { | ||||
| @@ -58,4 +66,16 @@ public class AnnotationService { | ||||
|         } | ||||
|         return this.annotationRepository.save(annotation.get()); | ||||
|     } | ||||
|  | ||||
|     public void updateAnnotationSectionCell(long idAnnotation, SectionCell sectionCell) { | ||||
|         Annotation annotation = getAnnotationById(idAnnotation); | ||||
|         annotation.setSectionCellAnnotation(sectionCell); | ||||
|         this.annotationRepository.save(annotation); | ||||
|     } | ||||
|  | ||||
|     public void updateAnnotationAdministrator(long idAnnotation, Administrator administrator) { | ||||
|         Annotation annotation = getAnnotationById(idAnnotation); | ||||
|         annotation.setAdministratorAnnotation(administrator); | ||||
|         this.annotationRepository.save(annotation); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -1,6 +1,7 @@ | ||||
| package enseirb.myinpulse.service.database; | ||||
|  | ||||
| import enseirb.myinpulse.model.Appointment; | ||||
| import enseirb.myinpulse.model.SectionCell; | ||||
| import enseirb.myinpulse.repository.AppointmentRepository; | ||||
|  | ||||
| import org.apache.logging.log4j.LogManager; | ||||
| @@ -47,13 +48,50 @@ public class AppointmentService { | ||||
|         this.appointmentRepository.deleteById(id); | ||||
|     } | ||||
|  | ||||
|     public void updateAppointmentDate(long idAppointment, LocalDate date) { | ||||
|         Appointment appointment = getAppointmentById(idAppointment); | ||||
|         appointment.setAppointmentDate(date); | ||||
|         this.appointmentRepository.save(appointment); | ||||
|     } | ||||
|  | ||||
|     public void updateAppointmentTime(long idAppointment, LocalTime time) { | ||||
|         Appointment appointment = getAppointmentById(idAppointment); | ||||
|         appointment.setAppointmentTime(time); | ||||
|         this.appointmentRepository.save(appointment); | ||||
|     } | ||||
|  | ||||
|     public void updateAppointmentDuration(long idAppointment, LocalTime duration) { | ||||
|         Appointment appointment = getAppointmentById(idAppointment); | ||||
|         appointment.setAppointmentDuration(duration); | ||||
|         this.appointmentRepository.save(appointment); | ||||
|     } | ||||
|  | ||||
|     public void updateAppointmentPlace(long idAppointment, String place) { | ||||
|         Appointment appointment = getAppointmentById(idAppointment); | ||||
|         appointment.setAppointmentPlace(place); | ||||
|         this.appointmentRepository.save(appointment); | ||||
|     } | ||||
|  | ||||
|     public void updateAppointmentSubject(long idAppointment, String subject) { | ||||
|         Appointment appointment = getAppointmentById(idAppointment); | ||||
|         appointment.setAppointmentSubject(subject); | ||||
|         this.appointmentRepository.save(appointment); | ||||
|     } | ||||
|  | ||||
|     public void updateAppointmentListSectionCell(long idAppointment, SectionCell sectionCell) { | ||||
|         Appointment appointment = getAppointmentById(idAppointment); | ||||
|         appointment.updateListSectionCell(sectionCell); | ||||
|         this.appointmentRepository.save(appointment); | ||||
|     } | ||||
|  | ||||
|     public Appointment updateAppointment( | ||||
|             Long id, | ||||
|             LocalDate appointmentDate, | ||||
|             LocalTime appointmentTime, | ||||
|             LocalTime appointmentDuration, | ||||
|             String appointmentPlace, | ||||
|             String appointmentSubject) { | ||||
|             String appointmentSubject, | ||||
|             SectionCell sectionCell) { | ||||
|         Optional<Appointment> appointment = this.appointmentRepository.findById(id); | ||||
|         if (appointment.isEmpty()) { | ||||
|             logger.error("updateAppointment : No appointment found with id {}", id); | ||||
| @@ -74,6 +112,9 @@ public class AppointmentService { | ||||
|         if (appointmentSubject != null) { | ||||
|             appointment.get().setAppointmentSubject(appointmentSubject); | ||||
|         } | ||||
|         if (sectionCell != null) { | ||||
|             appointment.get().updateListSectionCell(sectionCell); | ||||
|         } | ||||
|         return this.appointmentRepository.save(appointment.get()); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -1,6 +1,7 @@ | ||||
| package enseirb.myinpulse.service.database; | ||||
|  | ||||
| import enseirb.myinpulse.model.Entrepreneur; | ||||
| import enseirb.myinpulse.model.MakeAppointment; | ||||
| import enseirb.myinpulse.model.Project; | ||||
| import enseirb.myinpulse.repository.EntrepreneurRepository; | ||||
|  | ||||
| @@ -41,8 +42,52 @@ public class EntrepreneurService { | ||||
|         return this.entrepreneurRepository.save(entrepreneur); | ||||
|     } | ||||
|  | ||||
|     public void updateEntrepreneurSchool(long idEntrepreneur, String school) { | ||||
|         Entrepreneur entrepreneur = getEntrepreneurById(idEntrepreneur); | ||||
|         entrepreneur.setSchool(school); | ||||
|         this.entrepreneurRepository.save(entrepreneur); | ||||
|     } | ||||
|  | ||||
|     public void updateEntrepreneurCourse(long idEntrepreneur, String course) { | ||||
|         Entrepreneur entrepreneur = getEntrepreneurById(idEntrepreneur); | ||||
|         entrepreneur.setCourse(course); | ||||
|         this.entrepreneurRepository.save(entrepreneur); | ||||
|     } | ||||
|  | ||||
|     public void updateEntrepreneurSneeStatus(long idEntrepreneur, boolean status) { | ||||
|         Entrepreneur entrepreneur = getEntrepreneurById(idEntrepreneur); | ||||
|         entrepreneur.setSneeStatus(status); | ||||
|         this.entrepreneurRepository.save(entrepreneur); | ||||
|     } | ||||
|  | ||||
|     public void updateEntrepreneurProjectParticipation( | ||||
|             long idEntrepreneur, Project projectParticipation) { | ||||
|         Entrepreneur entrepreneur = getEntrepreneurById(idEntrepreneur); | ||||
|         entrepreneur.setProjectParticipation(projectParticipation); | ||||
|         this.entrepreneurRepository.save(entrepreneur); | ||||
|     } | ||||
|  | ||||
|     public void updateEntrepreneurProjectProposed(long idEntrepreneur, Project projectProposed) { | ||||
|         Entrepreneur entrepreneur = getEntrepreneurById(idEntrepreneur); | ||||
|         entrepreneur.setProjectParticipation(projectProposed); | ||||
|         this.entrepreneurRepository.save(entrepreneur); | ||||
|     } | ||||
|  | ||||
|     public void updateEntrepreneurMakeAppointment( | ||||
|             long idEntrepreneur, MakeAppointment makeAppointment) { | ||||
|         Entrepreneur entrepreneur = getEntrepreneurById(idEntrepreneur); | ||||
|         entrepreneur.setMakeAppointment(makeAppointment); | ||||
|         this.entrepreneurRepository.save(entrepreneur); | ||||
|     } | ||||
|  | ||||
|     public Entrepreneur updateEntrepreneur( | ||||
|             Long id, String school, String course, Boolean sneeStatus) { | ||||
|             Long id, | ||||
|             String school, | ||||
|             String course, | ||||
|             Boolean sneeStatus, | ||||
|             Project projectParticipation, | ||||
|             Project projectProposed, | ||||
|             MakeAppointment makeAppointment) { | ||||
|         Optional<Entrepreneur> entrepreneur = entrepreneurRepository.findById(id); | ||||
|         if (entrepreneur.isEmpty()) { | ||||
|             logger.error("updateEntrepreneur : No entrepreneur found with id {}", id); | ||||
| @@ -58,10 +103,33 @@ public class EntrepreneurService { | ||||
|         if (sneeStatus != null) { | ||||
|             entrepreneur.get().setSneeStatus(sneeStatus); | ||||
|         } | ||||
|         if (projectParticipation != null) { | ||||
|             entrepreneur.get().setProjectParticipation(projectParticipation); | ||||
|         } | ||||
|         if (projectProposed != null) { | ||||
|             entrepreneur.get().setProjectParticipation(projectProposed); | ||||
|         } | ||||
|         if (makeAppointment != null) { | ||||
|             entrepreneur.get().setMakeAppointment(makeAppointment); | ||||
|         } | ||||
|         return this.entrepreneurRepository.save(entrepreneur.get()); | ||||
|     } | ||||
|  | ||||
|     public Iterable<Entrepreneur> GetEntrepreneurByProject(Project project) { | ||||
|         return this.entrepreneurRepository.getEntrepreneurByProjectParticipation(project); | ||||
|     } | ||||
|  | ||||
|     public void deleteEntrepreneur(Entrepreneur e) { | ||||
|         this.entrepreneurRepository.delete(e); | ||||
|     } | ||||
|  | ||||
|     public void validateEntrepreneurById(Long id) { | ||||
|         System.out.println("\nVALIDATING\n"); | ||||
|         Optional<Entrepreneur> e = this.entrepreneurRepository.findById(id); | ||||
|         if (e.isEmpty()) { | ||||
|             throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Entrepreneur n'existe pas"); | ||||
|         } | ||||
|         e.get().setPending(false); | ||||
|         this.entrepreneurRepository.save(e.get()); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -2,9 +2,7 @@ package enseirb.myinpulse.service.database; | ||||
|  | ||||
| import static enseirb.myinpulse.model.ProjectDecisionValue.PENDING; | ||||
|  | ||||
| import enseirb.myinpulse.model.Administrator; | ||||
| import enseirb.myinpulse.model.Project; | ||||
| import enseirb.myinpulse.model.ProjectDecisionValue; | ||||
| import enseirb.myinpulse.model.*; | ||||
| import enseirb.myinpulse.repository.ProjectRepository; | ||||
|  | ||||
| import org.apache.logging.log4j.LogManager; | ||||
| @@ -14,7 +12,6 @@ import org.springframework.http.HttpStatus; | ||||
| import org.springframework.stereotype.Service; | ||||
| import org.springframework.web.server.ResponseStatusException; | ||||
|  | ||||
| import java.time.LocalDate; | ||||
| import java.util.List; | ||||
| import java.util.Optional; | ||||
|  | ||||
| @@ -52,12 +49,50 @@ public class ProjectService { | ||||
|         return this.projectRepository.save(project); | ||||
|     } | ||||
|  | ||||
|     public void updateProjectName(long idProject, String name) { | ||||
|         Project project = getProjectById(idProject); | ||||
|         project.setProjectName(name); | ||||
|         this.projectRepository.save(project); | ||||
|     } | ||||
|  | ||||
|     public void updateProjectLogo(long idProject, byte[] logo) { | ||||
|         Project project = getProjectById(idProject); | ||||
|         project.setLogo(logo); | ||||
|         this.projectRepository.save(project); | ||||
|     } | ||||
|  | ||||
|     public void updateProjectStatus(long idProject, ProjectDecisionValue status) { | ||||
|         Project project = getProjectById(idProject); | ||||
|         project.setProjectStatus(status); | ||||
|         this.projectRepository.save(project); | ||||
|     } | ||||
|  | ||||
|     public void updateProjectEntrepreneurParticipation( | ||||
|             long idProject, Entrepreneur entrepreneurParticipation) { | ||||
|         Project project = getProjectById(idProject); | ||||
|         project.updateListEntrepreneurParticipation(entrepreneurParticipation); | ||||
|         this.projectRepository.save(project); | ||||
|     } | ||||
|  | ||||
|     public void updateProjectListSectionCell(long idProject, SectionCell sectionCell) { | ||||
|         Project project = getProjectById(idProject); | ||||
|         project.updateListSectionCell(sectionCell); | ||||
|         this.projectRepository.save(project); | ||||
|     } | ||||
|  | ||||
|     public void updateProjectAdministrator(long idProject, Administrator administrator) { | ||||
|         Project project = getProjectById(idProject); | ||||
|         project.setProjectAdministrator(administrator); | ||||
|         this.projectRepository.save(project); | ||||
|     } | ||||
|  | ||||
|     public Project updateProject( | ||||
|             Long id, | ||||
|             String projectName, | ||||
|             byte[] logo, | ||||
|             LocalDate creationDate, | ||||
|             ProjectDecisionValue projectStatus, | ||||
|             Entrepreneur entrepreneurParticipation, | ||||
|             SectionCell sectionCell, | ||||
|             Administrator administrator) { | ||||
|         Optional<Project> project = this.projectRepository.findById(id); | ||||
|  | ||||
| @@ -73,11 +108,6 @@ public class ProjectService { | ||||
|         if (logo != null) { | ||||
|             project.get().setLogo(logo); | ||||
|         } | ||||
|  | ||||
|         if (creationDate != null) { | ||||
|             project.get().setCreationDate(creationDate); | ||||
|         } | ||||
|  | ||||
|         if (projectStatus != null) { | ||||
|             // TODO: check if this is really useful | ||||
|             /* | ||||
| @@ -89,7 +119,12 @@ public class ProjectService { | ||||
|             */ | ||||
|             project.get().setProjectStatus(projectStatus); | ||||
|         } | ||||
|  | ||||
|         if (entrepreneurParticipation != null) { | ||||
|             project.get().updateListEntrepreneurParticipation(entrepreneurParticipation); | ||||
|         } | ||||
|         if (sectionCell != null) { | ||||
|             project.get().updateListSectionCell(sectionCell); | ||||
|         } | ||||
|         if (administrator != null) { | ||||
|             project.get().setProjectAdministrator(administrator); | ||||
|         } | ||||
|   | ||||
| @@ -1,5 +1,6 @@ | ||||
| package enseirb.myinpulse.service.database; | ||||
|  | ||||
| import enseirb.myinpulse.model.Appointment; | ||||
| import enseirb.myinpulse.model.Report; | ||||
| import enseirb.myinpulse.repository.ReportRepository; | ||||
|  | ||||
| @@ -46,7 +47,19 @@ public class ReportService { | ||||
|         this.reportRepository.deleteById(id); | ||||
|     } | ||||
|  | ||||
|     public Report updateReport(Long id, String reportContent) { | ||||
|     public void updateReportContent(long idReport, String content) { | ||||
|         Report report = getReportById(idReport); | ||||
|         report.setReportContent(content); | ||||
|         this.reportRepository.save(report); | ||||
|     } | ||||
|  | ||||
|     public void updateReportAppointment(long idReport, Appointment appointment) { | ||||
|         Report report = getReportById(idReport); | ||||
|         report.setAppointmentReport(appointment); | ||||
|         this.reportRepository.save(report); | ||||
|     } | ||||
|  | ||||
|     public Report updateReport(Long id, String reportContent, Appointment appointment) { | ||||
|         Optional<Report> report = this.reportRepository.findById(id); | ||||
|         if (report.isEmpty()) { | ||||
|             logger.error("updateReport : No report found with id {}", id); | ||||
| @@ -55,6 +68,9 @@ public class ReportService { | ||||
|         if (reportContent != null) { | ||||
|             report.get().setReportContent(reportContent); | ||||
|         } | ||||
|         if (appointment != null) { | ||||
|             report.get().setAppointmentReport(appointment); | ||||
|         } | ||||
|         return this.reportRepository.save(report.get()); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -1,5 +1,6 @@ | ||||
| package enseirb.myinpulse.service.database; | ||||
|  | ||||
| import enseirb.myinpulse.model.Annotation; | ||||
| import enseirb.myinpulse.model.Appointment; | ||||
| import enseirb.myinpulse.model.Project; | ||||
| import enseirb.myinpulse.model.SectionCell; | ||||
| @@ -13,7 +14,10 @@ import org.springframework.stereotype.Service; | ||||
| import org.springframework.web.server.ResponseStatusException; | ||||
|  | ||||
| import java.time.LocalDateTime; | ||||
| import java.util.ArrayList; | ||||
| import java.util.HashMap; | ||||
| import java.util.List; | ||||
| import java.util.Map; | ||||
| import java.util.Optional; | ||||
|  | ||||
| @Service | ||||
| @@ -50,22 +54,63 @@ public class SectionCellService { | ||||
|         this.sectionCellRepository.deleteById(id); | ||||
|     } | ||||
|  | ||||
|     public void updateSectionCellReferenceId(Long idSectionCell, Long referenceId) { | ||||
|         SectionCell sectionCell = this.getSectionCellById(idSectionCell); | ||||
|         sectionCell.setIdReference(referenceId); | ||||
|         this.sectionCellRepository.save(sectionCell); | ||||
|     } | ||||
|  | ||||
|     public void updateSectionCellContent(long idSectionCell, String content) { | ||||
|         SectionCell sectionCell = getSectionCellById(idSectionCell); | ||||
|         sectionCell.setContentSectionCell(content); | ||||
|         this.sectionCellRepository.save(sectionCell); | ||||
|     } | ||||
|  | ||||
|     public void updateSectionCellListAppointment(long idSectionCell, Appointment appointment) { | ||||
|         SectionCell sectionCell = getSectionCellById(idSectionCell); | ||||
|         sectionCell.updateAppointmentSectionCell(appointment); | ||||
|         this.sectionCellRepository.save(sectionCell); | ||||
|     } | ||||
|  | ||||
|     public void updateSectionCellListAnnotation(long idSectionCell, Annotation annotation) { | ||||
|         SectionCell sectionCell = getSectionCellById(idSectionCell); | ||||
|         sectionCell.updateListAnnotation(annotation); | ||||
|         this.sectionCellRepository.save(sectionCell); | ||||
|     } | ||||
|  | ||||
|     public void updateSectionCellProject(long idSectionCell, Project project) { | ||||
|         SectionCell sectionCell = getSectionCellById(idSectionCell); | ||||
|         sectionCell.setProjectSectionCell(project); | ||||
|         this.sectionCellRepository.save(sectionCell); | ||||
|     } | ||||
|  | ||||
|     public SectionCell updateSectionCell( | ||||
|             Long id, Long sectionId, String contentSectionCell, LocalDateTime modificationDate) { | ||||
|             Long id, | ||||
|             String contentSectionCell, | ||||
|             Appointment appointment, | ||||
|             Annotation annotation, | ||||
|             Project project) { | ||||
|         Optional<SectionCell> sectionCell = this.sectionCellRepository.findById(id); | ||||
|         if (sectionCell.isEmpty()) { | ||||
|             logger.error("updateSectionCell : No sectionCell found with id {}", id); | ||||
|             throw new ResponseStatusException( | ||||
|                     HttpStatus.NOT_FOUND, "Cette cellule de section n'existe pas"); | ||||
|         } | ||||
|         if (sectionId != null) { | ||||
|             sectionCell.get().setSectionId(sectionId); | ||||
|         } | ||||
|         if (contentSectionCell != null) { | ||||
|             sectionCell.get().setContentSectionCell(contentSectionCell); | ||||
|             sectionCell.get().setModificationDate(LocalDateTime.now()); | ||||
|         } | ||||
|         if (modificationDate != null) { | ||||
|             sectionCell.get().setModificationDate(modificationDate); | ||||
|         if (appointment != null) { | ||||
|             sectionCell.get().updateAppointmentSectionCell(appointment); | ||||
|             sectionCell.get().setModificationDate(LocalDateTime.now()); | ||||
|         } | ||||
|         if (annotation != null) { | ||||
|             sectionCell.get().updateListAnnotation(annotation); | ||||
|             sectionCell.get().setModificationDate(LocalDateTime.now()); | ||||
|         } | ||||
|         if (project != null) { | ||||
|             sectionCell.get().setProjectSectionCell(project); | ||||
|             sectionCell.get().setModificationDate(LocalDateTime.now()); | ||||
|         } | ||||
|         return this.sectionCellRepository.save(sectionCell.get()); | ||||
|     } | ||||
| @@ -74,6 +119,18 @@ public class SectionCellService { | ||||
|         return this.sectionCellRepository.findByProjectSectionCellAndSectionId(project, sectionId); | ||||
|     } | ||||
|  | ||||
|     public Iterable<SectionCell> getSectionCellsByProject(Project project) { | ||||
|         logger.info("Fetching SectionCells for Project ID: {}", project.getIdProject()); | ||||
|         Iterable<SectionCell> sectionCells = | ||||
|                 this.sectionCellRepository.findByProjectSectionCell(project); | ||||
|         List<SectionCell> sectionCellList = new ArrayList<>(); | ||||
|         sectionCells.forEach( | ||||
|                 cell -> { | ||||
|                     sectionCellList.add(cell); | ||||
|                 }); | ||||
|         return sectionCellList; | ||||
|     } | ||||
|  | ||||
|     public Long getProjectId(Long sectionCellId) { | ||||
|         SectionCell sectionCell = getSectionCellById(sectionCellId); | ||||
|         Project sectionProject = sectionCell.getProjectSectionCell(); | ||||
| @@ -90,4 +147,37 @@ public class SectionCellService { | ||||
|         return sectionCellRepository.findByProjectSectionCellAndSectionIdAndModificationDateBefore( | ||||
|                 project, sectionId, date); | ||||
|     } | ||||
|  | ||||
|     public Iterable<SectionCell> getLatestSectionCellsByIdReferenceBeforeDate( | ||||
|             Project project, long sectionId, LocalDateTime date) { | ||||
|  | ||||
|         // 1. Fetch ALL relevant SectionCells modified before the date | ||||
|         Iterable<SectionCell> allMatchingCells = | ||||
|                 sectionCellRepository.findByProjectSectionCellAndSectionIdAndModificationDateBefore( | ||||
|                         project, sectionId, date); | ||||
|  | ||||
|         // 2. Find the latest for each idReference | ||||
|         Map<Long, SectionCell> latestCellsByIdReference = new HashMap<>(); | ||||
|  | ||||
|         for (SectionCell cell : allMatchingCells) { | ||||
|             Long idReference = cell.getIdReference(); | ||||
|  | ||||
|             // Check if we've seen this idReference before | ||||
|             if (latestCellsByIdReference.containsKey(idReference)) { | ||||
|                 // If yes, compare modification dates | ||||
|                 SectionCell existingLatest = latestCellsByIdReference.get(idReference); | ||||
|  | ||||
|                 // If the current cell is more recent, update the map | ||||
|                 if (cell.getModificationDate().isAfter(existingLatest.getModificationDate())) { | ||||
|                     latestCellsByIdReference.put(idReference, cell); | ||||
|                 } | ||||
|             } else { | ||||
|                 // If this is the first time we see this idReference, add it to the map | ||||
|                 latestCellsByIdReference.put(idReference, cell); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         // 3. Return the collection of the latest cells (the values from the map) | ||||
|         return latestCellsByIdReference.values(); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -30,6 +30,15 @@ public class UserService { | ||||
|         return this.userRepository.findAll(); | ||||
|     } | ||||
|  | ||||
|     public User getUserById(long id) { | ||||
|         Optional<User> user = this.userRepository.findById(id); | ||||
|         if (user.isEmpty()) { | ||||
|             logger.error("getUserById : No user found with id {}", id); | ||||
|             throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Cet utilisateur n'existe pas"); | ||||
|         } | ||||
|         return user.get(); | ||||
|     } | ||||
|  | ||||
|     // TODO | ||||
|     public User getUserByEmail(String email) { | ||||
|         Optional<User> opt_user = this.userRepository.findByPrimaryMail(email); | ||||
| @@ -49,6 +58,36 @@ public class UserService { | ||||
|         return this.userRepository.save(user); | ||||
|     } | ||||
|  | ||||
|     public void updateUserSurname(long idUser, String surname) { | ||||
|         User user = getUserById(idUser); | ||||
|         user.setUserSurname(surname); | ||||
|         this.userRepository.save(user); | ||||
|     } | ||||
|  | ||||
|     public void updateUserName(long idUser, String name) { | ||||
|         User user = getUserById(idUser); | ||||
|         user.setUserName(name); | ||||
|         this.userRepository.save(user); | ||||
|     } | ||||
|  | ||||
|     public void updateUserPrimaryMail(long idUser, String primaryMail) { | ||||
|         User user = getUserById(idUser); | ||||
|         user.setPrimaryMail(primaryMail); | ||||
|         this.userRepository.save(user); | ||||
|     } | ||||
|  | ||||
|     public void updateUserSecondaryMail(long idUser, String secondaryMail) { | ||||
|         User user = getUserById(idUser); | ||||
|         user.setSecondaryMail(secondaryMail); | ||||
|         this.userRepository.save(user); | ||||
|     } | ||||
|  | ||||
|     public void updateUserPhoneNumber(long idUser, String phoneNumber) { | ||||
|         User user = getUserById(idUser); | ||||
|         user.setPhoneNumber(phoneNumber); | ||||
|         this.userRepository.save(user); | ||||
|     } | ||||
|  | ||||
|     public User updateUser( | ||||
|             @PathVariable Long id, | ||||
|             String userSurname, | ||||
| @@ -78,4 +117,8 @@ public class UserService { | ||||
|         } | ||||
|         return this.userRepository.save(user.get()); | ||||
|     } | ||||
|  | ||||
|     public Iterable<User> getPendingAccounts() { | ||||
|         return this.userRepository.findAllByPendingEquals(true); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| spring.application.name=myinpulse | ||||
| spring.security.oauth2.resourceserver.jwt.jwk-set-uri=http://localhost:7080/realms/test/protocol/openid-connect/certs | ||||
| spring.security.oauth2.resourceserver.jwt.issuer-uri=http://localhost:7080/realms/test | ||||
| spring.security.oauth2.resourceserver.jwt.jwk-set-uri=http://localhost:7080/realms/${VITE_KEYCLOAK_REALM}/protocol/openid-connect/certs | ||||
| spring.security.oauth2.resourceserver.jwt.issuer-uri=http://localhost:7080/realms/${VITE_KEYCLOAK_REALM} | ||||
| spring.datasource.url=jdbc:postgresql://${DATABASE_URL}/${BACKEND_DB} | ||||
| spring.datasource.username=${BACKEND_USER} | ||||
| spring.datasource.password=${BACKEND_PASSWORD} | ||||
|   | ||||
| @@ -0,0 +1,13 @@ | ||||
| spring.application.name=myinpulse | ||||
| spring.security.oauth2.resourceserver.jwt.jwk-set-uri=http://localhost:7080/realms/test/protocol/openid-connect/certs | ||||
| spring.security.oauth2.resourceserver.jwt.issuer-uri=http://localhost:7080/realms/test | ||||
|  | ||||
| logging.pattern.console=%d{yyyy-MMM-dd HH:mm:ss.SSS} [%thread] %highlight(%-5level) %cyan(%logger{15}) - %msg %n | ||||
|  | ||||
| spring.datasource.url=jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1 | ||||
| spring.datasource.driverClassName=org.h2.Driver | ||||
| spring.datasource.username=sa | ||||
| spring.datasource.password= | ||||
| spring.jpa.database-platform=org.hibernate.dialect.H2Dialect | ||||
|  | ||||
| spring.jpa.hibernate.ddl-auto=create | ||||
| @@ -1,99 +0,0 @@ | ||||
| TRUNCATE project, user_inpulse, entrepreneur, administrator, section_cell, appointment, report, annotation CASCADE; | ||||
|  | ||||
| SELECT setval('annotation_id_annotation_seq', 1, false); | ||||
| SELECT setval('appointment_id_appointment_seq', 1, false); | ||||
| SELECT setval('make_appointment_id_make_appointment_seq', 1, false); | ||||
| SELECT setval('project_id_project_seq', 1, false); | ||||
| SELECT setval('report_id_report_seq', 1, false); | ||||
| SELECT setval('section_cell_id_section_cell_seq', 1, false); | ||||
| SELECT setval('user_inpulse_id_user_seq', 1, false); | ||||
|  | ||||
| INSERT INTO user_inpulse (user_surname, user_name, primary_mail, secondary_mail, phone_number) | ||||
| VALUES ('Dupont', 'Dupond', 'super@mail.fr', 'super2@mail.fr', '06 45 72 45 98'), | ||||
|        ('Martin', 'Matin', 'genial@mail.fr', 'genial2@mail.fr', '06 52 14 58 73'), | ||||
|        ('Charvet', 'Lautre', 'mieux@tmail.fr', 'mieux2@tmail.fr', '07 49 82 16 35'), | ||||
|        ('Leguez', 'Theo', 'bof@mesmails.fr', 'bof2@mesmails.fr', '+33 6 78 14 25 29'), | ||||
|        ('Kia', 'Bi', 'special@mail.fr', 'special2@mail.fr', '07 65 31 38 95'), | ||||
|        ('Ducaillou', 'Pierre', 'maildefou@xyz.fr', 'maildefou2@xyz.fr', '06 54 78 12 62'), | ||||
|        ('Janine', 'Dave', 'janine@labri.fr', 'janine2@labri.fr', '06 87 12 45 95'); | ||||
|  | ||||
| INSERT INTO administrator (id_administrator) | ||||
| VALUES (7); | ||||
|  | ||||
| INSERT INTO project (project_name, logo, creation_date, project_status, id_administrator) | ||||
| VALUES ('Eau du robinet', decode('013d7d16d7ad4fefb61bd95b765c8ceb', 'hex'), TO_DATE('01-OCT-2023', 'DD-MON-YYYY'), | ||||
|         'En cours', 7), | ||||
|        ('Air oxygéné', decode('150647a0984e8f228cd14b54', 'hex'), TO_DATE('04-APR-2024', 'DD-MON-YYYY'), 'En cours', 7), | ||||
|        ('Débat concours', decode('022024abd5486e245c145dda65116f', 'hex'), TO_DATE('22-NOV-2023', 'DD-MON-YYYY'), | ||||
|         'Suspendu', 7), | ||||
|        ('HDeirbMI', decode('ab548d6c1d595a2975e6476f544d14c55a', 'hex'), TO_DATE('07-DEC-2024', 'DD-MON-YYYY'), | ||||
|         'Lancement', 7); | ||||
|  | ||||
|  | ||||
| INSERT INTO entrepreneur (school, course, snee_status, id_entrepreneur, id_project_participation, id_project_proposed) | ||||
| VALUES ('ENSEIRB-MATMECA', 'INFO', TRUE, 1, 4, 4), | ||||
|        ('ENSC', 'Cognitique', TRUE, 2, 2, null), | ||||
|        ('ENSEIRB-MATMECA', 'MATMECA', FALSE, 3, 3, 3), | ||||
|        ('SupOptique', 'Classique', TRUE, 4, 1, 1), | ||||
|        ('ENSEGID', 'Géoscience', FALSE, 5, 1, null), | ||||
|        ('ENSMAC', 'Matériaux composites - Mécanique', FALSE, 6, 2, 2); | ||||
|  | ||||
|  | ||||
| INSERT INTO section_cell (title, content_section_cell, modification_date, id_project) | ||||
| VALUES ('Problème', 'les problèmes...', TO_TIMESTAMP('15-JAN-2025 09:30:20', 'DD-MON-YYYY, HH24:MI:SS'), 2), | ||||
|        ('Segment de client', 'Le segment AB passant le client n°8 est de longueur 32mm. | ||||
|     Le segment BC a quant à lui un longueur de 28mm. Quelle la longueur du segment AC ?', | ||||
|         TO_TIMESTAMP('12-OCT-2022 17:47:38', 'DD-MON-YYYY, HH24:MI:SS'), 3), | ||||
|        ('Proposition de valeur unique', '''Son prix est de 2594€'' ''Ah oui c''est unique en effet', | ||||
|         TO_TIMESTAMP('25-MAY-2024 11:12:04', 'DD-MON-YYYY, HH24:MI:SS'), 2), | ||||
|        ('Solution', 'Un problème ? Une solution', TO_TIMESTAMP('08-FEB-2024 10:17:53', 'DD-MON-YYYY, HH24:MI:SS'), 1), | ||||
|        ('Canaux', 'Ici nous avons la Seine, là-bas le Rhin, oh et plus loin le canal de Suez', | ||||
|         TO_TIMESTAMP('19-JUL-2023 19:22:45', 'DD-MON-YYYY, HH24:MI:SS'), 4), | ||||
|        ('Sources de revenus', 'Y''en n''a pas on est pas payé. Enfin y''a du café quoi', | ||||
|         TO_TIMESTAMP('12-JAN-2025 11:40:26', 'DD-MON-YYYY, HH24:MI:SS'), 1), | ||||
|        ('Structure des coûts', '''Ah oui là ça va faire au moins 1000€ par mois'', Eirbware', | ||||
|         TO_TIMESTAMP('06-FEB-2025 13:04:06', 'DD-MON-YYYY, HH24:MI:SS'), 3), | ||||
|        ('Indicateurs clés', 'On apprend les clés comme des badges, ça se fait', | ||||
|         TO_TIMESTAMP('05-FEB-2025 12:42:38', 'DD-MON-YYYY, HH24:MI:SS'), 4), | ||||
|        ('Avantages concurrentiel', 'On est meilleur', TO_TIMESTAMP('23-APR-2024 16:24:02', 'DD-MON-YYYY, HH24:MI:SS'), | ||||
|         2); | ||||
|  | ||||
| INSERT INTO appointment (appointment_date, appointment_time, appointment_duration, appointment_place, | ||||
|                          appointment_subject) | ||||
| VALUES (TO_DATE('24-DEC-2023', 'DD-MON-YYYY'), '00:00:00', '00:37:53', 'À la maison', 'Ouvrir les cadeaux'), | ||||
|        (TO_DATE('15-AUG-2024', 'DD-MON-YYYY'), '22:35:00', '00:12:36', 'Sur les quais ou dans un champ probablement', | ||||
|         'BOUM BOUM les feux d''artifices (on fête quoi déjà ?)'), | ||||
|        (TO_DATE('28-FEB-2023', 'DD-MON-YYYY'), '14:20:00', '00:20:00', 'Salle TD 15', | ||||
|         'Ah mince c''est pas une année bissextile !'), | ||||
|        (TO_DATE('23-JAN-2024', 'DD-MON-YYYY'), '12:56:27', '11:03:33', 'Là où le vent nous porte', | ||||
|         'Journée la plus importante de l''année'), | ||||
|        (TO_DATE('25-AUG-2025', 'DD-MON-YYYY'), '00:09:00', '01:00:00', 'Euh c''est par où l''amphi 56 ?', | ||||
|         'Rentrée scolaire (il fait trop froid c''est quoi ça on est en août)'); | ||||
|  | ||||
| INSERT INTO report (report_content, id_appointment) | ||||
| VALUES ('Ah oui ça c''est super, ah ouais j''aime bien, bien vu de penser à ça', 1), | ||||
|        ('Bonne réunion', 3), | ||||
|        ('Ouais, j''ai rien compris mais niquel on fait comme vous avez dit', 3), | ||||
|        ('Non non ça va pas du tout ce que tu me proposes, faut tout refaire', 4), | ||||
|        ('Réponse de la DSI : non', 2), | ||||
|        ('Trop dommage qu''Apple ait sorti leur logiciel avant nous, on avait la même idée et tout on aurait tellement pu leur faire de la concurrence', | ||||
|         5); | ||||
|  | ||||
| INSERT INTO annotation (comment, id_administrator, id_section_cell) | ||||
| VALUES ('faut changer ça hein', 7, 5), | ||||
|        ('??? sérieusement, vous pensez que c''est une bonne idée ?', 7, 7), | ||||
|        ('ok donc ça c''est votre business plan, bah glhf la team', 7, 2); | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| @@ -1,2 +0,0 @@ | ||||
| DROP TABLE IF EXISTS administrateurs, projets, utilisateurs, entrepreneurs, sections, rendez_vous, comptes_rendus, concerner CASCADE; | ||||
| DROP TABLE IF EXISTS administrator, project, user_inpulse, entrepreneur, section_cell, appointment, make_appointment, report, annotation, concern CASCADE; | ||||
							
								
								
									
										92
									
								
								MyINPulse-back/src/main/resources/import.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										92
									
								
								MyINPulse-back/src/main/resources/import.sql
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,92 @@ | ||||
| -- Initial Database State Script | ||||
|  | ||||
| -- Insert Administrators | ||||
| INSERT INTO administrator (idAdministrator) VALUES | ||||
| (1), | ||||
| (2), | ||||
| (3); -- Added more administrators | ||||
|  | ||||
| -- Insert User Inpulse (some pending) | ||||
| INSERT INTO user_inpulse (idUser, userSurname, userName, primaryMail, secondaryMail, phoneNumber, pending) VALUES | ||||
| (1, 'Doe', 'John', 'john.doe@example.com', NULL, '123-456-7890', FALSE), | ||||
| (2, 'Smith', 'Jane', 'jane.smith@example.com', 'jane.s@altmail.com', '987-654-3210', FALSE), | ||||
| (3, 'Williams', 'Peter', 'peter.w@example.com', NULL, NULL, TRUE), -- Pending user | ||||
| (4, 'Jones', 'Mary', 'mary.j@example.com', NULL, '555-123-4567', FALSE), | ||||
| (5, 'Brown', 'Michael', 'michael.b@example.com', 'mike.brown@work.com', '111-222-3333', FALSE), | ||||
| (6, 'Garcia', 'Maria', 'maria.g@example.com', NULL, '444-555-6666', TRUE), -- Another pending user | ||||
| (7, 'Miller', 'David', 'david.m@example.com', NULL, '777-888-9999', FALSE); | ||||
|  | ||||
| -- Insert Entrepreneurs | ||||
| INSERT INTO entrepreneur (idEntrepreneur, school, course, sneeStatus, idProjectParticipation, idProjectProposed, idMakeAppointment) VALUES | ||||
| (1, 'Business School A', 'MBA', TRUE, NULL, NULL, NULL), | ||||
| (2, 'Tech University B', 'Computer Science', FALSE, NULL, NULL, NULL), | ||||
| (3, 'Art Institute C', 'Graphic Design', TRUE, NULL, NULL, NULL), | ||||
| (4, 'Science College D', 'Biology', FALSE, NULL, NULL, NULL), | ||||
| (5, 'Engineering School E', 'Mechanical Engineering', TRUE, NULL, NULL, NULL); -- Added more entrepreneurs | ||||
|  | ||||
| -- Insert Projects | ||||
| -- Main project | ||||
| INSERT INTO project (IdProject, projectName, loga, creationDate, projectStatus, pending, idAdministrator, entrepreneurProposed) VALUES | ||||
| (101, 'Innovative Startup Idea', NULL, '2023-10-26', 'In Progress', FALSE, 1, NULL), | ||||
| (102, 'Pending Project Alpha', NULL, '2024-01-15', 'Planning', TRUE, 1, NULL), -- Pending project | ||||
| (103, 'Pending Project Beta', NULL, '2024-02-20', 'Idea Stage', TRUE, NULL, 1), -- Another pending project, proposed by entrepreneur 1 | ||||
| (104, 'E-commerce Platform Development', NULL, '2024-03-10', 'Completed', FALSE, 2, NULL), -- Completed project | ||||
| (105, 'Mobile App for Education', NULL, '2024-04-01', 'In Progress', FALSE, 3, NULL), | ||||
| (106, 'Pending Research Proposal', NULL, '2024-04-25', 'Drafting', TRUE, NULL, 4); -- Pending project proposed by entrepreneur 4 | ||||
|  | ||||
| -- Link Entrepreneurs to projects (Project Participation and Proposed) | ||||
| -- Based on the current schema, we'll update the entrepreneur table directly. | ||||
| -- This might need adjustment based on actual application logic if it's a many-to-many. | ||||
| UPDATE entrepreneur SET idProjectParticipation = 101 WHERE idEntrepreneur IN (1, 2); -- Entrepreneurs 1 and 2 participate in Project 101 | ||||
| UPDATE entrepreneur SET idProjectParticipation = 104 WHERE idEntrepreneur = 3; -- Entrepreneur 3 participates in Project 104 | ||||
| UPDATE entrepreneur SET idProjectProposed = 103 WHERE idEntrepreneur = 1; -- Entrepreneur 1 proposed Project 103 | ||||
| UPDATE entrepreneur SET idProjectProposed = 106 WHERE idEntrepreneur = 4; -- Entrepreneur 4 proposed Project 106 | ||||
|  | ||||
| -- Insert Section Cells for the main project (Project 101) and other projects | ||||
| INSERT INTO section_cell (idSectionCell, IdReference, sectionid, contentSectionCell, modificationDate, idProject) VALUES | ||||
| (1001, NULL, 1, 'Initial project description for Project 101.', '2023-10-26 10:00:00', 101), | ||||
| (1002, 1001, 2, 'Market analysis summary for Project 101.', '2023-10-27 14:30:00', 101), | ||||
| (1003, NULL, 3, 'Team member profiles for Project 101.', '2023-10-28 09:00:00', 101), | ||||
| (1004, NULL, 1, 'Project brief for Project 104.', '2024-03-10 11:00:00', 104), | ||||
| (1005, 1004, 2, 'Technical specifications for Project 104.', '2024-03-15 16:00:00', 104), | ||||
| (1006, NULL, 1, 'Initial concept for Project 105.', '2024-04-01 09:30:00', 105); | ||||
|  | ||||
| -- Insert Appointments | ||||
| INSERT INTO appointment (idAppointment, appointmentDate, appointmentTime, appointmentDuration, appointmentPlace, appointmentSubject) VALUES | ||||
| (2001, '2023-11-05', '11:00:00', '01:00:00', 'Meeting Room A', 'Project 101 Kick-off Meeting'), | ||||
| (2002, '2023-11-10', '14:00:00', '00:30:00', 'Online', 'Project 101 Follow-up Discussion'), | ||||
| (2003, '2024-03-20', '10:00:00', '01:30:00', 'Client Office', 'Project 104 Final Review'), | ||||
| (2004, '2024-04-10', '15:00:00', '00:45:00', 'Video Call', 'Project 105 Initial Sync'); -- Added more appointments | ||||
|  | ||||
| -- Insert Concerns (linking Appointments and Section Cells) | ||||
| INSERT INTO concern (IdAppointment, idSectionCell) VALUES | ||||
| (2001, 1001), -- Kick-off meeting concerns section 1001 (Project 101) | ||||
| (2001, 1002), -- Kick-off meeting concerns section 1002 (Project 101) | ||||
| (2002, 1002), -- Follow-up concerns section 1002 (Project 101) | ||||
| (2003, 1004), -- Project 104 review concerns section 1004 | ||||
| (2003, 1005), -- Project 104 review concerns section 1005 | ||||
| (2004, 1006); -- Project 105 sync concerns section 1006 | ||||
|  | ||||
| -- Insert Make Appointments (linking Appointments, Administrators, and Entrepreneurs) | ||||
| INSERT INTO make_appointment (idMakeAppointment, idAppointment, idAdministrator, idEntrepreneur) VALUES | ||||
| (3001, 2001, 1, 1), -- Admin 1 scheduled appointment 2001 with Entrepreneur 1 | ||||
| (3002, 2001, 1, 2), -- Admin 1 scheduled appointment 2001 with Entrepreneur 2 | ||||
| (3003, 2002, 1, 1), -- Admin 1 scheduled appointment 2002 with Entrepreneur 1 | ||||
| (3004, 2003, 2, 3), -- Admin 2 scheduled appointment 2003 with Entrepreneur 3 | ||||
| (3005, 2004, 3, 5); -- Admin 3 scheduled appointment 2004 with Entrepreneur 5 | ||||
|  | ||||
| -- Insert Annotations (linking to Section Cells and Administrators) | ||||
| INSERT INTO annotation (IdAnnotation, comment, idSectionCell, idAdministrator) VALUES | ||||
| (4001, 'Needs more detail on market size.', 1002, 1), | ||||
| (4002, 'Looks good.', 1001, 1), | ||||
| (4003, 'Confirm technical requirements.', 1005, 2), | ||||
| (4004, 'Initial thoughts on UI/UX.', 1006, 3); -- Added more annotations | ||||
|  | ||||
| -- Insert Reports (linking to Make Appointments) | ||||
| INSERT INTO report (IdReport, reportContent, idMakeAppointment) VALUES | ||||
| (5001, 'Discussed project scope and timelines for Project 101.', 3001), | ||||
| (5002, 'Reviewed market analysis feedback for Project 101.', 3003), | ||||
| (5003, 'Final sign-off on Project 104 deliverables.', 3004), | ||||
| (5004, 'Discussed initial concepts for Project 105.', 3005); -- Added more reports | ||||
|  | ||||
| -- The project ID for the main project is 101. | ||||
| @@ -4,14 +4,10 @@ import static enseirb.myinpulse.model.ProjectDecisionValue.*; | ||||
|  | ||||
| import static org.junit.jupiter.api.Assertions.*; | ||||
|  | ||||
| import enseirb.myinpulse.model.Administrator; | ||||
| import enseirb.myinpulse.model.Entrepreneur; | ||||
| import enseirb.myinpulse.model.Project; | ||||
| import enseirb.myinpulse.model.ProjectDecision; | ||||
| import enseirb.myinpulse.model.*; | ||||
| import enseirb.myinpulse.service.AdminApiService; | ||||
| import enseirb.myinpulse.service.database.AdministratorService; | ||||
| import enseirb.myinpulse.service.database.EntrepreneurService; | ||||
| import enseirb.myinpulse.service.database.ProjectService; | ||||
| import enseirb.myinpulse.service.UtilsService; | ||||
| import enseirb.myinpulse.service.database.*; | ||||
|  | ||||
| import org.junit.jupiter.api.BeforeAll; | ||||
| import org.junit.jupiter.api.Test; | ||||
| @@ -21,6 +17,8 @@ import org.springframework.transaction.annotation.Transactional; | ||||
| import org.springframework.web.server.ResponseStatusException; | ||||
|  | ||||
| import java.time.LocalDate; | ||||
| import java.time.LocalDateTime; | ||||
| import java.time.LocalTime; | ||||
| import java.util.ArrayList; | ||||
| import java.util.List; | ||||
|  | ||||
| @@ -30,14 +28,22 @@ public class AdminApiServiceTest { | ||||
|     private static long administratorid; | ||||
|     private static Administrator administrator; | ||||
|     private static Entrepreneur entrepreneur; | ||||
|     private static Appointment appt; | ||||
|     private static Project p; | ||||
|     @Autowired private AdminApiService adminApiService; | ||||
|     @Autowired private ProjectService projectService; | ||||
|     @Autowired private EntrepreneurService entrepreneurService; | ||||
|     @Autowired private SectionCellService sectionCellService; | ||||
|     @Autowired private AppointmentService appointmentService; | ||||
|     @Autowired private UtilsService utilsService; | ||||
|  | ||||
|     @BeforeAll | ||||
|     static void setup( | ||||
|             @Autowired AdministratorService administratorService, | ||||
|             @Autowired ProjectService projectService, | ||||
|             @Autowired EntrepreneurService entrepreneurService) { | ||||
|             @Autowired EntrepreneurService entrepreneurService, | ||||
|             @Autowired AppointmentService appoitmentService, | ||||
|             @Autowired SectionCellService sectionCellService) { | ||||
|         administratorService.addAdministrator( | ||||
|                 new Administrator( | ||||
|                         "admin", | ||||
| @@ -54,6 +60,7 @@ public class AdminApiServiceTest { | ||||
|                                 "testAdmin@example.com", | ||||
|                                 "")); | ||||
|         administratorid = administrator.getIdUser(); | ||||
|  | ||||
|         entrepreneur = | ||||
|                 new Entrepreneur( | ||||
|                         "JeSuisUnEntrepreneurDeCompet", | ||||
| @@ -65,14 +72,32 @@ public class AdminApiServiceTest { | ||||
|                         "info ofc", | ||||
|                         false); | ||||
|         entrepreneurService.addEntrepreneur(entrepreneur); | ||||
|         projectService.addNewProject( | ||||
|                 new Project( | ||||
|                         "sampleProjectAdminApiService", | ||||
|  | ||||
|         Entrepreneur entrepreneur2 = | ||||
|                 new Entrepreneur( | ||||
|                         "GDProjets", "", "Entrepreneur2@inpulse.com", "", "", "", "info ofc", true); | ||||
|         entrepreneurService.addEntrepreneur(entrepreneur2); | ||||
|  | ||||
|         p = | ||||
|                 projectService.addNewProject( | ||||
|                         new Project( | ||||
|                                 "sampleProjectAdminApiService", | ||||
|                                 null, | ||||
|                                 LocalDate.now(), | ||||
|                                 ACTIVE, | ||||
|                                 administratorService.getAdministratorByPrimaryMain( | ||||
|                                         "testAdminFull@example.com"))); | ||||
|  | ||||
|         entrepreneurService.updateEntrepreneurProjectParticipation(entrepreneur2.getIdUser(), p); | ||||
|  | ||||
|         appt = | ||||
|                 new Appointment( | ||||
|                         null, | ||||
|                         LocalDate.now(), | ||||
|                         ACTIVE, | ||||
|                         administratorService.getAdministratorByPrimaryMain( | ||||
|                                 "testAdminFull@example.com"))); | ||||
|                         LocalTime.now(), | ||||
|                         LocalTime.now(), | ||||
|                         "Salle TD 03", | ||||
|                         "Discussion importante"); | ||||
|     } | ||||
|  | ||||
|     private <T> List<T> IterableToList(Iterable<T> iterable) { | ||||
| @@ -106,7 +131,7 @@ public class AdminApiServiceTest { | ||||
|         List<Project> l = IterableToList(projects); | ||||
|         assertEquals(1, l.size()); | ||||
|         Project p = l.getFirst(); | ||||
|         assertEquals(p.getProjectName(), "sampleProjectAdminApiService"); | ||||
|         assertEquals("sampleProjectAdminApiService", p.getProjectName()); | ||||
|     } | ||||
|  | ||||
|     @Test | ||||
| @@ -180,7 +205,7 @@ public class AdminApiServiceTest { | ||||
|     @Test | ||||
|     void addProjectToAdmin() { | ||||
|         assertEquals(0, administrator.getListProject().size()); | ||||
|         Project p1 = new Project("assProjectToAdmin", null, LocalDate.now(), ACTIVE, administrator); | ||||
|         Project p1 = new Project("addProjectToAdmin", null, LocalDate.now(), ACTIVE, administrator); | ||||
|         this.adminApiService.addNewProject(p1); | ||||
|         assertEquals(1, administrator.getListProject().size()); | ||||
|     } | ||||
| @@ -189,7 +214,7 @@ public class AdminApiServiceTest { | ||||
|     void addProjectToUser() { | ||||
|         assertNull(entrepreneur.getProjectParticipation()); | ||||
|         Project p1 = | ||||
|                 new Project("assProjectToAdmin", null, LocalDate.now(), ACTIVE, null, entrepreneur); | ||||
|                 new Project("addProjectToAdmin", null, LocalDate.now(), ACTIVE, null, entrepreneur); | ||||
|         this.adminApiService.addNewProject(p1); | ||||
|         assertEquals(p1, entrepreneur.getProjectParticipation()); | ||||
|     } | ||||
| @@ -202,7 +227,7 @@ public class AdminApiServiceTest { | ||||
|         assertNull(e1.getProjectParticipation()); | ||||
|         assertNull(e2.getProjectParticipation()); | ||||
|         assertNull(e3.getProjectParticipation()); | ||||
|         Project p1 = new Project("assProjectToAdmin", null, LocalDate.now(), ACTIVE, null, null); | ||||
|         Project p1 = new Project("addProjectToAdmin", null, LocalDate.now(), ACTIVE, null, null); | ||||
|         p1.updateListEntrepreneurParticipation(e1); | ||||
|         p1.updateListEntrepreneurParticipation(e2); | ||||
|         p1.updateListEntrepreneurParticipation(e3); | ||||
| @@ -221,4 +246,86 @@ public class AdminApiServiceTest { | ||||
|         this.adminApiService.addNewProject(p1); | ||||
|         assertThrows(ResponseStatusException.class, () -> this.adminApiService.addNewProject(p2)); | ||||
|     } | ||||
|  | ||||
|     // We could do a delete active project, but it's not really useful. | ||||
|     @Test | ||||
|     void deletePendingProject() { | ||||
|         int oldsize = IterableToList(this.adminApiService.getPendingProjects()).size(); | ||||
|         Project p1 = | ||||
|                 new Project("PendingProjectAdminApiService2", null, LocalDate.now(), PENDING, null); | ||||
|         Project p2 = this.adminApiService.addNewProject(p1); | ||||
|  | ||||
|         assertEquals(oldsize + 1, IterableToList(this.adminApiService.getPendingProjects()).size()); | ||||
|         this.adminApiService.deleteProject(p2.getIdProject()); | ||||
|  | ||||
|         assertEquals(oldsize, IterableToList(this.adminApiService.getPendingProjects()).size()); | ||||
|         for (int i = 0; i < oldsize; i++) { | ||||
|             assertNotEquals( | ||||
|                     p1.getIdProject(), | ||||
|                     IterableToList(this.adminApiService.getPendingProjects()) | ||||
|                             .get(i) | ||||
|                             .getIdProject()); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     @Test | ||||
|     void getUpcommingAppointmentUnkwnownUser() { | ||||
|         assertThrows( | ||||
|                 ResponseStatusException.class, | ||||
|                 () -> { | ||||
|                     Iterable<Appointment> a = | ||||
|                             this.adminApiService.getUpcomingAppointments( | ||||
|                                     "entrepreneur-inexistent@mail.fr"); | ||||
|                 }); | ||||
|     } | ||||
|  | ||||
|     @Test | ||||
|     void getUpcommingAppointmentNoProject() { | ||||
|         assertThrows( | ||||
|                 ResponseStatusException.class, | ||||
|                 () -> { | ||||
|                     Iterable<Appointment> a = | ||||
|                             this.adminApiService.getUpcomingAppointments( | ||||
|                                     "Entrepreneur@inpulse.com"); | ||||
|                 }); | ||||
|     } | ||||
|  | ||||
|     @Test | ||||
|     void getUpcommingAppointmentEmpty() { | ||||
|         Iterable<Appointment> a = | ||||
|                 this.adminApiService.getUpcomingAppointments("Entrepreneur2@inpulse.com"); | ||||
|         assertEquals(0, IterableToList(a).size()); | ||||
|     } | ||||
|  | ||||
|     @Test | ||||
|     void validateEntrepreneurAccount() { | ||||
|         assertTrue(entrepreneurService.getEntrepreneurById(entrepreneur.getIdUser()).isPending()); | ||||
|         assertEquals(2, IterableToList(adminApiService.getPendingUsers()).size()); | ||||
|         adminApiService.validateEntrepreneurAccount(entrepreneur.getIdUser(), ""); | ||||
|         assertFalse(entrepreneurService.getEntrepreneurById(entrepreneur.getIdUser()).isPending()); | ||||
|         assertEquals(1, IterableToList(adminApiService.getPendingUsers()).size()); | ||||
|     } | ||||
|  | ||||
|     @Test | ||||
|     void testCreateApptRepport() { | ||||
|         System.err.println(appt.getIdAppointment()); | ||||
|         SectionCell s = | ||||
|                 sectionCellService.addNewSectionCell( | ||||
|                         new SectionCell(null, 1L, "jaja", LocalDateTime.now(), p)); | ||||
|         appointmentService.addNewAppointment(appt); | ||||
|  | ||||
|         appointmentService.updateAppointmentListSectionCell(appt.getIdAppointment(), s); | ||||
|         projectService.updateProjectListSectionCell(p.getIdProject(), s); | ||||
|         this.adminApiService.createAppointmentReport( | ||||
|                 appt.getIdAppointment(), | ||||
|                 new Report(null, "je rapporte de fou"), | ||||
|                 "testAdminFull@example.com"); | ||||
|     } | ||||
|  | ||||
|     @Test | ||||
|     void testSetAdmin() { | ||||
|         assertFalse(utilsService.isAnAdmin(entrepreneur.getPrimaryMail())); | ||||
|         adminApiService.setAdmin(entrepreneur.getIdUser(), ""); | ||||
|         assertTrue(utilsService.isAnAdmin(entrepreneur.getPrimaryMail())); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -0,0 +1,324 @@ | ||||
| package enseirb.myinpulse; | ||||
|  | ||||
| import static enseirb.myinpulse.model.ProjectDecisionValue.*; | ||||
|  | ||||
| import static org.junit.jupiter.api.Assertions.*; | ||||
|  | ||||
| import enseirb.myinpulse.model.*; | ||||
| import enseirb.myinpulse.service.EntrepreneurApiService; | ||||
| import enseirb.myinpulse.service.database.*; | ||||
|  | ||||
| import org.junit.jupiter.api.BeforeAll; | ||||
| import org.junit.jupiter.api.Test; | ||||
| import org.springframework.beans.factory.annotation.Autowired; | ||||
| import org.springframework.boot.test.context.SpringBootTest; | ||||
| import org.springframework.transaction.annotation.Transactional; | ||||
| import org.springframework.web.server.ResponseStatusException; | ||||
|  | ||||
| import java.time.LocalDate; | ||||
| import java.time.LocalDateTime; | ||||
| import java.time.LocalTime; | ||||
| import java.util.ArrayList; | ||||
| import java.util.List; | ||||
|  | ||||
| @SpringBootTest | ||||
| @Transactional | ||||
| public class EntrepreneurApiServiceTest { | ||||
|     private static Entrepreneur entrepreneur; | ||||
|     private static Project project; | ||||
|     private static Iterable<SectionCell> sectionCells2; | ||||
|     private static Iterable<SectionCell> sectionCells3; | ||||
|     @Autowired private EntrepreneurApiService entrepreneurApiService; | ||||
|     @Autowired private EntrepreneurService entrepreneurService; | ||||
|     @Autowired private ProjectService projectService; | ||||
|     @Autowired private SectionCellService sectionCellService; | ||||
|     @Autowired private AnnotationService annotationService; | ||||
|     @Autowired private AppointmentService appointmentService; | ||||
|  | ||||
|     @BeforeAll | ||||
|     static void setup( | ||||
|             @Autowired EntrepreneurService entrepreneurService, | ||||
|             @Autowired ProjectService projectService, | ||||
|             @Autowired SectionCellService sectionCellService) { | ||||
|         entrepreneur = | ||||
|                 entrepreneurService.addEntrepreneur( | ||||
|                         new Entrepreneur( | ||||
|                                 "entre", | ||||
|                                 "preneur", | ||||
|                                 "entrepreneur@mail.fr", | ||||
|                                 "entrepreneur2@mail.fr", | ||||
|                                 "01 45 71 25 48", | ||||
|                                 "ENSEIRB", | ||||
|                                 "Info", | ||||
|                                 false)); | ||||
|         entrepreneurService.addEntrepreneur( | ||||
|                 new Entrepreneur( | ||||
|                         "entre2", | ||||
|                         "preneur2", | ||||
|                         "testentrepreneur@mail.fr", | ||||
|                         "testentrepreneur2@mail.fr", | ||||
|                         "", | ||||
|                         "ENSEGID", | ||||
|                         "", | ||||
|                         true)); | ||||
|         project = | ||||
|                 projectService.addNewProject( | ||||
|                         new Project("Project", null, LocalDate.now(), ACTIVE, null, entrepreneur)); | ||||
|         entrepreneurService.updateEntrepreneurProjectProposed(entrepreneur.getIdUser(), project); | ||||
|         entrepreneurService.updateEntrepreneurProjectParticipation( | ||||
|                 entrepreneur.getIdUser(), project); | ||||
|         SectionCell s1 = | ||||
|                 sectionCellService.addNewSectionCell( | ||||
|                         new SectionCell( | ||||
|                                 null, | ||||
|                                 2L, | ||||
|                                 "contenu très intéressant", | ||||
|                                 LocalDateTime.now(), | ||||
|                                 project)); | ||||
|  | ||||
|         SectionCell s2 = | ||||
|                 sectionCellService.addNewSectionCell( | ||||
|                         new SectionCell( | ||||
|                                 null, | ||||
|                                 3L, | ||||
|                                 "contenu très intéressant2", | ||||
|                                 LocalDateTime.now(), | ||||
|                                 project)); | ||||
|         sectionCells2 = sectionCellService.getSectionCellsByProject(project, 2L); | ||||
|         sectionCells3 = sectionCellService.getSectionCellsByProject(project, 3L); | ||||
|     } | ||||
|  | ||||
|     private <T> List<T> IterableToList(Iterable<T> iterable) { | ||||
|         List<T> l = new ArrayList<>(); | ||||
|         iterable.forEach(l::add); | ||||
|         return l; | ||||
|     } | ||||
|  | ||||
|     @Test | ||||
|     void editValidSectionCell() { | ||||
|         System.out.println("editValidSectionCell : "); | ||||
|         SectionCell modified = IterableToList(sectionCells2).getLast(); | ||||
|         this.sectionCellService.updateSectionCellListAnnotation( | ||||
|                 modified.getIdSectionCell(), | ||||
|                 annotationService.addNewAnnotation(new Annotation(null, "oui j'annote encore"))); | ||||
|         this.sectionCellService.updateSectionCellListAppointment( | ||||
|                 modified.getIdSectionCell(), | ||||
|                 appointmentService.addNewAppointment( | ||||
|                         new Appointment( | ||||
|                                 null, | ||||
|                                 LocalDate.now(), | ||||
|                                 LocalTime.now(), | ||||
|                                 LocalTime.of(2, 5), | ||||
|                                 "TD14", | ||||
|                                 "clément s'est plaint"))); | ||||
|         entrepreneurApiService.editSectionCell( | ||||
|                 modified.getIdSectionCell(), "modified content", "entrepreneur@mail.fr"); | ||||
|         // We get the data from the database again. | ||||
|         SectionCell s = | ||||
|                 IterableToList(sectionCellService.getSectionCellsByProject(project, 2L)).getLast(); | ||||
|         assertEquals("modified content", s.getContentSectionCell()); | ||||
|         assertEquals( | ||||
|                 2, IterableToList(sectionCellService.getSectionCellsByProject(project, 2L)).size()); | ||||
|     } | ||||
|  | ||||
|     @Test | ||||
|     void editInvalidSectionCell() { | ||||
|         System.out.println("editInvalidSectionCell : "); | ||||
|         assertThrows( | ||||
|                 ResponseStatusException.class, | ||||
|                 () -> | ||||
|                         entrepreneurApiService.editSectionCell( | ||||
|                                 -1L, "should not be modified", "entrepreneur@mail.fr")); | ||||
|         SectionCell s = | ||||
|                 IterableToList(sectionCellService.getSectionCellsByProject(project, 2L)).getLast(); | ||||
|         assertEquals("contenu très intéressant", s.getContentSectionCell()); | ||||
|         assertEquals( | ||||
|                 1, IterableToList(sectionCellService.getSectionCellsByProject(project, 2L)).size()); | ||||
|     } | ||||
|  | ||||
|     @Test | ||||
|     void editSectionCellInvalidAccess() { | ||||
|         System.out.println("editSectionCellInvalidAccess : "); | ||||
|         assertThrows( | ||||
|                 ResponseStatusException.class, | ||||
|                 () -> | ||||
|                         entrepreneurApiService.editSectionCell( | ||||
|                                 IterableToList(sectionCells3).getFirst().getIdSectionCell(), | ||||
|                                 "should not be modified", | ||||
|                                 "testentrepreneur@mail.fr")); | ||||
|         SectionCell s = | ||||
|                 IterableToList(sectionCellService.getSectionCellsByProject(project, 2L)).getFirst(); | ||||
|  | ||||
|         assertEquals("contenu très intéressant", s.getContentSectionCell()); | ||||
|     } | ||||
|  | ||||
|     @Test | ||||
|     void editNullSectionCell() { | ||||
|         System.out.println("editNullSectionCell : "); | ||||
|         assertThrows( | ||||
|                 ResponseStatusException.class, | ||||
|                 () -> | ||||
|                         entrepreneurApiService.editSectionCell( | ||||
|                                 null, "modified content", "entrepreneur@mail.fr")); | ||||
|     } | ||||
|  | ||||
|     @Test | ||||
|     void removeValidSectionCell() { | ||||
|         System.out.println("removeValidSectionCell : "); | ||||
|         SectionCell tmpCell = | ||||
|                 sectionCellService.addNewSectionCell( | ||||
|                         new SectionCell( | ||||
|                                 null, 2L, "contenu temporaire", LocalDateTime.now(), project)); | ||||
|         assertEquals( | ||||
|                 2, IterableToList(sectionCellService.getSectionCellsByProject(project, 2L)).size()); | ||||
|         assertDoesNotThrow( | ||||
|                 () -> | ||||
|                         entrepreneurApiService.removeSectionCell( | ||||
|                                 tmpCell.getIdSectionCell(), "entrepreneur@mail.fr")); | ||||
|         assertEquals( | ||||
|                 tmpCell.getIdReference(), | ||||
|                 IterableToList(sectionCellService.getSectionCellsByProject(project, -1L)) | ||||
|                         .getLast() | ||||
|                         .getIdReference()); | ||||
|     } | ||||
|  | ||||
|     @Test | ||||
|     void removeInvalidSectionCell() { | ||||
|         System.out.println("removeInvalidSectionCell : "); | ||||
|         assertThrows( | ||||
|                 ResponseStatusException.class, | ||||
|                 () -> entrepreneurApiService.removeSectionCell(-1L, "entrepreneur@mail.fr")); | ||||
|         SectionCell s = | ||||
|                 IterableToList(sectionCellService.getSectionCellsByProject(project, 2L)).getFirst(); | ||||
|  | ||||
|         assertEquals("contenu très intéressant", s.getContentSectionCell()); | ||||
|     } | ||||
|  | ||||
|     @Test | ||||
|     void removeNullSectionCell() { | ||||
|         System.out.println("removeNullSectionCell : "); | ||||
|         assertThrows( | ||||
|                 ResponseStatusException.class, | ||||
|                 () -> entrepreneurApiService.removeSectionCell(null, "entrepreneur@mail.fr")); | ||||
|     } | ||||
|  | ||||
|     @Test | ||||
|     void addValidSectionCell() { | ||||
|         System.out.println("addValidSectionCell : "); | ||||
|         SectionCell added = | ||||
|                 sectionCellService.addNewSectionCell( | ||||
|                         new SectionCell(null, 2L, "contenu ajouté", LocalDateTime.now(), project)); | ||||
|         added.updateListAnnotation( | ||||
|                 annotationService.addNewAnnotation(new Annotation(null, "oui j'annote"))); | ||||
|         added.updateAppointmentSectionCell( | ||||
|                 appointmentService.addNewAppointment( | ||||
|                         new Appointment( | ||||
|                                 null, | ||||
|                                 LocalDate.now(), | ||||
|                                 LocalTime.now(), | ||||
|                                 LocalTime.of(2, 5), | ||||
|                                 "TD15", | ||||
|                                 "clément qui se plaint"))); | ||||
|         entrepreneurApiService.addSectionCell(added, "entrepreneur@mail.fr"); | ||||
|         SectionCell s = | ||||
|                 IterableToList(sectionCellService.getSectionCellsByProject(project, 2L)).getLast(); | ||||
|         assertEquals("contenu ajouté", s.getContentSectionCell()); | ||||
|         assertEquals( | ||||
|                 2, IterableToList(sectionCellService.getSectionCellsByProject(project, 2L)).size()); | ||||
|     } | ||||
|  | ||||
|     @Test | ||||
|     void addSectionCellInvalidAccess() { | ||||
|         System.out.println("addSectionCellInvalidAccess : "); | ||||
|         SectionCell added = | ||||
|                 new SectionCell(null, 2L, "contenu ajouté", LocalDateTime.now(), project); | ||||
|         assertThrows( | ||||
|                 ResponseStatusException.class, | ||||
|                 () -> entrepreneurApiService.addSectionCell(added, "fauxentrepreneur@mail.fr")); | ||||
|         SectionCell s = | ||||
|                 IterableToList(sectionCellService.getSectionCellsByProject(project, 2L)).getLast(); | ||||
|         assertEquals( | ||||
|                 1, IterableToList(sectionCellService.getSectionCellsByProject(project, 2L)).size()); | ||||
|         assertEquals("contenu très intéressant", s.getContentSectionCell()); | ||||
|     } | ||||
|  | ||||
|     @Test | ||||
|     void addInvalidSectionCell() { | ||||
|         System.out.println("addInvalidSectionCell : "); | ||||
|         SectionCell added = | ||||
|                 sectionCellService.addNewSectionCell( | ||||
|                         new SectionCell(null, -1L, "contenu ajouté", LocalDateTime.now(), project)); | ||||
|         assertThrows( | ||||
|                 ResponseStatusException.class, | ||||
|                 () -> entrepreneurApiService.addSectionCell(added, "entrepreneur@mail.fr")); | ||||
|     } | ||||
|  | ||||
|     @Test | ||||
|     void addNullSectionCell() { | ||||
|         System.out.println("addNullSectionCell : "); | ||||
|         assertThrows( | ||||
|                 ResponseStatusException.class, | ||||
|                 () -> entrepreneurApiService.addSectionCell(null, "entrepreneur@mail.fr")); | ||||
|     } | ||||
|  | ||||
|     @Test | ||||
|     void requestValidProject() { | ||||
|         System.out.println("requestValidProject : "); | ||||
|         int nb_project = IterableToList(this.projectService.getAllProjects()).size(); | ||||
|         Project validProject = | ||||
|                 new Project("validProject", null, LocalDate.now(), ACTIVE, null, entrepreneur); | ||||
|         validProject.updateListEntrepreneurParticipation( | ||||
|                 IterableToList(entrepreneurService.getAllEntrepreneurs()).getLast()); | ||||
|         validProject.updateListSectionCell((IterableToList(sectionCells2).getFirst())); | ||||
|         entrepreneurApiService.requestNewProject(validProject, "entrepreneur@mail.fr"); | ||||
|         assertEquals(PENDING, validProject.getProjectStatus()); | ||||
|         assertEquals((nb_project + 1), IterableToList(this.projectService.getAllProjects()).size()); | ||||
|         assertEquals( | ||||
|                 IterableToList(entrepreneurService.getAllEntrepreneurs()).getLast(), | ||||
|                 validProject.getListEntrepreneurParticipation().getLast()); | ||||
|         assertEquals( | ||||
|                 IterableToList(sectionCells2).getFirst().getIdSectionCell(), | ||||
|                 validProject.getListSectionCell().getFirst().getIdSectionCell()); | ||||
|     } | ||||
|  | ||||
|     @Test | ||||
|     void requestNullProject() { | ||||
|         System.out.println("requestNullProject : "); | ||||
|         assertThrows( | ||||
|                 ResponseStatusException.class, | ||||
|                 () -> entrepreneurApiService.requestNewProject(null, "entrepreneur@mail.fr")); | ||||
|     } | ||||
|  | ||||
|     @Test | ||||
|     void createNewValidAccount() { | ||||
|         System.out.println("createNewValidAccount : "); | ||||
|         int nb_entrepreneur = IterableToList(this.entrepreneurService.getAllEntrepreneurs()).size(); | ||||
|         Entrepreneur newEntrepreneur = | ||||
|                 new Entrepreneur( | ||||
|                         "New", | ||||
|                         "Test", | ||||
|                         "mailtest@test.fr", | ||||
|                         "mailtest2@test.fr", | ||||
|                         "0888888888", | ||||
|                         "ENSEIRB", | ||||
|                         "ELEC", | ||||
|                         false, | ||||
|                         true); | ||||
|         assertDoesNotThrow(() -> entrepreneurApiService.createAccount(newEntrepreneur)); | ||||
|         assertEquals( | ||||
|                 (nb_entrepreneur + 1), | ||||
|                 IterableToList(this.entrepreneurService.getAllEntrepreneurs()).size()); | ||||
|     } | ||||
|  | ||||
|     @Test | ||||
|     void createExistingAccount() { | ||||
|         System.out.println("createExistingAccount : "); | ||||
|         int nb_entrepreneur = IterableToList(this.entrepreneurService.getAllEntrepreneurs()).size(); | ||||
|         assertThrows( | ||||
|                 ResponseStatusException.class, | ||||
|                 () -> entrepreneurApiService.createAccount(entrepreneur)); | ||||
|         assertEquals( | ||||
|                 nb_entrepreneur, | ||||
|                 IterableToList(this.entrepreneurService.getAllEntrepreneurs()).size()); | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,922 @@ | ||||
| package enseirb.myinpulse; | ||||
|  | ||||
| import static enseirb.myinpulse.model.ProjectDecisionValue.*; | ||||
| import static org.junit.jupiter.api.Assertions.*; | ||||
| import static org.mockito.ArgumentMatchers.*; | ||||
| import static org.mockito.Mockito.when; | ||||
|  | ||||
| import enseirb.myinpulse.model.*; | ||||
| import enseirb.myinpulse.service.SharedApiService; | ||||
| import enseirb.myinpulse.service.database.*; | ||||
| import jakarta.persistence.EntityManager; | ||||
| import jakarta.persistence.PersistenceContext; | ||||
| import enseirb.myinpulse.service.UtilsService; | ||||
|  | ||||
| import org.junit.jupiter.api.BeforeAll; // Use BeforeAll for static setup | ||||
| import org.junit.jupiter.api.BeforeEach; | ||||
| import org.junit.jupiter.api.Test; // Keep this import | ||||
| import org.mockito.MockitoAnnotations; | ||||
| import org.springframework.beans.factory.annotation.Autowired; | ||||
| import org.springframework.boot.test.context.SpringBootTest; | ||||
| import org.springframework.transaction.annotation.Transactional; | ||||
| import org.springframework.web.server.ResponseStatusException; | ||||
| import org.springframework.http.HttpStatus; | ||||
| import org.springframework.test.context.bean.override.mockito.MockitoBean; | ||||
|  | ||||
| import java.time.LocalDate; | ||||
| import java.time.LocalDateTime; | ||||
| import java.time.LocalTime; | ||||
| import java.time.format.DateTimeFormatter; | ||||
| import java.util.ArrayList; | ||||
| import java.util.List; | ||||
| import java.util.Optional; | ||||
|  | ||||
| // Helper to easily convert Iterable to List | ||||
| class TestUtils { | ||||
|     static <T> List<T> toList(Iterable<T> iterable) { | ||||
|         List<T> list = new ArrayList<>(); | ||||
|         if (iterable != null) { | ||||
|             iterable.forEach(list::add); | ||||
|         } | ||||
|         return list; | ||||
|     } | ||||
| } | ||||
|  | ||||
| @SpringBootTest | ||||
| @Transactional // Each @Test method runs in a transaction that is rolled back | ||||
| public class SharedApiServiceTest { | ||||
|  | ||||
|     @Autowired private SharedApiService sharedApiService; | ||||
|  | ||||
|     // Autowire actual services to use in setup and test verification | ||||
|     @Autowired private ProjectService projectService; | ||||
|     @Autowired private AdministratorService administratorService; | ||||
|     @Autowired private EntrepreneurService entrepreneurService; | ||||
|     @Autowired private SectionCellService sectionCellService; | ||||
|     @Autowired private AppointmentService appointmentService; | ||||
|  | ||||
|     // Mock UtilsService to control authorization logic | ||||
|     @MockitoBean private UtilsService mockUtilsService; | ||||
|  | ||||
|     // Static variables for data created once before all tests | ||||
|     private static Project staticAuthorizedProject; | ||||
|     private static String staticAuthorizedMail; | ||||
|     private static Administrator staticAuthorizedAdmin; | ||||
|  | ||||
|     private static Project staticUnauthorizedProject; | ||||
|     private static String staticUnauthorizedMail; | ||||
|  | ||||
|     // --- Static Setup (Runs once before all tests) --- | ||||
|     // Use @BeforeAll static method with injected services | ||||
|     @BeforeAll | ||||
|     static void setupOnce( | ||||
|             @Autowired AdministratorService administratorService, | ||||
|             @Autowired ProjectService projectService, | ||||
|             @Autowired EntrepreneurService entrepreneurService) { | ||||
|  | ||||
|         // Create and Save core test data here using injected services | ||||
|         staticAuthorizedAdmin = | ||||
|                 administratorService.addAdministrator(getTestAdmin("static_authorized_admin")); | ||||
|         staticAuthorizedMail = staticAuthorizedAdmin.getPrimaryMail(); | ||||
|  | ||||
|         staticUnauthorizedProject = | ||||
|                 projectService.addNewProject( | ||||
|                         getTestProject( | ||||
|                                 "static_unauthorized_project", | ||||
|                                 administratorService.addAdministrator( | ||||
|                                         getTestAdmin("static_unauthorized_admin")))); | ||||
|         staticUnauthorizedMail = | ||||
|                 administratorService | ||||
|                         .addAdministrator(getTestAdmin("static_unauthorized_user")) | ||||
|                         .getPrimaryMail(); // User who is NOT admin of the unauthorized project | ||||
|  | ||||
|         staticAuthorizedProject = | ||||
|                 projectService.addNewProject( | ||||
|                         getTestProject("static_authorized_project", staticAuthorizedAdmin)); | ||||
|  | ||||
|         // Link a static entrepreneur to the authorized project if needed for some tests | ||||
|         // Entrepreneur staticLinkedEntrepreneur = | ||||
|         // entrepreneurService.addEntrepreneur(getTestEntrepreneur("static_linked_entrepreneur")); | ||||
|         // staticAuthorizedProject.updateListEntrepreneurParticipation(staticLinkedEntrepreneur); | ||||
|         // projectService.addNewProject(staticAuthorizedProject); // Re-save the project after | ||||
|         // updating lists | ||||
|     } | ||||
|  | ||||
|     // --- Per-Test Setup (Runs before each test method) --- | ||||
|     @BeforeEach | ||||
|     void setupForEach() { | ||||
|         // Reset mock expectations before each test | ||||
|         MockitoAnnotations.openMocks( | ||||
|                 this); // Needed for mocks if not using @ExtendWith(MockitoExtension.class) | ||||
|  | ||||
|         // --- Configure the mock UtilsService based on the actual authorization rules --- | ||||
|  | ||||
|         // Rule: Any admin can check any project. | ||||
|         // Assuming staticAuthorizedMail is an admin: | ||||
|         when(mockUtilsService.isAllowedToCheckProject(eq(staticAuthorizedMail), anyLong())) | ||||
|                 .thenReturn(true); // Admin allowed for ANY project ID | ||||
|  | ||||
|         // Rule: An entrepreneur can only check their own stuff. | ||||
|         // Assuming staticUnauthorizedMail is an entrepreneur NOT linked to staticAuthorizedProject | ||||
|         // or staticUnauthorizedProject: | ||||
|         when(mockUtilsService.isAllowedToCheckProject(eq(staticUnauthorizedMail), anyLong())) | ||||
|                 .thenReturn(false); // Unauthorized entrepreneur NOT allowed for ANY project ID by | ||||
|         // default | ||||
|     } | ||||
|  | ||||
|     // --- Helper Methods (Can remain non-static or static as needed) --- | ||||
|     private static Administrator getTestAdmin(String name) { | ||||
|         return new Administrator( | ||||
|                 name + "_surname", | ||||
|                 name, | ||||
|                 name + "@example.com", | ||||
|                 "secondary_" + name + "@example.com", | ||||
|                 "0123456789"); | ||||
|     } | ||||
|  | ||||
|     private static Entrepreneur getTestEntrepreneur(String name) { | ||||
|         return new Entrepreneur( | ||||
|                 name + "_surname", | ||||
|                 name, | ||||
|                 name + "@example.com", | ||||
|                 "secondary_" + name + "@example.com", | ||||
|                 "0123456789", | ||||
|                 "Test School", | ||||
|                 "Test Course", | ||||
|                 false); | ||||
|     } | ||||
|  | ||||
|     private static Project getTestProject(String name, Administrator admin) { | ||||
|         Project project = new Project(name, null, LocalDate.now(), ACTIVE, admin); | ||||
|         return project; | ||||
|     } | ||||
|  | ||||
|     private static SectionCell getTestSectionCell( | ||||
|             Project project, Long sectionId, String content, LocalDateTime date) { | ||||
|         SectionCell sectionCell = new SectionCell(); | ||||
|         sectionCell.setProjectSectionCell(project); | ||||
|         sectionCell.setSectionId(sectionId); | ||||
|         sectionCell.setContentSectionCell(content); | ||||
|         sectionCell.setModificationDate(date); | ||||
|         return sectionCell; | ||||
|     } | ||||
|  | ||||
|     private static SectionCell getTestSectionCell( | ||||
|             Project project, Long sectionId, String content, LocalDateTime date, Long refrenceId) { | ||||
|         SectionCell sectionCell = new SectionCell(); | ||||
|         sectionCell.setProjectSectionCell(project); | ||||
|         sectionCell.setSectionId(sectionId); | ||||
|         sectionCell.setContentSectionCell(content); | ||||
|         sectionCell.setModificationDate(date); | ||||
|         sectionCell.setIdReference(refrenceId); | ||||
|         return sectionCell; | ||||
|     } | ||||
|  | ||||
|     private static Appointment getTestAppointment( | ||||
|             LocalDate date, | ||||
|             LocalTime time, | ||||
|             LocalTime duration, | ||||
|             String place, | ||||
|             String subject, | ||||
|             List<SectionCell> sectionCells, | ||||
|             Report report) { | ||||
|         Appointment appointment = new Appointment(); | ||||
|         appointment.setAppointmentDate(date); | ||||
|         appointment.setAppointmentTime(time); | ||||
|         appointment.setAppointmentDuration(duration); | ||||
|         appointment.setAppointmentPlace(place); | ||||
|         appointment.setAppointmentSubject(subject); | ||||
|  | ||||
|         if (sectionCells != null) { | ||||
|             sectionCells.forEach(appointment::updateListSectionCell); | ||||
|         } | ||||
|  | ||||
|         if (report != null) { | ||||
|             appointment.setAppointmentReport(report); | ||||
|             report.setAppointmentReport(appointment); | ||||
|         } | ||||
|  | ||||
|         return appointment; | ||||
|     } | ||||
|  | ||||
|     private static Report getTestReport(String content) { | ||||
|         Report report = new Report(); | ||||
|         report.setReportContent(content); | ||||
|         return report; | ||||
|     } | ||||
|  | ||||
|     /* | ||||
|      *        _____         _   ____            _   _              ____     _ _ | ||||
|      *       |_   _|__  ___| |_/ ___|  ___  ___| |_(_) ___  _ __  / ___|___| | | | ||||
|      *         | |/ _ \/ __| __\___ \ / _ \/ __| __| |/ _ \| '_ \| |   / _ \ | | | ||||
|      *         | |  __/\__ \ |_ ___) |  __/ (__| |_| | (_) | | | | |__|  __/ | | | ||||
|      *         |_|\___||___/\__|____/ \___|\___|\__|_|\___/|_| |_|\____\___|_|_| | ||||
|      */ | ||||
|  | ||||
|     /* | ||||
|      * Tests retrieving section cells for a specific project and section ID before a given date | ||||
|      * when the user is authorized but no matching cells exist. | ||||
|      * Verifies that an empty list is returned. | ||||
|      */ | ||||
|     @Test | ||||
|     void testGetSectionCells_Authorized_NotFound() { | ||||
|         // Arrange: No specific section cells needed for this test, rely on clean @BeforeEach state | ||||
|         Long targetSectionId = 1L; | ||||
|         LocalDateTime dateFilter = LocalDateTime.now().plusDays(1); | ||||
|  | ||||
|         // Act | ||||
|         Iterable<SectionCell> result = | ||||
|                 sharedApiService.getSectionCells( | ||||
|                         staticAuthorizedProject.getIdProject(), | ||||
|                         targetSectionId, | ||||
|                         dateFilter.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm")), | ||||
|                         staticAuthorizedMail); | ||||
|  | ||||
|         List<SectionCell> resultList = TestUtils.toList(result); | ||||
|  | ||||
|         // Assert | ||||
|         assertTrue(resultList.isEmpty()); | ||||
|     } | ||||
|  | ||||
|     /* | ||||
|      * Tests retrieving section cells when the user is not authorized for the project. | ||||
|      * Verifies that an Unauthorized ResponseStatusException is thrown. | ||||
|      */ | ||||
|     @Test | ||||
|     void testGetSectionCells_Unauthorized() { | ||||
|         // Arrange: mockUtilsService configured in BeforeEach | ||||
|         // Act & Assert | ||||
|         ResponseStatusException exception = | ||||
|                 assertThrows( | ||||
|                         ResponseStatusException.class, | ||||
|                         () -> { | ||||
|                             sharedApiService.getSectionCells( | ||||
|                                     staticAuthorizedProject | ||||
|                                             .getIdProject(), // Project static user is not | ||||
|                                     // authorized for | ||||
|                                     1L, | ||||
|                                     LocalDateTime.now() | ||||
|                                             .format( | ||||
|                                                     DateTimeFormatter.ofPattern( | ||||
|                                                             "yyyy-MM-dd HH:mm")), | ||||
|                                     staticUnauthorizedMail); // Static unauthorized user mail | ||||
|                         }); | ||||
|  | ||||
|         assertEquals(HttpStatus.UNAUTHORIZED, exception.getStatusCode()); | ||||
|     } | ||||
|  | ||||
|     /* | ||||
|      * Tests retrieving all section cells for a project when the user is authorized | ||||
|      * but the project has no section cells. | ||||
|      * Verifies that an empty list is returned. | ||||
|      */ | ||||
|     @Test | ||||
|     void testGetAllSectionCells_Authorized_NoCells() { | ||||
|         // Arrange: staticAuthorizedProject has no section cells initially in BeforeAll | ||||
|         // Act | ||||
|         Iterable<SectionCell> result = | ||||
|                 sharedApiService.getAllSectionCells( | ||||
|                         staticAuthorizedProject.getIdProject(), staticAuthorizedMail); | ||||
|  | ||||
|         List<SectionCell> resultList = TestUtils.toList(result); | ||||
|  | ||||
|         // Assert | ||||
|         assertTrue(resultList.isEmpty()); | ||||
|     } | ||||
|  | ||||
|     /* | ||||
|      * Tests retrieving all section cells when the user is not authorized for the project. | ||||
|      * Verifies that an Unauthorized ResponseStatusException is thrown. | ||||
|      */ | ||||
|     @Test | ||||
|     void testGetAllSectionCells_Unauthorized() { | ||||
|         // Arrange: mockUtilsService configured in BeforeEach | ||||
|         // Act & Assert | ||||
|         ResponseStatusException exception = | ||||
|                 assertThrows( | ||||
|                         ResponseStatusException.class, | ||||
|                         () -> { | ||||
|                             sharedApiService.getAllSectionCells( | ||||
|                                     staticAuthorizedProject.getIdProject(), staticUnauthorizedMail); | ||||
|                         }); | ||||
|  | ||||
|         assertEquals(HttpStatus.UNAUTHORIZED, exception.getStatusCode()); | ||||
|     } | ||||
|  | ||||
|     /* | ||||
|      * Tests retrieving section cells for a specific project and section ID before a given date | ||||
|      * when the user is authorized and matching cells exist. | ||||
|      * Verifies that only the correct cells are returned. | ||||
|      */ | ||||
|     @Test | ||||
|     // Commenting out failing test | ||||
|     void testGetSectionCells_Authorized_Found() { | ||||
|         Long targetSectionId = 1L; | ||||
|         // Set a date filter slightly in the future so our "latest before" cell is included | ||||
|         LocalDateTime dateFilter = LocalDateTime.now().plusMinutes(5); | ||||
|  | ||||
|         // Creating versions of the SAME SectionCell (share the same idReference) | ||||
|  | ||||
|         // the first version. This will get a GENERATED idReference. | ||||
|         SectionCell firstVersion = | ||||
|                 getTestSectionCell( | ||||
|                         staticAuthorizedProject, | ||||
|                         targetSectionId, | ||||
|                         "Content V1 (Oldest)", | ||||
|                         LocalDateTime.now().minusDays(3) // Oldest date | ||||
|                         ); | ||||
|         sectionCellService.addNewSectionCell(firstVersion); | ||||
|  | ||||
|         Long sharedIdReference = firstVersion.getIdReference(); | ||||
|         assertNotNull( | ||||
|                 sharedIdReference, | ||||
|                 "idReference should be generated after saving the first version"); | ||||
|         System.out.println("Generated sharedIdReference: " + sharedIdReference); | ||||
|  | ||||
|         // Create subsequent versions and MANUALLY set the SAME idReference. | ||||
|         // These represent updates to the cell identified by sharedIdReference. | ||||
|  | ||||
|         SectionCell middleVersion = | ||||
|                 getTestSectionCell( | ||||
|                         staticAuthorizedProject, | ||||
|                         targetSectionId, | ||||
|                         "Content V2 (Middle)", | ||||
|                         LocalDateTime.now().minusDays(2), // Middle date, before filter | ||||
|                         sharedIdReference); | ||||
|         middleVersion = sectionCellService.addNewSectionCell(middleVersion); | ||||
|         sectionCellService.updateSectionCellReferenceId( | ||||
|                 middleVersion.getIdSectionCell(), sharedIdReference); | ||||
|  | ||||
|         SectionCell latestBeforeFilter = | ||||
|                 getTestSectionCell( | ||||
|                         staticAuthorizedProject, | ||||
|                         targetSectionId, | ||||
|                         "Content V3 (Latest Before Filter)", | ||||
|                         LocalDateTime.now().minusDays(1), // Latest date before filter | ||||
|                         sharedIdReference); | ||||
|         latestBeforeFilter = sectionCellService.addNewSectionCell(latestBeforeFilter); | ||||
|         sectionCellService.updateSectionCellReferenceId( | ||||
|                 latestBeforeFilter.getIdSectionCell(), sharedIdReference); | ||||
|  | ||||
|         SectionCell futureVersion = | ||||
|                 getTestSectionCell( | ||||
|                         staticAuthorizedProject, | ||||
|                         targetSectionId, | ||||
|                         "Content V4 (Future - Should Be Excluded)", | ||||
|                         LocalDateTime.now().plusDays(1), // Date is AFTER the filter | ||||
|                         sharedIdReference); | ||||
|         futureVersion = sectionCellService.addNewSectionCell(futureVersion); | ||||
|         sectionCellService.updateSectionCellReferenceId( | ||||
|                 futureVersion.getIdSectionCell(), sharedIdReference); | ||||
|  | ||||
|         // --- Create other SectionCells that should NOT be included (different sectionId or | ||||
|         // project) --- | ||||
|  | ||||
|         // Cell in a different section ID | ||||
|         sectionCellService.addNewSectionCell( | ||||
|                 getTestSectionCell( | ||||
|                         staticAuthorizedProject, | ||||
|                         99L, // Different sectionId | ||||
|                         "Content in Different Section", | ||||
|                         LocalDateTime.now())); | ||||
|  | ||||
|         // Act | ||||
|         Iterable<SectionCell> result = | ||||
|                 sharedApiService.getSectionCells( | ||||
|                         staticAuthorizedProject.getIdProject(), // Use static project ID | ||||
|                         targetSectionId, | ||||
|                         dateFilter.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm")), | ||||
|                         staticAuthorizedMail); // Use static authorized mail | ||||
|  | ||||
|         List<SectionCell> resultList = TestUtils.toList(result); | ||||
|  | ||||
|         assertEquals(1, resultList.size()); | ||||
|         // Verify that the returned cell is the 'latestBeforeFilter' cell | ||||
|         // Comparing by idSectionCell is a good way to verify the exact entity | ||||
|         assertEquals( | ||||
|                 latestBeforeFilter.getIdSectionCell(), | ||||
|                 resultList.get(0).getIdSectionCell(), | ||||
|                 "The returned SectionCell should be the one with the latest modification date before the filter."); | ||||
|  | ||||
|         // Also assert the idReference and content | ||||
|         assertEquals( | ||||
|                 sharedIdReference, | ||||
|                 resultList.get(0).getIdReference(), | ||||
|                 "The returned cell should have the shared idReference."); | ||||
|         assertEquals( | ||||
|                 "Content V3 (Latest Before Filter)", | ||||
|                 resultList.get(0).getContentSectionCell(), | ||||
|                 "The returned cell should have the correct content."); | ||||
|     } | ||||
|  | ||||
|     /* | ||||
|      * Tests retrieving the most recent section cell for each unique idReference | ||||
|      * within a project when the user is authorized and cells exist. | ||||
|      * Verifies that only the latest version of each referenced cell is returned. | ||||
|      */ | ||||
|     // Tests getAllSectionCells | ||||
|     @Test | ||||
|     // Commenting out failing test - Removed this comment as we are fixing it | ||||
|     void testGetAllSectionCells_Authorized_FoundLatest() { | ||||
|         // Arrange: Create specific SectionCells for this test | ||||
|         // Define the idReference values we will use for grouping | ||||
|         Long refIdGroup1 = 101L; | ||||
|         Long refIdGroup2 = 102L; | ||||
|         Long refIdOtherProject = 103L; | ||||
|  | ||||
|         // --- Create and Add Cells for Group 1 (refIdGroup1) --- | ||||
|         // Create the older cell for group 1 | ||||
|         SectionCell tempOldCell1 = | ||||
|                 getTestSectionCell( | ||||
|                         staticAuthorizedProject, // Project | ||||
|                         1L, // Section ID (assuming this groups by section within refId) | ||||
|                         "Ref1 Old", // Name | ||||
|                         LocalDateTime.now().minusDays(3), // Date (older) | ||||
|                         null); // Pass null or let getTestSectionCell handle it, we'll set | ||||
|         // idReference later | ||||
|         final SectionCell oldCell1 = | ||||
|                 sectionCellService.addNewSectionCell(tempOldCell1); // Add to DB | ||||
|  | ||||
|         // Create the newer cell for group 1 | ||||
|         SectionCell tempNewerCell1 = | ||||
|                 getTestSectionCell( | ||||
|                         staticAuthorizedProject, // Project | ||||
|                         1L, // Section ID | ||||
|                         "Ref1 Newer", // Name | ||||
|                         LocalDateTime.now().minusDays(2), // Date (newer than oldCell1) | ||||
|                         null); // Pass null | ||||
|         final SectionCell newerCell1 = | ||||
|                 sectionCellService.addNewSectionCell(tempNewerCell1); // Add to DB | ||||
|  | ||||
|         // Now, update the idReference for both cells in Group 1 to the desired value | ||||
|         sectionCellService.updateSectionCellReferenceId(oldCell1.getIdSectionCell(), refIdGroup1); | ||||
|         sectionCellService.updateSectionCellReferenceId(newerCell1.getIdSectionCell(), refIdGroup1); | ||||
|  | ||||
|         // --- Create and Add Cells for Group 2 (refIdGroup2) --- | ||||
|         // Create the older cell for group 2 | ||||
|         SectionCell tempOldCell2 = | ||||
|                 getTestSectionCell( | ||||
|                         staticAuthorizedProject, // Project | ||||
|                         2L, // Section ID (different section) | ||||
|                         "Ref2 Old", // Name | ||||
|                         LocalDateTime.now().minusDays(1), // Date (older than newerCell2) | ||||
|                         null); // Pass null | ||||
|         final SectionCell oldCell2 = | ||||
|                 sectionCellService.addNewSectionCell(tempOldCell2); // Add to DB | ||||
|  | ||||
|         // Create the newer cell for group 2 | ||||
|         SectionCell tempNewerCell2 = | ||||
|                 getTestSectionCell( | ||||
|                         staticAuthorizedProject, // Project | ||||
|                         2L, // Section ID | ||||
|                         "Ref2 Newer", // Name | ||||
|                         LocalDateTime.now(), // Date (latest) | ||||
|                         null); // Pass null | ||||
|         final SectionCell newerCell2 = | ||||
|                 sectionCellService.addNewSectionCell(tempNewerCell2); // Add to DB | ||||
|  | ||||
|         // Now, update the idReference for both cells in Group 2 to the desired value | ||||
|         sectionCellService.updateSectionCellReferenceId(oldCell2.getIdSectionCell(), refIdGroup2); | ||||
|         sectionCellService.updateSectionCellReferenceId(newerCell2.getIdSectionCell(), refIdGroup2); | ||||
|  | ||||
|         // --- Create and Add Cell for Other Project (refIdOtherProject) --- | ||||
|         Project otherProject = | ||||
|                 projectService.addNewProject( | ||||
|                         getTestProject( | ||||
|                                 "other_project_for_cell_test", | ||||
|                                 administratorService.addAdministrator( | ||||
|                                         getTestAdmin("other_admin_cell_test")))); | ||||
|  | ||||
|         SectionCell tempOtherProjectCell = | ||||
|                 getTestSectionCell( | ||||
|                         otherProject, // DIFFERENT Project | ||||
|                         1L, // Section ID | ||||
|                         "Other Project Cell", // Name | ||||
|                         LocalDateTime.now(), // Date | ||||
|                         null); // Pass null | ||||
|         final SectionCell otherProjectCell = | ||||
|                 sectionCellService.addNewSectionCell(tempOtherProjectCell); // Add to DB | ||||
|  | ||||
|         // Now, update the idReference for the Other Project cell | ||||
|         sectionCellService.updateSectionCellReferenceId( | ||||
|                 otherProjectCell.getIdSectionCell(), refIdOtherProject); | ||||
|  | ||||
|         // Act | ||||
|         // Ensure the service call uses the correct project ID and mail | ||||
|         Iterable<SectionCell> result = | ||||
|                 sharedApiService.getAllSectionCells( | ||||
|                         staticAuthorizedProject.getIdProject(), // Use static project ID | ||||
|                         staticAuthorizedMail); // Use static authorized mail | ||||
|  | ||||
|         List<SectionCell> resultList = TestUtils.toList(result); | ||||
|  | ||||
|         // Assert | ||||
|         // We expect 2 cells from the staticAuthorizedProject: | ||||
|         // - The latest one from refIdGroup1 (newerCell1) | ||||
|         // - The latest one from refIdGroup2 (newerCell2) | ||||
|         assertEquals(2, resultList.size()); | ||||
|  | ||||
|         // Assert that the result list contains the LATEST cell from each group within the correct | ||||
|         // project | ||||
|         assertTrue( | ||||
|                 resultList.stream() | ||||
|                         .anyMatch( | ||||
|                                 cell -> | ||||
|                                         cell.getIdSectionCell() | ||||
|                                                 .equals(newerCell1.getIdSectionCell())), | ||||
|                 "Should contain the latest cell for Group 1"); // Add assertion message | ||||
|         assertTrue( | ||||
|                 resultList.stream() | ||||
|                         .anyMatch( | ||||
|                                 cell -> | ||||
|                                         cell.getIdSectionCell() | ||||
|                                                 .equals(newerCell2.getIdSectionCell())), | ||||
|                 "Should contain the latest cell for Group 2"); // Add assertion message | ||||
|  | ||||
|         // Assert that the result list does NOT contain the OLDER cells from the correct project | ||||
|         assertFalse( | ||||
|                 resultList.stream() | ||||
|                         .anyMatch( | ||||
|                                 cell -> | ||||
|                                         cell.getIdSectionCell() | ||||
|                                                 .equals(oldCell1.getIdSectionCell())), | ||||
|                 "Should not contain the older cell for Group 1"); // Add assertion message | ||||
|         assertFalse( | ||||
|                 resultList.stream() | ||||
|                         .anyMatch( | ||||
|                                 cell -> | ||||
|                                         cell.getIdSectionCell() | ||||
|                                                 .equals(oldCell2.getIdSectionCell())), | ||||
|                 "Should not contain the older cell for Group 2"); // Add assertion message | ||||
|  | ||||
|         // Assert that the result list does NOT contain the cell from the other project | ||||
|         assertFalse( | ||||
|                 resultList.stream() | ||||
|                         .anyMatch( | ||||
|                                 cell -> | ||||
|                                         cell.getIdSectionCell() | ||||
|                                                 .equals(otherProjectCell.getIdSectionCell())), | ||||
|                 "Should not contain cells from other projects"); // Add assertion message | ||||
|     } | ||||
|  | ||||
|     /* | ||||
|      *        _____         _    ____      _   ____            _           _   ____ | ||||
|      *       |_   _|__  ___| |_ / ___| ___| |_|  _ \ _ __ ___ (_) ___  ___| |_| __ ) _   _ | ||||
|      *         | |/ _ \/ __| __| |  _ / _ \ __| |_) | '__/ _ \| |/ _ \/ __| __|  _ \| | | | | ||||
|      *         | |  __/\__ \ |_| |_| |  __/ |_|  __/| | | (_) | |  __/ (__| |_| |_) | |_| | | ||||
|      *        _|_|\___||___/\__|\____|\___|\__|_|   |_|  \___// |\___|\___|\__|____/ \__, | | ||||
|      *       |_ _|  _ \                                     |__/                     |___/ | ||||
|      *        | || | | | | ||||
|      *        | || |_| | | ||||
|      *       |___|____/ | ||||
|      */ | ||||
|  | ||||
|     /* | ||||
|      * Tests retrieving entrepreneurs linked to a project when the user is authorized | ||||
|      * but no entrepreneurs are linked. | ||||
|      * Verifies that an empty list is returned. | ||||
|      */ | ||||
|     @Test | ||||
|     void testGetEntrepreneursByProjectId_Authorized_NotFound() { | ||||
|         // Arrange: staticAuthorizedProject has no entrepreneurs linked initially in BeforeAll | ||||
|         // Act | ||||
|         Iterable<Entrepreneur> result = | ||||
|                 sharedApiService.getEntrepreneursByProjectId( | ||||
|                         staticAuthorizedProject.getIdProject(), staticAuthorizedMail); | ||||
|  | ||||
|         List<Entrepreneur> resultList = TestUtils.toList(result); | ||||
|  | ||||
|         // Assert | ||||
|         assertTrue(resultList.isEmpty()); | ||||
|     } | ||||
|  | ||||
|     /* | ||||
|      * Tests retrieving entrepreneurs linked to a project when the user is not authorized. | ||||
|      * Verifies that an Unauthorized ResponseStatusException is thrown. | ||||
|      */ | ||||
|     @Test | ||||
|     void testGetEntrepreneursByProjectId_Unauthorized() { | ||||
|         // Arrange: mockUtilsService configured in BeforeEach | ||||
|         // Act & Assert | ||||
|         ResponseStatusException exception = | ||||
|                 assertThrows( | ||||
|                         ResponseStatusException.class, | ||||
|                         () -> { | ||||
|                             sharedApiService.getEntrepreneursByProjectId( | ||||
|                                     staticAuthorizedProject.getIdProject(), staticUnauthorizedMail); | ||||
|                         }); | ||||
|  | ||||
|         assertEquals(HttpStatus.UNAUTHORIZED, exception.getStatusCode()); | ||||
|     } | ||||
|  | ||||
|     /* | ||||
|      *    _____         _    ____      _      _       _           _       ____ | ||||
|      *   |_   _|__  ___| |_ / ___| ___| |_   / \   __| |_ __ ___ (_)_ __ | __ ) _   _ | ||||
|      *     | |/ _ \/ __| __| |  _ / _ \ __| / _ \ / _` | '_ ` _ \| | '_ \|  _ \| | | | | ||||
|      *     | |  __/\__ \ |_| |_| |  __/ |_ / ___ \ (_| | | | | | | | | | | |_) | |_| | | ||||
|      *    _|_|\___||___/\__|\____|\___|\__/_/   \_\__,_|_| |_| |_|_|_| |_|____/ \__, | | ||||
|      *   |_ _|  _ \                                                             |___/ | ||||
|      *    | || | | | | ||||
|      *    | || |_| | | ||||
|      *   |___|____/ | ||||
|      * | ||||
|      */ | ||||
|  | ||||
|     /* | ||||
|      * Tests retrieving appointments linked to a project's section cells when the user is authorized | ||||
|      * but no such appointments exist. | ||||
|      * Verifies that an empty list is returned. | ||||
|      */ | ||||
|     @Test | ||||
|     void testGetAppointmentsByProjectId_Authorized_NotFound() { | ||||
|         // Arrange: staticAuthorizedProject has no linked section cells or appointments initially | ||||
|         // Act | ||||
|         Iterable<Appointment> result = | ||||
|                 sharedApiService.getAppointmentsByProjectId( | ||||
|                         staticAuthorizedProject.getIdProject(), staticAuthorizedMail); | ||||
|  | ||||
|         List<Appointment> resultList = TestUtils.toList(result); | ||||
|  | ||||
|         // Assert | ||||
|         assertTrue(resultList.isEmpty()); | ||||
|     } | ||||
|  | ||||
|     /* | ||||
|      * Tests retrieving the administrator linked to a project when the user is authorized | ||||
|      * and an administrator is linked. | ||||
|      * Verifies that the correct administrator is returned. | ||||
|      */ | ||||
|     // Tests getAdminByProjectId | ||||
|     @Test | ||||
|     void testGetAdminByProjectId_Authorized_Found() { | ||||
|         // Arrange: staticAuthorizedProject is created with staticAuthorizedAdmin in BeforeAll | ||||
|         // Act | ||||
|         Administrator result = | ||||
|                 sharedApiService.getAdminByProjectId( | ||||
|                         staticAuthorizedProject.getIdProject(), staticAuthorizedMail); | ||||
|  | ||||
|         // Assert | ||||
|         assertNotNull(result); | ||||
|         assertEquals(staticAuthorizedAdmin.getIdUser(), result.getIdUser()); | ||||
|     } | ||||
|  | ||||
|     /* | ||||
|      * Tests retrieving the administrator linked to a project when the user is not authorized. | ||||
|      * Verifies that an Unauthorized ResponseStatusException is thrown. | ||||
|      */ | ||||
|     @Test | ||||
|     void testGetAdminByProjectId_Unauthorized() { | ||||
|         // Arrange: mockUtilsService configured in BeforeEach | ||||
|         // Act & Assert | ||||
|         ResponseStatusException exception = | ||||
|                 assertThrows( | ||||
|                         ResponseStatusException.class, | ||||
|                         () -> { | ||||
|                             sharedApiService.getAdminByProjectId( | ||||
|                                     staticAuthorizedProject.getIdProject(), staticUnauthorizedMail); | ||||
|                         }); | ||||
|  | ||||
|         assertEquals(HttpStatus.UNAUTHORIZED, exception.getStatusCode()); | ||||
|     } | ||||
|  | ||||
|     /* | ||||
|      *    _____         _ | ||||
|      *   |_   _|__  ___| |_ | ||||
|      *     | |/ _ \/ __| __| | ||||
|      *     | |  __/\__ \ |_ | ||||
|      *     |_|\___||___/\__|        _       _                            _ | ||||
|      *      / \   _ __  _ __   ___ (_)_ __ | |_ ___ _ __ ___   ___ _ __ | |_ ___ | ||||
|      *     / _ \ | '_ \| '_ \ / _ \| | '_ \| __/ _ \ '_ ` _ \ / _ \ '_ \| __/ __| | ||||
|      *    / ___ \| |_) | |_) | (_) | | | | | ||  __/ | | | | |  __/ | | | |_\__ \ | ||||
|      *   /_/   \_\ .__/| .__/ \___/|_|_| |_|\__\___|_| |_| |_|\___|_| |_|\__|___/ | ||||
|      *           |_|   |_| | ||||
|      */ | ||||
|  | ||||
|     /* | ||||
|      * Tests retrieving appointments linked to a project's section cells when the user is not authorized. | ||||
|      * Verifies that an Unauthorized ResponseStatusException is thrown. | ||||
|      */ | ||||
|     @Test | ||||
|     void testGetAppointmentsByProjectId_Unauthorized() { | ||||
|         // Arrange: mockUtilsService configured in BeforeEach | ||||
|         // Act & Assert | ||||
|         ResponseStatusException exception = | ||||
|                 assertThrows( | ||||
|                         ResponseStatusException.class, | ||||
|                         () -> { | ||||
|                             sharedApiService.getAppointmentsByProjectId( | ||||
|                                     staticAuthorizedProject.getIdProject(), staticUnauthorizedMail); | ||||
|                         }); | ||||
|  | ||||
|         assertEquals(HttpStatus.UNAUTHORIZED, exception.getStatusCode()); | ||||
|     } | ||||
|  | ||||
|     @PersistenceContext // Inject EntityManager | ||||
|     private EntityManager entityManager; | ||||
|  | ||||
|     // Assume these static variables are defined elsewhere in your test class | ||||
|     // private static Project staticAuthorizedProject; | ||||
|     // private static String staticAuthorizedMail; | ||||
|     // private static Administrator staticAuthorizedAdmin; | ||||
|  | ||||
|     // Assume getTestSectionCell, getTestProject, getTestAdmin, getTestAppointment, TestUtils.toList | ||||
|     // are defined elsewhere | ||||
|  | ||||
|     @Test | ||||
|     void testGetAppointmentsByProjectId_Authorized_Found() { | ||||
|         // Arrange: Create specific SectionCells and Appointments for this test | ||||
|         SectionCell cell1 = | ||||
|                 sectionCellService.addNewSectionCell( | ||||
|                         getTestSectionCell( | ||||
|                                 staticAuthorizedProject, 1L, "Cell 1 Test", LocalDateTime.now())); | ||||
|         SectionCell cell2 = | ||||
|                 sectionCellService.addNewSectionCell( | ||||
|                         getTestSectionCell( | ||||
|                                 staticAuthorizedProject, 2L, "Cell 2 Test", LocalDateTime.now())); | ||||
|         Project otherProject = | ||||
|                 projectService.addNewProject( | ||||
|                         getTestProject( | ||||
|                                 "other_project_app_test", | ||||
|                                 administratorService.addAdministrator( | ||||
|                                         getTestAdmin("other_admin_app_test")))); | ||||
|         SectionCell otherProjectCell = | ||||
|                 sectionCellService.addNewSectionCell( | ||||
|                         getTestSectionCell( | ||||
|                                 otherProject, | ||||
|                                 1L, | ||||
|                                 "Other Project Cell App Test", | ||||
|                                 LocalDateTime.now())); | ||||
|  | ||||
|         // Create Appointments with SectionCells lists (Owning side) | ||||
|         Appointment app1 = | ||||
|                 getTestAppointment( | ||||
|                         LocalDate.now().plusDays(10), | ||||
|                         LocalTime.NOON, | ||||
|                         LocalTime.of(0, 30), | ||||
|                         "Place 1 App Test", | ||||
|                         "Subject 1 App Test", | ||||
|                         List.of(cell1), // This links Appointment to SectionCell | ||||
|                         null); | ||||
|         Appointment savedApp1 = appointmentService.addNewAppointment(app1); | ||||
|  | ||||
|         Appointment app2 = | ||||
|                 getTestAppointment( | ||||
|                         LocalDate.now().plusDays(11), | ||||
|                         LocalTime.NOON.plusHours(1), | ||||
|                         LocalTime.of(1, 0), | ||||
|                         "Place 2 App Test", | ||||
|                         "Subject 2 App Test", | ||||
|                         List.of(cell1, cell2), // This links Appointment to SectionCells | ||||
|                         null); | ||||
|         Appointment savedApp2 = appointmentService.addNewAppointment(app2); | ||||
|  | ||||
|         Appointment otherApp = | ||||
|                 getTestAppointment( | ||||
|                         LocalDate.now().plusDays(12), | ||||
|                         LocalTime.MIDNIGHT, | ||||
|                         LocalTime.of(0, 15), | ||||
|                         "Other Place App Test", | ||||
|                         "Other Subject App Test", | ||||
|                         List.of(otherProjectCell), // This links Appointment to SectionCell | ||||
|                         null); | ||||
|         Appointment savedOtherApp = | ||||
|                 appointmentService.addNewAppointment(otherApp); // Capture saved entity | ||||
|  | ||||
|         // --- IMPORTANT DEBUGGING STEPS --- | ||||
|         // Flush pending changes to the database (including join table inserts) | ||||
|         entityManager.flush(); | ||||
|         // Clear the persistence context cache to ensure entities are loaded fresh from the database | ||||
|         entityManager.clear(); | ||||
|         // --- END IMPORTANT DEBUGGING STEPS --- | ||||
|  | ||||
|         // --- Add Debug Logging Here --- | ||||
|         // Re-fetch cells to see their state after saving Appointments and flushing/clearing cache | ||||
|         // These fetches should load from the database due to entityManager.clear() | ||||
|         SectionCell fetchedCell1_postPersist = | ||||
|                 sectionCellService.getSectionCellById(cell1.getIdSectionCell()); | ||||
|         SectionCell fetchedCell2_postPersist = | ||||
|                 sectionCellService.getSectionCellById(cell2.getIdSectionCell()); | ||||
|         SectionCell fetchedOtherCell_postPersist = | ||||
|                 sectionCellService.getSectionCellById(otherProjectCell.getIdSectionCell()); | ||||
|  | ||||
|         // Access the lazy collections to see if they are populated from the DB | ||||
|         // This access should trigger lazy loading if the data is in the DB | ||||
|         List<Appointment> cell1Apps_postPersist = | ||||
|                 fetchedCell1_postPersist.getAppointmentSectionCell(); | ||||
|         List<Appointment> cell2Apps_postPersist = | ||||
|                 fetchedCell2_postPersist.getAppointmentSectionCell(); | ||||
|         List<Appointment> otherCellApps_postPersist = | ||||
|                 fetchedOtherCell_postPersist.getAppointmentSectionCell(); | ||||
|  | ||||
|         // Ensure logging is enabled in SharedApiService and SectionCellService methods called below | ||||
|         Iterable<Appointment> result = | ||||
|                 sharedApiService.getAppointmentsByProjectId( | ||||
|                         staticAuthorizedProject.getIdProject(), // Use static project ID | ||||
|                         staticAuthorizedMail); // Use static authorized mail | ||||
|  | ||||
|         List<Appointment> resultList = TestUtils.toList(result); | ||||
|  | ||||
|         // Assert | ||||
|         assertEquals(2, resultList.size()); | ||||
|  | ||||
|         assertTrue( | ||||
|                 resultList.stream() | ||||
|                         .anyMatch(a -> a.getIdAppointment().equals(savedApp1.getIdAppointment()))); | ||||
|         assertTrue( | ||||
|                 resultList.stream() | ||||
|                         .anyMatch(a -> a.getIdAppointment().equals(savedApp2.getIdAppointment()))); | ||||
|  | ||||
|         assertFalse( | ||||
|                 resultList.stream() | ||||
|                         .anyMatch( | ||||
|                                 a -> | ||||
|                                         a.getIdAppointment() | ||||
|                                                 .equals(savedOtherApp.getIdAppointment()))); | ||||
|     } | ||||
|  | ||||
|     /* | ||||
|      * Tests creating a new appointment request when the user is authorized | ||||
|      * for the project linked to the appointment's section cell. | ||||
|      * Verifies that the appointment and its relationships are saved correctly in the database. | ||||
|      */ | ||||
|     // Tests createAppointmentRequest | ||||
|     @Test | ||||
|     // Commenting out failing test | ||||
|     void testCreateAppointmentRequest_Authorized_Success() { | ||||
|         // Arrange: Create transient appointment linked to a cell in the static authorized project | ||||
|         LocalDate date = LocalDate.parse("2026-01-01"); | ||||
|         LocalTime time = LocalTime.parse("10:00:00"); | ||||
|         LocalTime duration = LocalTime.parse("00:30:00"); | ||||
|         String place = "Meeting Room Integrated"; | ||||
|         String subject = "Discuss Project Integrated"; | ||||
|  | ||||
|         SectionCell linkedCell = | ||||
|                 sectionCellService.addNewSectionCell( | ||||
|                         getTestSectionCell( | ||||
|                                 staticAuthorizedProject, | ||||
|                                 0L, | ||||
|                                 "Related Section Content Integrated", | ||||
|                                 LocalDateTime.now())); | ||||
|  | ||||
|         Report newReport = null; // getTestReport(reportContent); // Uses no-arg constructor | ||||
|  | ||||
|         Appointment newAppointment = | ||||
|                 getTestAppointment( | ||||
|                         date, time, duration, place, subject, List.of(linkedCell), newReport); | ||||
|  | ||||
|         // mockUtilsService is configured in BeforeEach to allow staticAuthorizedMail for | ||||
|         // staticAuthorizedProject | ||||
|  | ||||
|         // Act | ||||
|         // Allow the service method to call the actual appointmentService.addNewAppointment | ||||
|         assertDoesNotThrow( | ||||
|                 () -> | ||||
|                         sharedApiService.createAppointmentRequest( | ||||
|                                 newAppointment, staticAuthorizedMail)); | ||||
|  | ||||
|         // Assert: Retrieve the appointment from the DB and verify it and its relationships were | ||||
|         // saved | ||||
|         // We find it by looking for appointments linked to the authorized project's cells | ||||
|         Iterable<SectionCell> projectCells = | ||||
|                 sectionCellService.getSectionCellsByProject( | ||||
|                         staticAuthorizedProject, linkedCell.getSectionId()); // Fetch relevant cells | ||||
|         List<Appointment> projectAppointmentsList = new ArrayList<>(); | ||||
|         projectCells.forEach( | ||||
|                 cell -> | ||||
|                         projectAppointmentsList.addAll( | ||||
|                                 sectionCellService.getAppointmentsBySectionCellId( | ||||
|                                         cell.getIdSectionCell()))); // Get appointments for | ||||
|         // those cells | ||||
|  | ||||
|         Optional<Appointment> createdAppointmentOpt = | ||||
|                 projectAppointmentsList.stream() | ||||
|                         .filter( | ||||
|                                 a -> | ||||
|                                         a.getAppointmentDate().equals(date) | ||||
|                                                 && a.getAppointmentTime().equals(time) | ||||
|                                                 && a.getAppointmentPlace().equals(place) | ||||
|                                                 && a.getAppointmentSubject().equals(subject)) | ||||
|                         .findFirst(); | ||||
|  | ||||
|         assertTrue(createdAppointmentOpt.isPresent()); | ||||
|         Appointment createdAppointment = createdAppointmentOpt.get(); | ||||
|  | ||||
|         // FIX: Corrected bidirectional link check | ||||
|         assertEquals(1, createdAppointment.getAppointmentListSectionCell().size()); | ||||
|         assertTrue( | ||||
|                 createdAppointment.getAppointmentListSectionCell().stream() | ||||
|                         .anyMatch( | ||||
|                                 sc -> sc.getIdSectionCell().equals(linkedCell.getIdSectionCell()))); | ||||
|  | ||||
|         List<Appointment> appointmentsLinkedToCell = | ||||
|                 TestUtils.toList( | ||||
|                         sectionCellService.getAppointmentsBySectionCellId( | ||||
|                                 linkedCell.getIdSectionCell())); | ||||
|         assertTrue( | ||||
|                 appointmentsLinkedToCell.stream() | ||||
|                         .anyMatch( | ||||
|                                 a -> | ||||
|                                         a.getIdAppointment() | ||||
|                                                 .equals(createdAppointment.getIdAppointment()))); | ||||
|     } | ||||
| } | ||||
| @@ -16,7 +16,7 @@ BACKEND_PASSWORD=backend_db_user_password | ||||
| DATABASE_URL=localhost:5433 | ||||
|  | ||||
| VITE_KEYCLOAK_URL=http://localhost:7080 | ||||
| VITE_KEYCLOAK_CLIENT_ID=myinpulse-dev | ||||
| VITE_KEYCLOAK_REALM=test | ||||
| VITE_KEYCLOAK_CLIENT_ID=MyINPulse-vite | ||||
| VITE_KEYCLOAK_REALM=MyINPulse | ||||
| VITE_APP_URL=http://localhost:5173 | ||||
| VITE_BACKEND_URL=http://localhost:8081/ | ||||
|   | ||||
							
								
								
									
										13
									
								
								documentation/openapi/notes.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								documentation/openapi/notes.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,13 @@ | ||||
| ## API Endpoints notes | ||||
|  | ||||
| ### EntrepreneurApi and SharedApi | ||||
| #### Endpoint Name Changes | ||||
| - `/entrepreneur/lcsection/modify/{sectionId}` → `/entrepreneur/sectionCell/modify/{sectionId}` | ||||
|  | ||||
| ### Admin api | ||||
| - `/admin/appointments/report/{appointmentId}` has no PUT and DELETE | ||||
| - `/admin/request-join` and `/admin/request-join/decision/{joinRequestId}` have not yet been implemented | ||||
|  | ||||
| ### Unauth api | ||||
| - `/unauth/request-join/{projectId}` has not yet been implemented | ||||
|  | ||||
							
								
								
									
										11
									
								
								documentation/openapi/run_doc.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										11
									
								
								documentation/openapi/run_doc.sh
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,11 @@ | ||||
| #!/bin/bash | ||||
|  | ||||
| cd ./swagger-ui | ||||
|  | ||||
| if [ ! -d "./node_modules/" ] | ||||
| then | ||||
|     npm install | ||||
|     npm install swagger-cli | ||||
| fi | ||||
|  | ||||
| npm start | ||||
							
								
								
									
										387
									
								
								documentation/openapi/src/adminApi.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										387
									
								
								documentation/openapi/src/adminApi.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,387 @@ | ||||
| # Admin API Endpoints | ||||
| paths: | ||||
|   /admin/projects: | ||||
|     get: | ||||
|       operationId: getAdminProjects | ||||
|       summary: Get projects associated with the admin | ||||
|       tags: | ||||
|         - Admin API | ||||
|       security: | ||||
|         - MyINPulse: [MyINPulse-admin] | ||||
|       description: Retrieves a list of projects managed by the requesting admin, including details for overview. | ||||
|       responses: | ||||
|         "200": | ||||
|           description: OK - List of projects returned successfully. | ||||
|           content: | ||||
|             application/json: | ||||
|               schema: | ||||
|                 type: array | ||||
|                 items: | ||||
|                   $ref: "./main.yaml#/components/schemas/project" | ||||
|         "400": | ||||
|           description: Bad Request - Invalid project data provided (e.g., missing required fields). | ||||
|         "401": | ||||
|           description: Unauthorized - Authentication required or invalid token. | ||||
|         "403": | ||||
|           description: Bad Token - Invalid Keycloack configuration. | ||||
|            | ||||
|     post: | ||||
|       operationId: addProjectManually | ||||
|       summary: Manually add a new project | ||||
|       description: Creates a new project with the provided details. (NOTE that this meant for manually inserting projects, for example importing already existing projects). | ||||
|       tags: | ||||
|         - Admin API | ||||
|       security: | ||||
|         - MyINPulse: [MyINPulse-admin] | ||||
|       requestBody: | ||||
|         required: true | ||||
|         description: Project details to create. `idProject` and `creationDate` will be ignored if sent and set by the server. | ||||
|         content: | ||||
|           application/json: | ||||
|             schema: | ||||
|               $ref: "./main.yaml#/components/schemas/project" | ||||
|       responses: | ||||
|         "200": # Use 200 Created for successful creation | ||||
|           description: Created - Project added successfully. Returns the created project. | ||||
|           content: | ||||
|             application/json: | ||||
|               schema: | ||||
|                 $ref: "./main.yaml#/components/schemas/project" | ||||
|         "409": | ||||
|           description: Bad Request - Project already exists. | ||||
|         "401": | ||||
|           description: Unauthorized. | ||||
|         "403": | ||||
|           description: Bad Token - Invalid Keycloack configuration. | ||||
|            | ||||
|  | ||||
|   /admin/projects/pending: | ||||
|     get: | ||||
|       operationId: getPendingProjects | ||||
|       summary: Get projects awaiting validation | ||||
|       tags: | ||||
|         - Admin API | ||||
|       security: | ||||
|         - MyINPulse: [MyINPulse-admin] | ||||
|       description: Retrieves a list of projects submitted by entrepreneurs that are pending admin approval. | ||||
|       responses: | ||||
|         "200": | ||||
|           description: OK - List of pending projects returned. | ||||
|           content: | ||||
|             application/json: | ||||
|               schema: | ||||
|                 type: array | ||||
|                 items: | ||||
|                   $ref: "./main.yaml#/components/schemas/project" # Assuming pending projects use the same schema | ||||
|         "401": | ||||
|           description: Unauthorized. | ||||
|         "403": | ||||
|           description: Bad Token - Invalid Keycloack configuration.       | ||||
|  | ||||
|   /admin/request-join: | ||||
|     get: | ||||
|       operationId: getPendingProjects | ||||
|       summary: Get entrepreneurs project join requests | ||||
|       tags: | ||||
|         - Admin API | ||||
|       security: | ||||
|         - MyINPulse: [MyINPulse-admin] | ||||
|       description:  Retrieves a list of pending requests from entrepreneurs to join an existing project. | ||||
|       responses: | ||||
|         "200": | ||||
|           description: OK - List of pending project join requests. | ||||
|           content: | ||||
|             application/json: | ||||
|               schema: | ||||
|                 type: array | ||||
|                 items: | ||||
|                   $ref: "./main.yaml#/components/schemas/joinRequest" | ||||
|         "401": | ||||
|           description: Unauthorized. | ||||
|         "403": | ||||
|           description: Bad Token - Invalid Keycloack configuration. | ||||
|    | ||||
|   /admin/request-join/decision/{joinRequestId}: | ||||
|     post: | ||||
|       summary: Approve or reject a pending project join request | ||||
|       tags: | ||||
|         - Admin API | ||||
|       security: | ||||
|         - MyINPulse: [MyINPulse-admin] | ||||
|       parameters: | ||||
|         - in: path | ||||
|           name: joinRequestId | ||||
|           required: true | ||||
|           schema: | ||||
|             type: integer | ||||
|           description: The ID of the pending join request to decide upon. | ||||
|          | ||||
|       description: |- | ||||
|         Allows an admin to make a decision on an ebtrepreneur's request to join an existing project awaiting validation. | ||||
|         If approved (isAccepted=true), the entrepreneur is linked to the project with ID joinRequestId. | ||||
|         If rejected (isAccepted=false), the pending request data might be archived or deleted based on business logic. | ||||
|       responses: | ||||
|         "200": | ||||
|           description: OK - No Content, decision processed successfully.. | ||||
|           content: | ||||
|             application/json: | ||||
|               $ref: "./main.yaml#/components/schemas/joinRequestDecision" | ||||
|         "400": | ||||
|           description: Bad Request - Invalid input (e.g., missing decision). | ||||
|         "401": | ||||
|           description: Unauthorized. | ||||
|         "403": | ||||
|           description: Bad Token - Invalid Keycloack configuration.    | ||||
|  | ||||
|  | ||||
|   /admin/projects/pending/decision: | ||||
|     post: | ||||
|       operationId: decidePendingProject | ||||
|       summary: Approve or reject a pending project | ||||
|       tags: | ||||
|         - Admin API | ||||
|       description: |- | ||||
|         Allows an admin to make a decision on a project awaiting validation. | ||||
|         If approved (isAccepted=true), the project status changes, and it's linked to the involved users. | ||||
|         If rejected (isAccepted=false), the pending project data might be archived or deleted based on business logic. | ||||
|       security: | ||||
|           - MyINPulse: [MyINPulse-admin] | ||||
|       requestBody: | ||||
|         required: true | ||||
|         description: Decision payload. | ||||
|         content: | ||||
|           application/json: | ||||
|             schema: | ||||
|               $ref: './main.yaml#/components/schemas/projectDecision' | ||||
|       responses: | ||||
|         "200": # Use 200 No Content for successful action with no body | ||||
|           description: No Content - Decision processed successfully. | ||||
|         "400": | ||||
|           description: Bad Request - Invalid input (e.g., missing decision). | ||||
|         "401": | ||||
|           description: Unauthorized. | ||||
|         "403": | ||||
|           description: Bad Token - Invalid Keycloack configuration. | ||||
|            | ||||
|  | ||||
|   /admin/pending-accounts: # Path updated | ||||
|     get: | ||||
|       operationId: getPendingAccounts | ||||
|       summary: Get accounts awaiting validation | ||||
|       description: Retrieves a list of entrepreneur user accounts that are pending admin validation. | ||||
|       tags: | ||||
|         - Admin API | ||||
|       security: | ||||
|           - MyINPulse: [MyINPulse-admin] | ||||
|       responses: | ||||
|         "200": | ||||
|           description: OK - List of pending accounts returned. | ||||
|           content: | ||||
|             application/json: | ||||
|               schema: | ||||
|                 type: array | ||||
|                 items: | ||||
|                   $ref: "./main.yaml#/components/schemas/user-entrepreneur" | ||||
|         "401": | ||||
|           description: Unauthorized. | ||||
|         "403": | ||||
|           description: Bad Token - Invalid Keycloack configuration. | ||||
|  | ||||
|   /admin/accounts/validate/{userId}: | ||||
|     post: # Changed to POST as it changes state | ||||
|       operationId: validateUserAccount | ||||
|       summary: Validate a pending user account | ||||
|       description: Marks the user account specified by userId as validated/active. | ||||
|       tags: | ||||
|         - Admin API | ||||
|       security: | ||||
|           - MyINPulse: [MyINPulse-admin] | ||||
|       parameters: | ||||
|         - in: path | ||||
|           name: userId | ||||
|           required: true | ||||
|           schema: | ||||
|             type: integer | ||||
|           description: The ID of the user account to validate. | ||||
|           example: 102 | ||||
|       responses: | ||||
|         "200": | ||||
|           description: No Content - Account validated successfully. | ||||
|         "400": | ||||
|           description: Bad Request - Invalid user ID format. | ||||
|         "403": | ||||
|           description: Bad Token - Invalid Keycloack configuration.   | ||||
|         "401": | ||||
|           description: Unauthorized. | ||||
|  | ||||
|   /admin/appointments/upcoming: | ||||
|     get: | ||||
|       operationId: getUpcomingAppointments | ||||
|       summary: Get upcoming appointments for an admin | ||||
|       tags: | ||||
|         - Admin API | ||||
|       security: | ||||
|         - MyINPulse: [MyINPulse-admin] | ||||
|       description: Retrieves a list of appointments scheduled for an admin in the future. | ||||
|       responses: | ||||
|         "200": | ||||
|           description: OK - List of upcoming appointments. | ||||
|           content: | ||||
|             application/json: | ||||
|               schema: | ||||
|                 type: array | ||||
|                 items: | ||||
|                   $ref: "./main.yaml#/components/schemas/appointment" | ||||
|         "403": | ||||
|           description: Bad Token - Invalid Keycloack configuration. | ||||
|         "404": | ||||
|           description: no appointments found. | ||||
|         "401": | ||||
|           description: Unauthorized. | ||||
|  | ||||
|   /admin/appointments/report/{appointmentId}: | ||||
|     post: | ||||
|       operationId: createAppointmentReport | ||||
|       summary: Create a report for an appointment | ||||
|       description: Creates and links a new report (e.g., meeting minutes) to the specified appointment using the provided content. | ||||
|       tags: | ||||
|         - Admin API | ||||
|       security: | ||||
|         - MyINPulse: [MyINPulse-admin] | ||||
|       parameters: | ||||
|         - in: path | ||||
|           name: appointmentId | ||||
|           required: true | ||||
|           schema: | ||||
|             type: integer | ||||
|           description: ID of the appointment to add a report to. | ||||
|           example: 303 | ||||
|       requestBody: | ||||
|         required: true | ||||
|         description: Report content. `idReport` will be ignored if sent. | ||||
|         content: | ||||
|           application/json: | ||||
|             schema: | ||||
|               $ref: "./main.yaml#/components/schemas/report" | ||||
|       responses: | ||||
|         "200": | ||||
|           description: Created - Report created and linked successfully. Returns the created report. | ||||
|           content: | ||||
|              application/json: | ||||
|                 schema: { $ref: "./main.yaml#/components/schemas/report" } | ||||
|         "400": | ||||
|           description: Bad Request - Invalid input (e.g., missing content, invalid appointment ID format). | ||||
|         "403": | ||||
|           description: Bad Token - Invalid Keycloack configuration. | ||||
|         "401": | ||||
|           description: Unauthorized. | ||||
|            | ||||
|     put: # Changed to PUT for update/replacement | ||||
|       operationId: updateAppointmentReport | ||||
|       summary: Update an existing appointment report | ||||
|       description: Updates the content of an existing report linked to the specified appointment. Replaces the entire report content. | ||||
|       tags: | ||||
|         - Admin API | ||||
|       security: | ||||
|         - MyINPulse: [MyINPulse-admin] | ||||
|       parameters: | ||||
|         - in: path | ||||
|           name: appointmentId | ||||
|           required: true | ||||
|           schema: | ||||
|             type: integer | ||||
|           description: ID of the appointment whose report needs updating. | ||||
|           example: 303 | ||||
|       requestBody: | ||||
|         required: true | ||||
|         description: New report content. `idReport` in the body should match the existing report's ID or will be ignored. | ||||
|         content: | ||||
|           application/json: | ||||
|             schema: | ||||
|               $ref: "./main.yaml#/components/schemas/report" | ||||
|       responses: | ||||
|         "200": | ||||
|           description: OK - Report updated successfully. Returns the updated report. | ||||
|           content: | ||||
|              application/json: | ||||
|                 schema: { $ref: "./main.yaml#/components/schemas/report" } | ||||
|         "400": | ||||
|           description: Bad Request - Invalid input (e.g., missing content). | ||||
|         "403": | ||||
|           description: Bad Token - Invalid Keycloack configuration. | ||||
|         "401": | ||||
|           description: Unauthorized. | ||||
|  | ||||
|  | ||||
|   /admin/projects/{projectId}: | ||||
|     delete: | ||||
|       operationId: removeProject | ||||
|       summary: Remove a project | ||||
|       description: Permanently removes the project specified by projectId and potentially related data (use with caution). | ||||
|       tags: | ||||
|         - Admin API | ||||
|       security: | ||||
|           - MyINPulse: [MyINPulse-admin] | ||||
|       parameters: | ||||
|         - in: path | ||||
|           name: projectId | ||||
|           required: true | ||||
|           schema: | ||||
|             type: integer | ||||
|           description: The ID of the project to remove. | ||||
|           example: 12 | ||||
|       responses: | ||||
|         "200": | ||||
|           description: No Content - Project removed successfully. | ||||
|         "400": | ||||
|           description: Bad Request - Invalid project ID format. | ||||
|         "403": | ||||
|           description: Bad Token - Invalid Keycloack configuration. | ||||
|         "401": | ||||
|           description: Unauthorized. | ||||
|            | ||||
|  | ||||
|  | ||||
|   /admin/make-admin/{userId}: | ||||
|     post: | ||||
|       operationId: grantAdminRights | ||||
|       summary: Grant admin rights to a user | ||||
|       tags: | ||||
|         - Admin API | ||||
|       security: | ||||
|         - MyINPulse: [MyINPulse-admin] | ||||
|       description: Elevates the specified user to also have administrator privileges. Assumes the user already exists. | ||||
|       parameters: | ||||
|         - in: path | ||||
|           name: userId | ||||
|           required: true | ||||
|           schema: | ||||
|             type: integer | ||||
|           description: The ID of the user to grant admin rights. | ||||
|           example: 103 | ||||
|       responses: | ||||
|         "200": # Use 200 No Content | ||||
|           description: No Content - Admin rights granted successfully. | ||||
|         "400": | ||||
|           description: Bad Request - Invalid user ID format or user is already an admin. | ||||
|         "403": | ||||
|           description: Bad Token - Invalid Keycloack configuration. | ||||
|         "401": | ||||
|           description: Unauthorized. | ||||
|          | ||||
|   /admin/create-account: | ||||
|     post: | ||||
|       summary: Creates Admin out Jwt Token | ||||
|       tags: | ||||
|         - Admin API | ||||
|       security: | ||||
|         - MyINPulse: [MyINPulse-admin] | ||||
|       description: Create an admin instance in the MyINPulse DB of the information provided from the authenticated user's keycloack token. | ||||
|        The information required in the token are `userSurname`, `username`, `primaryMail`, `secondaryMail`, `phoneNumber`. | ||||
|       responses: | ||||
|         "200":  | ||||
|           description: No Content - Admin user created successfully. | ||||
|         "401": | ||||
|           description: Unauthorized. | ||||
|         "403": | ||||
|           description: Bad Token - Invalid Keycloack configuration. | ||||
							
								
								
									
										197
									
								
								documentation/openapi/src/entrepreneurApi.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										197
									
								
								documentation/openapi/src/entrepreneurApi.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,197 @@ | ||||
| # Entrepreneur API Endpoints | ||||
| paths: | ||||
|   /entrepreneur/projects/request: | ||||
|     post: | ||||
|       operationId: requestProjectCreation | ||||
|       summary: Request creation and validation of a new project | ||||
|       tags: | ||||
|         - Entrepreneurs API | ||||
|       description: |- | ||||
|         Submits a request for a new project. The project details are provided in the request body. | ||||
|         The requesting entrepreneur (identified by the token) will be associated to it. | ||||
|         The project is created with a 'pending' status, awaiting admin approval. | ||||
|       security: | ||||
|         - MyINPulse: [MyINPulse-entrepreneur] | ||||
|       requestBody: | ||||
|         required: true | ||||
|         description: Project details for the request. `status`, `creationDate` are required by the model when being sent but is ignored by the server;  | ||||
|           primarily expects a valid `projectId`, `name`, `logo`. | ||||
|         content: | ||||
|           application/json: | ||||
|             schema: | ||||
|               $ref: "./main.yaml#/components/schemas/project" | ||||
|       responses: | ||||
|         "200": | ||||
|           description: Accepted - Project creation request received and is pending validation. | ||||
|         "400": | ||||
|           description: Bad Request - Invalid input (e.g., missing name). | ||||
|         "401": | ||||
|           description: Unauthorized. | ||||
|         "403": | ||||
|           description: Bad Token - Invalid Keycloack configuration. | ||||
|          | ||||
|   /entrepreneur/sectionCells: # Base path | ||||
|     post: | ||||
|       operationId: addSectionCell | ||||
|       summary: Add a cell to a Lean Canvas section | ||||
|       description: Adds a new cell (like a sticky note) with the provided content to a specific section of the entrepreneur's project's Lean Canvas. Assumes project context is known based on user's token. | ||||
|         `idSectionCell` and `modificationDate` are server-generated so they're values in the request are ignored by the server. | ||||
|       tags: | ||||
|        - Entrepreneurs API | ||||
|       security: | ||||
|         - MyINPulse: [MyINPulse-entrepreneur] | ||||
|       requestBody: | ||||
|         required: true | ||||
|         description: Section cell details. `idSectionCell` and `modificationDate` will be ignored if sent. | ||||
|         content: | ||||
|           application/json: | ||||
|             schema: | ||||
|               $ref: "./main.yaml#/components/schemas/sectionCell" | ||||
|       responses: | ||||
|         "200": | ||||
|           description: Created - Section cell added successfully. Returns the created cell. | ||||
|         "400": | ||||
|           description: Bad Request - Invalid input (e.g., missing content or sectionId). | ||||
|         "401": | ||||
|           description: Unauthorized. | ||||
|         "403": | ||||
|           description: Bad Token - Invalid Keycloack configuration. | ||||
|            | ||||
|   /entrepreneur/sectionCells/{sectionCellId}: | ||||
|     put: | ||||
|       operationId: modifySectionCell | ||||
|       summary: Modify data in a Lean Canvas section cell | ||||
|       description: Updates the content of an existing Lean Canvas section cell specified by `sectionCellId`. The server "updates" (it keeps a record of the previous version to keep a history of all the sectionCells and creates a new ones with the specified modifications) the `modificationDate`. | ||||
|       tags: | ||||
|        - Entrepreneurs API | ||||
|       security: | ||||
|         - MyINPulse: [MyINPulse-entrepreneur] | ||||
|       parameters: | ||||
|         - in: path | ||||
|           name: sectionCellId | ||||
|           required: true | ||||
|           schema: | ||||
|             type: integer | ||||
|           description: The ID of the section cell to modify. | ||||
|           example: 508 | ||||
|       requestBody: | ||||
|         required: true | ||||
|         description: Updated section cell details. `sectionCellId` "the path parameter" is the only id that's consideredn the `sectionCellId` id in the request body is ignored. `modificationDate` should be updated by the server. | ||||
|         content: | ||||
|           application/json: | ||||
|             schema: | ||||
|               $ref: "./main.yaml#/components/schemas/sectionCell" | ||||
|       responses: | ||||
|         "200": | ||||
|           description: OK - Section cell updated successfully. Returns the updated cell. | ||||
|         "404": | ||||
|           description: Bad Request - Invalid input or ID mismatch. | ||||
|         "401": | ||||
|           description: Unauthorized. | ||||
|         "403": | ||||
|           description: Bad Token - Invalid Keycloack configuration. | ||||
|  | ||||
|     delete: | ||||
|       operationId: removeSectionCell | ||||
|       summary: Remove a Lean Canvas section cell | ||||
|       description: Deletes the Lean Canvas section cell specified by `sectionCellId`. | ||||
|       tags: | ||||
|         - Entrepreneurs API | ||||
|       security: | ||||
|         - MyINPulse: [MyINPulse-entrepreneur] | ||||
|       parameters: | ||||
|         - in: path | ||||
|           name: sectionCellId | ||||
|           required: true | ||||
|           schema: | ||||
|             type: integer | ||||
|           description: The ID of the section cell to remove. | ||||
|           example: 509 | ||||
|       responses: | ||||
|         "200": | ||||
|           description: No Content - Section cell removed successfully. | ||||
|         "400": | ||||
|           description: Bad Request - Invalid ID format. | ||||
|         "404": | ||||
|           description: Bad Request - sectionCell not found. | ||||
|         "401": | ||||
|           description: Unauthorized. | ||||
|         "403": | ||||
|           description: Bad Token - Invalid Keycloack configuration. | ||||
|  | ||||
|  | ||||
|   /entrepreneur/projects: | ||||
|     get: | ||||
|       summary: gets the projectId of the project associated with the entrepreneur  | ||||
|       description: returns a list of projectIds of the projects associated with the entrepreneur | ||||
|       tags: | ||||
|        - Entrepreneurs API | ||||
|       security: | ||||
|         - MyINPulse: [MyINPulse-entrepreneur] | ||||
|       parameters: | ||||
|       responses: | ||||
|         "200": | ||||
|           description: OK - Section cell updated successfully. Returns the updated cell. | ||||
|           content: | ||||
|             application/json: | ||||
|               schema: | ||||
|                 type: array | ||||
|                 items: | ||||
|                   $ref: "./main.yaml#/components/schemas/project" | ||||
|         "404": | ||||
|           description: Bad Request - Invalid input or ID mismatch. | ||||
|         "401": | ||||
|           description: Unauthorized or identity not found | ||||
|         "403": | ||||
|           description: Bad Token - Invalid Keycloack configuration. | ||||
|  | ||||
|  | ||||
|   /entrepreneur/projects/project-is-active: | ||||
|     get: | ||||
|       summary: checks if the project associated with an entrepreneur is active  | ||||
|       description: returns a boolean if the project associated with an entrepreneur has an active status  | ||||
|         (i.e has been validated by an admin). The user should be routed to LeanCanvas. any other response code  | ||||
|         should be treated as false | ||||
|       tags: | ||||
|        - Entrepreneurs API | ||||
|       security: | ||||
|         - MyINPulse: [MyINPulse-entrepreneur] | ||||
|       parameters: | ||||
|       responses: | ||||
|         "200": | ||||
|           description: OK - got the value successfully any other response code should be treated as false. | ||||
|           content: | ||||
|             application/json: | ||||
|               schema: | ||||
|                 type: boolean | ||||
|         "404": | ||||
|           description: Bad Request - Invalid input or ID mismatch. | ||||
|         "401": | ||||
|           description: Unauthorized or identity not found | ||||
|         "403": | ||||
|           description: Bad Token - Invalid Keycloack configuration. | ||||
|  | ||||
|   /entrepreneur/projects/has-pending-request: | ||||
|     get: | ||||
|       summary: checks if the user has a pending projectRequest  | ||||
|       description: returns a boolean if the project associated with an entrepreneur has a pending status  | ||||
|         (i.e has not yet been validated by an admin). The user should be routed to a page telling him that he should  | ||||
|         wait for admin validation. any other response code should be treated as false. | ||||
|       tags: | ||||
|        - Entrepreneurs API | ||||
|       security: | ||||
|         - MyINPulse: [MyINPulse-entrepreneur] | ||||
|       parameters: | ||||
|       responses: | ||||
|         "200": | ||||
|           description: OK - got the value successfully any other response code should be treated as false. | ||||
|           content: | ||||
|             application/json: | ||||
|               schema: | ||||
|                 type: boolean | ||||
|         "404": | ||||
|           description: Bad Request - Invalid input or ID mismatch. | ||||
|         "401": | ||||
|           description: Unauthorized or identity not found | ||||
|         "403": | ||||
|           description: Bad Token - Invalid Keycloack configuration. | ||||
							
								
								
									
										159
									
								
								documentation/openapi/src/main.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										159
									
								
								documentation/openapi/src/main.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,159 @@ | ||||
| openapi: 3.0.3 | ||||
| info: | ||||
|   title: MyInpulse Backend API | ||||
|   description: This serves as an OpenAPI documentation for the MyInpulse backend service, covering operations for Entrepreneurs, Admins, and shared functionalities. | ||||
|   version: 0.2.1 | ||||
|  | ||||
| tags: | ||||
|   - name: Entrepreneurs API | ||||
|     description: API endpoints primarily for Entrepreneur users. | ||||
|   - name: Admin API | ||||
|     description: API endpoints restricted to Admin users for management tasks. | ||||
|   - name: Shared API | ||||
|     description: API endpoints accessible by both Entrepreneurs and Admins. | ||||
|   - name: Unauth API | ||||
|     description: API endpoints related to user account management. | ||||
|  | ||||
| components: | ||||
|   schemas: | ||||
|     user: | ||||
|       $ref: "models.yaml#/user" | ||||
|     user-entrepreneur: | ||||
|       $ref: "models.yaml#/user-entrepreneur" | ||||
|     user-admin: | ||||
|       $ref: "models.yaml#/user-admin" | ||||
|     sectionCell: | ||||
|       $ref: "models.yaml#/sectionCell" | ||||
|     project: | ||||
|       $ref: "models.yaml#/project" | ||||
|     report: | ||||
|       $ref: "models.yaml#/report" | ||||
|     appointment: | ||||
|       $ref: "models.yaml#/appointment" | ||||
|     joinRequest: | ||||
|       $ref: "models.yaml#/joinRequest" | ||||
|     projectDecision: | ||||
|       $ref: "models.yaml#/projectDecision" | ||||
|     joinRequestDecision: | ||||
|       $ref: "models.yaml#/joinRequestDecision" | ||||
|  | ||||
|   securitySchemes: | ||||
|       MyINPulse: | ||||
|         type: oauth2 | ||||
|         description: OAuth2 authentication using Keycloak. | ||||
|         flows: | ||||
|           implicit: | ||||
|             authorizationUrl: '{keycloakBaseUrl}/realms/{keycloakRealm}/protocol/openid-connect/auth' | ||||
|             scopes: | ||||
|               MyINPulse-admin: Grants administrator access. | ||||
|               MyINPulse-entrepreneur: Grants standard entrepreneur user access. | ||||
|  | ||||
| servers: | ||||
|     - url: '{serverProtocol}://{serverHost}:{serverPort}' | ||||
|       description: API Server | ||||
|       variables: | ||||
|         serverProtocol: | ||||
|           enum: [http, https] | ||||
|           default: http | ||||
|         serverHost: | ||||
|           default: localhost | ||||
|         serverPort: | ||||
|           enum: ['8081']  | ||||
|           default: '8081' | ||||
|         keycloakBaseUrl: | ||||
|           default: http://localhost:7080 | ||||
|           description: Base URL for the Keycloak server. | ||||
|         keycloakRealm: | ||||
|           default: MyInpulseRealm  | ||||
|           description: Keycloak realm name. | ||||
|  | ||||
| paths: | ||||
|   #       _   _                   _   _        _          _  | ||||
|   #      | | | |_ __   __ _ _   _| |_| |__    / \   _ __ (_) | ||||
|   #      | | | | '_ \ / _` | | | | __| '_ \  / _ \ | '_ \| | | ||||
|   #      | |_| | | | | (_| | |_| | |_| | | |/ ___ \| |_) | | | ||||
|   #       \___/|_| |_|\__,_|\__,_|\__|_| |_/_/   \_\ .__/|_| | ||||
|   #                                                |_|       | ||||
|  | ||||
|   /unauth/finalize: | ||||
|     $ref: "./unauthApi.yaml#/paths/~1unauth~1finalize" | ||||
|   /unauth/request-join/{projectId}: | ||||
|     $ref: "./unauthApi.yaml#/paths/~1unauth~1request-join~1{projectId}" | ||||
|   /unauth/request-admin-role: | ||||
|     $ref: "./unauthApi.yaml#/paths/~1unauth~1request-admin-role" | ||||
|   /unauth/check-if-not-pending: | ||||
|     $ref: "./unauthApi.yaml#/paths/~1unauth~1check-if-not-pending" | ||||
|  | ||||
|   #          _    ____  __  __ ___ _   _      _    ____ ___ | ||||
|   #         / \  |  _ \|  \/  |_ _| \ | |    / \  |  _ \_ _| | ||||
|   #        / _ \ | | | | |\/| || ||  \| |   / _ \ | |_) | | | ||||
|   #       / ___ \| |_| | |  | || || |\  |  / ___ \|  __/| | | ||||
|   #      /_/   \_\____/|_|  |_|___|_| \_| /_/   \_\_|  |___| | ||||
|   # | ||||
|   /admin/pending-accounts: | ||||
|     $ref: "./adminApi.yaml#/paths/~1admin~1pending-accounts" | ||||
|   /admin/accounts/validate/{userId}: | ||||
|     $ref: "./adminApi.yaml#/paths/~1admin~1accounts~1validate~1{userId}" | ||||
|   /admin/request-join: | ||||
|     $ref: "./adminApi.yaml#/paths/~1admin~1request-join" | ||||
|   /admin/request-join/decision/{joinRequestId}: | ||||
|     $ref: "./adminApi.yaml#/paths/~1admin~1request-join~1decision~1{joinRequestId}" | ||||
|   /admin/projects: | ||||
|     $ref: "./adminApi.yaml#/paths/~1admin~1projects" | ||||
|   /admin/projects/pending: | ||||
|     $ref: "./adminApi.yaml#/paths/~1admin~1projects~1pending" | ||||
|   /admin/projects/pending/decision: | ||||
|     $ref: "./adminApi.yaml#/paths/~1admin~1projects~1pending~1decision" | ||||
|   /admin/appointments/report/{appointmentId}: | ||||
|     $ref: "./adminApi.yaml#/paths/~1admin~1appointments~1report~1{appointmentId}" | ||||
|   /admin/appointments/upcoming: | ||||
|     $ref: "./adminApi.yaml#/paths/~1admin~1appointments~1upcoming" | ||||
|   /admin/projects/{projectId}: | ||||
|     $ref: "./adminApi.yaml#/paths/~1admin~1projects~1{projectId}"  | ||||
|   /admin/make-admin/{userId}: | ||||
|     $ref: "./adminApi.yaml#/paths/~1admin~1make-admin~1{userId}" | ||||
|   /admin/create-account: | ||||
|     $ref: "./adminApi.yaml#/paths/~1admin~1create-account" | ||||
|  | ||||
|   #       ____  _                        _      _    ____ ___ | ||||
|   #      / ___|| |__   __ _ _ __ ___  __| |    / \  |  _ \_ _| | ||||
|   #      \___ \| '_ \ / _` | '__/ _ \/ _` |   / _ \ | |_) | | | ||||
|   #       ___) | | | | (_| | | |  __/ (_| |  / ___ \|  __/| | | ||||
|   #      |____/|_| |_|\__,_|_|  \___|\__,_| /_/   \_\_|  |___| | ||||
|   # | ||||
|   /shared/projects/sectionCells/{projectId}/{sectionId}/{date}: | ||||
|     $ref: "./sharedApi.yaml#/paths/~1shared~1projects~1sectionCells~1{projectId}~1{sectionId}~1{date}" | ||||
|   /shared/projects/entrepreneurs/{projectId}: | ||||
|     $ref: "./sharedApi.yaml#/paths/~1shared~1projects~1entrepreneurs~1{projectId}" | ||||
|   /shared/projects/admin/{projectId}: | ||||
|     $ref: "./sharedApi.yaml#/paths/~1shared~1projects~1admin~1{projectId}" | ||||
|   /shared/projects/appointments/{projectId}: | ||||
|     $ref: "./sharedApi.yaml#/paths/~1shared~1projects~1appointments~1{projectId}" | ||||
|   /shared/appointments/report/{appointmentId}: | ||||
|     $ref: "./sharedApi.yaml#/paths/~1shared~1appointments~1report~1{appointmentId}" | ||||
|   /shared/appointments/request: | ||||
|     $ref: "./sharedApi.yaml#/paths/~1shared~1appointments~1request" | ||||
|  | ||||
|   #      _____ _   _ _____ ____  _____ ____  ____  _____ _   _ _____ _   _ ____ | ||||
|   #     | ____| \ | |_   _|  _ \| ____|  _ \|  _ \| ____| \ | | ____| | | |  _ \ | ||||
|   #     |  _| |  \| | | | | |_) |  _| | |_) | |_) |  _| |  \| |  _| | | | | |_) | | ||||
|   #     | |___| |\  | | | |  _ <| |___|  __/|  _ <| |___| |\  | |___| |_| |  _ < | ||||
|   #     |_____|_|_\_| |_| |_| \_\_____|_|   |_| \_\_____|_| \_|_____|\___/|_| \_\ | ||||
|   #        / \  |  _ \_ _| | ||||
|   #       / _ \ | |_) | | | ||||
|   #      / ___ \|  __/| | | ||||
|   #     /_/   \_\_|  |___| | ||||
|   # | ||||
|  | ||||
|   /entrepreneur/projects: | ||||
|     $ref: "./entrepreneurApi.yaml#/paths/~1entrepreneur~1projects" | ||||
|   /entrepreneur/projects/request: | ||||
|     $ref: "./entrepreneurApi.yaml#/paths/~1entrepreneur~1projects~1request" | ||||
|   /entrepreneur/sectionCells: | ||||
|     $ref: "./entrepreneurApi.yaml#/paths/~1entrepreneur~1sectionCells" | ||||
|   /entrepreneur/sectionCells/{sectionCellId}: | ||||
|     $ref: "./entrepreneurApi.yaml#/paths/~1entrepreneur~1sectionCells~1{sectionCellId}" | ||||
|   /entrepreneur/projects/project-is-active: | ||||
|     $ref: "./entrepreneurApi.yaml#/paths/~1entrepreneur~1projects~1project-is-active" | ||||
|   /entrepreneur/projects/has-pending-request: | ||||
|     $ref: "./entrepreneurApi.yaml#/paths/~1entrepreneur~1projects~1has-pending-request" | ||||
							
								
								
									
										210
									
								
								documentation/openapi/src/models.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										210
									
								
								documentation/openapi/src/models.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,210 @@ | ||||
| # models.yaml | ||||
| user: | ||||
|   type: object | ||||
|   properties: | ||||
|     idUser: | ||||
|       type: integer | ||||
|       description: Unique identifier for the user. | ||||
|       #readOnly: true # Typically generated by the server | ||||
|       example: 101 | ||||
|     userSurname: | ||||
|       type: string | ||||
|       description: User's surname (last name). | ||||
|       example: "Doe" | ||||
|     userName: | ||||
|       type: string | ||||
|       description: User's given name (first name). | ||||
|       example: "John" | ||||
|     primaryMail: | ||||
|       type: string | ||||
|       format: email | ||||
|       description: User's primary email address. | ||||
|       example: "john.doe@example.com" | ||||
|     secondaryMail: | ||||
|       type: string | ||||
|       format: email | ||||
|       description: User's secondary email address (optional). | ||||
|       example: "j.doe@personal.com" | ||||
|     phoneNumber: | ||||
|       type: string | ||||
|       description: User's phone number. | ||||
|       example: "+33612345678" # Example using international format | ||||
|  | ||||
| user-entrepreneur: | ||||
|   allOf: | ||||
|     - $ref: "#/user" | ||||
|     - type: object | ||||
|       properties: | ||||
|         school: | ||||
|           type: string | ||||
|           description: The school the entrepreneur attends/attended. | ||||
|           example: "ENSEIRB-MATMECA" | ||||
|         course: | ||||
|           type: string | ||||
|           description: The specific course or program of study. | ||||
|           example: "Electronics" | ||||
|         sneeStatus: | ||||
|           type: boolean | ||||
|           description: Indicates if the user has SNEE status (Statut National d'Étudiant-Entrepreneur). | ||||
|           example: true | ||||
|   example: # Added full object example | ||||
|     idUser: 101 | ||||
|     userSurname: "Doe" | ||||
|     userName: "John" | ||||
|     primaryMail: "john.doe@example.com" | ||||
|     secondaryMail: "j.doe@personal.com" | ||||
|     phoneNumber: "+33612345678" | ||||
|     school: "ENSEIRB-MATMECA" | ||||
|     course: "Electronics" | ||||
|     sneeStatus: true | ||||
|  | ||||
| user-admin: | ||||
|   allOf: | ||||
|     - $ref: "#/user" | ||||
|   # No additional properties needed for this example | ||||
|   example: # Added full object example | ||||
|     idUser: 55 | ||||
|     userSurname: "Admin" | ||||
|     userName: "Super" | ||||
|     primaryMail: "admin@myinpulse.com" | ||||
|     phoneNumber: "+33512345678" | ||||
|  | ||||
| sectionCell: | ||||
|   type: object | ||||
|   description: Represents a cell (like a sticky note) within a specific section of a project's Lean Canvas. | ||||
|   properties: | ||||
|     idSectionCell: | ||||
|       type: integer | ||||
|       description: Unique identifier for the section cell. | ||||
|       #readOnly: true # Generated by server | ||||
|       example: 508 | ||||
|     sectionId: | ||||
|       type: integer | ||||
|       description: Identifier of the Lean Canvas section this cell belongs to (e.g., 1 for Problem, 2 for Solution). | ||||
|       example: 1 | ||||
|     contentSectionCell: | ||||
|       type: string | ||||
|       description: The text content of the section cell. | ||||
|       example: "Users find it hard to track project progress." | ||||
|     modificationDate: | ||||
|       type: string | ||||
|       format: date # Using Java LocalDate -> YYYY-MM-DD | ||||
|       description: The date when this cell was last modified. | ||||
|       #readOnly: true # Typically updated by the server on modification | ||||
|       example: "yyyy-MM-dd HH:mm" | ||||
|  | ||||
| project: | ||||
|   type: object | ||||
|   description: Represents a project being managed or developed. | ||||
|   properties: | ||||
|     idProject: | ||||
|       type: integer | ||||
|       description: Unique identifier for the project. | ||||
|       #readOnly: true # Generated by server | ||||
|       example: 12 | ||||
|     projectName: | ||||
|       type: string | ||||
|       description: The name of the project. | ||||
|       example: "MyInpulse Mobile App" | ||||
|     creationDate: | ||||
|       type: string | ||||
|       format: date # Using Java LocalDate -> YYYY-MM-DD | ||||
|       description: The date when the project was created in the system. | ||||
|       #readOnly: true # Set by server | ||||
|       example: "yyyy-MM-dd HH:mm" | ||||
|     logo: | ||||
|       type: string | ||||
|       format: byte | ||||
|       description: Base64 encoded string representing the project logo image. | ||||
|       example: "/*Base64 encoded string representing the project logo image*/" | ||||
|     status: | ||||
|       type: string | ||||
|       enum: [PENDING, ACTIVE, ENDED, ABORTED, REJECTED] | ||||
|       description: Corresponds to a status enum internal to the backend, it's value in in requests | ||||
|         incoming to the server should be ignored as the client shouldn't be specifying them. | ||||
|       example: "NaN" | ||||
|  | ||||
| joinRequest: | ||||
|   type: object | ||||
|   description: Represents a request from an entrepreneur to join an already existing project. | ||||
|   properties: | ||||
|     idProject: | ||||
|       type: integer | ||||
|       description: the ID of the project the entrepreneur wants to join. | ||||
|       example: 42 | ||||
|     entrepreneur: | ||||
|       $ref: "#/user-entrepreneur" | ||||
|        | ||||
|  | ||||
| report: | ||||
|   type: object | ||||
|   description: Represents a report associated with an appointment. | ||||
|   properties: | ||||
|     idReport: | ||||
|       type: integer | ||||
|       description: Unique identifier for the report. | ||||
|       #readOnly: true # Generated by server | ||||
|       example: 987 | ||||
|     reportContent: | ||||
|       type: string | ||||
|       description: The textual content of the report. Could be plain text or Markdown (specify if known). | ||||
|       example: "Discussed roadmap milestones for Q3. Agreed on preliminary UI mockups." | ||||
|  | ||||
| appointment: # Corrected typo | ||||
|   type: object | ||||
|   description: Represents a scheduled meeting or appointment. | ||||
|   properties: | ||||
|     idAppointment: # Assuming there's an ID | ||||
|         type: integer | ||||
|         description: Unique identifier for the appointment. | ||||
|         #readOnly: true | ||||
|         example: 303 | ||||
|     appointmentDate: | ||||
|       type: string | ||||
|       format: date # Using Java LocalDate -> YYYY-MM-DD | ||||
|       description: The date of the appointment. | ||||
|       example: "2025-05-10" | ||||
|     appointmentTime: | ||||
|       type: string | ||||
|       format: time # Using Java LocalTime -> HH:mm:ss | ||||
|       description: The time of the appointment (local time). | ||||
|       example: "14:30:00" | ||||
|     appointmentDuration: | ||||
|       type: string | ||||
|       description: Duration of the appointment in ISO 8601 duration format (e.g., PT1H30M for 1 hour 30 minutes). | ||||
|       example: "PT1H" # Example for 1 hour | ||||
|     appointmentPlace: | ||||
|       type: string | ||||
|       description: Location or meeting link for the appointment. | ||||
|       example: "Meeting Room 3 / https://meet.example.com/abc-def-ghi" | ||||
|     appointmentSubject: | ||||
|       type: string | ||||
|       description: The main topic or subject of the appointment. | ||||
|       example: "Q3 Roadmap Planning" | ||||
|     # Consider adding project ID or user IDs if relevant association exists | ||||
|  | ||||
| projectDecision: | ||||
|   type: object | ||||
|   description: Represents a decision from an admin to accept a pending project. | ||||
|   properties: | ||||
|     projectId: | ||||
|       type: integer | ||||
|       description: The ID of the project the entrepreneur wants to join. | ||||
|       example: 12 | ||||
|     adminId:  | ||||
|       type: integer | ||||
|       description: The ID of the project the admin who will supervise the project in case of admission. | ||||
|       example: 2 | ||||
|     isAccepted: | ||||
|       type: boolean | ||||
|       description: The boolean value of the decision. | ||||
|       example: "true" | ||||
|  | ||||
| joinRequestDecision: | ||||
|   type: object | ||||
|   description: Represents a decision from an admin to accept a pending project join request. | ||||
|   properties: | ||||
|     isAccepted: | ||||
|       type: boolean | ||||
|       description: The boolean value of the decision. | ||||
|       example: "true" | ||||
							
								
								
									
										193
									
								
								documentation/openapi/src/sharedApi.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										193
									
								
								documentation/openapi/src/sharedApi.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,193 @@ | ||||
| # Shared API Endpoints | ||||
| paths:     | ||||
|  | ||||
|   /shared/projects/sectionCells/{projectId}/{sectionId}/{date}:  | ||||
|     get: | ||||
|       operationId: getSectionCellsByDate | ||||
|       summary: Get project section cells modified on a specific date | ||||
|       tags: | ||||
|         - Shared API | ||||
|       security: | ||||
|         - MyINPulse: [MyINPulse-entrepreneur, MyINPulse-admin] | ||||
|       description: Retrieves section cells belonging to a specific section of a project, filtered by the last modification date. Requires user to have access to the project. | ||||
|       parameters: | ||||
|         - in: path | ||||
|           name: projectId | ||||
|           required: true | ||||
|           schema: { type: integer } | ||||
|           description: ID of the project. | ||||
|         - in: path | ||||
|           name: sectionId | ||||
|           required: true | ||||
|           schema: { type: integer } | ||||
|           description: ID of the Lean Canvas section. | ||||
|         - in: path | ||||
|           name: date | ||||
|           required: true | ||||
|           schema: { type: string, format: date } # Expect YYYY-MM-DD | ||||
|           description: The modification date to filter by (YYYY-MM-DD HH:mm). | ||||
|       responses: | ||||
|         "200": | ||||
|           description: OK - List of section cells matching the criteria. | ||||
|           content: | ||||
|             application/json: | ||||
|               schema: | ||||
|                 type: array | ||||
|                 items: | ||||
|                   $ref: "./main.yaml#/components/schemas/sectionCell" | ||||
|         "400": | ||||
|           description: Bad Request - Invalid parameter format. | ||||
|         "403": | ||||
|           description: Bad Token - Invalid Keycloack configuration. | ||||
|         "401": | ||||
|           description: Unauthorized. | ||||
|            | ||||
|  | ||||
|   /shared/projects/entrepreneurs/{projectId}: | ||||
|     get: | ||||
|       operationId: getProjectEntrepreneurs | ||||
|       summary: Get entrepreneurs associated with a project | ||||
|       tags: | ||||
|         - Shared API | ||||
|       security: | ||||
|         - MyINPulse: [MyINPulse-entrepreneur, MyINPulse-admin] | ||||
|       description: Retrieves a list of entrepreneur users associated with the specified project. Requires access to the project. | ||||
|       parameters: | ||||
|         - in: path | ||||
|           name: projectId | ||||
|           required: true | ||||
|           schema: { type: integer } | ||||
|           description: ID of the project. | ||||
|       responses: | ||||
|         "200": | ||||
|           description: OK - List of entrepreneurs. | ||||
|           content: | ||||
|             application/json: | ||||
|               schema: | ||||
|                 type: array | ||||
|                 items: | ||||
|                   $ref: "./main.yaml#/components/schemas/user-entrepreneur" | ||||
|         "401": | ||||
|           description: Unauthorized.        | ||||
|         "403": | ||||
|           description: Bad Token - Invalid Keycloack configuration.          | ||||
|         "404": | ||||
|           description: Not Found - Project not found.        | ||||
|  | ||||
|   /shared/projects/admin/{projectId}: # Path updated | ||||
|     get: | ||||
|       operationId: getProjectAdmin | ||||
|       summary: Get admin associated with a project | ||||
|       tags: | ||||
|         - Shared API | ||||
|       security: | ||||
|         - MyINPulse: [MyINPulse-entrepreneur, MyINPulse-admin] | ||||
|       description: Retrieves a list of admin users associated with the specified project. Requires access to the project. | ||||
|       parameters: | ||||
|         - in: path | ||||
|           name: projectId | ||||
|           required: true | ||||
|           schema: { type: integer } | ||||
|           description: ID of the project. | ||||
|       responses: | ||||
|         "200": | ||||
|           description: OK - admin. | ||||
|           content: | ||||
|             application/json: | ||||
|               schema: | ||||
|                 $ref: "./main.yaml#/components/schemas/user-admin" | ||||
|         "401": | ||||
|           description: Unauthorized.        | ||||
|         "403": | ||||
|           description: Bad Token - Invalid Keycloack configuration.     | ||||
|         "404": | ||||
|           description: Not Found - Project not found. | ||||
|            | ||||
|  | ||||
|   /shared/projects/appointments/{projectId}: | ||||
|     get: | ||||
|       operationId: getProjectAppointments | ||||
|       summary: Get appointments related to a project | ||||
|       tags: | ||||
|         - Shared API | ||||
|       security: | ||||
|         - MyINPulse: [MyINPulse-entrepreneur, MyINPulse-admin] | ||||
|       description: Retrieves a list of appointments associated with the specified project. Requires access to the project. | ||||
|       parameters: | ||||
|         - in: path | ||||
|           name: projectId | ||||
|           required: true | ||||
|           schema: { type: integer } | ||||
|           description: ID of the project. | ||||
|       responses: | ||||
|         "200": | ||||
|           description: OK - List of appointments. | ||||
|           content: | ||||
|             application/json: | ||||
|               schema: | ||||
|                 type: array | ||||
|                 items: | ||||
|                   $ref: "./main.yaml#/components/schemas/appointment" | ||||
|         "403": | ||||
|           description: Bad Token - Invalid Keycloack configuration. | ||||
|         "401": | ||||
|           description: Unauthorized. | ||||
|            | ||||
|  | ||||
|   /shared/appointments/report/{appointmentId}: # Path updated | ||||
|     get: | ||||
|       operationId: getAppointmentReport # Shared endpoint implies read-only access might be possible | ||||
|       summary: Get the report for an appointment | ||||
|       tags: | ||||
|         - Shared API | ||||
|       security: | ||||
|         - MyINPulse: [MyINPulse-entrepreneur, MyINPulse-admin] | ||||
|       description: Retrieves the report associated with a specific appointment. Requires user to have access to the appointment/project. | ||||
|       parameters: | ||||
|         - in: path | ||||
|           name: appointmentId | ||||
|           required: true | ||||
|           schema: { type: integer } | ||||
|           description: ID of the appointment. | ||||
|       responses: | ||||
|         "200": | ||||
|           description: OK - Report PDF returned. | ||||
|           content: | ||||
|             application/pdf: | ||||
|               schema: | ||||
|                 schema: | ||||
|                 type: string | ||||
|                 format: binary | ||||
|         "401": | ||||
|           description: Unauthorized. | ||||
|         "403": | ||||
|           description: Bad Token - Invalid Keycloack configuration. | ||||
|            | ||||
|  | ||||
|   /shared/appointments/request: | ||||
|     post: | ||||
|       operationId: requestAppointment | ||||
|       summary: Request a new appointment | ||||
|       tags: | ||||
|         - Shared API | ||||
|       security: | ||||
|         - MyINPulse: [MyINPulse-entrepreneur, MyINPulse-admin] | ||||
|       description: Allows a user (entrepreneur or admin) to request a new appointment, potentially with another user or regarding a project. Details in the body. The request might need confirmation or create a pending appointment. | ||||
|       requestBody: | ||||
|         required: true | ||||
|         description: Details of the appointment request. | ||||
|         content: | ||||
|           application/json: | ||||
|             schema: | ||||
|               $ref: "./main.yaml#/components/schemas/appointment" # Assuming request uses same model structure | ||||
|                 # Potentially add projectId or targetUserId here | ||||
|       responses: | ||||
|         "200": # Accepted seems appropriate for a request | ||||
|           description: Accepted - Appointment request submitted. | ||||
|         "400": | ||||
|           description: Bad Request - Invalid appointment details. | ||||
|         "401": | ||||
|           description: Unauthorized. | ||||
|         "403": | ||||
|           description: Bad Token - Invalid Keycloack configuration. | ||||
|            | ||||
							
								
								
									
										90
									
								
								documentation/openapi/src/unauthApi.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										90
									
								
								documentation/openapi/src/unauthApi.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,90 @@ | ||||
|  | ||||
| #       _   _                   _   _        _          _  | ||||
| #      | | | |_ __   __ _ _   _| |_| |__    / \   _ __ (_) | ||||
| #      | | | | '_ \ / _` | | | | __| '_ \  / _ \ | '_ \| | | ||||
| #      | |_| | | | | (_| | |_| | |_| | | |/ ___ \| |_) | | | ||||
| #       \___/|_| |_|\__,_|\__,_|\__|_| |_/_/   \_\ .__/|_| | ||||
| #                                                |_|       | ||||
|  | ||||
| paths: | ||||
|   /unauth/finalize: | ||||
|     post: | ||||
|       summary: Finalize account setup using authentication token | ||||
|       description: |- | ||||
|         Completes the user account creation/setup process in the MyInpulse system. | ||||
|         This endpoint requires the user to be authenticated via Keycloak (e.g., after initial login). | ||||
|         User details (name, email, etc.) are extracted from the authenticated user's token (e.g., Keycloak JWT). | ||||
|         No request body is needed. The account is marked as pending admin validation upon successful finalization. | ||||
|       tags: | ||||
|         - Unauth API | ||||
|       responses: | ||||
|         "200": | ||||
|           description: Created - Account finalized and pending admin validation. Returns the user profile. | ||||
|         "400": | ||||
|           description: Bad Request - Problem processing the token or user data derived from it. | ||||
|         "401": | ||||
|           description: Unauthorized - Valid authentication token required. | ||||
|         "403": | ||||
|           description: Bad Token - Invalid Keycloack configuration. | ||||
|   /unauth/request-join/{projectId}: | ||||
|     post: | ||||
|       summary: Request to join an existing project | ||||
|       description: Submits a request for the authenticated user (keycloack authenticated) to join the project specified by projectId. Their role is then changed to entrepreneur in server and Keycloak. This requires approval from a project admin. | ||||
|       tags: | ||||
|        - Unauth API | ||||
|       parameters: | ||||
|         - in: path | ||||
|           name: projectId | ||||
|           required: true | ||||
|           schema: | ||||
|             type: integer | ||||
|           description: The ID of the project to request joining. | ||||
|           example: 15 | ||||
|       responses: # Moved responses block to correct level | ||||
|         "200": | ||||
|           description: Accepted - Join request submitted and pending approval. | ||||
|         "400": | ||||
|           description: Bad Request - Invalid project ID format  | ||||
|         "409": | ||||
|           description: Already member/request pending. | ||||
|         "401": | ||||
|           description: Unauthorized. | ||||
|         "403": | ||||
|           description: Bad Token - Invalid Keycloack configuration. | ||||
|   /unauth/request-admin-role: | ||||
|     post: | ||||
|       summary: Request to become an admin | ||||
|       description: Submits a request for the authenticated user (keycloack authenticated) to become an admin. Their role is then changed to admin in server and Keycloak. This requires approval from a project admin. | ||||
|       tags: | ||||
|        - Unauth API | ||||
|       responses: | ||||
|         "200": | ||||
|           description: Accepted - Become admin request submitted and pending approval. | ||||
|         "400": | ||||
|           description: Bad Request - Invalid project ID format or already member/request pending. | ||||
|         "401": | ||||
|           description: Unauthorized. | ||||
|         "403": | ||||
|           description: Bad Token - Invalid Keycloack configuration. | ||||
|  | ||||
|   /unauth/check-if-not-pending: | ||||
|     get: | ||||
|       summary: Returns a boolean of whether the user's account is not pending | ||||
|       description: Returns a boolean with value `true` if the user's account is not pending and `false` if it is. | ||||
|       tags: | ||||
|        - Unauth API | ||||
|       responses: | ||||
|         "200": | ||||
|           description: Accepted - Become admin request submitted and pending approval. | ||||
|           content: | ||||
|             application/json: | ||||
|               schema: | ||||
|                 type: boolean   | ||||
|         "400": | ||||
|           description: Bad Request - Invalid project ID format or already member/request pending. | ||||
|         "401": | ||||
|           description: Unauthorized. | ||||
|         "404": | ||||
|           description: Bad Request - User not found in database. | ||||
|         "403": | ||||
|           description: Bad Token - Invalid Keycloack configuration. | ||||
							
								
								
									
										14
									
								
								documentation/openapi/swagger-ui/main.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								documentation/openapi/swagger-ui/main.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | ||||
| const express = require("express"); | ||||
| const swaggerUi = require("swagger-ui-express"); | ||||
| const yaml = require("js-yaml"); | ||||
| const fs = require("fs"); | ||||
|  | ||||
| const app = express(); | ||||
|  | ||||
| const swaggerDocument = yaml.load(fs.readFileSync("../src/bundled.yaml", "utf8")); | ||||
|  | ||||
| app.use("/api-docs", swaggerUi.serve, swaggerUi.setup(swaggerDocument)); | ||||
|  | ||||
| app.listen(3000, () => { | ||||
|     console.log("Swagger UI running at http://localhost:3000/api-docs"); | ||||
| }); | ||||
							
								
								
									
										2178
									
								
								documentation/openapi/swagger-ui/package-lock.json
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										2178
									
								
								documentation/openapi/swagger-ui/package-lock.json
									
									
									
										generated
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										21
									
								
								documentation/openapi/swagger-ui/package.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								documentation/openapi/swagger-ui/package.json
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,21 @@ | ||||
| { | ||||
|   "name": "swagger-ui", | ||||
|   "version": "1.0.0", | ||||
|   "main": "index.js", | ||||
|   "scripts": { | ||||
|     "test": "echo \"Error: no test specified\" && exit 1", | ||||
|     "bundle": "swagger-cli bundle -o ../src/bundled.yaml -t yaml ../src/main.yaml", | ||||
|     "start": "npm run bundle; node main.js" | ||||
|   }, | ||||
|   "keywords": [], | ||||
|   "author": "", | ||||
|   "license": "ISC", | ||||
|   "description": "", | ||||
|   "dependencies": { | ||||
|     "express": "^4.21.2", | ||||
|     "js-yaml": "^4.1.0", | ||||
|     "package.json": "^2.0.1", | ||||
|     "swagger-cli": "^4.0.4", | ||||
|     "swagger-ui-express": "^5.0.1" | ||||
|   } | ||||
| } | ||||
| @@ -1,63 +0,0 @@ | ||||
| { | ||||
|   "entrepreneurs": [ | ||||
|     { "id": 1, "name": "Alice", "email": "alice@example.com" }, | ||||
|     { "id": 2, "name": "Bob", "email": "bob@example.com" }, | ||||
|     { "id": 3, "name": "Charlie", "email": "charlie@example.com" } | ||||
|   ], | ||||
|   "data": [ | ||||
|     { | ||||
|       "projectId": 1, | ||||
|       "title": 1, | ||||
|       "title_text": "1. Problème", | ||||
|       "description": "3 problèmes essentiels à résoudre pour le client" | ||||
|     }, | ||||
|     { | ||||
|       "projectId": 1, | ||||
|       "title": 2, | ||||
|       "title_text": "2. Segments", | ||||
|       "description": "Les segments de clientèle visés" | ||||
|     }, | ||||
|     { | ||||
|       "projectId": 1, | ||||
|       "title": 3, | ||||
|       "title_text": "3. Valeur", | ||||
|       "description": "La proposition de valeur" | ||||
|     }, | ||||
|     { | ||||
|       "projectId": 1, | ||||
|       "title": 4, | ||||
|       "title_text": "4. Solution", | ||||
|       "description": "Les solutions proposées" | ||||
|     }, | ||||
|     { | ||||
|       "projectId": 1, | ||||
|       "title": 5, | ||||
|       "title_text": "5. Avantage", | ||||
|       "description": "Les avantages concurrentiels" | ||||
|     }, | ||||
|     { | ||||
|       "projectId": 1, | ||||
|       "title": 6, | ||||
|       "title_text": "6. Canaux", | ||||
|       "description": "Les canaux de distribution" | ||||
|     }, | ||||
|     { | ||||
|       "projectId": 1, | ||||
|       "title": 7, | ||||
|       "title_text": "7. Indicateurs", | ||||
|       "description": "Les indicateurs clés de performance" | ||||
|     }, | ||||
|     { | ||||
|       "projectId": 1, | ||||
|       "title": 8, | ||||
|       "title_text": "8. Coûts", | ||||
|       "description": "Les coûts associés" | ||||
|     }, | ||||
|     { | ||||
|       "projectId": 1, | ||||
|       "title": 9, | ||||
|       "title_text": "9. Revenus", | ||||
|       "description": "Les sources de revenus" | ||||
|     } | ||||
|   ] | ||||
| } | ||||
| @@ -1,2 +0,0 @@ | ||||
| #!/usr/bin/bash | ||||
| json-server --watch db.json --port 5000 | ||||
| @@ -1,13 +1,13 @@ | ||||
| <!DOCTYPE html> | ||||
| <!doctype html> | ||||
| <html lang=""> | ||||
|   <head> | ||||
|     <meta charset="UTF-8"> | ||||
|     <link rel="icon" href="/favicon.ico"> | ||||
|     <meta name="viewport" content="width=device-width, initial-scale=1.0"> | ||||
|     <title>Vite App</title> | ||||
|   </head> | ||||
|   <body> | ||||
|     <div id="app"></div> | ||||
|     <script type="module" src="/src/main.ts"></script> | ||||
|   </body> | ||||
|     <head> | ||||
|         <meta charset="UTF-8" /> | ||||
|         <link rel="icon" href="/favicon.ico" /> | ||||
|         <meta name="viewport" content="width=device-width, initial-scale=1.0" /> | ||||
|         <title>Vite App</title> | ||||
|     </head> | ||||
|     <body> | ||||
|         <div id="app"></div> | ||||
|         <script type="module" src="/src/main.ts"></script> | ||||
|     </body> | ||||
| </html> | ||||
|   | ||||
							
								
								
									
										10
									
								
								front/MyINPulse-front/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										10
									
								
								front/MyINPulse-front/package-lock.json
									
									
									
										generated
									
									
									
								
							| @@ -10,6 +10,7 @@ | ||||
|       "dependencies": { | ||||
|         "axios": "^1.7.9", | ||||
|         "cors": "^2.8.5", | ||||
|         "jwt-decode": "^4.0.0", | ||||
|         "keycloak-js": "^26.1.0", | ||||
|         "pinia": "^2.3.1", | ||||
|         "pinia-plugin-persistedstate": "^4.2.0", | ||||
| @@ -3588,6 +3589,15 @@ | ||||
|         "graceful-fs": "^4.1.6" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/jwt-decode": { | ||||
|       "version": "4.0.0", | ||||
|       "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-4.0.0.tgz", | ||||
|       "integrity": "sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==", | ||||
|       "license": "MIT", | ||||
|       "engines": { | ||||
|         "node": ">=18" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/keycloak-js": { | ||||
|       "version": "26.1.0", | ||||
|       "resolved": "https://registry.npmjs.org/keycloak-js/-/keycloak-js-26.1.0.tgz", | ||||
|   | ||||
| @@ -18,7 +18,8 @@ | ||||
|     "pinia": "^2.3.1", | ||||
|     "pinia-plugin-persistedstate": "^4.2.0", | ||||
|     "vue": "^3.5.13", | ||||
|     "vue-router": "^4.5.0" | ||||
|     "vue-router": "^4.5.0", | ||||
|     "jwt-decode": "^4.0.0" | ||||
|   }, | ||||
|   "devDependencies": { | ||||
|     "@playwright/test": "^1.49.1", | ||||
|   | ||||
							
								
								
									
										79
									
								
								front/MyINPulse-front/src/ApiClasses/Appointment.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										79
									
								
								front/MyINPulse-front/src/ApiClasses/Appointment.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,79 @@ | ||||
| // appointment.ts | ||||
| class Appointment { | ||||
|     private _idAppointment?: number; | ||||
|     private _appointmentDate?: string; | ||||
|     private _appointmentTime?: string; | ||||
|     private _appointmentDuration?: string; | ||||
|     private _appointmentPlace?: string; | ||||
|     private _appointmentSubject?: string; | ||||
|  | ||||
|     constructor(data: Partial<Appointment> = {}) { | ||||
|         this._idAppointment = data.idAppointment; | ||||
|         this._appointmentDate = data.appointmentDate; | ||||
|         this._appointmentTime = data.appointmentTime; | ||||
|         this._appointmentDuration = data.appointmentDuration; | ||||
|         this._appointmentPlace = data.appointmentPlace; | ||||
|         this._appointmentSubject = data.appointmentSubject; | ||||
|     } | ||||
|  | ||||
|     get idAppointment(): number | undefined { | ||||
|         return this._idAppointment; | ||||
|     } | ||||
|  | ||||
|     set idAppointment(value: number | undefined) { | ||||
|         this._idAppointment = value; | ||||
|     } | ||||
|  | ||||
|     get appointmentDate(): string | undefined { | ||||
|         return this._appointmentDate; | ||||
|     } | ||||
|  | ||||
|     set appointmentDate(value: string | undefined) { | ||||
|         this._appointmentDate = value; | ||||
|     } | ||||
|  | ||||
|     get appointmentTime(): string | undefined { | ||||
|         return this._appointmentTime; | ||||
|     } | ||||
|  | ||||
|     set appointmentTime(value: string | undefined) { | ||||
|         this._appointmentTime = value; | ||||
|     } | ||||
|  | ||||
|     get appointmentDuration(): string | undefined { | ||||
|         return this._appointmentDuration; | ||||
|     } | ||||
|  | ||||
|     set appointmentDuration(value: string | undefined) { | ||||
|         this._appointmentDuration = value; | ||||
|     } | ||||
|  | ||||
|     get appointmentPlace(): string | undefined { | ||||
|         return this._appointmentPlace; | ||||
|     } | ||||
|  | ||||
|     set appointmentPlace(value: string | undefined) { | ||||
|         this._appointmentPlace = value; | ||||
|     } | ||||
|  | ||||
|     get appointmentSubject(): string | undefined { | ||||
|         return this._appointmentSubject; | ||||
|     } | ||||
|  | ||||
|     set appointmentSubject(value: string | undefined) { | ||||
|         this._appointmentSubject = value; | ||||
|     } | ||||
|  | ||||
|     toObject() { | ||||
|         return { | ||||
|             idAppointment: this.idAppointment, | ||||
|             appointmentDate: this.appointmentDate, | ||||
|             appointmentTime: this.appointmentTime, | ||||
|             appointmentDuration: this.appointmentDuration, | ||||
|             appointmentPlace: this.appointmentPlace, | ||||
|             appointmentSubject: this.appointmentSubject, | ||||
|         }; | ||||
|     } | ||||
| } | ||||
|  | ||||
| export default Appointment; | ||||
							
								
								
									
										39
									
								
								front/MyINPulse-front/src/ApiClasses/JoinRequest.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								front/MyINPulse-front/src/ApiClasses/JoinRequest.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,39 @@ | ||||
| // joinRequest.ts | ||||
| import UserEntrepreneur from "./UserEntrepreneur"; | ||||
|  | ||||
| class JoinRequest { | ||||
|     private _idProject?: number; | ||||
|     private _entrepreneur?: UserEntrepreneur; | ||||
|  | ||||
|     constructor(data: Partial<JoinRequest> = {}) { | ||||
|         this._idProject = data.idProject; | ||||
|         this._entrepreneur = data.entrepreneur | ||||
|             ? new UserEntrepreneur(data.entrepreneur) | ||||
|             : undefined; | ||||
|     } | ||||
|  | ||||
|     get idProject(): number | undefined { | ||||
|         return this._idProject; | ||||
|     } | ||||
|  | ||||
|     set idProject(value: number | undefined) { | ||||
|         this._idProject = value; | ||||
|     } | ||||
|  | ||||
|     get entrepreneur(): UserEntrepreneur | undefined { | ||||
|         return this._entrepreneur; | ||||
|     } | ||||
|  | ||||
|     set entrepreneur(value: UserEntrepreneur | undefined) { | ||||
|         this._entrepreneur = value; | ||||
|     } | ||||
|  | ||||
|     toObject() { | ||||
|         return { | ||||
|             idProject: this.idProject, | ||||
|             entrepreneur: this.entrepreneur, | ||||
|         }; | ||||
|     } | ||||
| } | ||||
|  | ||||
| export default JoinRequest; | ||||
							
								
								
									
										24
									
								
								front/MyINPulse-front/src/ApiClasses/JoinRequestDecision.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								front/MyINPulse-front/src/ApiClasses/JoinRequestDecision.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,24 @@ | ||||
| // joinRequestDecision.ts | ||||
| class JoinRequestDecision { | ||||
|     private _isAccepted?: boolean; | ||||
|  | ||||
|     constructor(data: Partial<JoinRequestDecision> = {}) { | ||||
|         this._isAccepted = data.isAccepted; | ||||
|     } | ||||
|  | ||||
|     get isAccepted(): boolean | undefined { | ||||
|         return this._isAccepted; | ||||
|     } | ||||
|  | ||||
|     set isAccepted(value: boolean | undefined) { | ||||
|         this._isAccepted = value; | ||||
|     } | ||||
|  | ||||
|     toObject() { | ||||
|         return { | ||||
|             isAccepted: this._isAccepted, | ||||
|         }; | ||||
|     } | ||||
| } | ||||
|  | ||||
| export default JoinRequestDecision; | ||||
							
								
								
									
										89
									
								
								front/MyINPulse-front/src/ApiClasses/Project.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										89
									
								
								front/MyINPulse-front/src/ApiClasses/Project.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,89 @@ | ||||
| // project.ts | ||||
| class Project { | ||||
|     private _idProject?: number; | ||||
|     private _projectName?: string; | ||||
|     private _creationDate?: string; | ||||
|     private _logo?: string; | ||||
|     private _status?: "PENDING" | "ACTIVE" | "ENDED" | "ABORTED" | "REJECTED"; | ||||
|  | ||||
|     constructor(data: Partial<Project> = {}) { | ||||
|         this._idProject = data.idProject; | ||||
|         this._projectName = data.projectName; | ||||
|         this._creationDate = data.creationDate; | ||||
|         this._logo = data.logo; | ||||
|         this._status = data.status; | ||||
|     } | ||||
|  | ||||
|     get idProject(): number | undefined { | ||||
|         return this._idProject; | ||||
|     } | ||||
|  | ||||
|     set idProject(value: number | undefined) { | ||||
|         this._idProject = value; | ||||
|     } | ||||
|  | ||||
|     get projectName(): string { | ||||
|         return this._projectName ?? ""; | ||||
|     } | ||||
|  | ||||
|     set projectName(value: string | undefined) { | ||||
|         this._projectName = value; | ||||
|     } | ||||
|  | ||||
|     get creationDate(): string { | ||||
|         return this._creationDate ?? ""; | ||||
|     } | ||||
|  | ||||
|     set creationDate(value: string | undefined) { | ||||
|         this._creationDate = value; | ||||
|     } | ||||
|  | ||||
|     get logo(): string | undefined { | ||||
|         return this._logo; | ||||
|     } | ||||
|  | ||||
|     set logo(value: string | undefined) { | ||||
|         this._logo = value; | ||||
|     } | ||||
|  | ||||
|     get status(): | ||||
|         | "PENDING" | ||||
|         | "ACTIVE" | ||||
|         | "ENDED" | ||||
|         | "ABORTED" | ||||
|         | "REJECTED" | ||||
|         | undefined { | ||||
|         return this._status; | ||||
|     } | ||||
|  | ||||
|     set status( | ||||
|         value: | ||||
|             | "PENDING" | ||||
|             | "ACTIVE" | ||||
|             | "ENDED" | ||||
|             | "ABORTED" | ||||
|             | "REJECTED" | ||||
|             | undefined | ||||
|     ) { | ||||
|         this._status = value; | ||||
|     } | ||||
|  | ||||
|     toObject() { | ||||
|         return { | ||||
|             idProject: this.idProject, | ||||
|             projectName: this.projectName, | ||||
|             creationDate: this.creationDate, | ||||
|             logo: this.logo, | ||||
|             status: this.status, | ||||
|         }; | ||||
|     } | ||||
|  | ||||
|     toCreatePayload() { | ||||
|         return { | ||||
|             projectName: this.projectName, | ||||
|             logo: this.logo, | ||||
|         }; | ||||
|     } | ||||
| } | ||||
|  | ||||
| export default Project; | ||||
							
								
								
									
										46
									
								
								front/MyINPulse-front/src/ApiClasses/ProjectDecision.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								front/MyINPulse-front/src/ApiClasses/ProjectDecision.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,46 @@ | ||||
| // projectDecision.ts | ||||
| class ProjectDecision { | ||||
|     private _projectId?: number; | ||||
|     private _adminId?: number; | ||||
|     private _isAccepted?: boolean; | ||||
|  | ||||
|     constructor(data: Partial<ProjectDecision> = {}) { | ||||
|         this._projectId = data.projectId; | ||||
|         this._adminId = data.adminId; | ||||
|         this._isAccepted = data.isAccepted; | ||||
|     } | ||||
|  | ||||
|     get projectId(): number | undefined { | ||||
|         return this._projectId; | ||||
|     } | ||||
|  | ||||
|     set projectId(value: number | undefined) { | ||||
|         this._projectId = value; | ||||
|     } | ||||
|  | ||||
|     get adminId(): number | undefined { | ||||
|         return this._adminId; | ||||
|     } | ||||
|  | ||||
|     set adminId(value: number | undefined) { | ||||
|         this._adminId = value; | ||||
|     } | ||||
|  | ||||
|     get isAccepted(): boolean | undefined { | ||||
|         return this._isAccepted; | ||||
|     } | ||||
|  | ||||
|     set isAccepted(value: boolean | undefined) { | ||||
|         this._isAccepted = value; | ||||
|     } | ||||
|  | ||||
|     toObject() { | ||||
|         return { | ||||
|             projectId: this._projectId, | ||||
|             adminId: this._adminId, | ||||
|             isAccepted: this._isAccepted, | ||||
|         }; | ||||
|     } | ||||
| } | ||||
|  | ||||
| export default ProjectDecision; | ||||
							
								
								
									
										35
									
								
								front/MyINPulse-front/src/ApiClasses/Repport.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								front/MyINPulse-front/src/ApiClasses/Repport.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,35 @@ | ||||
| // report.ts | ||||
| class Report { | ||||
|     private _idReport?: number; | ||||
|     private _reportContent?: string; | ||||
|  | ||||
|     constructor(data: Partial<Report> = {}) { | ||||
|         this._idReport = data.idReport; | ||||
|         this._reportContent = data.reportContent; | ||||
|     } | ||||
|  | ||||
|     get idReport(): number | undefined { | ||||
|         return this._idReport; | ||||
|     } | ||||
|  | ||||
|     set idReport(value: number | undefined) { | ||||
|         this._idReport = value; | ||||
|     } | ||||
|  | ||||
|     get reportContent(): string | undefined { | ||||
|         return this._reportContent; | ||||
|     } | ||||
|  | ||||
|     set reportContent(value: string | undefined) { | ||||
|         this._reportContent = value; | ||||
|     } | ||||
|  | ||||
|     toObject() { | ||||
|         return { | ||||
|             idReport: this._idReport, | ||||
|             reportContent: this._reportContent, | ||||
|         }; | ||||
|     } | ||||
| } | ||||
|  | ||||
| export default Report; | ||||
							
								
								
									
										57
									
								
								front/MyINPulse-front/src/ApiClasses/SectionCell.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								front/MyINPulse-front/src/ApiClasses/SectionCell.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,57 @@ | ||||
| // sectionCell.ts | ||||
| class SectionCell { | ||||
|     private _idSectionCell?: number; | ||||
|     private _sectionId?: number; | ||||
|     private _contentSectionCell?: string; | ||||
|     private _modificationDate?: string; | ||||
|  | ||||
|     constructor(data: Partial<SectionCell> = {}) { | ||||
|         this._idSectionCell = data.idSectionCell; | ||||
|         this._sectionId = data.sectionId; | ||||
|         this._contentSectionCell = data.contentSectionCell; | ||||
|         this._modificationDate = data.modificationDate; | ||||
|     } | ||||
|  | ||||
|     get idSectionCell(): number | undefined { | ||||
|         return this._idSectionCell; | ||||
|     } | ||||
|  | ||||
|     set idSectionCell(value: number | undefined) { | ||||
|         this._idSectionCell = value; | ||||
|     } | ||||
|  | ||||
|     get sectionId(): number | undefined { | ||||
|         return this._sectionId; | ||||
|     } | ||||
|  | ||||
|     set sectionId(value: number | undefined) { | ||||
|         this._sectionId = value; | ||||
|     } | ||||
|  | ||||
|     get contentSectionCell(): string | undefined { | ||||
|         return this._contentSectionCell; | ||||
|     } | ||||
|  | ||||
|     set contentSectionCell(value: string | undefined) { | ||||
|         this._contentSectionCell = value; | ||||
|     } | ||||
|  | ||||
|     get modificationDate(): string | undefined { | ||||
|         return this._modificationDate; | ||||
|     } | ||||
|  | ||||
|     set modificationDate(value: string | undefined) { | ||||
|         this._modificationDate = value; | ||||
|     } | ||||
|  | ||||
|     toObject() { | ||||
|         return { | ||||
|             idSectionCell: this._idSectionCell, | ||||
|             sectionId: this._sectionId, | ||||
|             contentSectionCell: this._contentSectionCell, | ||||
|             modificationDate: this._modificationDate, | ||||
|         }; | ||||
|     } | ||||
| } | ||||
|  | ||||
| export default SectionCell; | ||||
							
								
								
									
										78
									
								
								front/MyINPulse-front/src/ApiClasses/User.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										78
									
								
								front/MyINPulse-front/src/ApiClasses/User.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,78 @@ | ||||
| class User { | ||||
|     private _idUser?: number; | ||||
|     private _userSurname?: string; | ||||
|     private _userName?: string; | ||||
|     private _primaryMail?: string; | ||||
|     private _secondaryMail?: string; | ||||
|     private _phoneNumber?: string; | ||||
|  | ||||
|     constructor(data: Partial<User> = {}) { | ||||
|         this._idUser = data.idUser; | ||||
|         this._userSurname = data.userSurname; | ||||
|         this._userName = data.userName; | ||||
|         this._primaryMail = data.primaryMail; | ||||
|         this._secondaryMail = data.secondaryMail; | ||||
|         this._phoneNumber = data.phoneNumber; | ||||
|     } | ||||
|  | ||||
|     get idUser(): number | undefined { | ||||
|         return this._idUser; | ||||
|     } | ||||
|  | ||||
|     set idUser(value: number | undefined) { | ||||
|         this._idUser = value; | ||||
|     } | ||||
|  | ||||
|     get userSurname(): string | undefined { | ||||
|         return this._userSurname; | ||||
|     } | ||||
|  | ||||
|     set userSurname(value: string | undefined) { | ||||
|         this._userSurname = value; | ||||
|     } | ||||
|  | ||||
|     get userName(): string | undefined { | ||||
|         return this._userName; | ||||
|     } | ||||
|  | ||||
|     set userName(value: string | undefined) { | ||||
|         this._userName = value; | ||||
|     } | ||||
|  | ||||
|     get primaryMail(): string | undefined { | ||||
|         return this._primaryMail; | ||||
|     } | ||||
|  | ||||
|     set primaryMail(value: string | undefined) { | ||||
|         this._primaryMail = value; | ||||
|     } | ||||
|  | ||||
|     get secondaryMail(): string | undefined { | ||||
|         return this._secondaryMail; | ||||
|     } | ||||
|  | ||||
|     set secondaryMail(value: string | undefined) { | ||||
|         this._secondaryMail = value; | ||||
|     } | ||||
|  | ||||
|     get phoneNumber(): string | undefined { | ||||
|         return this._phoneNumber; | ||||
|     } | ||||
|  | ||||
|     set phoneNumber(value: string | undefined) { | ||||
|         this._phoneNumber = value; | ||||
|     } | ||||
|  | ||||
|     toObject() { | ||||
|         return { | ||||
|             idUser: this._idUser, | ||||
|             userSurname: this._userSurname, | ||||
|             userName: this._userName, | ||||
|             primaryMail: this._primaryMail, | ||||
|             secondaryMail: this._secondaryMail, | ||||
|             phoneNumber: this._phoneNumber, | ||||
|         }; | ||||
|     } | ||||
| } | ||||
|  | ||||
| export default User; | ||||
							
								
								
									
										14
									
								
								front/MyINPulse-front/src/ApiClasses/UserAdmin.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								front/MyINPulse-front/src/ApiClasses/UserAdmin.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | ||||
| // user-admin.ts | ||||
| import User from "./User"; | ||||
|  | ||||
| class UserAdmin extends User { | ||||
|     constructor(data: Partial<UserAdmin> = {}) { | ||||
|         super(data); | ||||
|     } | ||||
|  | ||||
|     get idUser(): number | undefined { | ||||
|         return super.idUser; | ||||
|     } | ||||
| } | ||||
|  | ||||
| export default UserAdmin; | ||||
							
								
								
									
										50
									
								
								front/MyINPulse-front/src/ApiClasses/UserEntrepreneur.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								front/MyINPulse-front/src/ApiClasses/UserEntrepreneur.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,50 @@ | ||||
| // user-entrepreneur.ts | ||||
| import User from "./User"; | ||||
|  | ||||
| class UserEntrepreneur extends User { | ||||
|     private _school?: string; | ||||
|     private _course?: string; | ||||
|     private _sneeStatus?: boolean; | ||||
|  | ||||
|     constructor(data: Partial<UserEntrepreneur> = {}) { | ||||
|         super(data); | ||||
|         this._school = data.school; | ||||
|         this._course = data.course; | ||||
|         this._sneeStatus = data.sneeStatus; | ||||
|     } | ||||
|  | ||||
|     get school(): string | undefined { | ||||
|         return this._school; | ||||
|     } | ||||
|  | ||||
|     set school(value: string | undefined) { | ||||
|         this._school = value; | ||||
|     } | ||||
|  | ||||
|     get course(): string | undefined { | ||||
|         return this._course; | ||||
|     } | ||||
|  | ||||
|     set course(value: string | undefined) { | ||||
|         this._course = value; | ||||
|     } | ||||
|  | ||||
|     get sneeStatus(): boolean | undefined { | ||||
|         return this._sneeStatus; | ||||
|     } | ||||
|  | ||||
|     set sneeStatus(value: boolean | undefined) { | ||||
|         this._sneeStatus = value; | ||||
|     } | ||||
|  | ||||
|     toObject() { | ||||
|         return { | ||||
|             ...super.toObject(), | ||||
|             school: this._school, | ||||
|             course: this._course, | ||||
|             sneeStatus: this._sneeStatus, | ||||
|         }; | ||||
|     } | ||||
| } | ||||
|  | ||||
| export default UserEntrepreneur; | ||||
| @@ -1,15 +1,12 @@ | ||||
| <script setup lang="ts"> | ||||
| import { RouterLink, RouterView } from 'vue-router' | ||||
| import { /*RouterLink,*/ RouterView } from "vue-router"; | ||||
| import ErrorWrapper from "@/views/errorWrapper.vue"; | ||||
| </script> | ||||
|  | ||||
| <template> | ||||
|  | ||||
| <Header /> | ||||
|     <RouterLink to="/">Home</RouterLink> | | ||||
|     <RouterLink to="/canvas">Canvas</RouterLink> | ||||
|     <Header /> | ||||
|     <ErrorWrapper /> | ||||
|     <!--<RouterLink to="/">Home</RouterLink> | --> | ||||
|     <!--<RouterLink to="/canvas">Canvas</RouterLink> --> | ||||
|     <RouterView /> | ||||
| </template> | ||||
|  | ||||
|  | ||||
|  | ||||
|   | ||||
							
								
								
									
										98
									
								
								front/MyINPulse-front/src/components/AddProjectForm.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										98
									
								
								front/MyINPulse-front/src/components/AddProjectForm.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,98 @@ | ||||
| <template> | ||||
|     <form class="add-project-form" @submit.prevent="submitProject"> | ||||
|         <h2>Ajouter un projet</h2> | ||||
|  | ||||
|         <div class="form-group"> | ||||
|             <label for="projectName">Nom du projet</label> | ||||
|             <input | ||||
|                 id="projectName" | ||||
|                 v-model="project.projectName" | ||||
|                 type="text" | ||||
|                 required | ||||
|             /> | ||||
|         </div> | ||||
|  | ||||
|         <div class="form-group"> | ||||
|             <label for="logo">Logo</label> | ||||
|             <input | ||||
|                 id="logo" | ||||
|                 v-model="project.logo" | ||||
|                 type="text" | ||||
|                 placeholder="(Description)" | ||||
|             /> | ||||
|         </div> | ||||
|  | ||||
|         <button type="submit">Ajouter</button> | ||||
|     </form> | ||||
| </template> | ||||
|  | ||||
| <script setup lang="ts"> | ||||
| import { ref } from "vue"; | ||||
| import Project from "@/ApiClasses/Project"; | ||||
| import { addProjectManually } from "@/services/Apis/Admin"; | ||||
|  | ||||
| const project = ref(new Project({})); | ||||
|  | ||||
| function submitProject() { | ||||
|     addProjectManually( | ||||
|         project.value.toCreatePayload(), | ||||
|         (res) => console.log("Success:", res.data), | ||||
|         (err) => console.error("Error:", err) | ||||
|     ); | ||||
| } | ||||
| </script> | ||||
|  | ||||
| <style scoped> | ||||
| h2 { | ||||
|     font-size: 1.5rem; | ||||
|     color: #333; | ||||
|     margin-bottom: 1.2rem; | ||||
|     border-bottom: 2px solid #ddd; | ||||
|     padding-bottom: 0.5rem; | ||||
| } | ||||
|  | ||||
| .add-project-form { | ||||
|     max-width: 500px; | ||||
|     margin: 0 auto; | ||||
|     padding: 20px; | ||||
|     background: #fff; | ||||
|     border-radius: 10px; | ||||
|     box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); | ||||
| } | ||||
|  | ||||
| h2 { | ||||
|     margin-bottom: 20px; | ||||
|     font-size: 24px; | ||||
|     color: #333; | ||||
| } | ||||
|  | ||||
| .form-group { | ||||
|     margin-bottom: 15px; | ||||
|     display: flex; | ||||
|     flex-direction: column; | ||||
| } | ||||
|  | ||||
| label { | ||||
|     font-weight: bold; | ||||
|     margin-bottom: 5px; | ||||
| } | ||||
|  | ||||
| input { | ||||
|     padding: 8px; | ||||
|     border-radius: 5px; | ||||
|     border: 1px solid #ccc; | ||||
| } | ||||
|  | ||||
| button { | ||||
|     background-color: #4caf50; | ||||
|     color: white; | ||||
|     padding: 10px 15px; | ||||
|     border: none; | ||||
|     border-radius: 5px; | ||||
|     cursor: pointer; | ||||
| } | ||||
|  | ||||
| button:hover { | ||||
|     background-color: #45a049; | ||||
| } | ||||
| </style> | ||||
							
								
								
									
										192
									
								
								front/MyINPulse-front/src/components/AdminAppointments.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										192
									
								
								front/MyINPulse-front/src/components/AdminAppointments.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,192 @@ | ||||
| <template> | ||||
|     <div> | ||||
|         <h2>Ajouter / Modifier un Rapport pour un Rendez-vous</h2> | ||||
|  | ||||
|         <form @submit.prevent="submitReport"> | ||||
|             <div> | ||||
|                 <label for="appointmentId">ID du rendez-vous :</label> | ||||
|                 <div style="display: flex; align-items: center; gap: 0.5rem"> | ||||
|                     <button type="button" @click="decrementId">-</button> | ||||
|                     <input | ||||
|                         id="appointmentId" | ||||
|                         v-model.number="appointmentId" | ||||
|                         type="number" | ||||
|                         min="1" | ||||
|                         style="width: 60px; text-align: center" | ||||
|                     /> | ||||
|                     <button type="button" @click="incrementId">+</button> | ||||
|                 </div> | ||||
|             </div> | ||||
|  | ||||
|             <div> | ||||
|                 <label for="reportContent">Contenu du rapport :</label> | ||||
|                 <textarea | ||||
|                     id="reportContent" | ||||
|                     v-model="reportContent" | ||||
|                     required | ||||
|                 ></textarea> | ||||
|             </div> | ||||
|  | ||||
|             <button type="submit"> | ||||
|                 {{ isUpdate ? "Mettre à jour" : "Créer" }} le rapport | ||||
|             </button> | ||||
|         </form> | ||||
|  | ||||
|         <p v-if="responseMessage">{{ responseMessage }}</p> | ||||
|     </div> | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
| import { | ||||
|     createAppointmentReport, | ||||
|     updateAppointmentReport, | ||||
| } from "@/services/Apis/Admin"; | ||||
|  | ||||
| export default { | ||||
|     name: "AdminAppointmentsComponent", | ||||
|     data() { | ||||
|         return { | ||||
|             appointmentId: 1, | ||||
|             reportContent: "", | ||||
|             responseMessage: "", | ||||
|             isUpdate: false, | ||||
|         }; | ||||
|     }, | ||||
|     watch: { | ||||
|         appointmentId(newVal) { | ||||
|             if (newVal) { | ||||
|                 this.isUpdate = true; | ||||
|             } | ||||
|         }, | ||||
|     }, | ||||
|     methods: { | ||||
|         incrementId() { | ||||
|             this.appointmentId++; | ||||
|         }, | ||||
|         decrementId() { | ||||
|             if (this.appointmentId > 1) { | ||||
|                 this.appointmentId--; | ||||
|             } | ||||
|         }, | ||||
|         submitReport() { | ||||
|             const reportData = { content: this.reportContent }; | ||||
|             const onSuccess = (response) => { | ||||
|                 if (response.status === 201 || response.status === 200) { | ||||
|                     this.responseMessage = | ||||
|                         "Rapport " + | ||||
|                         (this.isUpdate ? "mis à jour" : "créé") + | ||||
|                         " avec succès."; | ||||
|                 } | ||||
|             }; | ||||
|             const onError = (error) => { | ||||
|                 console.error( | ||||
|                     "Erreur lors de l'envoi du rapport :", | ||||
|                     error.response || error.message | ||||
|                 ); | ||||
|                 if (error.response && error.response.status === 400) { | ||||
|                     this.responseMessage = | ||||
|                         "Requête invalide. Vérifiez les informations."; | ||||
|                 } else if (error.response && error.response.status === 401) { | ||||
|                     this.responseMessage = | ||||
|                         "Vous n'êtes pas autorisé à effectuer cette action."; | ||||
|                 } else { | ||||
|                     this.responseMessage = | ||||
|                         "Une erreur est survenue. Veuillez réessayer."; | ||||
|                 } | ||||
|             }; | ||||
|  | ||||
|             if (this.isUpdate) { | ||||
|                 updateAppointmentReport( | ||||
|                     this.appointmentId, | ||||
|                     reportData, | ||||
|                     onSuccess, | ||||
|                     onError | ||||
|                 ); | ||||
|             } else { | ||||
|                 createAppointmentReport( | ||||
|                     this.appointmentId, | ||||
|                     reportData, | ||||
|                     onSuccess, | ||||
|                     onError | ||||
|                 ); | ||||
|             } | ||||
|         }, | ||||
|     }, | ||||
| }; | ||||
| </script> | ||||
|  | ||||
| <style scoped> | ||||
| /* Centrer le formulaire */ | ||||
| div { | ||||
|     max-width: 600px; | ||||
|     margin: 0 auto; | ||||
|     padding: 1rem; | ||||
|     background-color: #f9f9f9; | ||||
|     border-radius: 8px; | ||||
|     box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); | ||||
| } | ||||
|  | ||||
| /* Titre */ | ||||
| h2 { | ||||
|     text-align: center; | ||||
|     color: #333; | ||||
|     margin-bottom: 1rem; | ||||
| } | ||||
|  | ||||
| /* Formulaire */ | ||||
| form { | ||||
|     display: flex; | ||||
|     flex-direction: column; | ||||
|     gap: 1rem; | ||||
| } | ||||
|  | ||||
| /* Boutons */ | ||||
| button { | ||||
|     padding: 0.5rem 1rem; | ||||
|     background-color: #007bff; | ||||
|     color: white; | ||||
|     border: none; | ||||
|     border-radius: 4px; | ||||
|     cursor: pointer; | ||||
|     transition: background-color 0.3s ease; | ||||
| } | ||||
|  | ||||
| button:hover { | ||||
|     background-color: #0056b3; | ||||
| } | ||||
|  | ||||
| /* Input et Textarea */ | ||||
| input[type="number"], | ||||
| textarea { | ||||
|     width: 100%; | ||||
|     padding: 0.5rem; | ||||
|     border: 1px solid #ccc; | ||||
|     border-radius: 4px; | ||||
|     font-size: 1rem; | ||||
| } | ||||
|  | ||||
| textarea { | ||||
|     height: 120px; | ||||
|     resize: none; | ||||
| } | ||||
|  | ||||
| /* Message de réponse */ | ||||
| p { | ||||
|     text-align: center; | ||||
|     font-weight: bold; | ||||
|     color: green; | ||||
| } | ||||
|  | ||||
| /* Boutons d'incrémentation/décrémentation */ | ||||
| div[style*="display: flex"] button { | ||||
|     background-color: #6c757d; | ||||
|     color: white; | ||||
|     border: none; | ||||
|     border-radius: 4px; | ||||
|     padding: 0.5rem; | ||||
| } | ||||
|  | ||||
| div[style*="display: flex"] button:hover { | ||||
|     background-color: #5a6268; | ||||
| } | ||||
| </style> | ||||
| @@ -1,75 +0,0 @@ | ||||
| <template> | ||||
|     <div id="agenda"> | ||||
|         <h3>Rendez-vous</h3> | ||||
|         <table> | ||||
|         <tbody> | ||||
|             <tr v-for=" (p, index) in projectRDV" :key="index" > | ||||
|                 <td>{{ p.projectName }} </td> <td>{{ p.date }}</td> <td>{{ p.lieu }}</td> | ||||
|             </tr> | ||||
|         </tbody> | ||||
|         </table> | ||||
|     </div> | ||||
| </template> | ||||
|  | ||||
| <script setup lang="ts"> | ||||
|     import { defineProps } from "vue"; | ||||
|  | ||||
|     interface rendezVous{ | ||||
|         projectName: String, | ||||
|         date: String, | ||||
|         lieu: String, | ||||
|     } | ||||
|  | ||||
|     const props = defineProps<{ | ||||
|         projectRDV: rendezVous[] | ||||
|     }>(); | ||||
| </script> | ||||
|  | ||||
| <style scoped> | ||||
|  #agenda {    | ||||
|     padding: 20px; | ||||
|   } | ||||
|  | ||||
|   /* Table Styling */ | ||||
|  table { | ||||
|     width: 100%; | ||||
|     border-collapse: collapse; | ||||
|     font-family: Arial, sans-serif; | ||||
|     text-align: left; | ||||
|     margin-top: 20px; | ||||
|     border: 1px solid #ccc; | ||||
|   } | ||||
|    | ||||
|   /* Header Row (if exists) */ | ||||
|   th { | ||||
|     background-color: #f4f4f4; | ||||
|     padding: 12px; | ||||
|     font-weight: bold; | ||||
|     border: 1px solid #ccc; | ||||
|   } | ||||
|    | ||||
|   /* Table Body Rows */ | ||||
|   tbody tr { | ||||
|     border-bottom: 1px solid #ddd; | ||||
|     transition: background-color 0.2s ease; /* Smooth hover effect */ | ||||
|   } | ||||
|    | ||||
|   tbody tr:hover { | ||||
|     background-color: #f9f9f9; /* Highlight row on hover */ | ||||
|   } | ||||
|    | ||||
|   /* Cells Styling */ | ||||
|   td { | ||||
|     padding: 10px; | ||||
|     border: 1px solid #eee; | ||||
|     font-size: 14px; | ||||
|     vertical-align: middle; /* Align text to middle */ | ||||
|   } | ||||
|    | ||||
|   /* First Column Styling */ | ||||
|   td:first-child { | ||||
|     text-align: center; | ||||
|     width: 50px; /* Adjust width as needed */ | ||||
|   } | ||||
|   | ||||
| </style> | ||||
							
								
								
									
										93
									
								
								front/MyINPulse-front/src/components/AgendaComponent.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										93
									
								
								front/MyINPulse-front/src/components/AgendaComponent.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,93 @@ | ||||
| <template> | ||||
|     <div id="agenda"> | ||||
|         <h3>Rendez-vous</h3> | ||||
|         <table> | ||||
|             <thead> | ||||
|                 <tr> | ||||
|                     <th>Projet</th> | ||||
|                     <th>Date</th> | ||||
|                     <th>Lieu</th> | ||||
|                 </tr> | ||||
|             </thead> | ||||
|             <tbody> | ||||
|                 <tr v-for="(p, index) in projectRDV" :key="index"> | ||||
|                     <td>{{ p.projectName }}</td> | ||||
|                     <td>{{ p.date }}</td> | ||||
|                     <td>{{ p.lieu }}</td> | ||||
|                 </tr> | ||||
|             </tbody> | ||||
|         </table> | ||||
|     </div> | ||||
| </template> | ||||
|  | ||||
| <script setup lang="ts"> | ||||
| import { defineProps } from "vue"; | ||||
|  | ||||
| interface rendezVous { | ||||
|     projectName: string; | ||||
|     date: string; | ||||
|     lieu: string; | ||||
| } | ||||
|  | ||||
| defineProps<{ | ||||
|     projectRDV: rendezVous[]; | ||||
| }>(); | ||||
| </script> | ||||
|  | ||||
| <style scoped> | ||||
| h3 { | ||||
|     font-size: 1.5rem; | ||||
|     color: #333; | ||||
|     margin-bottom: 1.2rem; | ||||
|     border-bottom: 2px solid #ddd; | ||||
|     padding-bottom: 0.5rem; | ||||
| } | ||||
|  | ||||
| #agenda { | ||||
|     padding: 20px; | ||||
|     background-color: white; | ||||
|     border-radius: 10px; | ||||
|     box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05); | ||||
| } | ||||
|  | ||||
| /* Table Styling */ | ||||
| table { | ||||
|     width: 100%; | ||||
|     border-collapse: collapse; | ||||
|     font-family: Arial, sans-serif; | ||||
|     text-align: left; | ||||
|     margin-top: 20px; | ||||
|     border: 1px solid #ccc; | ||||
| } | ||||
|  | ||||
| th { | ||||
|     background-color: #f0f2f5; | ||||
|     padding: 12px; | ||||
|     font-weight: 600; | ||||
|     color: #333; | ||||
| } | ||||
|  | ||||
| /* Table Body Rows */ | ||||
| tbody tr { | ||||
|     border-bottom: 1px solid #ddd; | ||||
|     transition: background-color 0.2s ease; /* Smooth hover effect */ | ||||
| } | ||||
|  | ||||
| tbody tr:hover { | ||||
|     background-color: #f9f9f9; /* Highlight row on hover */ | ||||
| } | ||||
|  | ||||
| /* Cells Styling */ | ||||
| td { | ||||
|     padding: 10px; | ||||
|     border: 1px solid #eee; | ||||
|     font-size: 14px; | ||||
|     vertical-align: middle; /* Align text to middle */ | ||||
| } | ||||
|  | ||||
| /* First Column Styling */ | ||||
| td:first-child { | ||||
|     text-align: center; | ||||
|     width: 50px; /* Adjust width as needed */ | ||||
| } | ||||
| </style> | ||||
							
								
								
									
										77
									
								
								front/MyINPulse-front/src/components/AllEntrep.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										77
									
								
								front/MyINPulse-front/src/components/AllEntrep.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,77 @@ | ||||
| <template> | ||||
|     <div class="entrepreneur-list"> | ||||
|         <button @click="fetchEntrepreneurs">Afficher les entrepreneurs</button> | ||||
|  | ||||
|         <ul v-if="entrepreneurs.length"> | ||||
|             <li | ||||
|                 v-for="entrepreneur in entrepreneurs" | ||||
|                 :key="entrepreneur.idUser" | ||||
|             > | ||||
|                 {{ entrepreneur.userName }} {{ entrepreneur.userSurname }} - | ||||
|                 {{ entrepreneur.primaryMail }} | ||||
|             </li> | ||||
|         </ul> | ||||
|  | ||||
|         <p v-else-if="loading">Chargement...</p> | ||||
|         <p v-else-if="errorMessage">{{ errorMessage }}</p> | ||||
|     </div> | ||||
| </template> | ||||
|  | ||||
| <script setup lang="ts"> | ||||
| import { ref } from "vue"; | ||||
| import type { AxiosResponse, AxiosError } from "axios"; | ||||
| import { getAllEntrepreneurs } from "@/services/Apis/Unauth"; // ajuste ce chemin selon ton projet | ||||
|  | ||||
| // Types | ||||
| interface Entrepreneur { | ||||
|     idUser: number; | ||||
|     userName: string; | ||||
|     userSurname: string; | ||||
|     primaryMail: string; | ||||
| } | ||||
|  | ||||
| // Refs | ||||
| const entrepreneurs = ref<Entrepreneur[]>([]); | ||||
| const loading = ref(false); | ||||
| const errorMessage = ref(""); | ||||
|  | ||||
| // Méthode | ||||
| function fetchEntrepreneurs() { | ||||
|     loading.value = true; | ||||
|     errorMessage.value = ""; | ||||
|     getAllEntrepreneurs( | ||||
|         (response: AxiosResponse) => { | ||||
|             if (Array.isArray(response.data)) { | ||||
|                 entrepreneurs.value = response.data; | ||||
|             } else { | ||||
|                 console.error("Format inattendu :", response.data); | ||||
|                 errorMessage.value = "Réponse inattendue du serveur."; | ||||
|             } | ||||
|             loading.value = false; | ||||
|         }, | ||||
|         (error: AxiosError) => { | ||||
|             errorMessage.value = "Erreur lors du chargement des entrepreneurs."; | ||||
|             console.error(error); | ||||
|             loading.value = false; | ||||
|         } | ||||
|     ); | ||||
| } | ||||
| </script> | ||||
|  | ||||
| <style scoped> | ||||
| button { | ||||
|     margin-bottom: 1rem; | ||||
|     padding: 0.5rem 1rem; | ||||
|     font-size: 1rem; | ||||
|     cursor: pointer; | ||||
| } | ||||
|  | ||||
| ul { | ||||
|     list-style-type: none; | ||||
|     padding: 0; | ||||
| } | ||||
|  | ||||
| li { | ||||
|     margin-bottom: 0.5rem; | ||||
| } | ||||
| </style> | ||||
							
								
								
									
										90
									
								
								front/MyINPulse-front/src/components/GetAppointments.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										90
									
								
								front/MyINPulse-front/src/components/GetAppointments.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,90 @@ | ||||
| <template> | ||||
|     <div class="appointment-list"> | ||||
|         <h2>Liste des rendez-vous</h2> | ||||
|         <h3>de {{ store.user.username }}</h3> | ||||
|         <ul v-if="appointments.length"> | ||||
|             <li v-for="appt in appointments" :key="appt.idAppointment"> | ||||
|                 <strong>Sujet :</strong> {{ appt.appointmentSubject }}<br /> | ||||
|                 <strong>Date :</strong> {{ appt.appointmentDate }}<br /> | ||||
|                 <strong>Heure :</strong> {{ appt.appointmentTime }}<br /> | ||||
|                 <strong>Durée :</strong> {{ appt.appointmentDuration }}<br /> | ||||
|                 <strong>Lieu :</strong> {{ appt.appointmentPlace }} | ||||
|                 <hr /> | ||||
|             </li> | ||||
|         </ul> | ||||
|  | ||||
|         <p v-else>Aucun rendez-vous trouvé.</p> | ||||
|     </div> | ||||
| </template> | ||||
|  | ||||
| <script setup lang="ts"> | ||||
| import { onMounted, ref } from "vue"; | ||||
| import { callApi } from "@/services/api"; | ||||
| import Appointment from "@/ApiClasses/Appointment"; | ||||
| import { store } from "@/main.ts"; | ||||
|  | ||||
| // Define the interface for the API response | ||||
| interface AppointmentResponse { | ||||
|     idAppointment: number; | ||||
|     appointmentSubject: string; | ||||
|     appointmentDate: string; | ||||
|     appointmentTime: string; | ||||
|     appointmentDuration: string; | ||||
|     appointmentPlace: string; | ||||
| } | ||||
|  | ||||
| const appointments = ref<Appointment[]>([]); | ||||
|  | ||||
| function loadAppointments() { | ||||
|     if (!store.user) { | ||||
|         console.error( | ||||
|             "L'utilisateur n'est pas connecté ou les données utilisateur ne sont pas disponibles." | ||||
|         ); | ||||
|         return; | ||||
|     } | ||||
|     //console.log("username :", store.user.username); | ||||
|     //console.log("token  :", store.user.token); | ||||
|     callApi( | ||||
|         "/admin/appointments/upcoming", | ||||
|         (response) => { | ||||
|             appointments.value = response.data.map( | ||||
|                 (item: AppointmentResponse) => new Appointment(item) | ||||
|             ); | ||||
|         }, | ||||
|         (error) => { | ||||
|             console.error( | ||||
|                 "Erreur lors de la récupération des rendez-vous :", | ||||
|                 error | ||||
|             ); | ||||
|         } | ||||
|     ); | ||||
| } | ||||
|  | ||||
| onMounted(() => { | ||||
|     loadAppointments(); | ||||
| }); | ||||
| </script> | ||||
|  | ||||
| <style scoped> | ||||
| .appointment-list { | ||||
|     max-width: 600px; | ||||
|     margin: 0 auto; | ||||
|     padding: 20px; | ||||
|     background: #fdfdfd; | ||||
|     border-radius: 10px; | ||||
|     box-shadow: 0 4px 10px rgba(0, 0, 0, 0.08); | ||||
| } | ||||
|  | ||||
| h2 { | ||||
|     font-size: 1.8rem; | ||||
|     margin-bottom: 20px; | ||||
|     color: #333; | ||||
|     border-bottom: 2px solid #ddd; | ||||
|     padding-bottom: 10px; | ||||
| } | ||||
|  | ||||
| li { | ||||
|     margin-bottom: 15px; | ||||
|     line-height: 1.6; | ||||
| } | ||||
| </style> | ||||
| @@ -1,26 +1,64 @@ | ||||
| <template> | ||||
|     <header> | ||||
|         <img src="./icons/logo inpulse.png" alt="INPulse" /> | ||||
|     <header class="header"> | ||||
|         <a | ||||
|             href="https://www.bordeaux-inp.fr/fr/lincubateur-bordeaux-inpulse" | ||||
|             target="_blank" | ||||
|             rel="noopener" | ||||
|         > | ||||
|             <img | ||||
|                 src="./icons/logo inpulse.png" | ||||
|                 alt="INPulse Logo" | ||||
|                 class="logo" | ||||
|             /> | ||||
|         </a> | ||||
|  | ||||
|         <div class="header-actions"> | ||||
|             <button class="logout" @click="store.logout">Logout</button> | ||||
|         </div> | ||||
|     </header> | ||||
| </template> | ||||
|  | ||||
| <script lang="ts"> | ||||
| export default { | ||||
|     name: "HeaderComponent", | ||||
| }; | ||||
| <script setup lang="ts"> | ||||
| import { store } from "@/main.ts"; | ||||
| </script> | ||||
|  | ||||
| <style scoped> | ||||
| header img { | ||||
|     width: 100px; | ||||
| } | ||||
| @import "@/components/canvas/style-project.css"; | ||||
|  | ||||
| header { | ||||
| .header { | ||||
|     display: flex; | ||||
|     justify-content: space-between; | ||||
|     align-items: center; | ||||
|     padding: 10px 20px; | ||||
|     background-color: #fff; | ||||
|     border-bottom: 2px solid #ddd; | ||||
|     padding: 15px 30px; | ||||
|     background-color: #f9f9f9; | ||||
|     box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); | ||||
| } | ||||
|  | ||||
| .logo { | ||||
|     height: 50px; | ||||
| } | ||||
|  | ||||
| .header-actions { | ||||
|     display: flex; | ||||
|     align-items: center; | ||||
|     gap: 20px; | ||||
|     position: relative; | ||||
| } | ||||
|  | ||||
| .logout { | ||||
|     background-color: #009cde; | ||||
|     color: white; | ||||
|     border: none; | ||||
|     padding: 10px 15px; | ||||
|     cursor: pointer; | ||||
|     font-size: 14px; | ||||
|     border-radius: 5px; | ||||
|     text-decoration: none; | ||||
|     transition: background-color 0.2s ease; | ||||
|     font-family: Arial, sans-serif; | ||||
| } | ||||
|  | ||||
| .logout:hover { | ||||
|     background-color: #007bad; | ||||
| } | ||||
| </style> | ||||
|   | ||||
							
								
								
									
										229
									
								
								front/MyINPulse-front/src/components/LoginComponent.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										229
									
								
								front/MyINPulse-front/src/components/LoginComponent.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,229 @@ | ||||
| <script lang="ts" setup> | ||||
| import { onMounted } from "vue"; | ||||
| import { useRouter } from "vue-router"; | ||||
| import { jwtDecode } from "jwt-decode"; // i hope this doesn't break the code later | ||||
| import { store } from "../main.ts"; | ||||
| import { checkPending } from "@/services/Apis/Unauth"; | ||||
| import Header from "@/components/HeaderComponent.vue"; | ||||
| const router = useRouter(); | ||||
|  | ||||
| type TokenPayload = { | ||||
|     realm_access?: { | ||||
|         roles?: string[]; | ||||
|     }; | ||||
| }; | ||||
|  | ||||
| //const customRequest = ref(""); | ||||
|  | ||||
| onMounted(() => { | ||||
|     if (store.authenticated && store.user.token) { | ||||
|         try { | ||||
|             const decoded = jwtDecode<TokenPayload>(store.user.token); | ||||
|             const roles = decoded.realm_access?.roles || []; | ||||
|  | ||||
|             if (roles.includes("MyINPulse-admin")) { | ||||
|                 router.push("/admin"); | ||||
|                 return; | ||||
|             } | ||||
|  | ||||
|             if (roles.includes("MyINPulse-entrepreneur")) { | ||||
|                 router.push("/canvas"); | ||||
|                 return; | ||||
|             } | ||||
|  | ||||
|             checkPending( | ||||
|                 (response) => { | ||||
|                     const isValidated = response.data === true; | ||||
|                     if ( | ||||
|                         isValidated && | ||||
|                         roles.includes("MyINPulse-entrepreneur") | ||||
|                     ) { | ||||
|                         router.push("/canvas"); | ||||
|                         //router.push("/JorCproject"); | ||||
|                     } else { | ||||
|                         router.push("/JorCproject"); | ||||
|                         //router.push("/finalize"); | ||||
|                     } | ||||
|                 }, | ||||
|                 (error) => { | ||||
|                     if (error.response?.status === 403) { | ||||
|                         router.push("/finalize"); | ||||
|                     } else { | ||||
|                         console.error( | ||||
|                             "Unexpected error during checkPending", | ||||
|                             error | ||||
|                         ); | ||||
|                     } | ||||
|                 } | ||||
|             ); | ||||
|         } catch (err) { | ||||
|             console.error("Failed to decode token", err); | ||||
|         } | ||||
|     } | ||||
| }); | ||||
| </script> | ||||
|  | ||||
| <template> | ||||
|     <Header /> | ||||
|     <error-wrapper></error-wrapper> | ||||
|     <div class="auth-container"> | ||||
|         <div class="auth-card"> | ||||
|             <h1>Bienvenue à MyINPulse</h1> | ||||
|  | ||||
|             <div | ||||
|                 class="status" | ||||
|                 :class="store.authenticated ? 'success' : 'error'" | ||||
|             > | ||||
|                 <p> | ||||
|                     {{ | ||||
|                         store.authenticated | ||||
|                             ? "✅ Authenticated" | ||||
|                             : "❌ Not Authenticated" | ||||
|                     }} | ||||
|                 </p> | ||||
|             </div> | ||||
|  | ||||
|             <div class="actions"> | ||||
|                 <button @click="store.login">Login</button> | ||||
|                 <button @click="store.logout">Logout</button> | ||||
|                 <!--<button @click="store.signup">Signup-admin</button> | ||||
|                 <button @click="store.signup">Signup-Entrepreneur</button> | ||||
|                 <button @click="store.refreshUserToken">Refresh Token</button>--> | ||||
|             </div> | ||||
|  | ||||
|             <!-- | ||||
|             <div v-if="store.authenticated" class="token-section"> | ||||
|                 <p><strong>Access Token:</strong></p> | ||||
|                 <pre>{{ store.user.token }}</pre> | ||||
|  | ||||
|                 <p><strong>Refresh Token:</strong></p> | ||||
|                 <pre>{{ store.user.refreshToken }}</pre> | ||||
|             </div> | ||||
|  | ||||
|             <div class="api-calls"> | ||||
|                 <h2>Test API Calls</h2> | ||||
|                 <button @click="callApi('random')"> | ||||
|                     Call Entrepreneur API | ||||
|                 </button> | ||||
|                 <button @click="callApi('random2')">Call Admin API</button> | ||||
|                 <button @click="callApi('unauth/dev')">Call Unauth API</button> | ||||
|  | ||||
|                 <div class="custom-call"> | ||||
|                     <input | ||||
|                         v-model="customRequest" | ||||
|                         placeholder="Custom endpoint" | ||||
|                     /> | ||||
|                     <button @click="callApi(customRequest)">Call</button> | ||||
|                 </div> | ||||
|             </div>--> | ||||
|         </div> | ||||
|     </div> | ||||
| </template> | ||||
|  | ||||
| <style scoped> | ||||
| .auth-container { | ||||
|     display: flex; | ||||
|     justify-content: center; | ||||
|     align-items: center; | ||||
|     padding: 3rem 1rem; | ||||
|     min-height: 100vh; | ||||
|     background-color: #eef1f5; | ||||
|     font-family: Arial, sans-serif; | ||||
| } | ||||
|  | ||||
| .auth-card { | ||||
|     background: white; | ||||
|     padding: 2rem; | ||||
|     border-radius: 12px; | ||||
|     box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1); | ||||
|     width: 100%; | ||||
|     max-width: 600px; | ||||
| } | ||||
|  | ||||
| h1 { | ||||
|     text-align: center; | ||||
|     margin-bottom: 1rem; | ||||
|     color: #333; | ||||
| } | ||||
|  | ||||
| .status { | ||||
|     text-align: center; | ||||
|     margin-bottom: 1.5rem; | ||||
|     font-weight: bold; | ||||
| } | ||||
| .success { | ||||
|     color: green; | ||||
| } | ||||
| .error { | ||||
|     color: red; | ||||
| } | ||||
|  | ||||
| .actions { | ||||
|     display: flex; | ||||
|     flex-wrap: wrap; | ||||
|     gap: 1rem; | ||||
|     justify-content: center; | ||||
|     margin-bottom: 1.5rem; | ||||
| } | ||||
|  | ||||
| .actions button { | ||||
|     padding: 0.6rem 1rem; | ||||
|     background-color: #4a90e2; | ||||
|     border: none; | ||||
|     color: white; | ||||
|     border-radius: 8px; | ||||
|     cursor: pointer; | ||||
|     transition: background-color 0.2s; | ||||
| } | ||||
| .actions button:hover { | ||||
|     background-color: #357abd; | ||||
| } | ||||
|  | ||||
| .token-section pre { | ||||
|     background: #f6f8fa; | ||||
|     padding: 0.5rem; | ||||
|     overflow-x: auto; | ||||
|     border: 1px solid #ddd; | ||||
|     border-radius: 6px; | ||||
|     margin-bottom: 1rem; | ||||
|     font-size: 0.85rem; | ||||
| } | ||||
|  | ||||
| .api-calls { | ||||
|     margin-top: 2rem; | ||||
| } | ||||
| .api-calls h2 { | ||||
|     margin-bottom: 1rem; | ||||
|     color: #444; | ||||
|     font-size: 1.1rem; | ||||
| } | ||||
| .api-calls button { | ||||
|     margin-right: 0.5rem; | ||||
|     margin-bottom: 0.5rem; | ||||
| } | ||||
|  | ||||
| .custom-call { | ||||
|     margin-top: 1rem; | ||||
|     display: flex; | ||||
|     gap: 0.5rem; | ||||
| } | ||||
| .custom-call input { | ||||
|     flex: 1; | ||||
|     padding: 0.5rem; | ||||
|     border: 1px solid #ccc; | ||||
|     border-radius: 6px; | ||||
| } | ||||
| /*  | ||||
| .status { | ||||
|   padding: 0.5rem 1rem; | ||||
|   border-radius: 8px; | ||||
|   display: inline-block; | ||||
|   background-color: #e0f7e9;  | ||||
|   color: #2e7d32; | ||||
| } | ||||
| */ | ||||
| .status.error { | ||||
|     background-color: #ffe2e2; | ||||
|     color: #c62828; | ||||
| } | ||||
| </style> | ||||
							
								
								
									
										132
									
								
								front/MyINPulse-front/src/components/PendingProjectComponent.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										132
									
								
								front/MyINPulse-front/src/components/PendingProjectComponent.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,132 @@ | ||||
| <template> | ||||
|     <div class="project"> | ||||
|         <div class="project-header"> | ||||
|             <div class="project-title"> | ||||
|                 <h2>{{ projectName }}</h2> | ||||
|                 <p>Projet mis le: {{ creationDate }}</p> | ||||
|             </div> | ||||
|             <div class="project-buttons"> | ||||
|                 <button id="accept" @click="acceptProject">Accepter</button> | ||||
|                 <button id="refus" @click="refuseProject">Refuser</button> | ||||
|             </div> | ||||
|         </div> | ||||
|     </div> | ||||
| </template> | ||||
|  | ||||
| <script setup lang="ts"> | ||||
| import { defineProps } from "vue"; | ||||
| import { addNewMessage, color } from "@/services/popupDisplayer"; | ||||
| import { decidePendingProject } from "@/services/Apis/Admin"; | ||||
| import ProjectDecision from "@/ApiClasses/ProjectDecision"; | ||||
|  | ||||
| const props = defineProps<{ | ||||
|     projectId?: number; | ||||
|     projectName: string; | ||||
|     creationDate: string; | ||||
|     adminId?: number; | ||||
| }>(); | ||||
|  | ||||
| const sendDecision = (isAccepted: boolean) => { | ||||
|     const decision = new ProjectDecision({ | ||||
|         projectId: props.projectId, | ||||
|         adminId: props.adminId, | ||||
|         isAccepted, | ||||
|     }); | ||||
|  | ||||
|     decidePendingProject( | ||||
|         decision, | ||||
|         () => { | ||||
|             addNewMessage( | ||||
|                 `Projet ${props.projectName} ${isAccepted ? "accepté" : "refusé"}`, | ||||
|                 color.Green | ||||
|             ); | ||||
|         }, | ||||
|         (err) => { | ||||
|             addNewMessage(`Erreur lors de la décision`, color.Red); | ||||
|             console.error(err); | ||||
|         } | ||||
|     ); | ||||
| }; | ||||
|  | ||||
| const acceptProject = () => sendDecision(true); | ||||
| const refuseProject = () => sendDecision(false); | ||||
| </script> | ||||
|  | ||||
| <style scoped> | ||||
| .project { | ||||
|     background: linear-gradient(to right, #f8f9fb, #ffffff); | ||||
|     border: 1px solid #e0e0e0; | ||||
|     border-radius: 16px; | ||||
|     padding: 1.5rem; | ||||
|     margin: 1.5rem 0; | ||||
|     box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05); | ||||
|     font-family: Arial, sans-serif; | ||||
|     transition: box-shadow 0.3s ease; | ||||
| } | ||||
|  | ||||
| .project:hover { | ||||
|     box-shadow: 0 6px 18px rgba(0, 0, 0, 0.08); | ||||
| } | ||||
|  | ||||
| .project-header { | ||||
|     display: flex; | ||||
|     justify-content: space-between; | ||||
|     align-items: center; | ||||
|     flex-wrap: wrap; | ||||
|     gap: 1rem; | ||||
| } | ||||
|  | ||||
| .project-title { | ||||
|     display: flex; | ||||
|     flex-direction: column; | ||||
| } | ||||
|  | ||||
| .project-title h2 { | ||||
|     font-size: 1.25rem; | ||||
|     color: #222; | ||||
|     margin: 0; | ||||
|     font-weight: 600; | ||||
| } | ||||
|  | ||||
| .project-title p { | ||||
|     font-size: 0.9rem; | ||||
|     color: #666; | ||||
|     margin-top: 0.25rem; | ||||
| } | ||||
|  | ||||
| .project-buttons { | ||||
|     display: flex; | ||||
|     gap: 0.75rem; | ||||
| } | ||||
|  | ||||
| button { | ||||
|     padding: 0.5rem 1.1rem; | ||||
|     color: white; | ||||
|     border: none; | ||||
|     border-radius: 8px; | ||||
|     font-size: 0.9rem; | ||||
|     font-weight: 500; | ||||
|     cursor: pointer; | ||||
|     transition: | ||||
|         background-color 0.2s ease, | ||||
|         transform 0.2s ease; | ||||
| } | ||||
|  | ||||
| #accept { | ||||
|     background-color: #4caf50; | ||||
| } | ||||
|  | ||||
| #accept:hover { | ||||
|     background-color: #3e8e41; | ||||
|     transform: translateY(-2px); | ||||
| } | ||||
|  | ||||
| #refus { | ||||
|     background-color: #e74c3c; | ||||
| } | ||||
|  | ||||
| #refus:hover { | ||||
|     background-color: #c0392b; | ||||
|     transform: translateY(-2px); | ||||
| } | ||||
| </style> | ||||
							
								
								
									
										228
									
								
								front/MyINPulse-front/src/components/PendingRequestsManager.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										228
									
								
								front/MyINPulse-front/src/components/PendingRequestsManager.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,228 @@ | ||||
| <!-- <template> | ||||
|     <section class="pending-requests"> | ||||
|       <h2>Comptes en attente</h2> | ||||
|       <ul v-if="pendingAccounts.length"> | ||||
|         <li v-for="account in pendingAccounts" :key="account.userId"> | ||||
|           {{ account.userName }} ({{ account.email }}) | ||||
|           <button @click="validateAccount(account.userId)">Valider</button> | ||||
|         </li> | ||||
|       </ul> | ||||
|       <p v-else>Aucun compte en attente</p> | ||||
|    | ||||
|       <h2>Demandes de participation aux projets</h2> | ||||
|       <ul v-if="joinRequests.length"> | ||||
|         <li v-for="request in joinRequests" :key="request.joinRequestId"> | ||||
|           {{ request.userName }} veut rejoindre le projet {{ request.projectName }} | ||||
|           <button @click="decideJoinRequest(request.joinRequestId, true)">Accepter</button> | ||||
|           <button @click="decideJoinRequest(request.joinRequestId, false)">Refuser</button> | ||||
|         </li> | ||||
|       </ul> | ||||
|       <p v-else>Aucune demande de participation</p> | ||||
|     </section> | ||||
|   </template> --> | ||||
|  | ||||
| <template> | ||||
|     <section class="section"> | ||||
|         <h2>Comptes en attente</h2> | ||||
|         <div v-if="pendingAccounts.length"> | ||||
|             <div | ||||
|                 v-for="account in pendingAccounts" | ||||
|                 :key="account.userId" | ||||
|                 class="request-item" | ||||
|             > | ||||
|                 <div class="request-info"> | ||||
|                     {{ account.userName }} ({{ account.email }}) | ||||
|                 </div> | ||||
|                 <div class="request-buttons"> | ||||
|                     <button | ||||
|                         class="accept" | ||||
|                         @click="validateAccount(account.userId)" | ||||
|                     > | ||||
|                         Valider | ||||
|                     </button> | ||||
|                 </div> | ||||
|             </div> | ||||
|         </div> | ||||
|         <div v-else class="empty-message">Aucun compte en attente</div> | ||||
|     </section> | ||||
|  | ||||
|     <section class="section"> | ||||
|         <h2>Demandes de participation aux projets</h2> | ||||
|         <div v-if="joinRequests.length"> | ||||
|             <div | ||||
|                 v-for="request in joinRequests" | ||||
|                 :key="request.joinRequestId" | ||||
|                 class="request-item" | ||||
|             > | ||||
|                 <div class="request-info"> | ||||
|                     {{ request.userName }} veut rejoindre le projet "{{ | ||||
|                         request.projectName | ||||
|                     }}" | ||||
|                 </div> | ||||
|                 <div class="request-buttons"> | ||||
|                     <button | ||||
|                         class="accept" | ||||
|                         @click="decideJoinRequest(request.joinRequestId, true)" | ||||
|                     > | ||||
|                         Accepter | ||||
|                     </button> | ||||
|                     <button | ||||
|                         class="reject" | ||||
|                         @click="decideJoinRequest(request.joinRequestId, false)" | ||||
|                     > | ||||
|                         Refuser | ||||
|                     </button> | ||||
|                 </div> | ||||
|             </div> | ||||
|         </div> | ||||
|         <div v-else class="empty-message">Aucune demande de participation</div> | ||||
|     </section> | ||||
| </template> | ||||
|  | ||||
| <script setup lang="ts"> | ||||
| import { ref, onMounted } from "vue"; | ||||
| import { | ||||
|     getPendingAccounts, | ||||
|     validateUserAccount, | ||||
|     getPendingProjectJoinRequests, | ||||
|     decideProjectJoinRequest, | ||||
| } from "@/services/Apis/Admin"; | ||||
|  | ||||
| type PendingAccount = { | ||||
|     userId: number; | ||||
|     userName: string; | ||||
|     email: string; | ||||
| }; | ||||
|  | ||||
| type JoinRequest = { | ||||
|     joinRequestId: number; | ||||
|     userName: string; | ||||
|     projectName: string; | ||||
| }; | ||||
|  | ||||
| const pendingAccounts = ref<PendingAccount[]>([]); | ||||
| const joinRequests = ref<JoinRequest[]>([]); | ||||
|  | ||||
| function fetchAccounts() { | ||||
|     getPendingAccounts( | ||||
|         (res) => (pendingAccounts.value = res.data), | ||||
|         (err) => { | ||||
|             console.error("Erreur lors du chargement des comptes", err); | ||||
|         } | ||||
|     ); | ||||
| } | ||||
|  | ||||
| function fetchJoinRequests() { | ||||
|     getPendingProjectJoinRequests( | ||||
|         (res) => (joinRequests.value = res.data), | ||||
|         (err) => { | ||||
|             console.error("Erreur lors du chargement des demandes", err); | ||||
|         } | ||||
|     ); | ||||
| } | ||||
|  | ||||
| function validateAccount(userId: number) { | ||||
|     validateUserAccount(userId, () => { | ||||
|         pendingAccounts.value = pendingAccounts.value.filter( | ||||
|             (a) => a.userId !== userId | ||||
|         ); | ||||
|     }); | ||||
| } | ||||
|  | ||||
| function decideJoinRequest(joinRequestId: number, isAccepted: boolean) { | ||||
|     decideProjectJoinRequest(joinRequestId, { isAccepted }, () => { | ||||
|         joinRequests.value = joinRequests.value.filter( | ||||
|             (r) => r.joinRequestId !== joinRequestId | ||||
|         ); | ||||
|     }); | ||||
| } | ||||
|  | ||||
| onMounted(() => { | ||||
|     fetchAccounts(); | ||||
|     fetchJoinRequests(); | ||||
| }); | ||||
| </script> | ||||
|  | ||||
| <style> | ||||
| .section { | ||||
|     background: #ffffff; | ||||
|     border-radius: 16px; | ||||
|     padding: 1.5rem; | ||||
|     box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05); | ||||
|     margin-bottom: 2rem; | ||||
| } | ||||
|  | ||||
| .section h2 { | ||||
|     font-size: 1.4rem; | ||||
|     color: #2c3e50; | ||||
|     border-bottom: 2px solid #ddd; | ||||
|     padding-bottom: 0.5rem; | ||||
|     margin-bottom: 1rem; | ||||
| } | ||||
|  | ||||
| .request-item { | ||||
|     display: flex; | ||||
|     justify-content: space-between; | ||||
|     align-items: center; | ||||
|     border: 1px solid #e0e0e0; | ||||
|     border-radius: 12px; | ||||
|     background: linear-gradient(to right, #f8f9fb, #ffffff); | ||||
|     padding: 1rem 1.25rem; | ||||
|     margin-bottom: 1rem; | ||||
|     transition: box-shadow 0.2s ease; | ||||
| } | ||||
|  | ||||
| .request-item:hover { | ||||
|     box-shadow: 0 6px 16px rgba(0, 0, 0, 0.06); | ||||
| } | ||||
|  | ||||
| .request-info { | ||||
|     flex: 1; | ||||
|     font-size: 1rem; | ||||
|     color: #333; | ||||
| } | ||||
|  | ||||
| .request-buttons { | ||||
|     display: flex; | ||||
|     gap: 0.75rem; | ||||
| } | ||||
|  | ||||
| .request-buttons button { | ||||
|     padding: 0.45rem 1rem; | ||||
|     font-size: 0.9rem; | ||||
|     border: none; | ||||
|     border-radius: 8px; | ||||
|     color: #fff; | ||||
|     font-weight: 500; | ||||
|     cursor: pointer; | ||||
|     transition: | ||||
|         background-color 0.2s ease, | ||||
|         transform 0.2s ease; | ||||
| } | ||||
|  | ||||
| .request-buttons button:hover { | ||||
|     transform: translateY(-1px); | ||||
| } | ||||
|  | ||||
| button.accept { | ||||
|     background-color: #4caf50; | ||||
| } | ||||
|  | ||||
| button.accept:hover { | ||||
|     background-color: #3e8e41; | ||||
| } | ||||
|  | ||||
| button.reject { | ||||
|     background-color: #e74c3c; | ||||
| } | ||||
|  | ||||
| button.reject:hover { | ||||
|     background-color: #c0392b; | ||||
| } | ||||
|  | ||||
| .empty-message { | ||||
|     font-style: italic; | ||||
|     color: #777; | ||||
|     padding: 1rem 0; | ||||
| } | ||||
| </style> | ||||
| @@ -1,130 +1,306 @@ | ||||
| <template> | ||||
|     <div @click="goToLink" class="project"> | ||||
|     <div class="project"> | ||||
|         <div class="project-header"> | ||||
|             <h2 >{{ projectName }}</h2> | ||||
|             <div class="project-buttons"> | ||||
|                 <button class="contact-btn">Contact</button> | ||||
|             <h2 @click="goToLink">{{ projectName }}</h2> | ||||
|             <div class="header-actions"> | ||||
|                 <div ref="dropdownRef" class="dropdown-wrapper"> | ||||
|                     <button class="contact-button" @click.stop="toggleDropdown"> | ||||
|                         Contact | ||||
|                     </button> | ||||
|                     <div | ||||
|                         v-if="entrepreneurEmails.length > 0" | ||||
|                         class="contact-dropdown" | ||||
|                         :class="{ 'dropdown-visible': isDropdownOpen }" | ||||
|                     > | ||||
|                         <button @click="contactAll">Contacter tous</button> | ||||
|                         <button | ||||
|                             v-for="(email, index) in entrepreneurEmails" | ||||
|                             :key="index" | ||||
|                             @click="contactSingle(email)" | ||||
|                         > | ||||
|                             {{ email }} | ||||
|                         </button> | ||||
|                     </div> | ||||
|                 </div> | ||||
|             </div> | ||||
|         </div> | ||||
|         <div class="project-body">    | ||||
|  | ||||
|         <!-- Toute cette partie est cliquable --> | ||||
|         <div class="project-body" @click="goToLink"> | ||||
|             <ul> | ||||
|                 <li v-for="(name, index) in listName" :key="index">{{ name }}</li> | ||||
|                 <li v-for="(name, index) in listName" :key="index"> | ||||
|                     {{ name }} | ||||
|                 </li> | ||||
|             </ul> | ||||
|         </div> | ||||
|     </div> | ||||
| </template> | ||||
|  | ||||
|  | ||||
|  | ||||
| <script setup lang="ts"> | ||||
| import { defineProps } from "vue"; | ||||
| import { useRouter } from 'vue-router' | ||||
| import { defineProps, ref, onMounted, onBeforeUnmount } from "vue"; | ||||
| import { useRouter } from "vue-router"; | ||||
| import { getProjectEntrepreneurs } from "@/services/Apis/Shared.ts"; | ||||
| import UserEntrepreneur from "@/ApiClasses/UserEntrepreneur.ts"; | ||||
|  | ||||
| const IS_MOCK_MODE = false; | ||||
| const dropdownRef = ref<HTMLElement | null>(null); | ||||
|  | ||||
| const props = defineProps<{ | ||||
|     projectName: string; | ||||
|     listName: string[]; | ||||
|     projectLink: string; | ||||
|     projectId: number; | ||||
| }>(); | ||||
|  | ||||
| const router = useRouter(); | ||||
|  | ||||
| const isDropdownOpen = ref(false); | ||||
| const entrepreneurEmails = ref<string[]>([]); | ||||
| const entrepreneurs = ref<UserEntrepreneur[]>([]); | ||||
|  | ||||
| const goToLink = () => { | ||||
|   if (props.projectLink) { | ||||
|     router.push(props.projectLink); | ||||
|   } | ||||
|   | ||||
|     if (props.projectLink) { | ||||
|         router.push(props.projectLink); | ||||
|     } | ||||
| }; | ||||
| </script> | ||||
|  | ||||
| const toggleDropdown = () => { | ||||
|     isDropdownOpen.value = !isDropdownOpen.value; | ||||
| }; | ||||
|  | ||||
| const fetchMockEntrepreneurs = () => { | ||||
|     const mockData = [ | ||||
|         { | ||||
|             userName: "Doe", | ||||
|             userSurname: "John", | ||||
|             primaryMail: "john.doe@example.com", | ||||
|         }, | ||||
|         { | ||||
|             userName: "Smith", | ||||
|             userSurname: "Anna", | ||||
|             primaryMail: "anna.smith@example.com", | ||||
|         }, | ||||
|         { | ||||
|             userName: "Mock", | ||||
|             userSurname: "User", | ||||
|             primaryMail: undefined, | ||||
|         }, | ||||
|     ]; | ||||
|  | ||||
|     entrepreneurs.value = mockData.map((item) => new UserEntrepreneur(item)); | ||||
|     entrepreneurEmails.value = entrepreneurs.value | ||||
|         .map((e) => e.primaryMail) | ||||
|         .filter((mail): mail is string => !!mail); | ||||
|  | ||||
|     console.log("Mock entrepreneurs chargés :", entrepreneurs.value); | ||||
| }; | ||||
|  | ||||
| const fetchEntrepreneurs = (projectId: number, useMock = false) => { | ||||
|     if (useMock) { | ||||
|         fetchMockEntrepreneurs(); | ||||
|     } else { | ||||
|         getProjectEntrepreneurs( | ||||
|             projectId, | ||||
|             (response) => { | ||||
|                 const rawData = response.data as Partial<UserEntrepreneur>[]; | ||||
|                 entrepreneurs.value = rawData.map( | ||||
|                     (item) => new UserEntrepreneur(item) | ||||
|                 ); | ||||
|                 entrepreneurEmails.value = entrepreneurs.value | ||||
|                     .map((e) => e.primaryMail) | ||||
|                     .filter((mail): mail is string => !!mail); | ||||
|             }, | ||||
|             (error) => { | ||||
|                 console.error( | ||||
|                     "Erreur lors de la récupération des entrepreneurs :", | ||||
|                     error | ||||
|                 ); | ||||
|             } | ||||
|         ); | ||||
|     } | ||||
| }; | ||||
|  | ||||
| const contactAll = () => { | ||||
|     const allEmails = entrepreneurEmails.value.join(", "); | ||||
|     navigator.clipboard | ||||
|         .writeText(allEmails) | ||||
|         .then(() => { | ||||
|             alert("Tous les emails copiés dans le presse-papiers !"); | ||||
|             window.open("https://partage.bordeaux-inp.fr/", "_blank"); | ||||
|         }) | ||||
|         .catch((err) => console.error("Erreur lors de la copie :", err)); | ||||
| }; | ||||
|  | ||||
| const contactSingle = (email: string) => { | ||||
|     navigator.clipboard | ||||
|         .writeText(email) | ||||
|         .then(() => { | ||||
|             alert(`Adresse copiée : ${email}`); | ||||
|             window.open("https://partage.bordeaux-inp.fr/", "_blank"); | ||||
|         }) | ||||
|         .catch((err) => console.error("Erreur lors de la copie :", err)); | ||||
| }; | ||||
|  | ||||
| const handleClickOutside = (event: MouseEvent) => { | ||||
|     if ( | ||||
|         isDropdownOpen.value && | ||||
|         dropdownRef.value && | ||||
|         !dropdownRef.value.contains(event.target as Node) | ||||
|     ) { | ||||
|         isDropdownOpen.value = false; | ||||
|     } | ||||
| }; | ||||
|  | ||||
| onMounted(() => { | ||||
|     fetchEntrepreneurs(props.projectId, IS_MOCK_MODE); | ||||
|     document.addEventListener("click", handleClickOutside); | ||||
| }); | ||||
|  | ||||
| onBeforeUnmount(() => { | ||||
|     document.removeEventListener("click", handleClickOutside); | ||||
| }); | ||||
| </script> | ||||
|  | ||||
| <style scoped> | ||||
| .project { | ||||
|     background: linear-gradient(to right, #f4f4f4, #ffffff); | ||||
|     border: 1px solid #ddd; | ||||
|     border-radius: 10px; | ||||
|     padding: 20px; | ||||
|     margin: 20px 0; | ||||
|     box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); | ||||
|     font-family: Arial, sans-serif; | ||||
|   } | ||||
|     background: linear-gradient(to right, #f8f9fb, #ffffff); | ||||
|     border: 1px solid #e0e0e0; | ||||
|     border-radius: 16px; | ||||
|     padding: 1.5rem; | ||||
|     margin: 1.5rem 0; | ||||
|     box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05); | ||||
|     transition: box-shadow 0.3s ease; | ||||
|     cursor: pointer; | ||||
| } | ||||
|  | ||||
|   /* Header Styling */ | ||||
|   .project-header { | ||||
| .project:hover { | ||||
|     box-shadow: 0 6px 18px rgba(0, 0, 0, 0.08); | ||||
| } | ||||
|  | ||||
| .project-header { | ||||
|     display: flex; | ||||
|     justify-content: space-between; | ||||
|     align-items: center; | ||||
|   } | ||||
|    | ||||
|   .project-header h2 { | ||||
|     font-size: 20px; | ||||
|     color: #333; | ||||
|     margin: 0; | ||||
|   } | ||||
|    | ||||
|   /* Button Container */ | ||||
|   .project-buttons { | ||||
|     display: flex; | ||||
|     gap: 10px; | ||||
|   } | ||||
|  | ||||
|  | ||||
|   .info-btn { | ||||
|     background-color: #4CAF50; | ||||
|     color: #fff; | ||||
|   } | ||||
|    | ||||
|   .info-btn:hover { | ||||
|     background-color: #45a049; | ||||
|     transform: scale(1.05); | ||||
|   } | ||||
|    | ||||
|   .contact-btn { | ||||
|     background-color: #007BFF; | ||||
|     color: #fff; | ||||
|   } | ||||
|    | ||||
|   .contact-btn:hover { | ||||
|     background-color: #0056b3; | ||||
|     transform: scale(1.05); | ||||
|   } | ||||
|    | ||||
|  | ||||
|  | ||||
|  | ||||
|   .project-body { | ||||
|     margin-top: 15px; | ||||
|   } | ||||
|    | ||||
|   .project-body p { | ||||
|     font-size: 16px; | ||||
|     color: #555; | ||||
|     margin-bottom: 10px; | ||||
|   } | ||||
|    | ||||
|   .project-body ul { | ||||
|     list-style-type: disc; | ||||
|     margin: 0; | ||||
|     padding-left: 20px; | ||||
|   } | ||||
|    | ||||
|   .project-body ul li { | ||||
|     font-size: 14px; | ||||
|     color: #666; | ||||
|     line-height: 1.6; | ||||
|   } | ||||
|  | ||||
|  | ||||
|   button { | ||||
|   padding: 10px 15px; | ||||
|   background-color: #007bff; | ||||
|   color: white; | ||||
|   border: none; | ||||
|   cursor: pointer; | ||||
|   border-radius: 5px; | ||||
|     flex-wrap: wrap; | ||||
|     gap: 0.5rem; | ||||
| } | ||||
|   button:hover { | ||||
|     background-color: #0056b3; | ||||
|   } | ||||
|  | ||||
| .project-header h2 { | ||||
|     font-size: 1.25rem; | ||||
|     color: #222; | ||||
|     margin: 0; | ||||
|     font-weight: 600; | ||||
| } | ||||
|  | ||||
| .project-buttons { | ||||
|     display: flex; | ||||
|     gap: 0.5rem; | ||||
| } | ||||
|  | ||||
| .contact-btn { | ||||
|     background-color: #007bff; | ||||
|     color: #fff; | ||||
|     padding: 0.5rem 1rem; | ||||
|     border: none; | ||||
|     border-radius: 8px; | ||||
|     font-size: 0.9rem; | ||||
|     font-weight: 500; | ||||
|     transition: | ||||
|         background-color 0.2s ease, | ||||
|         transform 0.2s ease; | ||||
| } | ||||
|  | ||||
| .contact-btn:hover { | ||||
|     background-color: #0056b3; | ||||
|     transform: translateY(-2px); | ||||
| } | ||||
|  | ||||
| .project-body { | ||||
|     margin-top: 1rem; | ||||
| } | ||||
|  | ||||
| .project-body ul { | ||||
|     list-style-type: disc; | ||||
|     padding-left: 1.25rem; | ||||
|     margin: 0; | ||||
| } | ||||
|  | ||||
| .project-body ul li { | ||||
|     font-size: 0.95rem; | ||||
|     color: #555; | ||||
|     line-height: 1.6; | ||||
| } | ||||
|  | ||||
| .header { | ||||
|     display: flex; | ||||
|     justify-content: space-between; | ||||
|     align-items: center; | ||||
|     padding: 15px 30px; | ||||
|     background-color: #f9f9f9; | ||||
|     box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); | ||||
| } | ||||
|  | ||||
| .logo { | ||||
|     height: 50px; | ||||
| } | ||||
|  | ||||
| .header-actions { | ||||
|     display: flex; | ||||
|     align-items: center; | ||||
|     gap: 20px; | ||||
|     position: relative; | ||||
| } | ||||
|  | ||||
| .contact-button, | ||||
| .return-button { | ||||
|     background-color: #009cde; | ||||
|     color: white; | ||||
|     border: none; | ||||
|     padding: 10px 15px; | ||||
|     cursor: pointer; | ||||
|     font-size: 14px; | ||||
|     border-radius: 5px; | ||||
|     text-decoration: none; | ||||
|     transition: background-color 0.2s ease; | ||||
|     font-family: Arial, sans-serif; | ||||
| } | ||||
|  | ||||
| .return-button:hover, | ||||
| .contact-button:hover { | ||||
|     background-color: #007bad; | ||||
| } | ||||
|  | ||||
| .contact-dropdown { | ||||
|     position: absolute; | ||||
|     top: 100%; | ||||
|     left: 0; | ||||
|     background-color: #000; | ||||
|     color: white; | ||||
|     box-shadow: 0px 4px 8px rgba(255, 255, 255, 0.2); | ||||
|     border-radius: 8px; | ||||
|     padding: 10px; | ||||
|     margin-top: 5px; | ||||
|     z-index: 1000; | ||||
|     min-width: 200px; | ||||
|     display: none; | ||||
| } | ||||
|  | ||||
| .contact-dropdown button { | ||||
|     display: block; | ||||
|     width: 100%; | ||||
|     padding: 5px; | ||||
|     text-align: left; | ||||
|     border: none; | ||||
|     background: none; | ||||
|     cursor: pointer; | ||||
|     color: white; | ||||
| } | ||||
|  | ||||
| .contact-dropdown button:hover { | ||||
|     background-color: #009cde; | ||||
| } | ||||
|  | ||||
| .contact-dropdown.dropdown-visible { | ||||
|     display: block; | ||||
| } | ||||
| </style> | ||||
| @@ -1,330 +1,687 @@ | ||||
| <template> | ||||
|   <div :class="['cell', { expanded }]" @click="handleClick"> | ||||
|     <h3>{{ title_text }}</h3> | ||||
|     <div :class="['cell', { expanded }]" @click="handleClick"> | ||||
|         <h3 class="fs-5 fw-medium">{{ titleText }}</h3> | ||||
|  | ||||
|     <div class="section-bloc" v-for="(desc, index) in currentDescriptions" :key="index"> | ||||
|       <!-- Mode affichage --> | ||||
|       <template v-if="!isEditing[index]"> | ||||
|         <div class="description"> | ||||
|           <p>{{ desc }}</p> | ||||
|         </div> | ||||
|         <div class="button-container"> | ||||
|           <button v-if="expanded" @click.stop="startEditing(index)" class="edit-button">Éditer</button> | ||||
|         </div> | ||||
|       </template> | ||||
|         <div class="tooltip-explain">{{ description }}</div> | ||||
|  | ||||
|       <!-- Mode édition --> | ||||
|       <template v-else> | ||||
|         <textarea v-model="editedDescriptions[index]" class="edit-input"></textarea> | ||||
|         <div class="button-container"> | ||||
|           <button @click.stop="saveEdit(index)" class="save-button">Enregistrer</button> | ||||
|           <button @click.stop="cancelEdit(index)" class="cancel-button">Annuler</button> | ||||
|         <template v-if="expanded"> | ||||
|             <div class="explain"> | ||||
|                 <p>{{ description }}</p> | ||||
|             </div> | ||||
|         </template> | ||||
|  | ||||
|         <div class="description-wrapper custom-flow"> | ||||
|             <div | ||||
|                 v-for="(desc, index) in currentDescriptions" | ||||
|                 :key="desc.idSectionCell || index" | ||||
|                 :class="[ | ||||
|                     'section-bloc', | ||||
|                     index % 2 === 0 ? 'from-left' : 'from-right', | ||||
|                 ]" | ||||
|             > | ||||
|                 <!-- ADMIN --------------------------------------------------------------------------------------------> | ||||
|                 <template v-if="IS_ADMIN"> | ||||
|                     <div class="description"> | ||||
|                         <p class="m-0">{{ desc.contentSectionCell }}</p> | ||||
|                     </div> | ||||
|                 </template> | ||||
|  | ||||
|                 <!-- ENTREP -------------------------------------------------------------------------------------------> | ||||
|                 <template v-else> | ||||
|                     <!-- Mode affichage --> | ||||
|                     <template v-if="!isEditing[index]"> | ||||
|                         <template v-if="expanded"> | ||||
|                             <button | ||||
|                                 class="delete-button" | ||||
|                                 title="Supprimer" | ||||
|                                 @click.stop=" | ||||
|                                     deleteSectionCell(desc.idSectionCell, index) | ||||
|                                 " | ||||
|                             > | ||||
|                                 ✕ | ||||
|                             </button> | ||||
|                         </template> | ||||
|  | ||||
|                         <div class="description"> | ||||
|                             <p class="m-0">{{ desc.contentSectionCell }}</p> | ||||
|                         </div> | ||||
|                         <div class="button-container"> | ||||
|                             <button | ||||
|                                 v-if="expanded" | ||||
|                                 class="edit-button" | ||||
|                                 @click.stop="startEditing(index)" | ||||
|                             > | ||||
|                                 Éditer | ||||
|                             </button> | ||||
|                         </div> | ||||
|                     </template> | ||||
|  | ||||
|                     <!-- Mode édition --> | ||||
|                     <template v-else> | ||||
|                         <div class="edit-row"> | ||||
|                             <textarea | ||||
|                                 v-model=" | ||||
|                                     editedDescriptions[index].contentSectionCell | ||||
|                                 " | ||||
|                                 class="edit-input" | ||||
|                             ></textarea> | ||||
|                             <div class="button-container"> | ||||
|                                 <button | ||||
|                                     class="save-button" | ||||
|                                     @click.stop="saveEdit(index)" | ||||
|                                 > | ||||
|                                     Enregistrer | ||||
|                                 </button> | ||||
|                                 <button | ||||
|                                     class="cancel-button" | ||||
|                                     @click.stop="cancelEdit(index)" | ||||
|                                 > | ||||
|                                     Annuler | ||||
|                                 </button> | ||||
|                             </div> | ||||
|                         </div> | ||||
|                     </template> | ||||
|                 </template> | ||||
|             </div> | ||||
|  | ||||
|             <template v-if="expanded"> | ||||
|                 <div class="canvas-exit-hint"> | ||||
|                     Cliquez n'importe où pour quitter le canvas (terminez | ||||
|                     d'abord vos modifications) | ||||
|                 </div> | ||||
|             </template> | ||||
|         </div> | ||||
|       </template> | ||||
|     </div> | ||||
|  | ||||
|   </div> | ||||
| </template> | ||||
|  | ||||
| <script setup lang="ts"> | ||||
| import { ref, defineProps } from "vue"; | ||||
| import axios from "axios"; | ||||
|  | ||||
| const IS_MOCK_MODE = true;  | ||||
| import { ref, defineProps, onMounted } from "vue"; | ||||
| import { getSectionCellsByDate } from "@/services/Apis/Shared.ts"; | ||||
| import { addSectionCell } from "@/services/Apis/Entrepreneurs.ts"; | ||||
| import SectionCell from "@/ApiClasses/SectionCell"; | ||||
| import { removeSectionCell } from "@/services/Apis/Entrepreneurs.ts"; | ||||
| import type { AxiosResponse, AxiosError } from "axios"; | ||||
|  | ||||
| const props = defineProps<{ | ||||
|   projectId: number; | ||||
|   title: number; | ||||
|   title_text: string; | ||||
|   description: string; | ||||
|     projectId: number; | ||||
|     title: number; | ||||
|     titleText: string; | ||||
|     description: string; | ||||
|     isAdmin: boolean; | ||||
| }>(); | ||||
|  | ||||
| const IS_MOCK_MODE = true; | ||||
| const IS_ADMIN = props.isAdmin; | ||||
|  | ||||
| const expanded = ref(false); | ||||
| const currentDescriptions = ref<string[]>([]); | ||||
| currentDescriptions.value[0] = props.description; | ||||
| const editedDescriptions = ref<string[]>([]); | ||||
| const currentDescriptions = ref<SectionCell[]>([]); | ||||
| const editedDescriptions = ref<SectionCell[]>([]); | ||||
| const isEditing = ref<boolean[]>([]); | ||||
|  | ||||
|  | ||||
| /* | ||||
| const fetchData = async () => { | ||||
|   try { | ||||
|     const response = await axios.get("http://localhost:5000/data"); // Met à jour l'URL | ||||
|     if (response.data.length > 0) { | ||||
|       currentDescription.value = response.data[0].canva_data; | ||||
|       editedDescription.value = response.data[0].canva_data; | ||||
|     } else { | ||||
|       console.warn("Aucune donnée reçue."); | ||||
|     } | ||||
|   } catch (error) { | ||||
|     console.error("Erreur lors de la récupération des données :", error); | ||||
|   } | ||||
| }; | ||||
| */ | ||||
|  | ||||
| // Fonction de simulation de l'API | ||||
| const mockFetch = async (projectId: number, title: number, date: string) => { | ||||
|   console.log(`Mock fetch pour projectId: ${projectId}, title: ${title}, date: ${date}`); | ||||
|  | ||||
|   return new Promise<{ txt: string }[]>((resolve) => { | ||||
|     setTimeout(() => { | ||||
|       resolve([ | ||||
|         { txt: "Ceci est une description 1 pour tester the damn front, Why are u still reading dumbass this is just some nonsense sentence XD" }, | ||||
|         { txt: "Ceci est une description 1 pour tester the damn front, Bruh are u still here?" }, | ||||
|         { txt: "Ceci est une description 1 pour tester the damn front, .-. BRUH" } | ||||
|       ]); | ||||
|     }, 500); // Simule un délai réseau de 500ms | ||||
|   }); | ||||
| }; | ||||
|  | ||||
| // Fonction fetchData avec possibilité d'utiliser le mock | ||||
| const fetchData = async (projectId: number, title: number, date: string, useMock = false) => { | ||||
|   try { | ||||
|     const responseData = useMock | ||||
|       ? await mockFetch(projectId, title, date) | ||||
|       : (await axios.get<{ txt: string }[]>( | ||||
|           `http://localhost:5000/shared/projects/lcsection/${projectId}/${title}/${date}` | ||||
|         )).data; | ||||
|  | ||||
|     if (responseData.length > 0) { | ||||
|       currentDescriptions.value = responseData.map((item) => item.txt); | ||||
|       editedDescriptions.value = [...currentDescriptions.value]; | ||||
|       isEditing.value = Array(responseData.length).fill(false); | ||||
|     } else { | ||||
|       console.warn("Aucune donnée reçue."); | ||||
|     } | ||||
|   } catch (error) { | ||||
|     console.error("Erreur lors de la récupération des données :", error); | ||||
|   } | ||||
| }; | ||||
|  | ||||
| // Utilisation du mock dans handleClick pour tester sans serveur | ||||
| const handleClick = async () => { | ||||
|   if (!expanded.value) { | ||||
|     await fetchData(props.projectId, props.title, "NaN", IS_MOCK_MODE); // true pour activer le mock | ||||
|   } else if (!isEditing.value.includes(true)) { | ||||
|     // Réinitialiser les descriptions si aucune édition n'est en cours | ||||
|     currentDescriptions.value = [props.description]; | ||||
|     editedDescriptions.value = [props.description]; | ||||
|   } | ||||
|  | ||||
|   if (!isEditing.value.includes(true)) { | ||||
|     expanded.value = !expanded.value; | ||||
|   } | ||||
| }; | ||||
|  | ||||
|  | ||||
| const startEditing = (index: number) => { | ||||
|   isEditing.value[index] = true; | ||||
| }; | ||||
|  | ||||
| /* | ||||
| const saveEdit = async (index: number) => { | ||||
|   try { | ||||
|     const id = index + 1; // À adapter selon l'ID réel des données | ||||
|     await axios.put(`http://localhost:5000/data/${id}`, { | ||||
|       canva_data: editedDescriptions.value[index] | ||||
|     }); | ||||
|  | ||||
|     // Mettre à jour l'affichage local après la mise à jour réussie | ||||
|     currentDescriptions.value[index] = editedDescriptions.value[index]; | ||||
|     isEditing.value[index] = false; | ||||
|   } catch (error) { | ||||
|     console.error("Erreur lors de la mise à jour des données :", error); | ||||
|   } | ||||
| }; | ||||
| */ | ||||
|  | ||||
| // Fonction de mock pour l'enregistrement | ||||
| const mockSaveEdit = async (index: number) => { | ||||
|   try { | ||||
|     const id = index + 1; // À adapter selon l'ID réel des données | ||||
|     console.log(`Mock save pour l'ID ${id} avec la description : ${editedDescriptions.value[index]}`); | ||||
|  | ||||
|     // Simuler un délai d'enregistrement comme une requête réseau | ||||
|     await new Promise((resolve) => setTimeout(resolve, 500)); // Simulation de délai réseau | ||||
|  | ||||
|     // Mettre à jour l'affichage local après la mise à jour réussie | ||||
|     currentDescriptions.value[index] = editedDescriptions.value[index]; | ||||
|     isEditing.value[index] = false; | ||||
|   } catch (error) { | ||||
|     console.error("Erreur lors de la mise à jour des données mockées :", error); | ||||
|   } | ||||
| }; | ||||
|  | ||||
| // Utilisation de `mockSaveEdit` au lieu de `saveEdit` dans `handleClick` ou tout autre endroit | ||||
| const saveEdit = async (index: number) => { | ||||
|   if (IS_MOCK_MODE) { | ||||
|     await mockSaveEdit(index); | ||||
|   } else { | ||||
|     try { | ||||
|       const id = index + 1; | ||||
|       await axios.put(`http://localhost:5000/data/${id}`, { | ||||
|         canva_data: editedDescriptions.value[index] | ||||
|       }); | ||||
|  | ||||
|       // Mettre à jour l'affichage local après la mise à jour réussie | ||||
|       currentDescriptions.value[index] = editedDescriptions.value[index]; | ||||
|       isEditing.value[index] = false; | ||||
|     } catch (error) { | ||||
|       console.error("Erreur lors de la mise à jour des données :", error); | ||||
|     } | ||||
|   } | ||||
|     isEditing.value[index] = true; | ||||
| }; | ||||
|  | ||||
| const cancelEdit = (index: number) => { | ||||
|   editedDescriptions.value[index] = currentDescriptions.value[index]; | ||||
|   isEditing.value[index] = false; | ||||
|     editedDescriptions.value[index].contentSectionCell = | ||||
|         currentDescriptions.value[index].contentSectionCell; | ||||
|     isEditing.value[index] = false; | ||||
| }; | ||||
|  | ||||
| const saveEdit = (index: number) => { | ||||
|     currentDescriptions.value[index].contentSectionCell = | ||||
|         editedDescriptions.value[index].contentSectionCell; | ||||
|     isEditing.value[index] = false; | ||||
|  | ||||
|     if (!IS_MOCK_MODE) { | ||||
|         addSectionCell( | ||||
|             currentDescriptions.value[index], | ||||
|             (response) => { | ||||
|                 console.log( | ||||
|                     "Modification enregistrée avec succès :", | ||||
|                     response.data | ||||
|                 ); | ||||
|             }, | ||||
|             (error) => { | ||||
|                 console.error("Erreur lors de l'enregistrement :", error); | ||||
|             } | ||||
|         ); | ||||
|     } | ||||
| }; | ||||
|  | ||||
| const deleteSectionCell = (id: number | undefined, index: number): void => { | ||||
|     if (id === -1) { | ||||
|         window.confirm("Êtes-vous sûr de vouloir supprimer cet élément ?"); | ||||
|         console.error("Delete ignored"); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     const confirmed = window.confirm( | ||||
|         "Êtes-vous sûr de vouloir supprimer cet élément ?" | ||||
|     ); | ||||
|  | ||||
|     if (!confirmed) return; | ||||
|  | ||||
|     if (id === undefined) { | ||||
|         console.error("ID de la cellule non défini"); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     removeSectionCell( | ||||
|         id, | ||||
|         () => { | ||||
|             currentDescriptions.value.splice(index, 1); | ||||
|             editedDescriptions.value.splice(index, 1); | ||||
|             isEditing.value.splice(index, 1); | ||||
|         }, | ||||
|         (error: AxiosError) => { | ||||
|             console.error("Erreur lors de la suppression :", error); | ||||
|         } | ||||
|     ); | ||||
| }; | ||||
|  | ||||
| const handleClick = () => { | ||||
|     if (expanded.value) { | ||||
|         const editingInProgress = isEditing.value.some((edit) => edit); | ||||
|         if (!editingInProgress) { | ||||
|             expanded.value = false; | ||||
|         } | ||||
|     } else { | ||||
|         expanded.value = true; | ||||
|     } | ||||
| }; | ||||
|  | ||||
| const handleFetchSuccess = (sectionCells: SectionCell[]) => { | ||||
|     currentDescriptions.value = sectionCells; | ||||
|     editedDescriptions.value = sectionCells.map( | ||||
|         (cell) => | ||||
|             new SectionCell({ | ||||
|                 idSectionCell: cell.idSectionCell, | ||||
|                 sectionId: cell.sectionId, | ||||
|                 contentSectionCell: cell.contentSectionCell, | ||||
|                 modificationDate: cell.modificationDate, | ||||
|             }) | ||||
|     ); | ||||
|     isEditing.value = Array(sectionCells.length).fill(false); | ||||
| }; | ||||
|  | ||||
| const handleFetchError = (error: unknown) => { | ||||
|     console.error("Erreur lors de la récupération des données :", error); | ||||
|  | ||||
|     const errorCell = new SectionCell({ | ||||
|         idSectionCell: -1, | ||||
|         sectionId: -1, | ||||
|         contentSectionCell: "Échec du chargement des données.", | ||||
|         modificationDate: new Date().toISOString(), | ||||
|     }); | ||||
|  | ||||
|     currentDescriptions.value = [errorCell]; | ||||
|     editedDescriptions.value = [errorCell]; | ||||
|     isEditing.value = [false]; | ||||
| }; | ||||
|  | ||||
| const fetchData = async ( | ||||
|     projectId: number, | ||||
|     title: number, | ||||
|     date: string, | ||||
|     useMock = false | ||||
| ) => { | ||||
|     try { | ||||
|         if (useMock) { | ||||
|             const responseData = await mockFetch(projectId, title, date); | ||||
|             handleFetchSuccess(responseData); | ||||
|         } else { | ||||
|             if (projectId == -1) { | ||||
|                 const errorCell = new SectionCell({ | ||||
|                     idSectionCell: -1, | ||||
|                     sectionId: -1, | ||||
|                     contentSectionCell: "Échec du chargement des données.", | ||||
|                     modificationDate: new Date().toISOString(), | ||||
|                 }); | ||||
|  | ||||
|                 currentDescriptions.value = [errorCell]; | ||||
|                 editedDescriptions.value = [errorCell]; | ||||
|                 isEditing.value = [false]; | ||||
|  | ||||
|                 console.error( | ||||
|                     "No sections to show because no project was found." | ||||
|                 ); | ||||
|                 return; | ||||
|             } | ||||
|             await new Promise<void>((resolve, reject) => { | ||||
|                 getSectionCellsByDate( | ||||
|                     projectId, | ||||
|                     title, | ||||
|                     date, | ||||
|                     (response: AxiosResponse) => { | ||||
|                         const data = response.data; | ||||
|  | ||||
|                         if (Array.isArray(data) && data.length > 0) { | ||||
|                             const sectionCells = data.map( | ||||
|                                 (cellData) => | ||||
|                                     new SectionCell({ | ||||
|                                         idSectionCell: cellData.idSectionCell, | ||||
|                                         sectionId: cellData.sectionId, | ||||
|                                         contentSectionCell: | ||||
|                                             cellData.contentSectionCell, | ||||
|                                         modificationDate: | ||||
|                                             cellData.modificationDate, | ||||
|                                     }) | ||||
|                             ); | ||||
|                             handleFetchSuccess(sectionCells); | ||||
|                         } else { | ||||
|                             console.warn( | ||||
|                                 "Aucune donnée reçue ou format inattendu :", | ||||
|                                 data | ||||
|                             ); | ||||
|                         } | ||||
|  | ||||
|                         resolve(); | ||||
|                     }, | ||||
|                     (error: AxiosError) => { | ||||
|                         handleFetchError(error); | ||||
|                         reject(error); | ||||
|                     } | ||||
|                 ); | ||||
|             }); | ||||
|         } | ||||
|     } catch (error) { | ||||
|         handleFetchError(error); | ||||
|     } | ||||
| }; | ||||
|  | ||||
| const mockFetch = async ( | ||||
|     projectId: number, | ||||
|     title: number, | ||||
|     date: string | ||||
| ): Promise<SectionCell[]> => { | ||||
|     console.log( | ||||
|         `Mock fetch pour projectId: ${projectId}, title: ${title}, date: ${date}` | ||||
|     ); | ||||
|  | ||||
|     const leanCanvasData: Record<number, string[]> = { | ||||
|         1: [ | ||||
|             "Les clients ont du mal à trouver des produits écoresponsables abordables.", | ||||
|             "Le processus d'achat en ligne est trop complexe.", | ||||
|             "Manque de transparence sur l’origine des produits.", | ||||
|             "Peu d’alternatives locales et durables sur le marché.", | ||||
|         ], | ||||
|         2: [ | ||||
|             "Jeunes urbains engagés dans la cause écologique.", | ||||
|             "Familles à revenu moyen voulant consommer responsable.", | ||||
|             "Entreprises soucieuses de leur empreinte carbone.", | ||||
|         ], | ||||
|         3: [ | ||||
|             "Une plateforme centralisée avec des produits écologiques certifiés.", | ||||
|             "Un service client humain et réactif.", | ||||
|             "Livraison éco-responsable avec suivi.", | ||||
|         ], | ||||
|         4: [ | ||||
|             "Application intuitive avec suggestions personnalisées.", | ||||
|             "Emballages recyclables et réutilisables.", | ||||
|         ], | ||||
|         5: [ | ||||
|             "Algorithme exclusif de recommandations durables.", | ||||
|             "Forte communauté engagée sur les réseaux.", | ||||
|         ], | ||||
|         6: [ | ||||
|             "Canaux digitaux : réseaux sociaux, SEO.", | ||||
|             "Partenariats avec influenceurs écoresponsables.", | ||||
|             "Boutique physique en pop-up stores.", | ||||
|         ], | ||||
|         7: [ | ||||
|             "Taux de rétention client mensuel.", | ||||
|             "Taux de satisfaction utilisateur (NPS).", | ||||
|         ], | ||||
|         8: [ | ||||
|             "Coût du développement logiciel initial.", | ||||
|             "Campagnes publicitaires et communication.", | ||||
|             "Frais logistiques (emballages, transport).", | ||||
|         ], | ||||
|         9: [ | ||||
|             "Ventes directes sur la plateforme.", | ||||
|             "Abonnement mensuel premium pour livraison gratuite.", | ||||
|             "Revenus via partenariats de marque.", | ||||
|         ], | ||||
|     }; | ||||
|  | ||||
|     const section = leanCanvasData[title] || ["Aucune donnée disponible."]; | ||||
|  | ||||
|     const result = section.map( | ||||
|         (txt, index) => | ||||
|             new SectionCell({ | ||||
|                 idSectionCell: index + 1, | ||||
|                 sectionId: title, | ||||
|                 contentSectionCell: txt, | ||||
|                 modificationDate: date, | ||||
|             }) | ||||
|     ); | ||||
|  | ||||
|     return new Promise<SectionCell[]>((resolve) => { | ||||
|         setTimeout(() => resolve(result), 500); | ||||
|     }); | ||||
| }; | ||||
|  | ||||
| function getCurrentFormattedDate(): string { | ||||
|     const now = new Date(); | ||||
|     const year = now.getFullYear(); | ||||
|     const month = String(now.getMonth() + 1).padStart(2, "0"); // +1 car janvier = 0 | ||||
|     const day = String(now.getDate()).padStart(2, "0"); | ||||
|     const hours = String(now.getHours()).padStart(2, "0"); | ||||
|     const minutes = String(now.getMinutes()).padStart(2, "0"); | ||||
|  | ||||
|     return `${year}-${month}-${day} ${hours}:${minutes}`; | ||||
| } | ||||
|  | ||||
| onMounted(() => { | ||||
|     fetchData( | ||||
|         props.projectId, | ||||
|         props.title, | ||||
|         getCurrentFormattedDate(), | ||||
|         IS_MOCK_MODE | ||||
|     ); | ||||
| }); | ||||
| </script> | ||||
|  | ||||
| <style scoped> | ||||
| @import "@/components/canvas/style-project.css"; | ||||
|  | ||||
| .cell { | ||||
|   display: flex; | ||||
|   flex-direction: column; | ||||
|   align-items: center; | ||||
|   justify-content: center; | ||||
|   text-align: center; | ||||
|   transition: all 0.3s ease; | ||||
|   cursor: pointer; | ||||
|   box-shadow: 0 4px 5px rgba(0, 0, 0, 0.1); | ||||
|     display: flex; | ||||
|     flex-direction: column; | ||||
|     align-items: center; | ||||
|     justify-content: center; | ||||
|     text-align: center; | ||||
|     transition: all 0.3s ease; | ||||
|     cursor: pointer; | ||||
|     box-shadow: 0 4px 5px rgba(0, 0, 0, 0.1); | ||||
| } | ||||
|  | ||||
| .expanded-content { | ||||
|   justify-content: flex-start !important; | ||||
|     justify-content: flex-start !important; | ||||
| } | ||||
|  | ||||
| .tooltip-explain { | ||||
|     position: absolute; | ||||
|     bottom: 101%; | ||||
|     left: 50%; | ||||
|     transform: translateX(-50%); | ||||
|     background-color: #333; | ||||
|     color: #fff; | ||||
|     padding: 6px 12px; | ||||
|     font-size: 13px; | ||||
|     border-radius: 6px; | ||||
|     white-space: nowrap; | ||||
|     opacity: 0; | ||||
|     pointer-events: none; | ||||
|     transition: opacity 0.3s; | ||||
|     z-index: 10; | ||||
| } | ||||
|  | ||||
| .cell:not(.expanded):hover .tooltip-explain { | ||||
|     opacity: 0.9; | ||||
| } | ||||
|  | ||||
| .cell:not(.expanded):hover { | ||||
|   transform: scale(1.05); | ||||
|   box-shadow: 0 8px 9px rgba(0, 0, 0, 0.2); | ||||
|     transform: scale(1.05); | ||||
|     box-shadow: 0 8px 9px rgba(0, 0, 0, 0.2); | ||||
| } | ||||
|  | ||||
| .cell h3 { | ||||
|   font-size: 20px; | ||||
|   font-weight: 500; | ||||
|   /*margin-bottom: 10px;*/ | ||||
|     font-size: 15px; | ||||
|     font-weight: 500; | ||||
|     font-family: "Arial", sans-serif; | ||||
| } | ||||
|  | ||||
| .cell p { | ||||
|   font-size: 14px; | ||||
|   color: #666; | ||||
| .p { | ||||
|     font-size: 10px; | ||||
|     color: #666; | ||||
|     font-family: "Arial", sans-serif; | ||||
| } | ||||
|  | ||||
| .expanded { | ||||
|   padding-top: 10%; | ||||
|   position: fixed; | ||||
|   top: 0; | ||||
|   left: 0; | ||||
|   width: 100%; | ||||
|   height: 100%; | ||||
|   background: white; | ||||
|   z-index: 10; | ||||
|   display: flex; | ||||
|   align-items: center; | ||||
|   justify-content: flex-start; | ||||
|   box-shadow: 0 0 10px rgba(0, 0, 0, 0.2); | ||||
|     padding-top: 10%; | ||||
|     position: fixed; | ||||
|     top: 0; | ||||
|     left: 0; | ||||
|     width: 100%; | ||||
|     height: 100%; | ||||
|     background: white; | ||||
|     z-index: 10; | ||||
|     display: flex; | ||||
|     align-items: center; | ||||
|     justify-content: flex-start; | ||||
|     box-shadow: 0 0 10px rgba(0, 0, 0, 0.2); | ||||
| } | ||||
|  | ||||
|  | ||||
| .description { | ||||
|   display: flex; | ||||
|   align-items: center;  /* Centre verticalement */ | ||||
|   justify-content: center; /* Centre horizontalement */ | ||||
|   text-align: center; /* Centre le texte à l'intérieur */ | ||||
|   width: 100%; | ||||
|   height: 100%; | ||||
|   font-size: 16px; | ||||
|   margin-top: 10px; | ||||
|   margin-left: 2%; | ||||
|   margin-right: 4%; | ||||
|     font-size: 5px; | ||||
|     color: #333; | ||||
|     word-break: break-word; | ||||
|     width: 90%; | ||||
|     margin: 5px 0; | ||||
| } | ||||
|  | ||||
| .description + .p { | ||||
|   align-items: center; | ||||
|   justify-content: center; | ||||
|   text-align: center; | ||||
|     align-items: center; | ||||
|     justify-content: center; | ||||
|     text-align: center; | ||||
| } | ||||
|  | ||||
| .edit-input { | ||||
|   width: 100%; | ||||
|   height: 100%; | ||||
|   font-size: 16px; | ||||
|   padding: 10px; | ||||
|   border: 1px solid #ccc; | ||||
|   border-radius: 5px; | ||||
|   margin-top: 10px; | ||||
|   box-sizing: border-box; | ||||
|   margin-left: 2%; | ||||
|     width: 100%; | ||||
|     height: 100%; | ||||
|     min-height: 100px; | ||||
|     padding: 10px; | ||||
|     border: 1px solid #ccc; | ||||
|     border-radius: 5px; | ||||
|     margin-top: 10px; | ||||
|     box-sizing: border-box; | ||||
|     margin-left: 2%; | ||||
|     max-height: none; | ||||
|     overflow: hidden; | ||||
| } | ||||
|  | ||||
|  | ||||
| .button-container { | ||||
|   display: block; | ||||
|   margin-top: 20px; | ||||
|   justify-content: center; | ||||
|   align-items: center; | ||||
|   gap: 10px; | ||||
|   padding-right: 1%; | ||||
|     display: block; | ||||
|     margin-top: 20px; | ||||
|     justify-content: center; | ||||
|     align-items: center; | ||||
|     gap: 10px; | ||||
|     padding-right: 1%; | ||||
| } | ||||
|  | ||||
| .section-bloc ,.editing-section-bloc { | ||||
|   width: 100%; | ||||
|   justify-content: center; | ||||
|   align-items: center; | ||||
|   display: flex; | ||||
|   margin-right: 10%; | ||||
|   margin: 10px; | ||||
| .section-bloc { | ||||
|     background-color: #f3f3f3; | ||||
|     border-radius: 8px; | ||||
|     padding: 10px 12px; | ||||
|     font-family: "Arial", sans-serif; | ||||
|     color: #333; | ||||
|     word-break: break-word; | ||||
|     flex-shrink: 0; | ||||
|     cursor: default; | ||||
|  | ||||
|     max-width: 100%; | ||||
|     width: fit-content; | ||||
|     overflow-wrap: break-word; | ||||
|     box-sizing: border-box; | ||||
|  | ||||
|     min-width: 120px; | ||||
| } | ||||
|  | ||||
| .editing-section-bloc { | ||||
|     width: 100%; | ||||
|     justify-content: center; | ||||
|     align-items: center; | ||||
|     display: flex; | ||||
|     margin-right: 10%; | ||||
|     margin: 10px; | ||||
| } | ||||
|  | ||||
| .edit-button { | ||||
|   width: 100px; | ||||
|   height: 40px; | ||||
|   border: none; | ||||
|   border-radius: 5px; | ||||
|   cursor: pointer; | ||||
|   transition: background 0.3s ease; | ||||
|   font-size: 12px; | ||||
|   margin-right: 20px; | ||||
|     width: 100px; | ||||
|     height: 40px; | ||||
|     border: none; | ||||
|     border-radius: 5px; | ||||
|     cursor: pointer; | ||||
|     transition: background 0.3s ease; | ||||
|     font-size: 12px; | ||||
|     margin-right: 20px; | ||||
| } | ||||
|  | ||||
| .save-button, .cancel-button { | ||||
|   width: 100px; | ||||
|   height: 40px; | ||||
|   border: none; | ||||
|   border-radius: 5px; | ||||
|   cursor: pointer; | ||||
|   transition: background 0.3s ease; | ||||
|   font-size: 12px; | ||||
|   margin-bottom: 5px; | ||||
| .description p { | ||||
|     font-size: 12px; | ||||
| } | ||||
|  | ||||
| .save-button, | ||||
| .cancel-button { | ||||
|     width: 100px; | ||||
|     height: 40px; | ||||
|     border: none; | ||||
|     border-radius: 5px; | ||||
|     cursor: pointer; | ||||
|     transition: background 0.3s ease; | ||||
|     font-size: 12px; | ||||
|     margin-bottom: 5px; | ||||
| } | ||||
|  | ||||
| .edit-button { | ||||
|   background-color: #007bff; | ||||
|   color: white; | ||||
|     background-color: #007bff; | ||||
|     color: white; | ||||
| } | ||||
|  | ||||
| .save-button { | ||||
|   background-color: #28a745; | ||||
|   color: white; | ||||
|     background-color: #28a745; | ||||
|     color: white; | ||||
| } | ||||
|  | ||||
| .cancel-button { | ||||
|   background-color: #dc3545; | ||||
|   color: white; | ||||
|     background-color: #dc3545; | ||||
|     color: white; | ||||
| } | ||||
|  | ||||
| .edit-button:hover { | ||||
|   background-color: #0056b3; | ||||
|     background-color: #0056b3; | ||||
| } | ||||
|  | ||||
| .save-button:hover { | ||||
|   background-color: #218838; | ||||
|     background-color: #218838; | ||||
| } | ||||
|  | ||||
| .cancel-button:hover { | ||||
|   background-color: #c82333; | ||||
|     background-color: #c82333; | ||||
| } | ||||
|  | ||||
| .canvas-exit-hint { | ||||
|     font-size: 0.75rem; | ||||
|     color: #666; | ||||
|     position: fixed; | ||||
|     bottom: 10px; | ||||
|     left: 0; | ||||
|     width: 100%; | ||||
|     text-align: center; | ||||
|     z-index: 1000; | ||||
| } | ||||
|  | ||||
| .description-wrapper { | ||||
|     display: flex; | ||||
|     flex-direction: column; | ||||
|     align-items: center; | ||||
|     justify-content: center; | ||||
|     text-align: center; | ||||
|  | ||||
|     padding: 10px; | ||||
|     overflow: hidden; | ||||
|     max-height: 100%; | ||||
|     width: 100%; | ||||
|     box-sizing: border-box; | ||||
|  | ||||
|     width: 100%; | ||||
|     overflow-x: hidden; | ||||
|     max-height: 100%; | ||||
|     box-sizing: border-box; | ||||
| } | ||||
|  | ||||
| .custom-flow { | ||||
|     display: flex; | ||||
|     flex-wrap: wrap; | ||||
|     gap: 10px; | ||||
|     justify-content: flex-start; | ||||
|     align-items: flex-start; | ||||
|     padding: 10px; | ||||
|  | ||||
|     width: 100%; | ||||
|     box-sizing: border-box; | ||||
|     overflow-x: hidden; | ||||
| } | ||||
|  | ||||
| .container { | ||||
|     display: grid; | ||||
|     grid-template-columns: repeat(auto-fill, minmax(100px, 1fr)); | ||||
|     grid-auto-rows: min-content; | ||||
|     grid-auto-flow: dense; | ||||
|     gap: 1rem; | ||||
| } | ||||
|  | ||||
| .float-up { | ||||
|     transform: translateY(-10px); | ||||
| } | ||||
| .float-left { | ||||
|     transform: translateX(-10px); | ||||
| } | ||||
| .float-right { | ||||
|     transform: translateX(10px); | ||||
| } | ||||
| .wiggle { | ||||
|     transform: rotate(1deg); | ||||
| } | ||||
| .tilt { | ||||
|     transform: rotate(-1deg); | ||||
| } | ||||
|  | ||||
| .from-left { | ||||
|     align-self: flex-start; | ||||
| } | ||||
|  | ||||
| .from-right { | ||||
|     align-self: flex-end; | ||||
| } | ||||
|  | ||||
| .section-bloc.from-left { | ||||
|     margin-right: auto; | ||||
|     margin-left: 20%; | ||||
| } | ||||
|  | ||||
| .section-bloc.from-right { | ||||
|     margin-left: auto; | ||||
|     margin-right: 20%; | ||||
| } | ||||
|  | ||||
| .explain { | ||||
|     font-size: 16px; | ||||
|     color: #444; | ||||
|     background-color: #f9f9f9; | ||||
|     padding: 7px; | ||||
|     border-radius: 6px; | ||||
|     margin-bottom: 20px; | ||||
|     margin-top: 20px; | ||||
|     line-height: 1.6; | ||||
|     font-family: "Segoe UI", sans-serif; | ||||
| } | ||||
|  | ||||
| .delete-button { | ||||
|     position: absolute; | ||||
|     top: 5px; | ||||
|     right: 10px; | ||||
|     background: transparent; | ||||
|     border: none; | ||||
|     color: #a00; | ||||
|     font-size: 1.2rem; | ||||
|     cursor: pointer; | ||||
|     z-index: 10; | ||||
| } | ||||
|  | ||||
| .section-bloc { | ||||
|     position: relative; | ||||
| } | ||||
| </style> | ||||
| @@ -1,120 +1,239 @@ | ||||
| <template> | ||||
|   <header> | ||||
|     <img src="../icons/logo inpulse.png" alt="INPulse Logo"> | ||||
|     <div class="header-buttons"> | ||||
|       <div class="menu"> | ||||
|         <button class="contact-button" @click="toggleDropdown">Contact</button> | ||||
|         <div class="contact-dropdown" v-bind:class="{ 'dropdown-visible': isDropdownOpen }"> | ||||
|           <button @click="contactAll">Contact All</button> | ||||
|           <button v-for="(email, index) in entrepreneurEmails" :key="index"> | ||||
|             {{ email }} | ||||
|           </button> | ||||
|     <header class="header"> | ||||
|         <a | ||||
|             href="https://www.bordeaux-inp.fr/fr/lincubateur-bordeaux-inpulse" | ||||
|             target="_blank" | ||||
|             rel="noopener" | ||||
|         > | ||||
|             <img | ||||
|                 src="../icons/logo inpulse.png" | ||||
|                 alt="INPulse Logo" | ||||
|                 class="logo" | ||||
|             /> | ||||
|         </a> | ||||
|  | ||||
|         <div class="header-actions"> | ||||
|             <button class="return-button" @click="store.logout">Logout</button> | ||||
|  | ||||
|             <div v-if="props.isAdmin"> | ||||
|                 <div ref="dropdownRef" class="dropdown-wrapper"> | ||||
|                     <button class="contact-button" @click.stop="toggleDropdown"> | ||||
|                         Contact | ||||
|                     </button> | ||||
|                     <div | ||||
|                         v-if="entrepreneurEmails.length > 0" | ||||
|                         class="contact-dropdown" | ||||
|                         :class="{ 'dropdown-visible': isDropdownOpen }" | ||||
|                     > | ||||
|                         <button @click="contactAll">Contacter tous</button> | ||||
|                         <button | ||||
|                             v-for="(email, index) in entrepreneurEmails" | ||||
|                             :key="index" | ||||
|                             @click="contactSingle(email)" | ||||
|                         > | ||||
|                             {{ email }} | ||||
|                         </button> | ||||
|                     </div> | ||||
|                 </div> | ||||
|             </div> | ||||
|         </div> | ||||
|         <button class="return-button"> | ||||
|             <RouterLink to="/">Return to list project</RouterLink> | ||||
|         </button> | ||||
|       </div> | ||||
|     </div> | ||||
|   </header> | ||||
|     </header> | ||||
| </template> | ||||
|  | ||||
|  | ||||
| <script setup lang="ts"> | ||||
| import { ref, onMounted } from "vue"; | ||||
| import axios from "axios"; | ||||
| import { ref, onMounted, onBeforeUnmount } from "vue"; | ||||
| import { store } from "@/main.ts"; | ||||
| import { getProjectEntrepreneurs } from "@/services/Apis/Shared.ts"; | ||||
| import UserEntrepreneur from "@/ApiClasses/UserEntrepreneur.ts"; | ||||
|  | ||||
| const dropdownRef = ref<HTMLElement | undefined>(undefined); | ||||
|  | ||||
| const props = defineProps<{ | ||||
|     projectId: number; | ||||
|     isAdmin: boolean; | ||||
| }>(); | ||||
|  | ||||
| const isDropdownOpen = ref(false); | ||||
| const entrepreneurEmails = ref([]); | ||||
| const entrepreneurEmails = ref<string[]>([]); | ||||
| const entrepreneurs = ref<UserEntrepreneur[]>([]); | ||||
| const IS_MOCK_MODE = false; | ||||
|  | ||||
| const toggleDropdown = () => { | ||||
|   isDropdownOpen.value = !isDropdownOpen.value; | ||||
|   console.log("Dropdown toggled:", isDropdownOpen.value); // for debug purposes | ||||
|     isDropdownOpen.value = !isDropdownOpen.value; | ||||
| }; | ||||
|  | ||||
| const fetchEntrepreneurs = async () => { | ||||
|   try { | ||||
|     const response = await axios.get("http://localhost:5000/entrepreneurs"); | ||||
|     entrepreneurEmails.value = response.data.map((e: { email: string }) => e.email); | ||||
|   } catch (error) { | ||||
|     console.error("Erreur lors de la récupération des entrepreneurs:", error); | ||||
|   } | ||||
| const fetchMockEntrepreneurs = () => { | ||||
|     const mockData = [ | ||||
|         { | ||||
|             userName: "Doe", | ||||
|             userSurname: "John", | ||||
|             primaryMail: "john.doe@example.com", | ||||
|         }, | ||||
|         { | ||||
|             userName: "Smith", | ||||
|             userSurname: "Anna", | ||||
|             primaryMail: "anna.smith@example.com", | ||||
|         }, | ||||
|         { | ||||
|             userName: "Mock", | ||||
|             userSurname: "User", | ||||
|             primaryMail: undefined, | ||||
|         }, | ||||
|     ]; | ||||
|  | ||||
|     entrepreneurs.value = mockData.map((item) => new UserEntrepreneur(item)); | ||||
|     entrepreneurEmails.value = entrepreneurs.value | ||||
|         .map((e) => e.primaryMail) | ||||
|         .filter((mail): mail is string => !!mail); | ||||
|  | ||||
|     console.log("Mock entrepreneurs chargés :", entrepreneurs.value); | ||||
| }; | ||||
|  | ||||
| const fetchEntrepreneurs = (projectId: number, useMock = false) => { | ||||
|     if (useMock) { | ||||
|         fetchMockEntrepreneurs(); | ||||
|     } else { | ||||
|         if (projectId == -1) { | ||||
|             console.error("No contact to show because no project was found."); | ||||
|             return; | ||||
|         } | ||||
|         getProjectEntrepreneurs( | ||||
|             projectId, | ||||
|             (response) => { | ||||
|                 const rawData = response.data as Partial<UserEntrepreneur>[]; | ||||
|                 entrepreneurs.value = rawData.map( | ||||
|                     (item) => new UserEntrepreneur(item) | ||||
|                 ); | ||||
|                 entrepreneurEmails.value = entrepreneurs.value | ||||
|                     .map((e) => e.primaryMail) | ||||
|                     .filter((mail): mail is string => !!mail); | ||||
|             }, | ||||
|             (error) => { | ||||
|                 console.error( | ||||
|                     "Erreur lors de la récupération des entrepreneurs :", | ||||
|                     error | ||||
|                 ); | ||||
|             } | ||||
|         ); | ||||
|     } | ||||
| }; | ||||
|  | ||||
| const contactAll = () => { | ||||
|   alert("Contacter tous les entrepreneurs : " + entrepreneurEmails.value.join(", ")); | ||||
|     const allEmails = entrepreneurEmails.value.join(", "); | ||||
|     navigator.clipboard | ||||
|         .writeText(allEmails) | ||||
|         .then(() => { | ||||
|             alert("Tous les emails copiés dans le presse-papiers !"); | ||||
|             window.open("https://partage.bordeaux-inp.fr/", "_blank"); | ||||
|         }) | ||||
|         .catch((err) => console.error("Erreur lors de la copie :", err)); | ||||
| }; | ||||
|  | ||||
| onMounted(fetchEntrepreneurs); | ||||
| </script> | ||||
| const contactSingle = (email: string) => { | ||||
|     navigator.clipboard | ||||
|         .writeText(email) | ||||
|         .then(() => { | ||||
|             alert(`Adresse copiée : ${email}`); | ||||
|             window.open("https://partage.bordeaux-inp.fr/", "_blank"); | ||||
|         }) | ||||
|         .catch((err) => console.error("Erreur lors de la copie :", err)); | ||||
| }; | ||||
|  | ||||
| const handleClickOutside = (event: MouseEvent) => { | ||||
|     if ( | ||||
|         isDropdownOpen.value && | ||||
|         dropdownRef.value && | ||||
|         !dropdownRef.value.contains(event.target as Node) | ||||
|     ) { | ||||
|         isDropdownOpen.value = false; | ||||
|     } | ||||
| }; | ||||
|  | ||||
| onMounted(() => { | ||||
|     if (props.isAdmin) { | ||||
|         fetchEntrepreneurs(props.projectId, IS_MOCK_MODE); | ||||
|     } | ||||
|     document.addEventListener("click", handleClickOutside); | ||||
| }); | ||||
|  | ||||
| onBeforeUnmount(() => { | ||||
|     document.removeEventListener("click", handleClickOutside); | ||||
| }); | ||||
| </script> | ||||
|  | ||||
| <style scoped> | ||||
| @import "@/components/canvas/style-project.css"; | ||||
|  | ||||
| header { | ||||
|   display: flex; | ||||
|   justify-content: space-between; | ||||
|   align-items: flex-start; | ||||
|   padding: 10px; | ||||
| .header { | ||||
|     display: flex; | ||||
|     justify-content: space-between; | ||||
|     align-items: center; | ||||
|     padding: 10px 20px; | ||||
|     background-color: #f9f9f9; | ||||
|     box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); | ||||
| } | ||||
|  | ||||
| .header-buttons { | ||||
|   display: flex; | ||||
|   align-items: flex-start; | ||||
| .logo { | ||||
|     height: 50px; | ||||
| } | ||||
|  | ||||
| .menu { | ||||
|   display: flex; | ||||
|   flex-direction: column; | ||||
|   gap: 10px; | ||||
| .header-actions { | ||||
|     display: flex; | ||||
|     align-items: center; | ||||
|     gap: 20px; | ||||
|     position: relative; | ||||
| } | ||||
|  | ||||
| .contact-button, .return-button { | ||||
|   background-color: #000; | ||||
|   color: white; | ||||
|   border: none; | ||||
|   padding: 10px; | ||||
|   cursor: pointer; | ||||
|   font-size: 14px; | ||||
|   text-align: center; | ||||
| .contact-button, | ||||
| .return-button { | ||||
|     background-color: #009cde; | ||||
|     color: white; | ||||
|     border: none; | ||||
|     padding: 10px 15px; | ||||
|     cursor: pointer; | ||||
|     font-size: 14px; | ||||
|     border-radius: 5px; | ||||
|     text-decoration: none; | ||||
|     transition: background-color 0.2s ease; | ||||
|     font-family: Arial, sans-serif; | ||||
| } | ||||
|  | ||||
| .return-button a { | ||||
|   color: white; | ||||
|   text-decoration: none; | ||||
| .return-button:hover, | ||||
| .contact-button:hover { | ||||
|     background-color: #007bad; | ||||
| } | ||||
|  | ||||
| .contact-dropdown { | ||||
|   display: none; | ||||
|   position: absolute; | ||||
|   background-color: white; | ||||
|   box-shadow: 0px 4px 8px rgba(0, 0, 0, 0.2); | ||||
|   border-radius: 8px; | ||||
|   padding: 10px; | ||||
|   margin-top: 5px; | ||||
|   z-index: 1000; | ||||
|     position: absolute; | ||||
|     top: 100%; | ||||
|     left: 0; | ||||
|     background-color: #000; | ||||
|     color: white; | ||||
|     box-shadow: 0px 4px 8px rgba(255, 255, 255, 0.2); | ||||
|     border-radius: 8px; | ||||
|     padding: 10px; | ||||
|     margin-top: 5px; | ||||
|     z-index: 1000; | ||||
|     min-width: 200px; | ||||
|     display: none; | ||||
| } | ||||
|  | ||||
| .contact-dropdown button { | ||||
|   display: block; | ||||
|   width: 100%; | ||||
|   padding: 5px; | ||||
|   text-align: left; | ||||
|   border: none; | ||||
|   background: none; | ||||
|   cursor: pointer; | ||||
|     display: block; | ||||
|     width: 100%; | ||||
|     padding: 5px; | ||||
|     text-align: left; | ||||
|     border: none; | ||||
|     background: none; | ||||
|     cursor: pointer; | ||||
|     color: white; | ||||
| } | ||||
|  | ||||
| .contact-dropdown button:hover { | ||||
|   background-color: #f0f0f0; | ||||
|     background-color: #009cde; | ||||
| } | ||||
|  | ||||
| .dropdown-visible { | ||||
|   display: block; | ||||
| } | ||||
|  | ||||
| header img { | ||||
|   width: 100px; | ||||
|   height: auto; | ||||
| .contact-dropdown.dropdown-visible { | ||||
|     display: block; | ||||
| } | ||||
| </style> | ||||
|   | ||||
| @@ -1,59 +1,174 @@ | ||||
| <template> | ||||
|   <div class="canvas"> | ||||
|     <CanvasItem | ||||
|       v-for="(item, index) in items" | ||||
|       :key="index" | ||||
|       :title="item.title" | ||||
|       :title_text="item.title_text" | ||||
|       :description="item.description" | ||||
|       :projectId="item.projectId" | ||||
|       :class="item.class" | ||||
|     /> | ||||
|   </div> | ||||
|     <div class="canvas container-fluid"> | ||||
|         <CanvasItem | ||||
|             v-for="(item, index) in items" | ||||
|             :key="index" | ||||
|             :title="item.title" | ||||
|             :title-text="item.title_text" | ||||
|             :description="item.description" | ||||
|             :project-id="props.projectId" | ||||
|             :class="['canvas-item', item.class, 'card', 'shadow', 'p-3']" | ||||
|             :is-admin="props.isAdmin" | ||||
|         /> | ||||
|     </div> | ||||
| </template> | ||||
|  | ||||
| <script setup lang="ts"> | ||||
| import { ref } from "vue"; | ||||
| import CanvasItem from "@/components/canvas/CanvasItem.vue"; | ||||
|  | ||||
| const items = ref([ | ||||
|   { projectId: 1, title: 1, title_text: "1. Problème", description: "3 problèmes essentiels à résoudre pour le client", class: "Probleme" }, | ||||
|   { projectId: 1, title: 2, title_text: "2. Segments", description: "Les segments de clientèle visés", class: "Segments" }, | ||||
|   { projectId: 1, title: 3, title_text: "3. Valeur", description: "La proposition de valeur", class: "Valeur" }, | ||||
|   { projectId: 1, title: 4, title_text: "4. Solution", description: "Les solutions proposées", class: "Solution" }, | ||||
|   { projectId: 1, title: 5, title_text: "5. Avantage", description: "Les avantages concurrentiels", class: "Avantage" }, | ||||
|   { projectId: 1, title: 6, title_text: "6. Canaux", description: "Les canaux de distribution", class: "Canaux" }, | ||||
|   { projectId: 1, title: 7, title_text: "7. Indicateurs", description: "Les indicateurs clés de performance", class: "Indicateurs" }, | ||||
|   { projectId: 1, title: 8, title_text: "8. Coûts", description: "Les coûts associés", class: "Couts" }, | ||||
|   { projectId: 1, title: 9, title_text: "9. Revenus", description: "Les sources de revenus", class: "Revenus" } | ||||
| ]); | ||||
| const props = defineProps<{ | ||||
|     projectId: number; | ||||
|     isAdmin: boolean; | ||||
| }>(); | ||||
|  | ||||
| const items = ref([ | ||||
|     { | ||||
|         title: 1, | ||||
|         title_text: "1. Problème", | ||||
|         description: "3 problèmes essentiels à résoudre pour le client", | ||||
|         class: "Probleme", | ||||
|     }, | ||||
|     { | ||||
|         title: 2, | ||||
|         title_text: "2. Segments", | ||||
|         description: "Les segments de clientèle visés", | ||||
|         class: "Segments", | ||||
|     }, | ||||
|     { | ||||
|         title: 3, | ||||
|         title_text: "3. Valeur", | ||||
|         description: "La proposition de valeur", | ||||
|         class: "Valeur", | ||||
|     }, | ||||
|     { | ||||
|         title: 4, | ||||
|         title_text: "4. Solution", | ||||
|         description: "Les solutions proposées", | ||||
|         class: "Solution", | ||||
|     }, | ||||
|     { | ||||
|         title: 5, | ||||
|         title_text: "5. Avantage", | ||||
|         description: "Les avantages concurrentiels", | ||||
|         class: "Avantage", | ||||
|     }, | ||||
|     { | ||||
|         title: 6, | ||||
|         title_text: "6. Canaux", | ||||
|         description: "Les canaux de distribution", | ||||
|         class: "Canaux", | ||||
|     }, | ||||
|     { | ||||
|         title: 7, | ||||
|         title_text: "7. Indicateurs", | ||||
|         description: "Les indicateurs clés de performance", | ||||
|         class: "Indicateurs", | ||||
|     }, | ||||
|     { | ||||
|         title: 8, | ||||
|         title_text: "8. Coûts", | ||||
|         description: "Les coûts associés", | ||||
|         class: "Couts", | ||||
|     }, | ||||
|     { | ||||
|         title: 9, | ||||
|         title_text: "9. Revenus", | ||||
|         description: "Les sources de revenus", | ||||
|         class: "Revenus", | ||||
|     }, | ||||
| ]); | ||||
| </script> | ||||
|  | ||||
| <style scoped> | ||||
| @import "@/components/canvas/style-project.css"; | ||||
|  | ||||
| .canvas { | ||||
|   display: grid; | ||||
|   grid-template-columns: repeat(10, 1fr); | ||||
|   grid-template-rows: repeat(6, 1fr); | ||||
|   gap: 10px; | ||||
|   padding: 10px; | ||||
|   max-width: 1200px; | ||||
|   margin: 20px auto; | ||||
|   background-color: #fff;  | ||||
|   position: relative; | ||||
|   height: 80vh; | ||||
|   overflow: auto; | ||||
|     display: grid; | ||||
|     grid-template-columns: repeat(10, minmax(0, 1fr)); | ||||
|     grid-auto-rows: min-content; | ||||
|     gap: 12px; | ||||
|     padding: 30px; | ||||
|     position: relative; | ||||
|     height: auto; | ||||
|     max-height: none; | ||||
|     box-sizing: border-box; | ||||
|     overflow: visible; | ||||
| } | ||||
|  | ||||
| .Probleme { grid-column: 1 / 3; grid-row: 1 / 5; padding-top: 20px;} | ||||
| .Segments { grid-column: 9 / 11; grid-row: 1 / 5; padding-top: 20px;} | ||||
| .Valeur { grid-column: 5 / 7; grid-row: 1 / 5; padding-top: 20px;} | ||||
| .Solution { grid-column: 3 / 5; grid-row: 1 / 3; padding-top: 20px;} | ||||
| .Avantage { grid-column: 7 / 9; grid-row: 1 / 3; padding-top: 20px;} | ||||
| .Canaux { grid-column: 7 / 9; grid-row: 3 / 5; padding-top: 20px;} | ||||
| .Indicateurs { grid-column: 3 / 5; grid-row: 3 / 5; padding-top: 20px;} | ||||
| .Couts { grid-column: 1 / 6; grid-row: 5 / 7; padding-top: 20px;} | ||||
| .Revenus { grid-column: 6 / 11; grid-row: 5 / 7; padding-top: 20px;} | ||||
| @media (max-width: 768px) { | ||||
|     .canvas { | ||||
|         grid-template-columns: repeat(1, 1fr); | ||||
|     } | ||||
| } | ||||
|  | ||||
| .Probleme { | ||||
|     grid-column: 1 / 3; | ||||
|     grid-row: 1 / 5; | ||||
| } | ||||
| .Segments { | ||||
|     grid-column: 9 / 11; | ||||
|     grid-row: 1 / 5; | ||||
| } | ||||
| .Valeur { | ||||
|     grid-column: 5 / 7; | ||||
|     grid-row: 1 / 5; | ||||
| } | ||||
| .Solution { | ||||
|     grid-column: 3 / 5; | ||||
|     grid-row: 1 / 3; | ||||
| } | ||||
| .Avantage { | ||||
|     grid-column: 7 / 9; | ||||
|     grid-row: 1 / 3; | ||||
| } | ||||
| .Canaux { | ||||
|     grid-column: 7 / 9; | ||||
|     grid-row: 3 / 5; | ||||
| } | ||||
| .Indicateurs { | ||||
|     grid-column: 3 / 5; | ||||
|     grid-row: 3 / 5; | ||||
| } | ||||
| .Couts { | ||||
|     grid-column: 1 / 6; | ||||
|     grid-row: 5 / 7; | ||||
| } | ||||
| .Revenus { | ||||
|     grid-column: 6 / 11; | ||||
|     grid-row: 5 / 7; | ||||
| } | ||||
|  | ||||
| .canvas-item { | ||||
|     border: 1px solid #dee2e6; | ||||
|     border-radius: 0.5rem; | ||||
| } | ||||
|  | ||||
| .Probleme { | ||||
|     background-color: #ffdddd; | ||||
| } | ||||
| .Segments { | ||||
|     background-color: #ddffdd; | ||||
| } | ||||
| .Valeur { | ||||
|     background-color: #ddddff; | ||||
| } | ||||
| .Solution { | ||||
|     background-color: #fff0b3; | ||||
| } | ||||
| .Avantage { | ||||
|     background-color: #d1c4e9; | ||||
| } | ||||
| .Canaux { | ||||
|     background-color: #b2ebf2; | ||||
| } | ||||
| .Indicateurs { | ||||
|     background-color: #ffe082; | ||||
| } | ||||
| .Couts { | ||||
|     background-color: #ffcdd2; | ||||
| } | ||||
| .Revenus { | ||||
|     background-color: #c8e6c9; | ||||
| } | ||||
| </style> | ||||
|   | ||||
| @@ -2,63 +2,62 @@ body { | ||||
|     font-family: Arial, sans-serif; | ||||
|     margin: 0; | ||||
|     padding: 10px; | ||||
|   } | ||||
| } | ||||
|  | ||||
|   .row { | ||||
| .row { | ||||
|     display: flex; | ||||
|   } | ||||
| } | ||||
|  | ||||
|   .cell { | ||||
| .cell { | ||||
|     flex: 1; | ||||
|     border: 1px solid #ddd; | ||||
|     padding: 10px; | ||||
|     text-align: center; | ||||
|     background-color: #f1f1f1; | ||||
|   } | ||||
| } | ||||
|  | ||||
|   .produit { | ||||
| .produit { | ||||
|     background-color: #f9e4e4; | ||||
|   } | ||||
| } | ||||
|  | ||||
|   .marche { | ||||
| .marche { | ||||
|     background-color: #e4f1f9; | ||||
|   } | ||||
| } | ||||
|  | ||||
|   .valeur { | ||||
| .valeur { | ||||
|     background-color: #f9f4e4; | ||||
|   } | ||||
| } | ||||
|  | ||||
|   h3 { | ||||
| h3 { | ||||
|     margin: 0; | ||||
|     font-size: 18px; | ||||
|     color: #333; | ||||
|   } | ||||
| } | ||||
|  | ||||
|   p { | ||||
| p { | ||||
|     margin: 5px 0 0; | ||||
|     font-size: 14px; | ||||
|   } | ||||
| } | ||||
|  | ||||
|  | ||||
|   body { | ||||
| body { | ||||
|     font-family: Arial, sans-serif; | ||||
|     margin: 0; | ||||
|     padding: 0; | ||||
|     background-color: #f9f9f9; | ||||
|   } | ||||
| } | ||||
|  | ||||
|   h1 img { | ||||
| h1 img { | ||||
|     height: 80px; | ||||
|     margin: 40px; | ||||
|     text-align: center; | ||||
|   } | ||||
| } | ||||
|  | ||||
|   .row { | ||||
| .row { | ||||
|     display: flex; | ||||
|     margin-bottom: 10px; | ||||
|   } | ||||
| } | ||||
|  | ||||
|   #ade { | ||||
| #ade { | ||||
|     max-width: 1200px; | ||||
|     margin: 20px auto; | ||||
|     padding: 20px; | ||||
| @@ -66,49 +65,51 @@ body { | ||||
|     background-color: #e8f5e9; | ||||
|     border: 2px solid #4caf50; | ||||
|     border-radius: 10px; | ||||
|   } | ||||
| } | ||||
|  | ||||
|   #ade h3 { | ||||
| #ade h3 { | ||||
|     color: #2e7d32; | ||||
|   } | ||||
| } | ||||
|  | ||||
|   #ade p { | ||||
| #ade p { | ||||
|     margin: 10px 0; | ||||
|     font-size: 16px; | ||||
|     color: #333; | ||||
|   } | ||||
|   header { | ||||
| } | ||||
| header { | ||||
|     display: flex; | ||||
|     justify-content: space-between; | ||||
|     align-items: center; | ||||
|     padding: 10px 20px; | ||||
|     background-color: #fff; | ||||
|     border-bottom: 2px solid #ddd; | ||||
|   } | ||||
| } | ||||
|  | ||||
|   header img { | ||||
| header img { | ||||
|     height: 60px; | ||||
|   } | ||||
| } | ||||
|  | ||||
|   header .contact-menu { | ||||
| header .contact-menu { | ||||
|     position: relative; | ||||
|   } | ||||
| } | ||||
|  | ||||
|   .contact-button, .return { | ||||
| .contact-button, | ||||
| .return { | ||||
|     padding: 10px 15px; | ||||
|     border: none; | ||||
|     border-radius: 4px; | ||||
|     background-color: #2196f3; | ||||
|     color: #fff; | ||||
|     cursor: pointer; | ||||
|   } | ||||
| } | ||||
|  | ||||
|  .contact-button:hover, .return:hover { | ||||
| .contact-button:hover, | ||||
| .return:hover { | ||||
|     background-color: #1976d2; | ||||
|   } | ||||
| } | ||||
|  | ||||
|   /* Dropdown styling */ | ||||
|   .contact-dropdown { | ||||
| /* Dropdown styling */ | ||||
| .contact-dropdown { | ||||
|     position: absolute; | ||||
|     right: 0; | ||||
|     top: 50px; | ||||
| @@ -121,36 +122,35 @@ body { | ||||
|     border-radius: 8px; | ||||
|     box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); | ||||
|     z-index: 10; | ||||
|   } | ||||
| } | ||||
|  | ||||
|   .contact-dropdown button { | ||||
| .contact-dropdown button { | ||||
|     padding: 8px 12px; | ||||
|     border: none; | ||||
|     border-radius: 4px; | ||||
|     background-color: #4caf50; | ||||
|     color: #fff; | ||||
|     cursor: pointer; | ||||
|   } | ||||
| } | ||||
|  | ||||
|   .contact-dropdown button:hover { | ||||
| .contact-dropdown button:hover { | ||||
|     background-color: #388e3c; | ||||
|   } | ||||
| } | ||||
|  | ||||
|   .return { | ||||
| .return { | ||||
|     background-color: #f44336; | ||||
|   } | ||||
| } | ||||
|  | ||||
|   .return:hover { | ||||
| .return:hover { | ||||
|     background-color: #d32f2f; | ||||
|   } | ||||
| } | ||||
|  | ||||
|   .header-buttons { | ||||
| .header-buttons { | ||||
|     display: flex; | ||||
|     align-items: center; | ||||
|     gap: 15px; | ||||
|   } | ||||
| } | ||||
|  | ||||
|  | ||||
|   a{ | ||||
| a { | ||||
|     color: white; | ||||
|   } | ||||
| } | ||||
|   | ||||
							
								
								
									
										0
									
								
								front/MyINPulse-front/src/components/contact.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								front/MyINPulse-front/src/components/contact.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -26,11 +26,10 @@ keycloakService.CallInit(() => { | ||||
|         console.error(e); | ||||
|         createApp(App).mount("#app"); | ||||
|     } | ||||
|  | ||||
| }) | ||||
| }); | ||||
|  | ||||
| // this shit made by me so i can run the canva vue app | ||||
| createApp(App).use(router).mount('#app'); | ||||
| //createApp(App).use(router).mount('#app'); | ||||
|  | ||||
| // TODO: fix the comment | ||||
| /* | ||||
| @@ -71,7 +70,4 @@ app.use(VueKeyCloak,{ | ||||
| }  ); | ||||
| */ | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| export { store }; | ||||
|   | ||||
| @@ -1,14 +1,16 @@ | ||||
| // file: src/plugins/authStore.js | ||||
|  | ||||
| import { useAuthStore } from "@/stores/authStore.ts"; | ||||
| import keycloakService from '@/services/keycloak'; | ||||
| import keycloakService from "@/services/keycloak"; | ||||
| import type { Pinia } from "pinia"; | ||||
| import type { App } from "vue"; | ||||
| // Setup auth store as a plugin so it can be accessed globally in our FE | ||||
| const authStorePlugin = { | ||||
|     install(app: any, option: any) { | ||||
|     install(app: App, option: { pinia: Pinia }) { | ||||
|         const store = useAuthStore(option.pinia); | ||||
|         app.config.globalProperties.$store = store; | ||||
|         keycloakService.CallInitStore(store); | ||||
|     } | ||||
| } | ||||
|     }, | ||||
| }; | ||||
|  | ||||
| export default authStorePlugin; | ||||
							
								
								
									
										0
									
								
								front/MyINPulse-front/src/router/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								front/MyINPulse-front/src/router/index.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -1,30 +1,58 @@ | ||||
| import { createRouter, createWebHistory } from "vue-router"; | ||||
|  | ||||
| const router = createRouter({ | ||||
|   history: createWebHistory(import.meta.env.BASE_URL), | ||||
|   routes: [ | ||||
|     { | ||||
|       path: '/test', | ||||
|       name: 'test', | ||||
|       // route level code-splitting | ||||
|       // this generates a separate chunk (About.[hash].js) for this route | ||||
|       // which is lazy-loaded when the route is visited. | ||||
|       component: () => import('../views/testComponent.vue'), | ||||
|     }, | ||||
|     history: createWebHistory(import.meta.env.BASE_URL), | ||||
|     routes: [ | ||||
|         { | ||||
|             path: "/test", | ||||
|             name: "test", | ||||
|             // route level code-splitting | ||||
|             // this generates a separate chunk (About.[hash].js) for this route | ||||
|             // which is lazy-loaded when the route is visited. | ||||
|             component: () => import("../views/testComponent.vue"), | ||||
|         }, | ||||
|         { | ||||
|             path: "/", | ||||
|             name: "login", | ||||
|             component: () => import("../components/LoginComponent.vue"), | ||||
|         }, | ||||
|         { | ||||
|             path: "/admin", | ||||
|             name: "Admin-main", | ||||
|             component: () => import("../views/AdminMain.vue"), | ||||
|         }, | ||||
|  | ||||
|     { | ||||
|       path: '/', | ||||
|       name: 'Admin-main', | ||||
|       component: () => import('../views/AdminMain.vue'), | ||||
|     }, | ||||
|         // route pour les canvas (made by adnane), in fact the two vue apps are separated for now | ||||
|         { | ||||
|             path: "/canvas", | ||||
|             name: "canvas", | ||||
|             component: () => import("../views/CanvasView.vue"), | ||||
|         }, | ||||
|  | ||||
| // route pour les canvas (made by adnane), in fact the two vue apps are separated for now   | ||||
|     { | ||||
|       path: '/canvas', | ||||
|       name: 'canvas', | ||||
|       component: () => import('../views/CanvasView.vue'), | ||||
|     }, | ||||
|   ], | ||||
| }) | ||||
|         { | ||||
|             path: "/signup", | ||||
|             name: "signup", | ||||
|             component: () => import("../views/EntrepSignUp.vue"), | ||||
|         }, | ||||
|  | ||||
|         { | ||||
|             path: "/JorCproject", | ||||
|             name: "JorCproject", | ||||
|             component: () => import("../views/JoinOrCreatProjectForEntrep.vue"), | ||||
|         }, | ||||
|  | ||||
|         { | ||||
|             path: "/finalize", | ||||
|             name: "finalize", | ||||
|             component: () => import("../views/FinalizeAccount.vue"), | ||||
|         }, | ||||
|  | ||||
|         { | ||||
|             path: "/pending-approval", | ||||
|             name: "PendingApproval", | ||||
|             component: () => import("@/views/PendingApproval.vue"), | ||||
|         }, | ||||
|     ], | ||||
| }); | ||||
|  | ||||
| export default router; | ||||
|   | ||||
							
								
								
									
										340
									
								
								front/MyINPulse-front/src/services/Apis/Admin.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										340
									
								
								front/MyINPulse-front/src/services/Apis/Admin.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,340 @@ | ||||
| import { type AxiosError, type AxiosResponse } from "axios"; | ||||
| import Report from "@/ApiClasses/Repport"; | ||||
| import ProjectDecision from "@/ApiClasses/ProjectDecision"; | ||||
| //import UserAdmin from "@/ApiClasses/UserAdmin"; | ||||
| import { | ||||
|     axiosInstance, | ||||
|     defaultApiErrorHandler, | ||||
|     defaultApiSuccessHandler, | ||||
| } from "@/services/api"; | ||||
|  | ||||
| // Admin API | ||||
| function getPendingAccounts( | ||||
|     onSuccessHandler?: (response: AxiosResponse) => void, | ||||
|     onErrorHandler?: (error: AxiosError) => void | ||||
| ): void { | ||||
|     axiosInstance | ||||
|         .get("/admin/pending-accounts") | ||||
|         .then((response) => { | ||||
|             if (onSuccessHandler) { | ||||
|                 onSuccessHandler(response); | ||||
|             } else { | ||||
|                 defaultApiSuccessHandler(response); | ||||
|             } | ||||
|         }) | ||||
|         .catch((error: AxiosError) => { | ||||
|             if (onErrorHandler) { | ||||
|                 onErrorHandler(error); | ||||
|             } else { | ||||
|                 defaultApiErrorHandler(error); | ||||
|             } | ||||
|         }); | ||||
| } | ||||
|  | ||||
| function validateUserAccount( | ||||
|     userId: number, | ||||
|     onSuccessHandler?: (response: AxiosResponse) => void, | ||||
|     onErrorHandler?: (error: AxiosError) => void | ||||
| ): void { | ||||
|     axiosInstance | ||||
|         .post(`/admin/accounts/validate/${userId}`) | ||||
|         .then((response) => { | ||||
|             if (onSuccessHandler) { | ||||
|                 onSuccessHandler(response); | ||||
|             } else { | ||||
|                 defaultApiSuccessHandler(response); | ||||
|             } | ||||
|         }) | ||||
|         .catch((error: AxiosError) => { | ||||
|             if (onErrorHandler) { | ||||
|                 onErrorHandler(error); | ||||
|             } else { | ||||
|                 defaultApiErrorHandler(error); | ||||
|             } | ||||
|         }); | ||||
| } | ||||
|  | ||||
| function getPendingProjectJoinRequests( // Not yet implemented | ||||
|     onSuccessHandler?: (response: AxiosResponse) => void, | ||||
|     onErrorHandler?: (error: AxiosError) => void | ||||
| ): void { | ||||
|     axiosInstance | ||||
|         .get("/admin/request-join") | ||||
|         .then((response) => { | ||||
|             if (onSuccessHandler) { | ||||
|                 onSuccessHandler(response); | ||||
|             } else { | ||||
|                 defaultApiSuccessHandler(response); | ||||
|             } | ||||
|         }) | ||||
|         .catch((error: AxiosError) => { | ||||
|             if (onErrorHandler) { | ||||
|                 onErrorHandler(error); | ||||
|             } else { | ||||
|                 defaultApiErrorHandler(error); | ||||
|             } | ||||
|         }); | ||||
| } | ||||
| function decideProjectJoinRequest( // Not yet implemented | ||||
|     joinRequestId: number, | ||||
|     decision: { isAccepted: boolean }, | ||||
|     onSuccessHandler?: (response: AxiosResponse) => void, | ||||
|     onErrorHandler?: (error: AxiosError) => void | ||||
| ): void { | ||||
|     axiosInstance | ||||
|         .post(`/admin/request-join/decision/${joinRequestId}`, decision) | ||||
|         .then((response) => { | ||||
|             if (onSuccessHandler) { | ||||
|                 onSuccessHandler(response); | ||||
|             } else { | ||||
|                 defaultApiSuccessHandler(response); | ||||
|             } | ||||
|         }) | ||||
|         .catch((error: AxiosError) => { | ||||
|             if (onErrorHandler) { | ||||
|                 onErrorHandler(error); | ||||
|             } else { | ||||
|                 defaultApiErrorHandler(error); | ||||
|             } | ||||
|         }); | ||||
| } | ||||
|  | ||||
| function getAdminProjects( | ||||
|     onSuccessHandler?: (response: AxiosResponse) => void, | ||||
|     onErrorHandler?: (error: AxiosError) => void | ||||
| ): void { | ||||
|     axiosInstance | ||||
|         .get("/admin/projects") | ||||
|         .then((response) => { | ||||
|             if (onSuccessHandler) { | ||||
|                 onSuccessHandler(response); | ||||
|             } else { | ||||
|                 defaultApiSuccessHandler(response); | ||||
|             } | ||||
|         }) | ||||
|         .catch((error: AxiosError) => { | ||||
|             if (onErrorHandler) { | ||||
|                 onErrorHandler(error); | ||||
|             } else { | ||||
|                 defaultApiErrorHandler(error); | ||||
|             } | ||||
|         }); | ||||
| } | ||||
|  | ||||
| type ProjectCreatePayload = { | ||||
|     projectName: string; | ||||
|     logo?: string; | ||||
| }; | ||||
|  | ||||
| function addProjectManually( | ||||
|     payload: ProjectCreatePayload, | ||||
|     onSuccess?: (response: AxiosResponse) => void, | ||||
|     onError?: (error: AxiosError) => void | ||||
| ): void { | ||||
|     axiosInstance | ||||
|         .post("/admin/projects", payload) | ||||
|         .then((response) => onSuccess?.(response)) | ||||
|         .catch((error: AxiosError) => onError?.(error)); | ||||
| } | ||||
|  | ||||
| function getPendingProjects( | ||||
|     onSuccessHandler?: (response: AxiosResponse) => void, | ||||
|     onErrorHandler?: (error: AxiosError) => void | ||||
| ): void { | ||||
|     axiosInstance | ||||
|         .get("/admin/projects/pending") | ||||
|         .then((response) => { | ||||
|             if (onSuccessHandler) { | ||||
|                 onSuccessHandler(response); | ||||
|             } else { | ||||
|                 defaultApiSuccessHandler(response); | ||||
|             } | ||||
|         }) | ||||
|         .catch((error: AxiosError) => { | ||||
|             if (onErrorHandler) { | ||||
|                 onErrorHandler(error); | ||||
|             } else { | ||||
|                 defaultApiErrorHandler(error); | ||||
|             } | ||||
|         }); | ||||
| } | ||||
|  | ||||
| function decidePendingProject( | ||||
|     decision: ProjectDecision, | ||||
|     onSuccessHandler?: (response: AxiosResponse) => void, | ||||
|     onErrorHandler?: (error: AxiosError) => void | ||||
| ): void { | ||||
|     axiosInstance | ||||
|         .post(`/admin/projects/pending/decision`, decision.toObject()) | ||||
|         .then((response) => { | ||||
|             if (onSuccessHandler) { | ||||
|                 onSuccessHandler(response); | ||||
|             } else { | ||||
|                 defaultApiSuccessHandler(response); | ||||
|             } | ||||
|         }) | ||||
|         .catch((error: AxiosError) => { | ||||
|             if (onErrorHandler) { | ||||
|                 onErrorHandler(error); | ||||
|             } else { | ||||
|                 defaultApiErrorHandler(error); | ||||
|             } | ||||
|         }); | ||||
| } | ||||
|  | ||||
| function createAppointmentReport( | ||||
|     appointmentId: number, | ||||
|     reportContent: Report, // Replace 'any' with a proper type for report content if available | ||||
|     onSuccessHandler?: (response: AxiosResponse) => void, | ||||
|     onErrorHandler?: (error: AxiosError) => void | ||||
| ): void { | ||||
|     axiosInstance | ||||
|         .post(`/admin/appointments/report/${appointmentId}`, reportContent) | ||||
|         .then((response) => { | ||||
|             if (onSuccessHandler) { | ||||
|                 onSuccessHandler(response); | ||||
|             } else { | ||||
|                 defaultApiSuccessHandler(response); | ||||
|             } | ||||
|         }) | ||||
|         .catch((error: AxiosError) => { | ||||
|             if (onErrorHandler) { | ||||
|                 onErrorHandler(error); | ||||
|             } else { | ||||
|                 defaultApiErrorHandler(error); | ||||
|             } | ||||
|         }); | ||||
| } | ||||
|  | ||||
| function updateAppointmentReport( | ||||
|     appointmentId: number, | ||||
|     reportContent: Report, // Replace 'any' with a proper type for report content if available | ||||
|     onSuccessHandler?: (response: AxiosResponse) => void, | ||||
|     onErrorHandler?: (error: AxiosError) => void | ||||
| ): void { | ||||
|     axiosInstance | ||||
|         .put(`/admin/appointments/report/${appointmentId}`, reportContent) | ||||
|         .then((response) => { | ||||
|             if (onSuccessHandler) { | ||||
|                 onSuccessHandler(response); | ||||
|             } else { | ||||
|                 defaultApiSuccessHandler(response); | ||||
|             } | ||||
|         }) | ||||
|         .catch((error: AxiosError) => { | ||||
|             if (onErrorHandler) { | ||||
|                 onErrorHandler(error); | ||||
|             } else { | ||||
|                 defaultApiErrorHandler(error); | ||||
|             } | ||||
|         }); | ||||
| } | ||||
|  | ||||
| function getUpcomingAppointments( | ||||
|     onSuccessHandler?: (response: AxiosResponse) => void, | ||||
|     onErrorHandler?: (error: AxiosError) => void | ||||
| ): void { | ||||
|     axiosInstance | ||||
|         .get("/admin/appointments/upcoming") | ||||
|         .then((response) => { | ||||
|             if (onSuccessHandler) { | ||||
|                 onSuccessHandler(response); | ||||
|             } else { | ||||
|                 defaultApiSuccessHandler(response); | ||||
|             } | ||||
|         }) | ||||
|         .catch((error: AxiosError) => { | ||||
|             if (onErrorHandler) { | ||||
|                 onErrorHandler(error); | ||||
|             } else { | ||||
|                 defaultApiErrorHandler(error); | ||||
|             } | ||||
|         }); | ||||
| } | ||||
|  | ||||
| function removeProject( | ||||
|     projectId: number, | ||||
|     onSuccessHandler?: (response: AxiosResponse) => void, | ||||
|     onErrorHandler?: (error: AxiosError) => void | ||||
| ): void { | ||||
|     axiosInstance | ||||
|         .delete(`/admin/projects/${projectId}`) | ||||
|         .then((response) => { | ||||
|             if (onSuccessHandler) { | ||||
|                 onSuccessHandler(response); | ||||
|             } else { | ||||
|                 defaultApiSuccessHandler(response); | ||||
|             } | ||||
|         }) | ||||
|         .catch((error: AxiosError) => { | ||||
|             if (onErrorHandler) { | ||||
|                 onErrorHandler(error); | ||||
|             } else { | ||||
|                 defaultApiErrorHandler(error); | ||||
|             } | ||||
|         }); | ||||
| } | ||||
|  | ||||
| function grantAdminRights( | ||||
|     userId: number, | ||||
|     onSuccessHandler?: (response: AxiosResponse) => void, | ||||
|     onErrorHandler?: (error: AxiosError) => void | ||||
| ): void { | ||||
|     axiosInstance | ||||
|         .post(`/admin/make-admin/${userId}`) | ||||
|         .then((response) => { | ||||
|             if (onSuccessHandler) { | ||||
|                 onSuccessHandler(response); | ||||
|             } else { | ||||
|                 defaultApiSuccessHandler(response); | ||||
|             } | ||||
|         }) | ||||
|         .catch((error: AxiosError) => { | ||||
|             if (onErrorHandler) { | ||||
|                 onErrorHandler(error); | ||||
|             } else { | ||||
|                 defaultApiErrorHandler(error); | ||||
|             } | ||||
|         }); | ||||
| } | ||||
|  | ||||
| function createAdmin( | ||||
|     onSuccessHandler?: (response: AxiosResponse) => void, | ||||
|     onErrorHandler?: (error: AxiosError) => void | ||||
| ): void { | ||||
|     axiosInstance | ||||
|         .post("/admin/create-account") | ||||
|         .then((response) => { | ||||
|             if (onSuccessHandler) { | ||||
|                 onSuccessHandler(response); | ||||
|             } else { | ||||
|                 defaultApiSuccessHandler(response); | ||||
|             } | ||||
|         }) | ||||
|         .catch((error: AxiosError) => { | ||||
|             if (onErrorHandler) { | ||||
|                 onErrorHandler(error); | ||||
|             } else { | ||||
|                 defaultApiErrorHandler(error); | ||||
|             } | ||||
|         }); | ||||
| } | ||||
|  | ||||
| export { | ||||
|     axiosInstance, | ||||
|     //requestJoinProject, // Not yet implemented [cite: 4] | ||||
|     getPendingAccounts, | ||||
|     validateUserAccount, | ||||
|     getPendingProjectJoinRequests, // Not yet implemented [cite: 3] | ||||
|     decideProjectJoinRequest, // Not yet implemented [cite: 3] | ||||
|     getAdminProjects, | ||||
|     addProjectManually, | ||||
|     getPendingProjects, | ||||
|     decidePendingProject, | ||||
|     createAppointmentReport, | ||||
|     updateAppointmentReport, | ||||
|     getUpcomingAppointments, | ||||
|     removeProject, | ||||
|     grantAdminRights, | ||||
|     createAdmin, | ||||
| }; | ||||
							
								
								
									
										180
									
								
								front/MyINPulse-front/src/services/Apis/Entrepreneurs.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										180
									
								
								front/MyINPulse-front/src/services/Apis/Entrepreneurs.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,180 @@ | ||||
| import { type AxiosError, type AxiosResponse } from "axios"; | ||||
| import Project from "@/ApiClasses/Project"; | ||||
| import SectionCell from "@/ApiClasses/SectionCell"; | ||||
| import { | ||||
|     axiosInstance, | ||||
|     defaultApiErrorHandler, | ||||
|     defaultApiSuccessHandler, | ||||
| } from "@/services/api"; | ||||
|  | ||||
| // Entrepreneurs API | ||||
| function getEntrepreneurProjectId( | ||||
|     onSuccessHandler?: (response: AxiosResponse<number[]>) => void, | ||||
|     onErrorHandler?: (error: AxiosError) => void | ||||
| ): void { | ||||
|     axiosInstance | ||||
|         .get("/entrepreneur/projects") | ||||
|         .then((response) => { | ||||
|             if (onSuccessHandler) { | ||||
|                 onSuccessHandler(response); | ||||
|             } else { | ||||
|                 defaultApiSuccessHandler(response); | ||||
|             } | ||||
|         }) | ||||
|         .catch((error: AxiosError) => { | ||||
|             if (onErrorHandler) { | ||||
|                 onErrorHandler(error); | ||||
|             } else { | ||||
|                 defaultApiErrorHandler(error); | ||||
|             } | ||||
|         }); | ||||
| } | ||||
|  | ||||
| function requestProjectCreation( | ||||
|     projectDetails: Project, | ||||
|     onSuccessHandler?: (response: AxiosResponse) => void, | ||||
|     onErrorHandler?: (error: AxiosError) => void | ||||
| ): void { | ||||
|     axiosInstance | ||||
|         .post("/entrepreneur/projects/request", projectDetails.toObject()) | ||||
|         .then((response) => { | ||||
|             if (onSuccessHandler) { | ||||
|                 onSuccessHandler(response); | ||||
|             } else { | ||||
|                 defaultApiSuccessHandler(response); | ||||
|             } | ||||
|         }) | ||||
|         .catch((error: AxiosError) => { | ||||
|             if (onErrorHandler) { | ||||
|                 onErrorHandler(error); | ||||
|             } else { | ||||
|                 defaultApiErrorHandler(error); | ||||
|             } | ||||
|         }); | ||||
| } | ||||
|  | ||||
| function addSectionCell( | ||||
|     sectionCellDetails: SectionCell, | ||||
|     onSuccessHandler?: (response: AxiosResponse) => void, | ||||
|     onErrorHandler?: (error: AxiosError) => void | ||||
| ): void { | ||||
|     axiosInstance | ||||
|         .post("/entrepreneur/sectionCells", sectionCellDetails.toObject()) | ||||
|         .then((response) => { | ||||
|             if (onSuccessHandler) { | ||||
|                 onSuccessHandler(response); | ||||
|             } else { | ||||
|                 defaultApiSuccessHandler(response); | ||||
|             } | ||||
|         }) | ||||
|         .catch((error: AxiosError) => { | ||||
|             if (onErrorHandler) { | ||||
|                 onErrorHandler(error); | ||||
|             } else { | ||||
|                 defaultApiErrorHandler(error); | ||||
|             } | ||||
|         }); | ||||
| } | ||||
|  | ||||
| function modifySectionCell( | ||||
|     sectionCellId: number, | ||||
|     sectionCellDetails: SectionCell, | ||||
|     onSuccessHandler?: (response: AxiosResponse) => void, | ||||
|     onErrorHandler?: (error: AxiosError) => void | ||||
| ): void { | ||||
|     axiosInstance | ||||
|         .put(`/entrepreneur/sectionCells/${sectionCellId}`, sectionCellDetails) | ||||
|         .then((response) => { | ||||
|             if (onSuccessHandler) { | ||||
|                 onSuccessHandler(response); | ||||
|             } else { | ||||
|                 defaultApiSuccessHandler(response); | ||||
|             } | ||||
|         }) | ||||
|         .catch((error: AxiosError) => { | ||||
|             if (onErrorHandler) { | ||||
|                 onErrorHandler(error); | ||||
|             } else { | ||||
|                 defaultApiErrorHandler(error); | ||||
|             } | ||||
|         }); | ||||
| } | ||||
|  | ||||
| function removeSectionCell( | ||||
|     sectionCellId: number, | ||||
|     onSuccessHandler?: (response: AxiosResponse) => void, | ||||
|     onErrorHandler?: (error: AxiosError) => void | ||||
| ): void { | ||||
|     axiosInstance | ||||
|         .delete(`/entrepreneur/sectionCells/${sectionCellId}`) | ||||
|         .then((response) => { | ||||
|             if (onSuccessHandler) { | ||||
|                 onSuccessHandler(response); | ||||
|             } else { | ||||
|                 defaultApiSuccessHandler(response); | ||||
|             } | ||||
|         }) | ||||
|         .catch((error: AxiosError) => { | ||||
|             if (onErrorHandler) { | ||||
|                 onErrorHandler(error); | ||||
|             } else { | ||||
|                 defaultApiErrorHandler(error); | ||||
|             } | ||||
|         }); | ||||
| } | ||||
|  | ||||
| // Checks if the entrepreneur has a pending project request | ||||
| function checkPendingProjectRequest( | ||||
|     onSuccessHandler?: (response: AxiosResponse<boolean>) => void, | ||||
|     onErrorHandler?: (error: AxiosError) => void | ||||
| ): void { | ||||
|     axiosInstance | ||||
|         .get("/entrepreneur/projects/has-pending-request") | ||||
|         .then((response) => { | ||||
|             if (onSuccessHandler) { | ||||
|                 onSuccessHandler(response); | ||||
|             } else { | ||||
|                 defaultApiSuccessHandler(response); | ||||
|             } | ||||
|         }) | ||||
|         .catch((error: AxiosError) => { | ||||
|             if (onErrorHandler) { | ||||
|                 onErrorHandler(error); | ||||
|             } else { | ||||
|                 defaultApiErrorHandler(error); | ||||
|             } | ||||
|         }); | ||||
| } | ||||
|  | ||||
| // Checks if the entrepreneur has an active project | ||||
| function checkIfProjectIsActive( | ||||
|     onSuccessHandler?: (response: AxiosResponse<boolean>) => void, | ||||
|     onErrorHandler?: (error: AxiosError) => void | ||||
| ): void { | ||||
|     axiosInstance | ||||
|         .get("/entrepreneur/projects/project-is-active") | ||||
|         .then((response) => { | ||||
|             if (onSuccessHandler) { | ||||
|                 onSuccessHandler(response); | ||||
|             } else { | ||||
|                 defaultApiSuccessHandler(response); | ||||
|             } | ||||
|         }) | ||||
|         .catch((error: AxiosError) => { | ||||
|             if (onErrorHandler) { | ||||
|                 onErrorHandler(error); | ||||
|             } else { | ||||
|                 defaultApiErrorHandler(error); | ||||
|             } | ||||
|         }); | ||||
| } | ||||
|  | ||||
| export { | ||||
|     getEntrepreneurProjectId, | ||||
|     requestProjectCreation, | ||||
|     addSectionCell, | ||||
|     modifySectionCell, | ||||
|     removeSectionCell, | ||||
|     checkPendingProjectRequest, | ||||
|     checkIfProjectIsActive, | ||||
| }; | ||||
							
								
								
									
										157
									
								
								front/MyINPulse-front/src/services/Apis/Shared.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										157
									
								
								front/MyINPulse-front/src/services/Apis/Shared.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,157 @@ | ||||
| import { type AxiosError, type AxiosResponse } from "axios"; | ||||
| import Appointment from "@/ApiClasses/Appointment"; | ||||
| import { | ||||
|     axiosInstance, | ||||
|     defaultApiErrorHandler, | ||||
|     defaultApiSuccessHandler, | ||||
| } from "@/services/api"; | ||||
|  | ||||
| // Shared API | ||||
| function getSectionCellsByDate( | ||||
|     projectId: number, | ||||
|     sectionId: number, | ||||
|     date: string, // Use string for date in 'YYYY-MM-DD HH:mm' format | ||||
|     onSuccessHandler?: (response: AxiosResponse) => void, | ||||
|     onErrorHandler?: (error: AxiosError) => void | ||||
| ): void { | ||||
|     axiosInstance | ||||
|         .get(`/shared/projects/sectionCells/${projectId}/${sectionId}/${date}`) | ||||
|         .then((response) => { | ||||
|             if (onSuccessHandler) { | ||||
|                 onSuccessHandler(response); | ||||
|             } else { | ||||
|                 defaultApiSuccessHandler(response); | ||||
|             } | ||||
|         }) | ||||
|         .catch((error: AxiosError) => { | ||||
|             if (onErrorHandler) { | ||||
|                 onErrorHandler(error); | ||||
|             } else { | ||||
|                 defaultApiErrorHandler(error); | ||||
|             } | ||||
|         }); | ||||
| } | ||||
|  | ||||
| function getProjectEntrepreneurs( | ||||
|     projectId: number, | ||||
|     onSuccessHandler?: (response: AxiosResponse) => void, | ||||
|     onErrorHandler?: (error: AxiosError) => void | ||||
| ): void { | ||||
|     axiosInstance | ||||
|         .get(`/shared/projects/entrepreneurs/${projectId}`) | ||||
|         .then((response) => { | ||||
|             if (onSuccessHandler) { | ||||
|                 onSuccessHandler(response); | ||||
|             } else { | ||||
|                 defaultApiSuccessHandler(response); | ||||
|             } | ||||
|         }) | ||||
|         .catch((error: AxiosError) => { | ||||
|             if (onErrorHandler) { | ||||
|                 onErrorHandler(error); | ||||
|             } else { | ||||
|                 defaultApiErrorHandler(error); | ||||
|             } | ||||
|         }); | ||||
| } | ||||
|  | ||||
| function getProjectAdmin( | ||||
|     projectId: number, | ||||
|     onSuccessHandler?: (response: AxiosResponse) => void, | ||||
|     onErrorHandler?: (error: AxiosError) => void | ||||
| ): void { | ||||
|     axiosInstance | ||||
|         .get(`/shared/projects/admin/${projectId}`) | ||||
|         .then((response) => { | ||||
|             if (onSuccessHandler) { | ||||
|                 onSuccessHandler(response); | ||||
|             } else { | ||||
|                 defaultApiSuccessHandler(response); | ||||
|             } | ||||
|         }) | ||||
|         .catch((error: AxiosError) => { | ||||
|             if (onErrorHandler) { | ||||
|                 onErrorHandler(error); | ||||
|             } else { | ||||
|                 defaultApiErrorHandler(error); | ||||
|             } | ||||
|         }); | ||||
| } | ||||
|  | ||||
| function getProjectAppointments( | ||||
|     projectId: number, | ||||
|     onSuccessHandler?: (response: AxiosResponse) => void, | ||||
|     onErrorHandler?: (error: AxiosError) => void | ||||
| ): void { | ||||
|     axiosInstance | ||||
|         .get(`/shared/projects/appointments/${projectId}`) | ||||
|         .then((response) => { | ||||
|             if (onSuccessHandler) { | ||||
|                 onSuccessHandler(response); | ||||
|             } else { | ||||
|                 defaultApiSuccessHandler(response); | ||||
|             } | ||||
|         }) | ||||
|         .catch((error: AxiosError) => { | ||||
|             if (onErrorHandler) { | ||||
|                 onErrorHandler(error); | ||||
|             } else { | ||||
|                 defaultApiErrorHandler(error); | ||||
|             } | ||||
|         }); | ||||
| } | ||||
|  | ||||
| function getAppointmentReport( | ||||
|     appointmentId: number, | ||||
|     onSuccessHandler?: (response: AxiosResponse) => void, | ||||
|     onErrorHandler?: (error: AxiosError) => void | ||||
| ): void { | ||||
|     axiosInstance | ||||
|         .get(`/shared/appointments/report/${appointmentId}`) | ||||
|         .then((response) => { | ||||
|             if (onSuccessHandler) { | ||||
|                 onSuccessHandler(response); | ||||
|             } else { | ||||
|                 defaultApiSuccessHandler(response); | ||||
|             } | ||||
|         }) | ||||
|         .catch((error: AxiosError) => { | ||||
|             if (onErrorHandler) { | ||||
|                 onErrorHandler(error); | ||||
|             } else { | ||||
|                 defaultApiErrorHandler(error); | ||||
|             } | ||||
|         }); | ||||
| } | ||||
|  | ||||
| function requestAppointment( | ||||
|     appointmentDetails: Appointment, // Replace 'any' with a proper type for appointment details if available | ||||
|     onSuccessHandler?: (response: AxiosResponse) => void, | ||||
|     onErrorHandler?: (error: AxiosError) => void | ||||
| ): void { | ||||
|     axiosInstance | ||||
|         .post("/shared/appointments/request", appointmentDetails) | ||||
|         .then((response) => { | ||||
|             if (onSuccessHandler) { | ||||
|                 onSuccessHandler(response); | ||||
|             } else { | ||||
|                 defaultApiSuccessHandler(response); | ||||
|             } | ||||
|         }) | ||||
|         .catch((error: AxiosError) => { | ||||
|             if (onErrorHandler) { | ||||
|                 onErrorHandler(error); | ||||
|             } else { | ||||
|                 defaultApiErrorHandler(error); | ||||
|             } | ||||
|         }); | ||||
| } | ||||
|  | ||||
| export { | ||||
|     getSectionCellsByDate, | ||||
|     getProjectEntrepreneurs, | ||||
|     getProjectAdmin, | ||||
|     getProjectAppointments, | ||||
|     getAppointmentReport, | ||||
|     requestAppointment, | ||||
| }; | ||||
							
								
								
									
										103
									
								
								front/MyINPulse-front/src/services/Apis/Unauth.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										103
									
								
								front/MyINPulse-front/src/services/Apis/Unauth.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,103 @@ | ||||
| import { type AxiosError, type AxiosResponse } from "axios"; | ||||
|  | ||||
| import { | ||||
|     axiosInstance, | ||||
|     defaultApiErrorHandler, | ||||
|     defaultApiSuccessHandler, | ||||
| } from "@/services/api"; | ||||
|  | ||||
| // Unauth API | ||||
| function finalizeAccount( | ||||
|     onSuccessHandler?: (response: AxiosResponse) => void, | ||||
|     onErrorHandler?: (error: AxiosError) => void | ||||
| ): void { | ||||
|     axiosInstance | ||||
|         .post("/unauth/finalize") | ||||
|         .then((response) => { | ||||
|             if (onSuccessHandler) { | ||||
|                 onSuccessHandler(response); | ||||
|             } else { | ||||
|                 defaultApiSuccessHandler(response); | ||||
|             } | ||||
|         }) | ||||
|         .catch((error: AxiosError) => { | ||||
|             if (onErrorHandler) { | ||||
|                 onErrorHandler(error); | ||||
|             } else { | ||||
|                 defaultApiErrorHandler(error); | ||||
|             } | ||||
|         }); | ||||
| } | ||||
|  | ||||
| // function requestJoinProject( // Not yet implemented [cite: 4] | ||||
| //     projectId: number, | ||||
| //     onSuccessHandler?: (response: AxiosResponse) => void, | ||||
| //     onErrorHandler?: (error: AxiosError) => void | ||||
| // ): void { | ||||
| //     axiosInstance | ||||
| //         .post(`/unauth/request-join/${projectId}`) | ||||
| //          .then((response) => { | ||||
| //             if (onSuccessHandler) { | ||||
| //                 onSuccessHandler(response); | ||||
| //             } else { | ||||
| //                 defaultApiSuccessHandler(response); | ||||
| //             } | ||||
| //         }) | ||||
| //         .catch((error: AxiosError) => { | ||||
| //             if (onErrorHandler) { | ||||
| //                 onErrorHandler(error); | ||||
| //             } else { | ||||
| //                 defaultApiErrorHandler(error); | ||||
| //             } | ||||
| //         }); | ||||
| // } | ||||
|  | ||||
| function getAllEntrepreneurs( | ||||
|     onSuccessHandler?: (response: AxiosResponse) => void, | ||||
|     onErrorHandler?: (error: AxiosError) => void | ||||
| ): void { | ||||
|     axiosInstance | ||||
|         .get("/unauth/getAllEntrepreneurs") | ||||
|         .then((response) => { | ||||
|             if (onSuccessHandler) { | ||||
|                 onSuccessHandler(response); | ||||
|             } else { | ||||
|                 defaultApiSuccessHandler(response); | ||||
|             } | ||||
|         }) | ||||
|         .catch((error: AxiosError) => { | ||||
|             if (onErrorHandler) { | ||||
|                 onErrorHandler(error); | ||||
|             } else { | ||||
|                 defaultApiErrorHandler(error); | ||||
|             } | ||||
|         }); | ||||
| } | ||||
|  | ||||
| function checkPending( | ||||
|     onSuccessHandler?: (response: AxiosResponse<boolean>) => void, | ||||
|     onErrorHandler?: (error: AxiosError) => void | ||||
| ): void { | ||||
|     axiosInstance | ||||
|         .get("/unauth/check-if-not-pending") | ||||
|         .then((response) => { | ||||
|             if (onSuccessHandler) { | ||||
|                 onSuccessHandler(response); | ||||
|             } else { | ||||
|                 defaultApiSuccessHandler(response); | ||||
|             } | ||||
|         }) | ||||
|         .catch((error: AxiosError) => { | ||||
|             if (onErrorHandler) { | ||||
|                 onErrorHandler(error); | ||||
|             } else { | ||||
|                 defaultApiErrorHandler(error); | ||||
|             } | ||||
|         }); | ||||
| } | ||||
| export { | ||||
|     finalizeAccount, | ||||
|     getAllEntrepreneurs, | ||||
|     checkPending, | ||||
|     // requestJoinProject, // Not yet implemented [cite: 4] | ||||
| }; | ||||
| @@ -1,6 +1,7 @@ | ||||
| import axios, { type AxiosError, type AxiosResponse } from "axios"; | ||||
| import { store } from "@/main.ts"; | ||||
| import { addNewMessage, color } from "@/services/popupDisplayer.ts"; | ||||
| import router from "@/router/router"; | ||||
|  | ||||
| const axiosInstance = axios.create({ | ||||
|     baseURL: import.meta.env.VITE_BACKEND_URL, | ||||
| @@ -9,6 +10,19 @@ const axiosInstance = axios.create({ | ||||
|     }, | ||||
| }); | ||||
|  | ||||
| axiosInstance.interceptors.request.use( | ||||
|     (config) => { | ||||
|         const token = store.user?.token; // Récupérez le token depuis le store | ||||
|         if (token) { | ||||
|             config.headers["Authorization"] = `Bearer ${token}`; // Ajoutez le token dans l'en-tête | ||||
|         } | ||||
|         return config; | ||||
|     }, | ||||
|     (error) => { | ||||
|         return Promise.reject(error); | ||||
|     } | ||||
| ); | ||||
|  | ||||
| axiosInstance.interceptors.response.use( | ||||
|     (response) => response, // Directly return successful responses. | ||||
|     async (error) => { | ||||
| @@ -19,19 +33,17 @@ axiosInstance.interceptors.response.use( | ||||
|             !originalRequest._retry && | ||||
|             store.authenticated | ||||
|         ) { | ||||
|             originalRequest._retry = true; // Mark the request as retried to avoid infinite loops. | ||||
|             originalRequest._retry = true; | ||||
|             try { | ||||
|                 await store.refreshUserToken(); | ||||
|                 // Update the authorization header with the new access token. | ||||
|                 axiosInstance.defaults.headers.common["Authorization"] = | ||||
|                     `Bearer ${store.user.token}`; | ||||
|                 return axiosInstance(originalRequest); // Retry the original request with the new access token. | ||||
|                 return axiosInstance(originalRequest); | ||||
|             } catch (refreshError) { | ||||
|                 // Handle refresh token errors by clearing stored tokens and redirecting to the login page. | ||||
|                 console.error("Token refresh failed:", refreshError); | ||||
|                 localStorage.removeItem("accessToken"); | ||||
|                 localStorage.removeItem("refreshToken"); | ||||
|                 window.location.href = "/login"; | ||||
|                 router.push("/login"); | ||||
|                 return Promise.reject(refreshError); | ||||
|             } | ||||
|         } | ||||
| @@ -41,7 +53,9 @@ axiosInstance.interceptors.response.use( | ||||
|  | ||||
| // TODO: spawn a error modal | ||||
| function defaultApiErrorHandler(err: AxiosError) { | ||||
|     addNewMessage(err.message, color.Red); | ||||
|     const errorMessage = | ||||
|         (err.response?.data as { message?: string })?.message ?? err.message; | ||||
|     addNewMessage(errorMessage, color.Red); | ||||
| } | ||||
|  | ||||
| function defaultApiSuccessHandler(response: AxiosResponse) { | ||||
| @@ -65,4 +79,36 @@ function callApi( | ||||
|         ); | ||||
| } | ||||
|  | ||||
| export { callApi }; | ||||
| function postApi( | ||||
|     endpoint: string, | ||||
|     data: unknown, //to fix eslint issue, go back here if errors occurs later | ||||
|     onSuccessHandler?: (response: AxiosResponse) => void, | ||||
|     onErrorHandler?: (error: AxiosError) => void | ||||
| ): void { | ||||
|     axiosInstance | ||||
|         .post(endpoint, data) | ||||
|         .then(onSuccessHandler ?? defaultApiSuccessHandler) | ||||
|         .catch(onErrorHandler ?? defaultApiErrorHandler); | ||||
| } | ||||
|  | ||||
| function deleteApi( | ||||
|     endpoint: string, | ||||
|     onSuccessHandler?: (response: AxiosResponse) => void, | ||||
|     onErrorHandler?: (error: AxiosError) => void | ||||
| ): void { | ||||
|     axiosInstance | ||||
|         .delete(endpoint) | ||||
|         .then(onSuccessHandler ?? defaultApiSuccessHandler) | ||||
|         .catch(onErrorHandler ?? defaultApiErrorHandler); | ||||
| } | ||||
|  | ||||
| //export { axiosInstance, callApi, postApi, deleteApi }; | ||||
|  | ||||
| export { | ||||
|     axiosInstance, | ||||
|     defaultApiErrorHandler, | ||||
|     defaultApiSuccessHandler, | ||||
|     callApi, | ||||
|     postApi, | ||||
|     deleteApi, | ||||
| }; | ||||
|   | ||||
							
								
								
									
										24
									
								
								front/MyINPulse-front/src/services/tools.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								front/MyINPulse-front/src/services/tools.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,24 @@ | ||||
| import { jwtDecode } from "jwt-decode"; | ||||
| import { store } from "@/main"; | ||||
|  | ||||
| type TokenPayload = { | ||||
|     realm_access?: { | ||||
|         roles?: string[]; | ||||
|     }; | ||||
| }; | ||||
|  | ||||
| function isAdmin(): boolean { | ||||
|     if (store.authenticated && store.user.token) { | ||||
|         const decoded = jwtDecode<TokenPayload>(store.user.token); | ||||
|         const roles = decoded.realm_access?.roles || []; | ||||
|         if (roles.includes("MyINPulse-admin")) { | ||||
|             return true; | ||||
|         } else { | ||||
|             return false; | ||||
|         } | ||||
|     } else { | ||||
|         return false; | ||||
|     } | ||||
| } | ||||
|  | ||||
| export { isAdmin }; | ||||
| @@ -54,7 +54,7 @@ const useAuthStore = defineStore("storeAuth", { | ||||
|         async logout() { | ||||
|             try { | ||||
|                 await keycloakService.CallLogout( | ||||
|                     import.meta.env.VITE_APP_URL + "/test" | ||||
|                     import.meta.env.VITE_APP_URL + "/" //redirect to login page instead of test... | ||||
|                 ); | ||||
|                 await this.clearUserData(); | ||||
|             } catch (error) { | ||||
|   | ||||
| @@ -1,68 +1,226 @@ | ||||
| <template> | ||||
|     <Header /> | ||||
|     <error-wrapper></error-wrapper> | ||||
|     <error-wrapper /> | ||||
|  | ||||
|     <div id="container"> | ||||
|       <div id="main"> | ||||
|           <ProjectComp  | ||||
|             v-for="(project, index) in projects"  | ||||
|             :key="index" | ||||
|             :projectName="project.name" | ||||
|             :listName="project.members" | ||||
|             :projectLink="project.link" | ||||
|           /> | ||||
|       </div> | ||||
|         <div id="main"> | ||||
|             <h3>Projet en cours</h3> | ||||
|             <ProjectComp | ||||
|                 v-for="(project, index) in projects" | ||||
|                 :key="index" | ||||
|                 :project-name="project.name" | ||||
|                 :list-name="project.members" | ||||
|                 :project-link="project.link" | ||||
|                 :project-id="0" | ||||
|             /> | ||||
|  | ||||
|       <Agenda :projectRDV="rendezVous" /> | ||||
|             <!-- <AllEntrep /> --> | ||||
|  | ||||
|             <h3>Projet en attente</h3> | ||||
|             <PendingProjectComponent | ||||
|                 v-for="(project, index) in pendingProjects" | ||||
|                 :key="index" | ||||
|                 :project-name="project.projectName" | ||||
|                 :creation-date="project.creationDate" | ||||
|             /> | ||||
|  | ||||
|             <AddProjectForm /> | ||||
|             <PendingRequestsManager /> | ||||
|         </div> | ||||
|  | ||||
|         <Agenda :project-r-d-v="rendezVous" /> | ||||
|     </div> | ||||
|      | ||||
| </template> | ||||
|  | ||||
| <script setup lang="ts"> | ||||
| import Header from '../components/HeaderComponent.vue'; | ||||
| import Agenda from "../components/Agenda.vue" | ||||
| import ProjectComp from '../components/ProjectComponent.vue'; | ||||
| import { ref, onMounted } from "vue"; | ||||
| import { getAdminProjects, getPendingProjects } from "@/services/Apis/Admin"; | ||||
| import { getProjectEntrepreneurs } from "@/services/Apis/Shared"; | ||||
| import { type AxiosError, type AxiosResponse } from "axios"; | ||||
|  | ||||
| import { ref } from "vue"; | ||||
| import Header from "../components/HeaderComponent.vue"; | ||||
| import Agenda from "../components/AgendaComponent.vue"; | ||||
| import ProjectComp from "../components/ProjectComponent.vue"; | ||||
| import PendingProjectComponent from "@/components/PendingProjectComponent.vue"; | ||||
| import AddProjectForm from "@/components/AddProjectForm.vue"; | ||||
| import PendingRequestsManager from "@/components/PendingRequestsManager.vue"; | ||||
| import { createAdmin } from "@/services/Apis/Admin"; | ||||
| import Project from "@/ApiClasses/Project"; | ||||
| import UserEntrepreneur from "@/ApiClasses/UserEntrepreneur"; | ||||
| //import UserAdmin from "@/ApiClasses/UserAdmin"; | ||||
| //import AllEntrep from "../components/AllEntrep.vue"; | ||||
| const projects = ref< | ||||
|     { | ||||
|         name: string; | ||||
|         link: string; | ||||
|         members: string[]; | ||||
|     }[] | ||||
| >([]); | ||||
|  | ||||
| const projects = ref([ | ||||
|   { | ||||
|     name: "Projet Alpha", | ||||
|     link: "/canvas", // to test | ||||
|     members: ["Alice", "Bob", "Charlie"], | ||||
|   }, | ||||
|   { | ||||
|     name: "Projet Beta", | ||||
|     link: "./canvas", // to test | ||||
|     members: ["David", "Eve", "Frank"], | ||||
|   }, | ||||
| ]); | ||||
| const fallbackProjects = [ | ||||
|     { | ||||
|         name: "Projet Alpha", | ||||
|         link: "/canvas", | ||||
|         members: ["Alice", "Bob", "Charlie"], | ||||
|     }, | ||||
|     { | ||||
|         name: "Projet Beta", | ||||
|         link: "./canvas", | ||||
|         members: ["David", "Eve", "Frank"], | ||||
|     }, | ||||
| ]; | ||||
|  | ||||
| const createFirstAdmin = () => { | ||||
|     createAdmin( | ||||
|         (response) => { | ||||
|             console.log("Admin créé avec succès :", response.data); | ||||
|         }, | ||||
|         (error) => { | ||||
|             console.error( | ||||
|                 "Erreur lors de la création de l'admin :", | ||||
|                 error.message | ||||
|             ); | ||||
|         } | ||||
|     ); | ||||
| }; | ||||
|  | ||||
| onMounted(createFirstAdmin); | ||||
|  | ||||
| const fetchProjects = () => { | ||||
|     getAdminProjects( | ||||
|         (response: AxiosResponse) => { | ||||
|             const projectList = response.data.map( | ||||
|                 (p: Project) => new Project(p) | ||||
|             ); | ||||
|  | ||||
|             const projectPromises = projectList.map( | ||||
|                 (project: Project) => | ||||
|                     new Promise<void>((resolve) => { | ||||
|                         getProjectEntrepreneurs( | ||||
|                             project.idProject!, | ||||
|                             (memberResponse: AxiosResponse) => { | ||||
|                                 const members = memberResponse.data.map( | ||||
|                                     (m: UserEntrepreneur) => | ||||
|                                         new UserEntrepreneur(m).userName || | ||||
|                                         "Unknown" | ||||
|                                 ); | ||||
|                                 projects.value.push({ | ||||
|                                     name: | ||||
|                                         project.projectName || | ||||
|                                         "Unnamed Project", | ||||
|                                     link: `/project/${project.idProject}`, | ||||
|                                     members, | ||||
|                                 }); | ||||
|                                 resolve(); | ||||
|                             }, | ||||
|                             () => { | ||||
|                                 projects.value.push({ | ||||
|                                     name: | ||||
|                                         project.projectName || | ||||
|                                         "Unnamed Project", | ||||
|                                     link: `/project/${project.idProject}`, | ||||
|                                     members: [], | ||||
|                                 }); | ||||
|                                 resolve(); | ||||
|                             } | ||||
|                         ); | ||||
|                     }) | ||||
|             ); | ||||
|  | ||||
|             Promise.all(projectPromises).catch(() => { | ||||
|                 projects.value = fallbackProjects; | ||||
|             }); | ||||
|         }, | ||||
|         (error: AxiosError) => { | ||||
|             console.error("Error fetching admin projects:", error); | ||||
|             projects.value = fallbackProjects; | ||||
|         } | ||||
|     ); | ||||
| }; | ||||
|  | ||||
| const pendingProjects = ref<Project[]>([]); | ||||
|  | ||||
| const mockPendingProjects = [ | ||||
|     new Project({ projectName: "l'eau", creationDate: "26-02-2024" }), | ||||
|     new Project({ projectName: "l'air", creationDate: "09-03-2023" }), | ||||
| ]; | ||||
|  | ||||
| onMounted(fetchProjects); | ||||
| onMounted(() => { | ||||
|     getPendingProjects( | ||||
|         (response) => { | ||||
|             pendingProjects.value = response.data.map( | ||||
|                 (p: Project) => | ||||
|                     new Project({ | ||||
|                         projectName: p.projectName, | ||||
|                         creationDate: p.creationDate, | ||||
|                     }) | ||||
|             ); | ||||
|         }, | ||||
|         (error) => { | ||||
|             console.error( | ||||
|                 "Failed to fetch pending projects, using mock:", | ||||
|                 error | ||||
|             ); | ||||
|             pendingProjects.value = mockPendingProjects.map( | ||||
|                 (p) => | ||||
|                     new Project({ | ||||
|                         projectName: p.projectName, | ||||
|                         creationDate: p.creationDate, | ||||
|                     }) | ||||
|             ); | ||||
|         } | ||||
|     ); | ||||
| }); | ||||
|  | ||||
| const rendezVous = ref([ | ||||
|   { projectName: "Projet Alpha", date: "2025-03-10", lieu: "P106" }, | ||||
|   { projectName: "Projet Beta", date: "2025-04-15", lieu: "Td10" }, | ||||
|     { projectName: "Projet Alpha", date: "2025-03-10", lieu: "P106" }, | ||||
|     { projectName: "Projet Beta", date: "2025-04-15", lieu: "Td10" }, | ||||
| ]); | ||||
|  | ||||
| </script> | ||||
|  | ||||
| <style scoped> | ||||
|  | ||||
| #container { | ||||
|     margin: 0; | ||||
|     display: grid; | ||||
|     grid-template-columns: 3fr 1fr; /* Main body takes 3/4, agenda 1/4 */ | ||||
|     height: 100vh; /* Full viewport height */ | ||||
|     grid-template-columns: 3fr 1fr; | ||||
|     gap: 2rem; | ||||
|     padding: 2rem; | ||||
|     background-color: #f4f6f9; | ||||
|     min-height: 100vh; | ||||
|     box-sizing: border-box; | ||||
| } | ||||
|  | ||||
| #main { | ||||
|     background-color: #fff; | ||||
|     padding: 2rem; | ||||
|     border-radius: 12px; | ||||
|     box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05); | ||||
| } | ||||
|  | ||||
| h3 { | ||||
|     font-size: 1.5rem; | ||||
|     color: #333; | ||||
|     margin-bottom: 1.2rem; | ||||
|     border-bottom: 2px solid #ddd; | ||||
|     padding-bottom: 0.5rem; | ||||
| } | ||||
|  | ||||
| button { | ||||
|   padding: 10px 15px; | ||||
|   background-color: #007bff; | ||||
|   color: white; | ||||
|   border: none; | ||||
|   cursor: pointer; | ||||
|   border-radius: 5px; | ||||
| } | ||||
| button:hover { | ||||
|   background-color: #0056b3; | ||||
|     padding: 10px 15px; | ||||
|     background-color: #007bff; | ||||
|     color: white; | ||||
|     border: none; | ||||
|     cursor: pointer; | ||||
|     border-radius: 6px; | ||||
|     font-weight: 500; | ||||
|     transition: background-color 0.2s ease; | ||||
| } | ||||
|  | ||||
| button:hover { | ||||
|     background-color: #0056b3; | ||||
| } | ||||
|  | ||||
| #main > * + * { | ||||
|     margin-top: 2rem; | ||||
| } | ||||
| </style> | ||||
| @@ -1,18 +1,181 @@ | ||||
| <template> | ||||
|     <div> | ||||
|       <header> | ||||
|         <HeaderCanvas /> | ||||
|       </header> | ||||
|         <header> | ||||
|             <HeaderCanvas :project-id="projectId" :is-admin="isAdmin_" /> | ||||
|         </header> | ||||
|     </div> | ||||
|     <div> | ||||
|       <h1>Page Canvas</h1> | ||||
|       <LeanCanvas /> | ||||
|         <h1 class="page-title">PAGE CANVAS</h1> | ||||
|         <p class="canvas-help-text"> | ||||
|             Cliquez sur un champ du tableau pour afficher son contenu en détail | ||||
|             ci-dessous. | ||||
|         </p> | ||||
|         <LeanCanvas :project-id="projectId" :is-admin="isAdmin_" /> | ||||
|         <div class="info-box"> | ||||
|             <p v-if="admin"> | ||||
|                 Responsable : | ||||
|                 <strong>{{ admin.userName }} {{ admin.userSurname }}</strong | ||||
|                 ><br /> | ||||
|                 Contact : | ||||
|                 <a :href="`mailto:${admin.primaryMail}`">{{ | ||||
|                     admin.primaryMail | ||||
|                 }}</a> | ||||
|                 | | ||||
|                 <a :href="`tel:${admin.phoneNumber}`">{{ | ||||
|                     admin.phoneNumber | ||||
|                 }}</a> | ||||
|             </p> | ||||
|             <p v-else>Chargement des informations du responsable...</p> | ||||
|         </div> | ||||
|     </div> | ||||
| </template> | ||||
|  | ||||
| <script setup lang="ts"> | ||||
| // @ts-ignore | ||||
| import HeaderCanvas from "../components/canvas/HeaderCanvas.vue"; | ||||
| import LeanCanvas from '../components/canvas/LeanCanvas.vue'; | ||||
| import LeanCanvas from "../components/canvas/LeanCanvas.vue"; | ||||
| import { ref, onMounted } from "vue"; | ||||
| import { isAdmin } from "@/services/tools.ts"; | ||||
| import { getProjectAdmin } from "@/services/Apis/Shared.ts"; | ||||
| import UserAdmin from "@/ApiClasses/UserAdmin.ts"; | ||||
| import type { AxiosResponse, AxiosError } from "axios"; | ||||
| import { getEntrepreneurProjectId } from "@/services/Apis/Entrepreneurs"; | ||||
|  | ||||
| const IS_MOCK_MODE = false; | ||||
| const isAdmin_ = isAdmin(); | ||||
| const admin = ref<UserAdmin | null>(null); | ||||
| const projectId = ref<number>(-1); | ||||
|  | ||||
| const mockAdminData = new UserAdmin({ | ||||
|     idUser: 1, | ||||
|     userSurname: "ALAMI", | ||||
|     userName: "Adnane", | ||||
|     primaryMail: "mock.admin@example.com", | ||||
|     secondaryMail: "admin.backup@example.com", | ||||
|     phoneNumber: "0600000000", | ||||
| }); | ||||
|  | ||||
| function getProjectId(): Promise<number> { | ||||
|     return new Promise((resolve) => { | ||||
|         getEntrepreneurProjectId( | ||||
|             (response) => { | ||||
|                 const projectIds = response.data; | ||||
|                 if (!Array.isArray(projectIds) || projectIds.length === 0) { | ||||
|                     console.warn("Aucun projet trouvé pour cet entrepreneur."); | ||||
|                     resolve(-1); | ||||
|                     return; | ||||
|                 } | ||||
|                 resolve(projectIds[0]); | ||||
|             }, | ||||
|             (error) => { | ||||
|                 console.error("Erreur API :", error); | ||||
|                 resolve(-1); | ||||
|             } | ||||
|         ); | ||||
|     }); | ||||
| } | ||||
|  | ||||
| async function fetchProjectId() { | ||||
|     try { | ||||
|         projectId.value = await getProjectId(); | ||||
|         console.log("ProjectId :", projectId.value); | ||||
|     } catch (error) { | ||||
|         console.error("Erreur lors de la récupération du projet :", error); | ||||
|     } | ||||
| } | ||||
|  | ||||
| const fetchAdminData = (projectId: number, useMock = IS_MOCK_MODE) => { | ||||
|     if (useMock) { | ||||
|         console.log("Utilisation des données mockées pour l'administrateur"); | ||||
|         admin.value = mockAdminData; | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     if (projectId === -1) { | ||||
|         admin.value = new UserAdmin({ | ||||
|             idUser: 0, | ||||
|             userSurname: "Erreur", | ||||
|             userName: "Chargement", | ||||
|             primaryMail: "N/A", | ||||
|             secondaryMail: "N/A", | ||||
|             phoneNumber: "N/A", | ||||
|         }); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     getProjectAdmin( | ||||
|         projectId, | ||||
|         (response: AxiosResponse) => { | ||||
|             admin.value = new UserAdmin(response.data); | ||||
|         }, | ||||
|         (error: AxiosError) => { | ||||
|             console.error( | ||||
|                 "Erreur lors de la récupération des données de l'administrateur :", | ||||
|                 error | ||||
|             ); | ||||
|  | ||||
|             admin.value = new UserAdmin({ | ||||
|                 idUser: 0, | ||||
|                 userSurname: "Erreur", | ||||
|                 userName: "Chargement", | ||||
|                 primaryMail: "N/A", | ||||
|                 secondaryMail: "N/A", | ||||
|                 phoneNumber: "N/A", | ||||
|             }); | ||||
|         } | ||||
|     ); | ||||
| }; | ||||
|  | ||||
| onMounted(() => { | ||||
|     fetchProjectId(); | ||||
|     fetchAdminData(projectId.value); | ||||
| }); | ||||
| </script> | ||||
|  | ||||
| <style scoped> | ||||
| .page-title { | ||||
|     text-align: center; | ||||
|     font-size: 2.5rem; | ||||
|     margin-top: 20px; | ||||
| } | ||||
|  | ||||
| .canvas-help-text { | ||||
|     text-align: center; | ||||
|     font-size: 0.7rem; | ||||
|     color: #666; | ||||
| } | ||||
|  | ||||
| .info-box { | ||||
|     background-color: #f9f9f9; | ||||
|     padding: 15px; | ||||
|     border-radius: 8px; | ||||
|     box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1); | ||||
|     font-family: Arial, sans-serif; | ||||
|     width: 30%; | ||||
|     max-width: 600px; | ||||
|     margin: 20px auto; | ||||
| } | ||||
|  | ||||
| .info-box p { | ||||
|     font-size: 16px; | ||||
|     line-height: 1.5; | ||||
|     color: #333; | ||||
| } | ||||
|  | ||||
| .info-box a { | ||||
|     color: #007bff; | ||||
|     text-decoration: none; | ||||
| } | ||||
|  | ||||
| .info-box a:hover { | ||||
|     text-decoration: underline; | ||||
| } | ||||
|  | ||||
| .canvas-help-text { | ||||
|     margin-top: 20px; | ||||
|     margin-bottom: -10px; | ||||
| } | ||||
|  | ||||
| div:last-child { | ||||
|     margin-bottom: 60px; | ||||
| } | ||||
| </style> | ||||
|   | ||||
							
								
								
									
										185
									
								
								front/MyINPulse-front/src/views/EntrepSignUp.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										185
									
								
								front/MyINPulse-front/src/views/EntrepSignUp.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,185 @@ | ||||
| <template> | ||||
|     <form class="add-project-form" @submit.prevent="submitForm"> | ||||
|         <h2>Ajouter un projet</h2> | ||||
|  | ||||
|         <div class="form-group"> | ||||
|             <label for="name">Nom du projet</label> | ||||
|             <input id="name" v-model="form.name" type="text" required /> | ||||
|         </div> | ||||
|  | ||||
|         <h3>Entrepreneur</h3> | ||||
|  | ||||
|         <div class="form-group"> | ||||
|             <label for="founderName">Nom</label> | ||||
|             <input | ||||
|                 id="founderName" | ||||
|                 v-model="form.founder.userName" | ||||
|                 type="text" | ||||
|                 required | ||||
|             /> | ||||
|         </div> | ||||
|         <div class="form-group"> | ||||
|             <label for="founderSurname">Prénom</label> | ||||
|             <input | ||||
|                 id="founderSurname" | ||||
|                 v-model="form.founder.userSurname" | ||||
|                 type="text" | ||||
|                 required | ||||
|             /> | ||||
|         </div> | ||||
|         <div class="form-group"> | ||||
|             <label for="founderPrimaryMail">Email Principal</label> | ||||
|             <input | ||||
|                 id="founderPrimaryMail" | ||||
|                 v-model="form.founder.primaryMail" | ||||
|                 type="email" | ||||
|                 required | ||||
|             /> | ||||
|         </div> | ||||
|         <div class="form-group"> | ||||
|             <label for="founderSecondaryMail">Email Secondaire</label> | ||||
|             <input | ||||
|                 id="founderSecondaryMail" | ||||
|                 v-model="form.founder.secondaryMail" | ||||
|                 type="email" | ||||
|             /> | ||||
|         </div> | ||||
|         <div class="form-group"> | ||||
|             <label for="founderPhoneNumber">Numéro de téléphone</label> | ||||
|             <input | ||||
|                 id="founderPhoneNumber" | ||||
|                 v-model="form.founder.phoneNumber" | ||||
|                 type="tel" | ||||
|                 required | ||||
|             /> | ||||
|         </div> | ||||
|         <div class="form-group"> | ||||
|             <label for="founderSchool">École</label> | ||||
|             <input | ||||
|                 id="founderSchool" | ||||
|                 v-model="form.founder.school" | ||||
|                 type="text" | ||||
|                 required | ||||
|             /> | ||||
|         </div> | ||||
|         <div class="form-group"> | ||||
|             <label for="founderCourse">Département</label> | ||||
|             <input | ||||
|                 id="founderCourse" | ||||
|                 v-model="form.founder.course" | ||||
|                 type="text" | ||||
|                 required | ||||
|             /> | ||||
|         </div> | ||||
|         <div class="form-group"> | ||||
|             <label for="founderSneeStatus">Statut étudiant entrepreneur</label> | ||||
|             <input | ||||
|                 id="founderSneeStatus" | ||||
|                 v-model="form.founder.sneeStatus" | ||||
|                 type="checkbox" | ||||
|             /> | ||||
|         </div> | ||||
|  | ||||
|         <button type="submit">Soumettre</button> | ||||
|     </form> | ||||
| </template> | ||||
|  | ||||
| <script setup lang="ts"> | ||||
| import { ref } from "vue"; | ||||
| import { postApi } from "@/services/api"; | ||||
|  | ||||
| const form = ref({ | ||||
|     name: "", | ||||
|     founder: { | ||||
|         userSurname: "", | ||||
|         userName: "", | ||||
|         primaryMail: "", | ||||
|         secondaryMail: "", | ||||
|         phoneNumber: "", | ||||
|         school: "", | ||||
|         course: "", | ||||
|         sneeStatus: false, | ||||
|     }, | ||||
| }); | ||||
|  | ||||
| function submitForm() { | ||||
|     postApi("/entrepreneur/projects/request", form.value); | ||||
| } | ||||
| </script> | ||||
|  | ||||
| <style scoped> | ||||
| @import url("https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap"); | ||||
|  | ||||
| .add-project-form { | ||||
|     font-family: "Inter", sans-serif; | ||||
|     max-width: 600px; | ||||
|     margin: 0 auto; | ||||
|     padding: 20px; | ||||
|     background: #fff; | ||||
|     border-radius: 10px; | ||||
|     box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); | ||||
| } | ||||
|  | ||||
| /* Le reste reste inchangé */ | ||||
| h2 { | ||||
|     margin-bottom: 20px; | ||||
|     font-size: 24px; | ||||
|     color: #333; | ||||
| } | ||||
|  | ||||
| h3 { | ||||
|     margin-top: 20px; | ||||
|     font-size: 20px; | ||||
|     color: #555; | ||||
| } | ||||
|  | ||||
| .form-group { | ||||
|     margin-bottom: 15px; | ||||
|     display: flex; | ||||
|     flex-direction: column; | ||||
| } | ||||
|  | ||||
| label { | ||||
|     font-weight: 500; | ||||
|     margin-bottom: 5px; | ||||
| } | ||||
|  | ||||
| input { | ||||
|     padding: 8px; | ||||
|     border-radius: 5px; | ||||
|     border: 1px solid #ccc; | ||||
|     font-size: 1em; | ||||
| } | ||||
|  | ||||
| input[type="checkbox"] { | ||||
|     width: auto; | ||||
|     margin-right: 10px; | ||||
| } | ||||
|  | ||||
| button { | ||||
|     background-color: #4caf50; | ||||
|     color: white; | ||||
|     padding: 10px 15px; | ||||
|     border: none; | ||||
|     border-radius: 5px; | ||||
|     cursor: pointer; | ||||
|     font-size: 1em; | ||||
|     width: 100%; | ||||
|     font-weight: 500; | ||||
| } | ||||
|  | ||||
| button:hover { | ||||
|     background-color: #45a049; | ||||
| } | ||||
|  | ||||
| button:active { | ||||
|     background-color: #388e3c; | ||||
| } | ||||
|  | ||||
| input[type="text"]:focus, | ||||
| input[type="email"]:focus, | ||||
| input[type="tel"]:focus { | ||||
|     border-color: #4caf50; | ||||
|     outline: none; | ||||
| } | ||||
| </style> | ||||
							
								
								
									
										81
									
								
								front/MyINPulse-front/src/views/FinalizeAccount.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										81
									
								
								front/MyINPulse-front/src/views/FinalizeAccount.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,81 @@ | ||||
| <template> | ||||
|     <Header /> | ||||
|     <div class="finalize-page"> | ||||
|         <div class="loader-container"> | ||||
|             <button class="return-button" @click="store.logout">Logout</button> | ||||
|             <div class="spinner"></div> | ||||
|             <p>Finalisation du compte en cours...</p> | ||||
|         </div> | ||||
|     </div> | ||||
| </template> | ||||
|  | ||||
| <script setup lang="ts"> | ||||
| import { onMounted } from "vue"; | ||||
| //import { useRouter } from "vue-router"; | ||||
| import { finalizeAccount } from "@/services/Apis/Unauth"; | ||||
| import Header from "@/components/HeaderComponent.vue"; | ||||
| import { store } from "@/main.ts"; | ||||
| //const router = useRouter(); | ||||
|  | ||||
| onMounted(() => { | ||||
|     finalizeAccount( | ||||
|         () => { | ||||
|             console.log("finalize sended"); | ||||
|         }, | ||||
|         (error) => { | ||||
|             console.error("Erreur lors de la finalisation :", error); | ||||
|         } | ||||
|     ); | ||||
| }); | ||||
| </script> | ||||
|  | ||||
| <style scoped> | ||||
| .finalize-page { | ||||
|     display: flex; | ||||
|     justify-content: center; | ||||
|     align-items: center; | ||||
|     min-height: 80vh; | ||||
|     background-color: #f9fbfd; | ||||
| } | ||||
|  | ||||
| .loader-container { | ||||
|     display: flex; | ||||
|     flex-direction: column; | ||||
|     align-items: center; | ||||
|     gap: 1rem; | ||||
|     color: #333; | ||||
|     font-size: 1.1rem; | ||||
|     font-style: italic; | ||||
| } | ||||
|  | ||||
| .spinner { | ||||
|     width: 50px; | ||||
|     height: 50px; | ||||
|     border: 5px solid #cfd8dc; | ||||
|     border-top: 5px solid #3498db; | ||||
|     border-radius: 50%; | ||||
|     animation: spin 0.8s linear infinite; | ||||
| } | ||||
|  | ||||
| .return-button { | ||||
|     background-color: #009cde; | ||||
|     color: white; | ||||
|     border: none; | ||||
|     padding: 10px 15px; | ||||
|     cursor: pointer; | ||||
|     font-size: 14px; | ||||
|     border-radius: 5px; | ||||
|     text-decoration: none; | ||||
|     transition: background-color 0.2s ease; | ||||
|     font-family: Arial, sans-serif; | ||||
| } | ||||
|  | ||||
| @keyframes spin { | ||||
|     0% { | ||||
|         transform: rotate(0deg); | ||||
|     } | ||||
|     100% { | ||||
|         transform: rotate(360deg); | ||||
|     } | ||||
| } | ||||
| </style> | ||||
							
								
								
									
										185
									
								
								front/MyINPulse-front/src/views/JoinOrCreatProjectForEntrep.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										185
									
								
								front/MyINPulse-front/src/views/JoinOrCreatProjectForEntrep.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,185 @@ | ||||
| <template> | ||||
|     <Header /> | ||||
|     <header class="header"> | ||||
|         <img | ||||
|             src="@/components/icons/logo inpulse.png" | ||||
|             alt="INPulse Logo" | ||||
|             class="logo" | ||||
|         /> | ||||
|         <button class="return-button" @click="store.logout">Logout</button> | ||||
|     </header> | ||||
|  | ||||
|     <div class="choix-projet"> | ||||
|         <h1>Bienvenue sur MyINPulse</h1> | ||||
|         <p> | ||||
|             Souhaitez-vous créer un nouveau projet ou joindre un projet existant | ||||
|             ? | ||||
|         </p> | ||||
|  | ||||
|         <div class="button-group"> | ||||
|             <button @click="choisir('creer')">Créer un projet</button> | ||||
|             <button @click="choisir('joindre')">Joindre un projet</button> | ||||
|         </div> | ||||
|  | ||||
|         <div v-if="choix === 'creer'" class="form-creer"> | ||||
|             <h2>Créer un projet</h2> | ||||
|             <label for="nomProjet">Nom du projet :</label> | ||||
|             <input | ||||
|                 id="nomProjet" | ||||
|                 v-model="nomProjet" | ||||
|                 type="text" | ||||
|                 placeholder="Nom du projet" | ||||
|             /> | ||||
|             <button @click="validerCreation">Valider</button> | ||||
|         </div> | ||||
|  | ||||
|         <div v-if="choix === 'joindre'" class="message-indispo"> | ||||
|             <h2>Joindre un projet</h2> | ||||
|             <p>Cette fonctionnalité n'est pas encore disponible.</p> | ||||
|         </div> | ||||
|     </div> | ||||
| </template> | ||||
|  | ||||
| <script setup lang="ts"> | ||||
| import { ref, onMounted } from "vue"; | ||||
| import { useRouter } from "vue-router"; | ||||
| import Project from "@/ApiClasses/Project"; | ||||
| import Header from "../components/HeaderComponent.vue"; | ||||
| import { store } from "@/main.ts"; | ||||
| import { | ||||
|     requestProjectCreation, | ||||
|     checkIfProjectIsActive, | ||||
|     checkPendingProjectRequest, | ||||
| } from "@/services/Apis/Entrepreneurs"; | ||||
|  | ||||
| const router = useRouter(); | ||||
| const choix = ref<string | null>(null); | ||||
| const nomProjet = ref(""); | ||||
|  | ||||
| const choisir = (option: "creer" | "joindre") => { | ||||
|     choix.value = option; | ||||
| }; | ||||
|  | ||||
| const validerCreation = () => { | ||||
|     if (!nomProjet.value.trim()) { | ||||
|         alert("Veuillez entrer un nom de projet."); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     const today = new Date(); | ||||
|     const yyyy = today.getFullYear(); | ||||
|     const mm = String(today.getMonth() + 1).padStart(2, "0"); | ||||
|     const dd = String(today.getDate()).padStart(2, "0"); | ||||
|     const formattedDate = `${yyyy}-${mm}-${dd}`; | ||||
|  | ||||
|     const nouveauProjet = new Project({ | ||||
|         projectName: nomProjet.value.trim(), | ||||
|         creationDate: formattedDate, | ||||
|         status: "PENDING", | ||||
|     }); | ||||
|  | ||||
|     requestProjectCreation( | ||||
|         nouveauProjet, | ||||
|         (response) => { | ||||
|             console.log("Projet créé :", response.data); | ||||
|             alert(`Projet "${nomProjet.value}" créé avec succès !`); | ||||
|         }, | ||||
|         (error) => { | ||||
|             console.error("Erreur lors de la création du projet :", error); | ||||
|             alert("Une erreur est survenue lors de la création du projet."); | ||||
|         } | ||||
|     ); | ||||
| }; | ||||
|  | ||||
| onMounted(() => { | ||||
|     checkIfProjectIsActive( | ||||
|         (response) => { | ||||
|             if (response.data === true) { | ||||
|                 router.push("/canvas"); | ||||
|             } | ||||
|         }, | ||||
|         () => { | ||||
|             checkPendingProjectRequest( | ||||
|                 (response) => { | ||||
|                     if (response.data === true) { | ||||
|                         router.push("/pending-approval"); | ||||
|                     } | ||||
|                 }, | ||||
|                 (error) => { | ||||
|                     console.warn("No active or pending project:", error); | ||||
|                 } | ||||
|             ); | ||||
|         } | ||||
|     ); | ||||
| }); | ||||
| </script> | ||||
|  | ||||
| <style scoped> | ||||
| @import "@/components/canvas/style-project.css"; | ||||
|  | ||||
| .choix-projet { | ||||
|     max-width: 500px; | ||||
|     margin: auto; | ||||
|     padding: 2rem; | ||||
|     background-color: #fefefe; | ||||
|     border-radius: 10px; | ||||
|     font-family: "Inter", sans-serif; | ||||
|     box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); | ||||
|     text-align: center; | ||||
| } | ||||
|  | ||||
| .button-group { | ||||
|     margin: 20px 0; | ||||
|     display: flex; | ||||
|     justify-content: space-around; | ||||
| } | ||||
|  | ||||
| button { | ||||
|     padding: 10px 20px; | ||||
|     font-size: 1em; | ||||
|     background-color: #4caf50; | ||||
|     color: white; | ||||
|     border: none; | ||||
|     border-radius: 6px; | ||||
|     cursor: pointer; | ||||
| } | ||||
|  | ||||
| button:hover { | ||||
|     background-color: #43a047; | ||||
| } | ||||
|  | ||||
| input { | ||||
|     padding: 10px; | ||||
|     margin-top: 10px; | ||||
|     width: 80%; | ||||
|     font-size: 1em; | ||||
|     border-radius: 5px; | ||||
|     border: 1px solid #ccc; | ||||
| } | ||||
|  | ||||
| .header { | ||||
|     display: flex; | ||||
|     justify-content: space-between; | ||||
|     align-items: center; | ||||
|     padding: 15px 30px; | ||||
|     background-color: #f9f9f9; | ||||
|     box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); | ||||
| } | ||||
|  | ||||
| .logo { | ||||
|     height: 50px; | ||||
| } | ||||
|  | ||||
| .return-button { | ||||
|     background-color: #009cde; | ||||
|     color: white; | ||||
|     border: none; | ||||
|     padding: 10px 15px; | ||||
|     cursor: pointer; | ||||
|     font-size: 14px; | ||||
|     border-radius: 5px; | ||||
|     text-decoration: none; | ||||
|     transition: background-color 0.2s ease; | ||||
|     font-family: Arial, sans-serif; | ||||
| } | ||||
| </style> | ||||
							
								
								
									
										41
									
								
								front/MyINPulse-front/src/views/PendingApproval.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								front/MyINPulse-front/src/views/PendingApproval.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,41 @@ | ||||
| <template> | ||||
|     <Header /> | ||||
|     <div class="pending-container"> | ||||
|         <h1>Projet en attente de validation</h1> | ||||
|         <p> | ||||
|             Votre demande de création de projet a bien été reçue.<br /> | ||||
|             Un administrateur doit valider votre projet avant que vous puissiez | ||||
|             continuer. | ||||
|         </p> | ||||
|     </div> | ||||
| </template> | ||||
|  | ||||
| <script setup lang="ts"> | ||||
| import Header from "@/components/HeaderComponent.vue"; | ||||
| </script> | ||||
|  | ||||
| <style scoped> | ||||
| .pending-container { | ||||
|     max-width: 600px; | ||||
|     margin: 100px auto; | ||||
|     padding: 2rem; | ||||
|     text-align: center; | ||||
|     background-color: #fffdf8; | ||||
|     border: 1px solid #ececec; | ||||
|     border-radius: 12px; | ||||
|     box-shadow: 0 6px 16px rgba(0, 0, 0, 0.1); | ||||
|     font-family: "Inter", sans-serif; | ||||
| } | ||||
|  | ||||
| h1 { | ||||
|     color: #f57c00; | ||||
|     font-size: 1.8rem; | ||||
|     margin-bottom: 1rem; | ||||
| } | ||||
|  | ||||
| p { | ||||
|     font-size: 1.1rem; | ||||
|     color: #333; | ||||
|     margin-bottom: 1.5rem; | ||||
| } | ||||
| </style> | ||||
| @@ -15,13 +15,11 @@ import ErrorModal from "@/components/errorModal.vue"; | ||||
| </template> | ||||
|  | ||||
| <style scoped> | ||||
|  | ||||
| .error-wrapper{ | ||||
|   position: absolute; | ||||
|   left: 70%; | ||||
|   /*background-color: blue;*/ | ||||
|   height: 100%; | ||||
|   width: 30%; | ||||
|  | ||||
| .error-wrapper { | ||||
|     position: absolute; | ||||
|     left: 70%; | ||||
|     /*background-color: blue;*/ | ||||
|     height: 100%; | ||||
|     width: 30%; | ||||
| } | ||||
| </style> | ||||
|   | ||||
| @@ -4,6 +4,7 @@ import { callApi } from "@/services/api.ts"; | ||||
| import { ref } from "vue"; | ||||
|  | ||||
| const CustomRequest = ref(""); | ||||
| const USERID = ref(""); | ||||
| </script> | ||||
|  | ||||
| <template> | ||||
| @@ -34,30 +35,7 @@ const CustomRequest = ref(""); | ||||
|                 <td>Current refresh token</td> | ||||
|                 <td>{{ store.user.refreshToken }}</td> | ||||
|             </tr> | ||||
|             <tr> | ||||
|                 <td>Entrepreneur API call</td> | ||||
|                 <td> | ||||
|                     <button @click="callApi('random')">call</button> | ||||
|                 </td> | ||||
|                 <td>res</td> | ||||
|                 <td></td> | ||||
|             </tr> | ||||
|             <tr> | ||||
|                 <td>Admin API call</td> | ||||
|                 <td> | ||||
|                     <button @click="callApi('random2')">call</button> | ||||
|                 </td> | ||||
|                 <td>res</td> | ||||
|                 <td></td> | ||||
|             </tr> | ||||
|             <tr> | ||||
|                 <td>Unauth API call</td> | ||||
|                 <td> | ||||
|                     <button @click="callApi('unauth/dev')">call</button> | ||||
|                 </td> | ||||
|                 <td>res</td> | ||||
|                 <td id="3"></td> | ||||
|             </tr> | ||||
|  | ||||
|             <tr> | ||||
|                 <td> | ||||
|                     <input v-model="CustomRequest" placeholder="edit me" /> | ||||
| @@ -66,6 +44,83 @@ const CustomRequest = ref(""); | ||||
|                     <button @click="callApi(CustomRequest)">call</button> | ||||
|                 </td> | ||||
|             </tr> | ||||
|  | ||||
|             <tr> | ||||
|                 <td>Create an account</td> | ||||
|                 <td> | ||||
|                     <button @click="callApi('unauth/create_account')"> | ||||
|                         call | ||||
|                     </button> | ||||
|                 </td> | ||||
|                 <td>res</td> | ||||
|                 <td id="4"></td> | ||||
|             </tr> | ||||
|             <tr> | ||||
|                 <td>Get Pending Accounts</td> | ||||
|                 <td> | ||||
|                     <button @click="callApi('/admin/pending-accounts')"> | ||||
|                         call | ||||
|                     </button> | ||||
|                 </td> | ||||
|                 <td>res</td> | ||||
|                 <td id="6"></td> | ||||
|             </tr> | ||||
|             <tr> | ||||
|                 <td>admin/validate_user_account/{id}</td> | ||||
|                 <td> | ||||
|                     <button | ||||
|                         @click=" | ||||
|                             callApi('admin/validate_user_account/' + USERID) | ||||
|                         " | ||||
|                     > | ||||
|                         call | ||||
|                     </button> | ||||
|                 </td> | ||||
|  | ||||
|                 <td> | ||||
|                     <input v-model="USERID" placeholder="user ID" /> | ||||
|                 </td> | ||||
|  | ||||
|                 <td id="5"></td> | ||||
|             </tr> | ||||
|  | ||||
|             <tr> | ||||
|                 <td>admin/setadmin/{uid}</td> | ||||
|                 <td> | ||||
|                     <button @click="callApi('admin/setadmin/' + USERID)"> | ||||
|                         call | ||||
|                     </button> | ||||
|                 </td> | ||||
|  | ||||
|                 <td> | ||||
|                     <input v-model="USERID" placeholder="user ID" /> | ||||
|                 </td> | ||||
|             </tr> | ||||
|             <tr> | ||||
|                 <td>Unauth API call</td> | ||||
|                 <td> | ||||
|                     <button @click="callApi('unauth/dev')">call</button> | ||||
|                 </td> | ||||
|                 <td>res</td> | ||||
|                 <td id="8"></td> | ||||
|             </tr> | ||||
|             <tr> | ||||
|                 <td>Unauth API call</td> | ||||
|                 <td> | ||||
|                     <button @click="callApi('unauth/dev')">call</button> | ||||
|                 </td> | ||||
|                 <td>res</td> | ||||
|                 <td id="9"></td> | ||||
|             </tr> | ||||
|  | ||||
|             <tr> | ||||
|                 <td>Unauth API call</td> | ||||
|                 <td> | ||||
|                     <button @click="callApi('unauth/dev')">call</button> | ||||
|                 </td> | ||||
|                 <td>res</td> | ||||
|                 <td id="10"></td> | ||||
|             </tr> | ||||
|         </tbody> | ||||
|     </table> | ||||
| </template> | ||||
|   | ||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user