304 Commits

Author SHA1 Message Date
47be7d340b fix: formatter
All checks were successful
Format / formatting (push) Successful in 6s
Build / build (push) Successful in 41s
CI / build (push) Successful in 13s
Format / formatting (pull_request) Successful in 7s
2025-05-14 09:06:52 +02:00
232d10b164 fix: formatter
Some checks failed
Format / formatting (push) Failing after 6s
Build / build (push) Successful in 43s
CI / build (push) Successful in 13s
Format / formatting (pull_request) Failing after 6s
2025-05-14 09:04:35 +02:00
bc7ce888ad fix: build
Some checks failed
Format / formatting (push) Failing after 6s
Build / build (push) Successful in 44s
CI / build (push) Successful in 12s
Format / formatting (pull_request) Failing after 5s
2025-05-13 20:00:51 +02:00
ed67a3734a fix: fix build
Some checks failed
Format / formatting (push) Failing after 6s
Build / build (push) Successful in 43s
CI / build (push) Failing after 12s
2025-05-13 19:56:03 +02:00
95eb154556 fix: eslint
Some checks failed
Format / formatting (push) Failing after 7s
Build / build (push) Successful in 43s
CI / build (push) Failing after 11s
2025-05-13 19:52:31 +02:00
19fef63b0e fix: merge
Some checks failed
Format / formatting (push) Failing after 6s
Build / build (push) Successful in 45s
CI / build (push) Failing after 11s
2025-05-11 21:18:38 +02:00
1fd95265ea fix: this is not gonna work in time, switching to mock mod for canvas so we can show what we supposed to have atleast (local canvas) and we can explain what is the problem (you can disable the mock by putting the var USE_MOCK to false in CanvasItem.vue but dont do it) 2025-05-11 21:17:09 +02:00
3ef2d8a198 fix: join or create
Some checks failed
Format / formatting (push) Failing after 6s
Build / build (push) Successful in 43s
CI / build (push) Failing after 10s
2025-05-11 20:37:57 +02:00
6b49bbbe57 fix: handlers weren't used properly in the unauth code
Some checks failed
Format / formatting (push) Failing after 6s
Build / build (push) Successful in 42s
CI / build (push) Failing after 10s
2025-05-10 23:35:30 +02:00
4c15cab607 fix: still working on the pending
Some checks failed
Format / formatting (push) Failing after 6s
Build / build (push) Successful in 42s
CI / build (push) Failing after 12s
2025-05-10 23:16:08 +02:00
abfe92bc87 Merge: branch 'backend-test' into front_foundation
Some checks failed
Format / formatting (push) Failing after 6s
Build / build (push) Successful in 42s
CI / build (push) Failing after 9s
2025-05-10 21:54:20 +02:00
85b4fe6a4c fix: finalize logic
Some checks failed
Format / formatting (push) Failing after 6s
Build / build (push) Successful in 42s
CI / build (push) Failing after 10s
2025-05-10 21:48:41 +02:00
f2448a029f added two endpoints necessary for routing in project request phase
All checks were successful
Format / formatting (push) Successful in 6s
Build / build (push) Successful in 41s
CI / build (push) Successful in 11s
2025-05-10 20:39:45 +02:00
cef4daef15 feat: finalize2
Some checks failed
Format / formatting (push) Failing after 6s
Build / build (push) Successful in 42s
CI / build (push) Failing after 13s
2025-05-10 18:10:39 +02:00
f5aba70017 feat: add finalise logic
Some checks failed
Format / formatting (push) Failing after 6s
Build / build (push) Successful in 41s
CI / build (push) Failing after 12s
2025-05-10 17:59:05 +02:00
27adc81ddc fix: minor change
Some checks failed
Format / formatting (push) Failing after 6s
Build / build (push) Successful in 41s
CI / build (push) Failing after 9s
2025-05-10 01:16:00 +02:00
48f14e8a04 Merge branch 'backend-test' into front_foundation
Some checks failed
Format / formatting (push) Failing after 6s
Build / build (push) Successful in 41s
CI / build (push) Failing after 10s
2025-05-09 22:59:23 +02:00
d4533ea725 added an endpoint to see if useraccuont is pending or not
All checks were successful
Format / formatting (push) Successful in 6s
Build / build (push) Successful in 42s
CI / build (push) Successful in 11s
2025-05-09 21:23:35 +02:00
7fc06035c7 fix: unauth users redirection
Some checks failed
Format / formatting (push) Successful in 6s
Build / build (push) Successful in 43s
CI / build (push) Failing after 10s
2025-05-09 18:47:37 +02:00
1b559f29b7 Merge branch 'front_foundation' of https://gitea.piair.dev/piair/MyINPulse into front_foundation 2025-05-09 02:24:33 +02:00
63327bc312 fix: config stuff, do make keycloak first
Some checks failed
Format / formatting (push) Successful in 6s
Build / build (push) Successful in 43s
CI / build (push) Failing after 10s
2025-05-09 00:40:41 +02:00
f0cef41e2b Merge branch 'main' of https://gitea.piair.dev/piair/MyINPulse into front_foundation 2025-05-08 18:46:27 +02:00
7f16cdc86f fix: commit before merge 2025-05-08 18:11:50 +02:00
72d6f49995 Merge remote-tracking branch 'origin/main' into front_foundation 2025-05-08 18:08:18 +02:00
695ec5d9b8 fix: merge depuis backend-test
Some checks failed
Format / formatting (push) Successful in 6s
Build / build (push) Successful in 42s
CI / build (push) Failing after 9s
Format / formatting (pull_request) Successful in 7s
2025-05-08 16:32:06 +02:00
0abafb4f7f Merge remote-tracking branch 'origin/backend-test' into front_foundation 2025-05-08 16:30:44 +02:00
3cd63e78e9 fix: changement de makefile 2025-05-08 16:30:42 +02:00
255af7ee7f feat: final test in sharedApi passing, it took a while to find where the bug is getAppointments by project
All checks were successful
Format / formatting (push) Successful in 6s
Build / build (push) Successful in 41s
CI / build (push) Successful in 11s
2025-05-07 20:57:03 +02:00
3b308cfa6d fix: my bad 403 error codes are never thrown by src code, now is up to date
All checks were successful
Format / formatting (push) Successful in 5s
Build / build (push) Successful in 39s
CI / build (push) Successful in 10s
2025-05-07 11:44:09 +02:00
d039105f0a Merge remote-tracking branch 'origin/backend-test' into front_foundation 2025-05-07 11:16:15 +02:00
0a15dbbf2d fix: merge errors 2025-05-07 11:16:12 +02:00
d1fce63ac5 Merge remote-tracking branch 'origin/main' into front_foundation 2025-05-07 11:09:51 +02:00
d9aaa225aa Merge pull request 'Fix 403 errors' (#12) from backend-test into main
All checks were successful
Format / formatting (push) Successful in 6s
Build / build (push) Successful in 40s
CI / build (push) Successful in 11s
Reviewed-on: #12
Reviewed-by: adnane <adnane.alami@bordeaux-inp.fr>
Reviewed-by: omar <omar.el_alaoui_el_ismaili@bordeaux-inp.fr>
Reviewed-by: Theo <tlelez@enseirb-matmeca.fr>
Reviewed-by: anas <anas.maillal@bordeaux-inp.fr>
2025-05-07 11:08:57 +02:00
d31bf259dd Merge branch 'main' into backend-test
All checks were successful
Format / formatting (push) Successful in 6s
Build / build (push) Successful in 40s
CI / build (push) Successful in 11s
Format / formatting (pull_request) Successful in 6s
2025-05-07 11:06:30 +02:00
43b40c9432 feat: just added 403 response
All checks were successful
Format / formatting (push) Successful in 6s
Build / build (push) Successful in 40s
CI / build (push) Successful in 10s
Format / formatting (pull_request) Successful in 5s
2025-05-07 11:04:24 +02:00
e84f69c21a fix: unused imports
All checks were successful
Format / formatting (push) Successful in 6s
Build / build (push) Successful in 41s
CI / build (push) Successful in 11s
2025-05-07 11:02:08 +02:00
cc1fc9b45b fix: eslint
All checks were successful
Format / formatting (push) Successful in 6s
Build / build (push) Successful in 40s
CI / build (push) Successful in 13s
2025-05-07 11:01:57 +02:00
c76e83f2bf feat: changed endpoints
Some checks failed
Format / formatting (push) Successful in 6s
Build / build (push) Successful in 41s
CI / build (push) Failing after 8s
2025-05-07 11:00:15 +02:00
0d0ec255a5 Merge branch 'front_foundation' of https://gitea.piair.dev/piair/MyINPulse into front_foundation
Some checks failed
Format / formatting (push) Successful in 6s
Build / build (push) Successful in 41s
CI / build (push) Failing after 8s
2025-05-07 10:53:09 +02:00
e0c43a5c95 fix: merge 2025-05-07 10:53:02 +02:00
1f0f9196c4 feat: fixed 403 errors
All checks were successful
Format / formatting (push) Successful in 6s
Build / build (push) Successful in 40s
CI / build (push) Successful in 11s
2025-05-07 10:45:38 +02:00
40e577ef07 Merge pull request 'backend-test' (#10) from backend-test into main
All checks were successful
Format / formatting (push) Successful in 6s
Build / build (push) Successful in 41s
CI / build (push) Successful in 11s
Reviewed-on: #10
Reviewed-by: adnane <adnane.alami@bordeaux-inp.fr>
Reviewed-by: anas <anas.maillal@bordeaux-inp.fr>
Reviewed-by: omar <omar.el_alaoui_el_ismaili@bordeaux-inp.fr>
2025-05-07 10:43:30 +02:00
60302c44d2 "reverting the fix, last ditch was no good: last ditch effort with .env variables, using default variable to set db connection values"
Some checks failed
Format / formatting (push) Successful in 10s
Build / build (push) Successful in 49s
CI / build (push) Failing after 12s
This reverts commit a6e4f80a01.
2025-05-07 00:04:26 +02:00
a6e4f80a01 fix: last ditch effort with .env variables, using default variable to set db connection values
Some checks failed
Format / formatting (push) Successful in 7s
Build / build (push) Successful in 51s
CI / build (push) Failing after 13s
2025-05-06 23:57:03 +02:00
02bff19de0 fix: il ya encors un problem de config! des erreurs de type (Connection to localhost:5433 refused. Check that the hostname and port are correct and that the postmaster is accepting TCP/IP connections)
Some checks failed
Format / formatting (push) Successful in 7s
Build / build (push) Successful in 56s
CI / build (push) Failing after 25s
2025-05-06 23:31:01 +02:00
ae36549de9 Merge branch 'front_foundation' of https://gitea.piair.dev/piair/MyINPulse into front_foundation 2025-05-06 22:38:28 +02:00
b1a4c874ec trying out config to see if this is where 403 responses are comming from
All checks were successful
Format / formatting (push) Successful in 15s
Build / build (push) Successful in 1m4s
CI / build (push) Successful in 39s
2025-05-06 21:17:52 +02:00
829baac85e apply prettier to adminappointment
All checks were successful
Format / formatting (push) Successful in 5s
Build / build (push) Successful in 40s
CI / build (push) Successful in 13s
2025-05-05 20:36:09 +02:00
2e9d841709 utilisation des fonctions Apis/admin.ts pour mettre un raport a un appointmentid
Some checks failed
Format / formatting (push) Successful in 6s
Build / build (push) Successful in 43s
CI / build (push) Failing after 9s
2025-05-05 20:32:09 +02:00
25235f418a fix: merging from the main 2025-05-04 20:23:19 +02:00
13845394e3 feat: added doc for endpoint make-admin
All checks were successful
Format / formatting (push) Successful in 25s
Build / build (push) Successful in 44s
CI / build (push) Successful in 13s
Format / formatting (pull_request) Successful in 6s
2025-05-04 20:15:03 +02:00
73aac1875a Merge branch 'front_foundation' of https://gitea.piair.dev/piair/MyINPulse into front_foundation
All checks were successful
Format / formatting (push) Successful in 7s
Build / build (push) Successful in 42s
CI / build (push) Successful in 14s
Format / formatting (pull_request) Successful in 6s
2025-05-02 01:52:52 +02:00
c3ad092512 fix: api integration 2025-05-02 01:26:08 +02:00
f4589c6306 fix: bug on CORS discussed before
All checks were successful
Format / formatting (push) Successful in 6s
Build / build (push) Successful in 46s
CI / build (push) Successful in 13s
Format / formatting (pull_request) Successful in 6s
2025-05-01 21:03:23 +02:00
6004bce4e8 Merge branch 'main' into backend-test
All checks were successful
Format / formatting (push) Successful in 6s
Build / build (push) Successful in 42s
CI / build (push) Successful in 11s
2025-05-01 20:53:18 +02:00
0730275e75 feat: added create-account
All checks were successful
Format / formatting (push) Successful in 6s
Build / build (push) Successful in 44s
CI / build (push) Successful in 11s
2025-05-01 20:51:10 +02:00
4356a01e4a feat: adding delete button with the corresponding endpoint
All checks were successful
Format / formatting (push) Successful in 6s
Build / build (push) Successful in 44s
CI / build (push) Successful in 12s
Format / formatting (pull_request) Successful in 6s
2025-05-01 19:23:21 +02:00
592331236e added endpoints to see all user without any auth for testing
Some checks failed
Format / formatting (push) Successful in 5s
Build / build (push) Successful in 45s
CI / build (push) Failing after 12s
Format / formatting (pull_request) Successful in 7s
2025-05-01 17:45:49 +02:00
49e52e1826 Merge branch 'front_foundation' of https://gitea.piair.dev/piair/MyINPulse into front_foundation
Some checks failed
Format / formatting (push) Successful in 6s
Build / build (push) Successful in 49s
CI / build (push) Failing after 10s
Format / formatting (pull_request) Successful in 5s
2025-05-01 17:37:45 +02:00
efcea7f680 fix: backend fix 'adding PUT ... to the file' 2025-05-01 17:37:40 +02:00
8154814805 fix: type errors 2025-05-01 17:35:04 +02:00
7d41da97de prettier api.ts
Some checks failed
Format / formatting (push) Successful in 5s
Build / build (push) Successful in 1m43s
CI / build (push) Failing after 12s
Format / formatting (pull_request) Successful in 6s
2025-05-01 16:11:07 +02:00
35f314498f list des upcoming appointments prettier
Some checks failed
Format / formatting (push) Successful in 6s
Build / build (push) Successful in 1m0s
CI / build (push) Failing after 10s
Format / formatting (pull_request) Successful in 6s
2025-05-01 16:05:46 +02:00
f46c3756f0 lister tout les upcoming appointmens
Some checks failed
Format / formatting (push) Successful in 6s
Build / build (push) Successful in 43s
CI / build (push) Failing after 11s
Format / formatting (pull_request) Successful in 6s
2025-05-01 15:26:05 +02:00
92696c3e16 fix/feat: Now the projectId is correctly fetched yet we cant test shit since the db is empty
Some checks failed
Format / formatting (push) Successful in 6s
Build / build (push) Successful in 41s
CI / build (push) Failing after 12s
Format / formatting (pull_request) Successful in 6s
2025-04-29 23:18:26 +02:00
5130c00796 fix: adding toObject in ApiClasses
Some checks failed
Format / formatting (push) Successful in 7s
Build / build (push) Successful in 44s
CI / build (push) Failing after 12s
Format / formatting (pull_request) Successful in 6s
2025-04-29 22:15:20 +02:00
5183a088e7 fix: forgot to remove a pram in an endpoint in docs
All checks were successful
Format / formatting (push) Successful in 6s
Build / build (push) Successful in 45s
CI / build (push) Successful in 13s
2025-04-29 21:41:25 +02:00
8ec569e6ff fix: prettier
All checks were successful
Format / formatting (push) Successful in 6s
Build / build (push) Successful in 46s
CI / build (push) Successful in 12s
Format / formatting (pull_request) Successful in 6s
2025-04-29 21:35:00 +02:00
b503cae235 fix: styling
All checks were successful
Format / formatting (push) Successful in 6s
Build / build (push) Successful in 1m33s
CI / build (push) Successful in 11s
2025-04-29 21:25:03 +02:00
fcf4e1c01d feat: added an endpoint for fething the project an entrepreneur is associated with
Some checks failed
Format / formatting (push) Failing after 6s
Build / build (push) Successful in 1m28s
CI / build (push) Successful in 11s
2025-04-29 21:22:13 +02:00
4f90da69f3 Merge branch 'front_foundation' of https://gitea.piair.dev/piair/MyINPulse into front_foundation
Some checks failed
Format / formatting (push) Successful in 6s
Build / build (push) Successful in 41s
CI / build (push) Failing after 10s
Format / formatting (pull_request) Successful in 6s
2025-04-29 20:58:09 +02:00
0fe64fd844 fix: using the new admins functions for apis 2025-04-29 20:57:51 +02:00
eb302268ba fix: still having 403 bit fixed some bugs
Some checks failed
Format / formatting (push) Successful in 9s
Build / build (push) Successful in 42s
CI / build (push) Failing after 10s
Format / formatting (pull_request) Successful in 6s
2025-04-29 20:19:24 +02:00
3f18304028 formatting works time in and time out, empty commit to fix
All checks were successful
Format / formatting (push) Successful in 6s
Build / build (push) Successful in 42s
CI / build (push) Successful in 11s
2025-04-29 19:31:28 +02:00
bbb4debcd8 fix: changed the data structure used in getAllSectionCells for processing the latest version of sectioneCells as it gave some exception (it doesn't like being iterated and modified at the same time)
Some checks failed
Format / formatting (push) Failing after 6s
Build / build (push) Successful in 41s
CI / build (push) Successful in 11s
2025-04-29 19:18:12 +02:00
6f7fc70c4c didn't use the correct function for seting ids, test fixed
All checks were successful
Format / formatting (push) Successful in 6s
Build / build (push) Successful in 41s
CI / build (push) Successful in 16s
2025-04-29 18:50:25 +02:00
4044a95dd1 fix: added a logout button in the header
Some checks failed
Format / formatting (push) Successful in 8s
Build / build (push) Successful in 41s
CI / build (push) Failing after 9s
Format / formatting (pull_request) Successful in 7s
2025-04-29 16:20:11 +02:00
aee8e8797c feat: added an initial in mermory database configuration for seting up front
All checks were successful
Format / formatting (push) Successful in 6s
Build / build (push) Successful in 43s
CI / build (push) Successful in 11s
Format / formatting (pull_request) Successful in 6s
2025-04-29 14:11:45 +02:00
3d57ecb01a fix: fixed some sectionCell fetching logic (previously wasn't grouping by idReference), found out that idRefrence in sectionCell is always being incremented at inserion still looking for a fix
All checks were successful
Format / formatting (push) Successful in 6s
Build / build (push) Successful in 41s
CI / build (push) Successful in 11s
2025-04-29 13:59:05 +02:00
1f91ab72d8 This reverts commit cbef042e97. I was working on front and back and pushed stuff to the wrong end
All checks were successful
Format / formatting (push) Successful in 6s
Build / build (push) Successful in 40s
CI / build (push) Successful in 12s
Format / formatting (pull_request) Successful in 5s
2025-04-29 13:00:58 +02:00
a4e13b0f0a Revert "forgot to comment out said test"
Some checks failed
Format / formatting (push) Failing after 5s
Build / build (push) Failing after 41s
CI / build (push) Successful in 12s
Format / formatting (pull_request) Failing after 6s
This reverts commit b116771375.
2025-04-29 13:00:18 +02:00
b116771375 forgot to comment out said test
All checks were successful
Format / formatting (push) Successful in 6s
Build / build (push) Successful in 40s
CI / build (push) Successful in 11s
Format / formatting (pull_request) Successful in 5s
2025-04-29 12:55:50 +02:00
cbef042e97 found that idReference Incrementing when it shoudn't still looking for a fix, and fixed some sectioncell fetching logic in SectionCellService.java
Some checks failed
Format / formatting (push) Failing after 6s
Build / build (push) Failing after 40s
CI / build (push) Successful in 12s
Format / formatting (pull_request) Failing after 6s
2025-04-29 12:48:29 +02:00
63f6f16d83 fix: tiny bug
All checks were successful
Format / formatting (push) Successful in 6s
Build / build (push) Successful in 40s
CI / build (push) Successful in 12s
Format / formatting (pull_request) Successful in 5s
2025-04-29 11:39:02 +02:00
864bbbb9fd fix: eslint + prettier
Some checks failed
Format / formatting (push) Successful in 6s
Build / build (push) Successful in 40s
CI / build (push) Failing after 12s
Format / formatting (pull_request) Successful in 6s
2025-04-29 11:32:30 +02:00
e890a03a48 feat: all Canvas endpoints implemented, just waiting for projectId to test
Some checks failed
Format / formatting (push) Successful in 6s
Build / build (push) Successful in 42s
CI / build (push) Failing after 9s
Format / formatting (pull_request) Successful in 6s
2025-04-29 11:24:26 +02:00
351727f3d5 fix: prettier
All checks were successful
Format / formatting (push) Successful in 5s
Build / build (push) Successful in 40s
CI / build (push) Successful in 12s
Format / formatting (pull_request) Successful in 6s
2025-04-29 00:42:59 +02:00
b5637e4552 fix: bugs fixed, we still have the 404 error
Some checks failed
Format / formatting (push) Successful in 5s
Build / build (push) Successful in 40s
CI / build (push) Failing after 9s
Format / formatting (pull_request) Successful in 6s
2025-04-29 00:40:30 +02:00
8024b8070b merge: enstrpenrue issuees
Some checks failed
Format / formatting (push) Successful in 6s
Build / build (push) Successful in 40s
CI / build (push) Failing after 10s
Format / formatting (pull_request) Successful in 5s
2025-04-29 00:34:41 +02:00
008e003207 fix: conflict on Apis 2025-04-29 00:32:03 +02:00
820c979c94 fix: bugs
Some checks failed
Format / formatting (push) Successful in 6s
Build / build (push) Successful in 41s
CI / build (push) Failing after 11s
Format / formatting (pull_request) Successful in 6s
2025-04-29 00:28:56 +02:00
2ebea35e5d fix: confits 2025-04-29 00:03:44 +02:00
c3dded1e05 fix:
Some checks failed
Format / formatting (push) Successful in 6s
Build / build (push) Successful in 41s
CI / build (push) Failing after 8s
Format / formatting (pull_request) Successful in 6s
2025-04-29 00:00:39 +02:00
8b48a55bf0 Merge branch 'front_foundation' of https://gitea.piair.dev/piair/MyINPulse into front_foundation
Some checks failed
Format / formatting (push) Successful in 5s
Build / build (push) Successful in 40s
CI / build (push) Failing after 8s
Format / formatting (pull_request) Successful in 6s
2025-04-28 23:54:53 +02:00
32de0d6ca8 feat: function that tells if an authentified person is an admin 2025-04-28 23:54:38 +02:00
7534d0d9c6 feat: logout button
Some checks failed
Format / formatting (push) Successful in 6s
Build / build (push) Successful in 41s
CI / build (push) Failing after 9s
Format / formatting (pull_request) Successful in 6s
2025-04-28 23:54:04 +02:00
a0cbd5e324 fix: fixing a small bug
Some checks failed
Format / formatting (push) Successful in 6s
Build / build (push) Successful in 41s
CI / build (push) Failing after 10s
Format / formatting (pull_request) Successful in 6s
2025-04-28 23:25:54 +02:00
09eeaacfa6 Merge branch 'front_foundation' of https://gitea.piair.dev/piair/MyINPulse into front_foundation
Some checks failed
Format / formatting (push) Successful in 6s
Build / build (push) Successful in 40s
CI / build (push) Failing after 9s
Format / formatting (pull_request) Successful in 6s
2025-04-28 23:08:59 +02:00
de09ab2891 fix: api service 2025-04-28 23:08:19 +02:00
6ae50f9cf7 fix: idk
Some checks failed
Format / formatting (push) Successful in 6s
Build / build (push) Successful in 40s
CI / build (push) Failing after 9s
Format / formatting (pull_request) Successful in 6s
2025-04-28 23:05:04 +02:00
0199e9d0dc Merge remote-tracking branch 'origin/backend-test' into front_foundation 2025-04-28 22:52:09 +02:00
05a56dc022 fix: merge
Some checks failed
Format / formatting (push) Successful in 5s
Build / build (push) Successful in 38s
CI / build (push) Failing after 10s
Format / formatting (pull_request) Successful in 6s
2025-04-28 22:51:21 +02:00
9f4596d0ba feat: trying to connect to the bach 2025-04-28 22:49:07 +02:00
be73815861 fix: separating the apis
Some checks failed
Format / formatting (push) Successful in 5s
Build / build (push) Successful in 39s
CI / build (push) Failing after 9s
Format / formatting (pull_request) Successful in 6s
2025-04-28 22:35:56 +02:00
6d84b3ff93 feat: api fonctions for each class, will be sperated shortly
All checks were successful
Format / formatting (push) Successful in 7s
Build / build (push) Successful in 40s
CI / build (push) Successful in 13s
Format / formatting (pull_request) Successful in 5s
2025-04-28 20:57:12 +02:00
549028b1d0 apply prettier to appointement
All checks were successful
Format / formatting (push) Successful in 5s
Build / build (push) Successful in 42s
CI / build (push) Successful in 13s
Format / formatting (pull_request) Successful in 6s
2025-04-27 21:58:32 +02:00
3de1ec71ff suppression de Appointement
Some checks failed
Format / formatting (push) Successful in 7s
Build / build (push) Successful in 42s
CI / build (push) Failing after 11s
Format / formatting (pull_request) Successful in 6s
2025-04-27 21:52:43 +02:00
a895b0afda fix des erreurs eslint de AdminAppointement?
Some checks failed
Format / formatting (push) Successful in 6s
Build / build (push) Successful in 44s
CI / build (push) Failing after 13s
Format / formatting (pull_request) Successful in 5s
2025-04-27 21:46:24 +02:00
bb55209946 Merge branch 'front_foundation' of https://gitea.piair.dev/piair/MyINPulse into front_foundation
Some checks failed
Format / formatting (push) Successful in 6s
Build / build (push) Successful in 38s
CI / build (push) Failing after 10s
Format / formatting (pull_request) Successful in 7s
2025-04-27 13:29:57 +02:00
a8e22de4a3 feat: classes to ecapsulate the api calls better 2025-04-27 13:29:31 +02:00
f9ee12ab6f Merge branch 'front_foundation' of https://gitea.piair.dev/piair/MyINPulse into front_foundation
Some checks failed
Format / formatting (push) Successful in 6s
Build / build (push) Successful in 39s
CI / build (push) Failing after 8s
Format / formatting (pull_request) Successful in 5s
2025-04-27 10:15:03 +02:00
4d3aae1249 feat: enhancing the layout + we need to start using the classes 2025-04-27 10:14:57 +02:00
fa96bd2b0d d
Some checks failed
Format / formatting (push) Successful in 7s
Build / build (push) Successful in 38s
CI / build (push) Failing after 8s
Format / formatting (pull_request) Successful in 6s
2025-04-27 06:23:15 +02:00
3673aa379c formatting
All checks were successful
Format / formatting (push) Successful in 6s
Build / build (push) Successful in 40s
CI / build (push) Successful in 11s
2025-04-23 12:21:56 +02:00
b672b2e9f9 just changed test location in file, hope these commits don't diverge
Some checks failed
Format / formatting (push) Failing after 6s
Build / build (push) Successful in 42s
CI / build (push) Successful in 11s
2025-04-23 12:11:57 +02:00
193876e51c I forgot to push the package-lock.json probably gonna need it since half the stuff used for swagger-ui is old unfortunately 2025-04-23 12:11:57 +02:00
b8c7c6f587 feat: spotted some responses on post requests I forgot to remove 2025-04-23 12:11:57 +02:00
bee47473d5 feat: finsihed tests for EntrepreneurApiService
All checks were successful
Format / formatting (push) Successful in 6s
Build / build (push) Successful in 40s
CI / build (push) Successful in 13s
2025-04-23 11:54:18 +02:00
e7739af80b feat: more tests
All checks were successful
Format / formatting (push) Successful in 6s
Build / build (push) Successful in 41s
CI / build (push) Successful in 11s
2025-04-23 11:52:08 +02:00
32557f8f87 forgot to format
Some checks failed
Format / formatting (push) Successful in 6s
Build / build (push) Failing after 39s
CI / build (push) Successful in 11s
2025-04-23 11:50:48 +02:00
8ee06b93a6 fix: added null check in sharedService and updated test
Some checks failed
Format / formatting (push) Failing after 5s
Build / build (push) Failing after 39s
CI / build (push) Successful in 11s
2025-04-23 11:47:41 +02:00
fcc20d1f42 fix: (forgot to say that the layout in enhanced in last commit) + fix du prettier
All checks were successful
Format / formatting (push) Successful in 6s
Build / build (push) Successful in 38s
CI / build (push) Successful in 11s
Format / formatting (pull_request) Successful in 6s
2025-04-23 11:07:32 +02:00
28b0e69da1 suppresion de local database 2025-04-23 11:02:21 +02:00
6861d07dfc tests ?
Some checks failed
Format / formatting (push) Successful in 6s
Build / build (push) Failing after 39s
CI / build (push) Successful in 14s
2025-04-23 10:48:40 +02:00
6226c9f632 Merge branch 'front_foundation' of https://gitea.piair.dev/piair/MyINPulse into front_foundation
All checks were successful
Format / formatting (push) Successful in 6s
Build / build (push) Successful in 38s
CI / build (push) Successful in 12s
Format / formatting (pull_request) Successful in 6s
2025-04-23 10:06:32 +02:00
7fce831c75 feat: rooting corrected 2025-04-23 10:05:27 +02:00
8403bc0592 fixing formatting: sure is fun
All checks were successful
Format / formatting (push) Successful in 6s
Build / build (push) Successful in 40s
CI / build (push) Successful in 10s
2025-04-22 09:51:31 +02:00
5edcf9ffc8 fix: fixed formatting to be compatible with workflow
Some checks failed
Format / formatting (push) Failing after 6s
Build / build (push) Successful in 41s
CI / build (push) Successful in 11s
2025-04-22 09:46:07 +02:00
5615b0fb11 fix: update doc to encompose all response codes hopefully
Some checks failed
Format / formatting (push) Failing after 6s
Build / build (push) Successful in 41s
CI / build (push) Successful in 10s
2025-04-22 00:21:08 +02:00
8a13993d8a fix: made endpoints match documentation naming conventions 'just the mapping' and noted endpoints not yet implemented in documentation/openapi/notes.md
Some checks failed
Format / formatting (push) Failing after 6s
Build / build (push) Successful in 40s
CI / build (push) Successful in 11s
2025-04-21 23:55:14 +02:00
561f6d16b3 tests: implemented some tests, overall percentage of coverage is 40%, still trying to find a way arround class comparaison
Some checks failed
Format / formatting (push) Failing after 6s
Build / build (push) Successful in 41s
CI / build (push) Successful in 11s
2025-04-21 23:17:32 +02:00
7199e7ebbf fix:build
All checks were successful
Format / formatting (push) Successful in 6s
Build / build (push) Successful in 38s
CI / build (push) Successful in 12s
Format / formatting (pull_request) Successful in 5s
2025-04-21 22:17:48 +02:00
9762ca27fb fix:lint
Some checks failed
Format / formatting (push) Successful in 6s
Build / build (push) Successful in 40s
CI / build (push) Failing after 12s
Format / formatting (pull_request) Successful in 5s
2025-04-21 22:13:39 +02:00
ca8c5d9209 Merge branch 'front_foundation' of https://gitea.piair.dev/piair/MyINPulse into front_foundation
Some checks failed
Format / formatting (push) Successful in 5s
Build / build (push) Successful in 39s
CI / build (push) Failing after 8s
Format / formatting (pull_request) Successful in 6s
2025-04-21 19:29:30 +02:00
4b6d501adc feat: contact fixed 2025-04-21 19:29:25 +02:00
08706af6c2 fix: proper fix
All checks were successful
Format / formatting (push) Successful in 6s
Build / build (push) Successful in 38s
CI / build (push) Successful in 12s
Format / formatting (pull_request) Successful in 6s
2025-04-21 19:24:38 +02:00
01f062211a fix: awful fix but works for now ?
Some checks failed
Format / formatting (push) Successful in 5s
Build / build (push) Successful in 38s
CI / build (push) Failing after 11s
Format / formatting (pull_request) Successful in 6s
2025-04-21 19:16:42 +02:00
bca88a7b20 feat: adding join file
Some checks failed
Format / formatting (push) Successful in 5s
Build / build (push) Successful in 43s
CI / build (push) Failing after 12s
Format / formatting (pull_request) Successful in 6s
2025-04-21 19:07:31 +02:00
c60fb8945b fix: with prettier
Some checks failed
Format / formatting (push) Successful in 6s
Build / build (push) Successful in 42s
CI / build (push) Failing after 10s
Format / formatting (pull_request) Successful in 5s
2025-04-21 19:01:15 +02:00
e20556ed0f Merge branch 'main' into front_foundation
Some checks failed
Format / formatting (push) Successful in 6s
Build / build (push) Successful in 42s
CI / build (push) Failing after 10s
Format / formatting (pull_request) Successful in 6s
2025-04-21 18:37:23 +02:00
d9c5f7bacf fix: eslint
Some checks failed
Format / formatting (push) Successful in 6s
Build / build (push) Successful in 42s
CI / build (push) Failing after 10s
Format / formatting (pull_request) Successful in 6s
2025-04-21 18:33:51 +02:00
fdae3e4c04 fix: linter error
Some checks failed
Format / formatting (push) Successful in 6s
Build / build (push) Successful in 42s
CI / build (push) Failing after 9s
Format / formatting (pull_request) Successful in 6s
2025-04-21 18:21:21 +02:00
36db3c2968 fix: conflits
Some checks failed
Format / formatting (push) Successful in 6s
Build / build (push) Successful in 42s
CI / build (push) Failing after 9s
Format / formatting (pull_request) Successful in 6s
2025-04-21 17:40:10 +02:00
a0eeb6715e fix: fixing the eslint stuff 2025-04-21 17:38:14 +02:00
6d875d9df1 fix: eslint
Some checks failed
Format / formatting (push) Successful in 6s
Build / build (push) Successful in 43s
CI / build (push) Failing after 9s
Format / formatting (pull_request) Successful in 6s
2025-04-21 17:10:43 +02:00
7c8f3ba36a fix: css updating for better lisibilty
Some checks failed
Format / formatting (push) Successful in 6s
Build / build (push) Successful in 38s
CI / build (push) Failing after 8s
Format / formatting (pull_request) Successful in 5s
2025-04-21 15:43:35 +02:00
832539f43b feat: added doc for upcoming endpoints to finish up entrepreneur join, response codes remaining to update
All checks were successful
Format / formatting (push) Successful in 6s
Build / build (push) Successful in 39s
CI / build (push) Successful in 10s
2025-04-21 10:54:50 +02:00
dfea20b9c4 fix: aadded changes, doc is ssomewhat coherent still need to change some endpoint names in controller and some minor changes
All checks were successful
Format / formatting (push) Successful in 6s
Build / build (push) Successful in 38s
CI / build (push) Successful in 10s
2025-04-16 12:31:00 +02:00
8b863ee4b1 fix: adminview
Some checks failed
Format / formatting (push) Successful in 5s
Build / build (push) Successful in 38s
CI / build (push) Failing after 8s
Format / formatting (pull_request) Successful in 7s
2025-04-16 11:59:03 +02:00
f96872fb6b fix: not red anymore
All checks were successful
Format / formatting (push) Successful in 6s
Build / build (push) Successful in 38s
CI / build (push) Successful in 11s
2025-04-16 11:55:54 +02:00
0140672812 why is cache enabled again ??
Some checks failed
Format / formatting (push) Successful in 5s
Build / build (push) Failing after 38s
CI / build (push) Successful in 11s
2025-04-16 11:29:25 +02:00
7df2c768c8 fix : tests
Some checks failed
CI / build (push) Waiting to run
Format / formatting (push) Successful in 5s
Build / build (push) Has been cancelled
2025-04-16 11:26:25 +02:00
6b3cb2610d feat: adding signup page
Some checks failed
Format / formatting (push) Successful in 5s
Build / build (push) Successful in 38s
CI / build (push) Failing after 8s
Format / formatting (pull_request) Successful in 5s
2025-04-16 10:59:23 +02:00
6029457735 fix: more tests working, still need fixes
Some checks failed
Format / formatting (push) Failing after 4s
Build / build (push) Failing after 5m8s
CI / build (push) Successful in 10s
2025-04-16 10:36:59 +02:00
ba99b3c2b0 Merge branch 'front_foundation' of https://gitea.piair.dev/piair/MyINPulse into front_foundation
Some checks failed
Format / formatting (push) Successful in 6s
Build / build (push) Successful in 37s
CI / build (push) Failing after 8s
Format / formatting (pull_request) Successful in 5s
2025-04-16 10:36:59 +02:00
31d82f6271 fix:... 2025-04-16 10:36:54 +02:00
37e631a096 fix: fixing conflits 2025-04-16 10:36:06 +02:00
6306a00eca feat: contact button opens the partage page 2025-04-16 10:33:33 +02:00
55112c8508 test: added initial test file mainly definitions and descritions of tests haven't finshed
Some checks failed
Format / formatting (push) Failing after 6s
Build / build (push) Failing after 5m6s
CI / build (push) Successful in 11s
2025-04-16 10:15:13 +02:00
676f1204cb feat: updated doc to reflect details of server, still not done editing it
Some checks failed
CI / build (push) Waiting to run
Format / formatting (push) Successful in 7s
Build / build (push) Has been cancelled
2025-04-16 10:12:16 +02:00
c4ba7646d5 fix: typo
Some checks failed
Format / formatting (push) Successful in 8s
Build / build (push) Successful in 47s
CI / build (push) Failing after 12s
Format / formatting (pull_request) Successful in 6s
2025-04-15 23:13:02 +02:00
4e1908d528 Merge branch 'front_foundation' of https://gitea.piair.dev/piair/MyINPulse into front_foundation
Some checks failed
Format / formatting (push) Successful in 6s
Build / build (push) Successful in 38s
CI / build (push) Failing after 11s
Format / formatting (pull_request) Successful in 5s
2025-04-14 01:33:06 +02:00
03bbc77e8a fix: linting all the files possible error may still occur but i just ignored them cuz it's commented code, probably will fix them later if it persists 2025-04-14 01:32:29 +02:00
ad1fd45bed Merge branch 'front_foundation' of https://gitea.piair.dev/piair/MyINPulse into front_foundation
Some checks failed
Format / formatting (push) Successful in 5s
Build / build (push) Successful in 38s
Format / formatting (pull_request) Successful in 5s
CI / build (push) Failing after 8s
2025-04-12 22:23:20 +02:00
f0c4a3a10d adding formulaire pour le signup d'un entrepreneur (not yet working) 2025-04-12 22:23:17 +02:00
70658e4fb9 fix: fixed an issue with the server by adding the dependency of jwt
Some checks failed
CI / build (push) Waiting to run
Format / formatting (push) Successful in 6s
Build / build (push) Has been cancelled
Format / formatting (pull_request) Successful in 6s
2025-04-12 22:22:22 +02:00
2b31753265 feat: added an login page but the auth issue is still persisting, found a way to interprete the token the line is commneted in case it breaks code or needs a change
Some checks failed
Format / formatting (push) Successful in 6s
Build / build (push) Successful in 39s
CI / build (push) Failing after 8s
Format / formatting (pull_request) Successful in 5s
2025-04-12 03:55:30 +02:00
f8991e90ab feat: using bootstrap..
Some checks failed
Format / formatting (push) Successful in 7s
Build / build (push) Successful in 43s
CI / build (push) Failing after 11s
Format / formatting (pull_request) Successful in 6s
2025-04-11 19:42:38 +02:00
66be0baca6 feat: created a better account creation flow
All checks were successful
Format / formatting (push) Successful in 6s
Build / build (push) Successful in 5m20s
CI / build (push) Successful in 11s
2025-04-09 17:39:43 +02:00
60290956ec feat: added form to add project and pending project section for admin, i forgot to push them
Some checks failed
Format / formatting (push) Successful in 5s
Build / build (push) Successful in 38s
CI / build (push) Failing after 8s
Format / formatting (pull_request) Successful in 5s
2025-04-09 00:46:44 +02:00
b9647ce36e Merge branch 'front_foundation' of https://gitea.piair.dev/piair/MyINPulse into front_foundation
Some checks failed
Format / formatting (push) Successful in 5s
Build / build (push) Successful in 38s
CI / build (push) Failing after 8s
Format / formatting (pull_request) Successful in 5s
2025-04-09 00:10:04 +02:00
8c4b9ceb9d feat: still enhancing the visuals
Some checks failed
Format / formatting (push) Successful in 5s
Build / build (push) Successful in 38s
CI / build (push) Has been cancelled
Format / formatting (pull_request) Successful in 5s
2025-04-09 00:19:56 +02:00
84d8d4523b feat: added a function for post and delete that follows callApi that only does get 2025-04-09 00:09:58 +02:00
647812576e feat: enhancing visuals
Some checks failed
Format / formatting (push) Successful in 5s
Build / build (push) Successful in 38s
CI / build (push) Failing after 8s
Format / formatting (pull_request) Successful in 5s
2025-04-09 00:05:15 +02:00
2dfee66958 fix: app
Some checks failed
Format / formatting (push) Successful in 7s
Build / build (push) Successful in 39s
CI / build (push) Failing after 8s
Format / formatting (pull_request) Successful in 5s
2025-04-08 23:06:02 +02:00
7e1271cfe2 Merge pull request 'fix: reverted previous commit, cache juste does not work' (#8) from fix_cache into main
All checks were successful
Format / formatting (push) Successful in 5s
Build / build (push) Successful in 44s
CI / build (push) Successful in 11s
Reviewed-on: #8
2025-04-06 20:56:39 +02:00
801ecb3817 Merge branch 'main' into fix_cache
All checks were successful
Format / formatting (push) Successful in 5s
Build / build (push) Successful in 46s
CI / build (push) Successful in 11s
Format / formatting (pull_request) Successful in 6s
2025-04-06 20:56:22 +02:00
cc89d4c79f fix: reverted previous commit, cache juste does not work
Some checks are pending
Format / formatting (push) Waiting to run
Build / build (push) Waiting to run
CI / build (push) Waiting to run
Format / formatting (pull_request) Successful in 6s
2025-04-06 20:55:35 +02:00
adf9a93e2e Merge pull request 'feat: enabled graddle cache. Subsequent actions should be fasters' (#7) from fix_cache into main
Some checks failed
CI / build (push) Waiting to run
Format / formatting (push) Successful in 5s
Build / build (push) Has been cancelled
Reviewed-on: #7
2025-04-06 20:41:13 +02:00
37d8bcc719 feat: enabled graddle cache. Subsequent actions should be fasters
Some checks failed
Format / formatting (push) Successful in 5s
Build / build (push) Has been cancelled
CI / build (push) Successful in 12s
Format / formatting (pull_request) Successful in 6s
2025-04-06 20:39:45 +02:00
385c5cd8d0 fix: added back the cache since the action ran once on the main branch
All checks were successful
Format / formatting (push) Successful in 5s
Build / build (push) Successful in 5m16s
CI / build (push) Successful in 12s
2025-04-06 20:32:45 +02:00
b672dd200c fix: name coherence + test logic
All checks were successful
Format / formatting (push) Successful in 6s
Build / build (push) Successful in 43s
CI / build (push) Successful in 13s
2025-04-06 20:30:29 +02:00
9e1f568ea4 fix: comparaison between two projects instead of their IDs
Some checks failed
Format / formatting (push) Successful in 6s
Build / build (push) Failing after 38s
CI / build (push) Successful in 14s
2025-04-06 20:14:43 +02:00
aaa6e46d0c fix (kinda) : refactored update of data, still trying to fix bug
Some checks failed
Format / formatting (push) Successful in 6s
Build / build (push) Failing after 39s
CI / build (push) Successful in 11s
from EntrepreneurApiServiceTests (code is a bit messy with prints and comments dw)
2025-04-06 19:22:18 +02:00
2b1666c949 ongoing fix: working through the auth issues, we got the test working again but the requests now give an error code 403
Some checks failed
Format / formatting (push) Successful in 6s
Build / build (push) Successful in 39s
CI / build (push) Failing after 8s
Format / formatting (pull_request) Successful in 8s
2025-04-06 02:02:28 +02:00
0c724cae7f Merge branch 'front_foundation' of https://gitea.piair.dev/piair/MyINPulse into front_foundation
Some checks failed
Format / formatting (push) Successful in 8s
Build / build (push) Successful in 43s
CI / build (push) Failing after 8s
Format / formatting (pull_request) Successful in 5s
2025-04-05 21:10:23 +02:00
6de45801d2 unfix: pushing a version that doesn't work for review cuz i don't really get what' wrong 2025-04-05 21:10:12 +02:00
03897e1139 feat: mode admin added 'first try'
Some checks failed
Format / formatting (push) Successful in 6s
Build / build (push) Successful in 37s
CI / build (push) Failing after 7s
Format / formatting (pull_request) Successful in 5s
2025-04-02 11:51:39 +02:00
9b9cfbdb2e merged openapi with backend-test
Some checks failed
Format / formatting (push) Successful in 6s
Build / build (push) Failing after 37s
CI / build (push) Successful in 13s
2025-04-02 11:17:28 +02:00
00a733c03b fix: manine try now
Some checks failed
Format / formatting (push) Successful in 6s
Build / build (push) Successful in 38s
CI / build (push) Failing after 8s
Format / formatting (pull_request) Successful in 5s
2025-04-02 10:52:41 +02:00
5b6b647697 feat: started test for EntrepreneurServiceApi
Some checks failed
Format / formatting (push) Successful in 5s
Build / build (push) Failing after 37s
CI / build (push) Successful in 11s
2025-04-02 10:25:50 +02:00
3dc8131c33 merging
Some checks failed
Format / formatting (push) Successful in 7s
Build / build (push) Successful in 39s
CI / build (push) Failing after 8s
Format / formatting (pull_request) Successful in 6s
2025-04-02 08:44:23 +02:00
7c271d8c47 Merge branch 'main' into front_foundation
Some checks failed
Format / formatting (push) Successful in 6s
Build / build (push) Successful in 39s
CI / build (push) Failing after 8s
Format / formatting (pull_request) Successful in 5s
2025-04-01 01:38:19 +02:00
81ce4fdb4c fix: commented docker back lines in order to push after trying to fix the error in tests
All checks were successful
Format / formatting (push) Successful in 6s
Build / build (push) Successful in 39s
CI / build (push) Successful in 11s
2025-03-30 19:35:14 +02:00
ebd76a30ee feat: updated date format next is descriptions and setting up http request tests 2025-03-27 17:34:41 +01:00
ead11215ba Merge pull request 'backend-api' (#6) from backend-api into main
All checks were successful
Format / formatting (push) Successful in 6s
Build / build (push) Successful in 38s
CI / build (push) Successful in 10s
Reviewed-on: #6
Reviewed-by: Theo <tlelez@enseirb-matmeca.fr>
Reviewed-by: adnane <adnane.alami@bordeaux-inp.fr>
Reviewed-by: mohamed_maoulainine <mohamed_maoulainine.maoulainine@bordeaux-inp.fr>
2025-03-26 19:04:08 +01:00
259d56271c Merge branch 'openapi_integration' into front_foundation 2025-03-26 12:20:25 +01:00
b9f3bbbe15 fix: Adnane?
Some checks failed
CI / build (push) Failing after 8s
2025-03-26 11:59:16 +01:00
14a2a59786 merging
Some checks failed
CI / build (push) Failing after 7s
2025-03-26 11:57:13 +01:00
0ae6e7dfda merging 2025-03-26 11:53:49 +01:00
15ccb5630a MERGING 2025-03-26 11:52:04 +01:00
4ec292cca7 fix: merging 2025-03-26 11:50:23 +01:00
14a953536a merging 2025-03-26 11:45:40 +01:00
288f983816 merging 2025-03-26 11:44:49 +01:00
dd6032f3ef fix: css.. 2025-03-26 11:38:33 +01:00
7e2f5bc506 Merge branch 'front_foundation' of https://gitea.piair.dev/piair/MyINPulse into front_foundation
Some checks are pending
CI / build (push) Waiting to run
2025-03-26 11:32:14 +01:00
4ef92efd0e fix: js to ts in header 2025-03-26 11:31:54 +01:00
e769dd6757 fix: css fixing (again) 2025-03-26 11:30:59 +01:00
550a51523f Merge branch 'front_foundation' of https://gitea.piair.dev/piair/MyINPulse into front_foundation 2025-03-26 11:24:28 +01:00
323cb05388 fix: css fixing 2025-03-26 11:07:50 +01:00
6ff6ce5052 fix: just fixed duplicate test files due to bad merge
All checks were successful
Format / formatting (push) Successful in 9s
Build / build (push) Successful in 43s
CI / build (push) Successful in 11s
2025-03-26 10:04:46 +01:00
7e0851bfef Merge branch 'front_foundation' of https://gitea.piair.dev/piair/MyINPulse into front_foundation 2025-03-24 12:43:04 +01:00
98b6d167e8 fix: minor bugs on views 2025-03-24 12:34:17 +01:00
79baddb8f6 fix + feat: fixed some bugs + added a mock parser since the damn backend is not working, not finished tho (bugs) 2025-03-23 21:59:27 +01:00
60ec920cff fix: split openapi src files and made bash file to run
Some checks failed
Format / formatting (push) Successful in 5s
Build / build (push) Failing after 32s
CI / build (push) Successful in 11s
2025-03-23 17:12:31 +01:00
900a4c5bdc merged backend-api onto openapi to avoid working with worktree
Some checks failed
Format / formatting (push) Successful in 7s
Build / build (push) Failing after 30s
CI / build (push) Successful in 11s
2025-03-23 15:41:44 +01:00
d0b615c59d fix: the endpoints reflect the db models and changes since the last version of the doc, notes.md indicate some changes that are needed to happen in the controller and questions to be discussed
All checks were successful
CI / build (push) Successful in 11s
2025-03-21 09:42:57 +01:00
0f8c83c2e2 feat: adding the endpoints' doc to the branch for an easy access 2025-03-21 01:20:34 +01:00
fad52644d2 fix : merged the main with the front branch and fixed all possible issues 2025-03-21 01:16:49 +01:00
5f51a1008b fix: fixing conflicts 2025-03-20 22:42:49 +01:00
eccf116f49 feat: added swagger-ui to read docs and test api, fix: minor changes to yaml file, more to come
All checks were successful
CI / build (push) Successful in 13s
2025-03-20 19:11:32 +01:00
8491c9b3cf added minor change 2025-03-19 22:02:28 +01:00
137bc84c21 Merge branch 'backend-api' of ssh://gitea.piair.dev:2222/piair/MyINPulse into backend-api
All checks were successful
Format / formatting (push) Successful in 6s
Build / build (push) Successful in 38s
CI / build (push) Successful in 10s
Format / formatting (pull_request) Successful in 5s
2025-03-19 12:06:00 +01:00
3c61fdca93 feat: finished implementing apiService functions 2025-03-19 12:05:56 +01:00
5ee3755548 feat: added new tests and fixed few issues
All checks were successful
Format / formatting (push) Successful in 7s
Build / build (push) Successful in 38s
CI / build (push) Successful in 12s
2025-03-19 12:05:01 +01:00
279c171ba2 fix: Doc added (for me) 2025-03-19 11:54:34 +01:00
9ba8e3e84e feat: fake-data can be edited, switching to end-points branch 2025-03-19 10:52:12 +01:00
52511dd4c4 fix: Makefile now run everything needed to build the app 2025-03-19 10:42:46 +01:00
6de38a9725 feat: edit-mode added, to test it use the fake api in the 'fake_data' folder 2025-03-19 09:48:04 +01:00
84b70f8f38 fix: sometimes, project administrators may be null. Fixing nullPointerException
All checks were successful
Format / formatting (push) Successful in 6s
Build / build (push) Successful in 37s
CI / build (push) Successful in 11s
2025-03-17 09:18:21 +01:00
834d68949c fix: tabulation error
Some checks failed
Format / formatting (push) Successful in 6s
Build / build (push) Failing after 39s
CI / build (push) Successful in 11s
2025-03-17 09:08:33 +01:00
fea8687664 feat: now running tests
All checks were successful
Format / formatting (push) Successful in 6s
CI / build (push) Successful in 10s
2025-03-17 09:05:24 +01:00
c94d3654ce fix: updating foreign keys when adding new entity to the db
All checks were successful
Format / formatting (push) Successful in 7s
Build / build (push) Successful in 31s
CI / build (push) Successful in 11s
2025-03-15 15:23:18 +01:00
d5c89bf8f4 fix: spelling
All checks were successful
Format / formatting (push) Successful in 5s
Build / build (push) Successful in 27s
CI / build (push) Successful in 11s
2025-03-12 12:25:26 +01:00
78c72bdd72 fix: linter
Some checks failed
Format / formatting (push) Failing after 5s
Build / build (push) Successful in 27s
CI / build (push) Successful in 10s
2025-03-12 12:23:27 +01:00
307c7e700b merge
Some checks failed
Format / formatting (push) Failing after 5s
Build / build (push) Successful in 27s
CI / build (push) Successful in 10s
2025-03-12 12:19:35 +01:00
8d486dce89 feat: continued implementing adminApiService 2025-03-12 12:16:01 +01:00
653f923693 fix: bugfix
All checks were successful
Format / formatting (push) Successful in 5s
Build / build (push) Successful in 27s
CI / build (push) Successful in 11s
2025-03-12 12:08:49 +01:00
64da3c9ab0 feat: tests on AdminApiService 2025-03-12 12:07:48 +01:00
419ceec1bc feat: switched from String to ProjectDecisionValues
All checks were successful
Format / formatting (push) Successful in 6s
Build / build (push) Successful in 28s
CI / build (push) Successful in 11s
2025-03-12 10:25:19 +01:00
e011a5534e feat: switched from String to ProjectDecisionValues
All checks were successful
Format / formatting (push) Successful in 6s
Build / build (push) Successful in 30s
CI / build (push) Successful in 15s
2025-03-12 10:21:08 +01:00
ef964c4d35 fix: removed id + renamed mainEmail to primaryEmail everywhere
All checks were successful
Format / formatting (push) Successful in 8s
Build / build (push) Successful in 28s
CI / build (push) Successful in 11s
2025-03-11 13:02:17 +01:00
5608b12f84 fix: removed id + renamed mainEmail to primaryEmail everywhere 2025-03-11 13:01:53 +01:00
467babab79 fix: removed id + renamed mainEmail to primaryEmail everywhere 2025-03-11 13:01:28 +01:00
a2e2395cc2 feat: added new tests and coverage report 2025-03-11 13:00:38 +01:00
e3393c8834 test
All checks were successful
Format / formatting (push) Successful in 6s
Build / build (push) Successful in 28s
CI / build (push) Successful in 10s
2025-03-09 21:20:20 +01:00
5f8fe4a374 feat: precommit hook for google java format 2025-03-09 21:19:00 +01:00
c5e7736a16 fix: wtf does idea do ?? Why d methods move ? fixed linter again...
All checks were successful
Format / formatting (push) Successful in 5s
Build / build (push) Successful in 27s
CI / build (push) Successful in 10s
2025-03-09 21:10:25 +01:00
04589392cb fix: removed debug logging
Some checks failed
Format / formatting (push) Failing after 6s
Build / build (push) Successful in 29s
CI / build (push) Successful in 11s
2025-03-09 21:07:29 +01:00
1106cf8478 feat: added tests. 2025-03-09 21:06:31 +01:00
215d80ad70 fix: removed contradictive @NotNull preventing to add data to database. 2025-03-09 21:04:52 +01:00
dded62c25a fix: take latest implementation of logging module + imported inmemory database for testing 2025-03-09 20:22:20 +01:00
3de7ebe2b1 fix: remoed debug logging 2025-03-09 20:21:32 +01:00
8153496a0f feat: implemented database for testing purposes
All checks were successful
Format / formatting (push) Successful in 7s
Build / build (push) Successful in 28s
CI / build (push) Successful in 11s
2025-03-04 18:42:14 +01:00
861e7495a7 fix: re-enabled cache to drastically reduce action time. This should be fixed later
All checks were successful
Format / formatting (push) Successful in 6s
Build / build (push) Successful in 27s
CI / build (push) Successful in 10s
2025-03-01 01:00:52 +01:00
d78e43f7e0 fix: improved the workflow
Some checks failed
Format / formatting (push) Successful in 5s
Build / build (push) Has been cancelled
CI / build (push) Successful in 11s
2025-03-01 00:58:50 +01:00
3ca97cf378 fix: improved the workflow
All checks were successful
Format / formatting (push) Successful in 6s
Build / build (push) Successful in 28s
CI / build (push) Successful in 12s
2025-03-01 00:57:34 +01:00
e6a8d98d63 fix: improved the workflow
Some checks failed
Format / formatting (push) Successful in 6s
Build / build (push) Failing after 34s
CI / build (push) Successful in 11s
2025-02-28 12:20:48 +01:00
8894fea6d4 fix: improved the workflow
Some checks failed
Format / formatting (push) Successful in 6s
Build / build (push) Failing after 18s
CI / build (push) Successful in 10s
2025-02-28 12:20:05 +01:00
236bb0d167 fix: improved the workflow
Some checks failed
Format / formatting (push) Successful in 5s
Build / build (push) Failing after 6s
CI / build (push) Successful in 11s
2025-02-28 12:18:36 +01:00
d4dcc95d9b fix: removed cache to speed up things
Some checks failed
Format / formatting (push) Successful in 5s
Build / build (push) Failing after 14s
CI / build (push) Successful in 11s
2025-02-28 12:16:43 +01:00
dc843299eb fix: action create the gradlew wrapper
Some checks failed
Format / formatting (push) Successful in 5s
Build / build (push) Failing after 4m49s
CI / build (push) Successful in 11s
2025-02-28 12:10:13 +01:00
f3eaf8fe34 fix: action create the gradlew wrapper
Some checks failed
Format / formatting (push) Successful in 6s
Build / build (push) Failing after 4m46s
CI / build (push) Successful in 11s
2025-02-28 12:04:11 +01:00
628c61fb8b feat: pipeline should now test if the project builds
Some checks failed
Format / formatting (push) Successful in 5s
Build / build (push) Failing after 5m49s
CI / build (push) Successful in 10s
2025-02-28 11:55:43 +01:00
4880f3829c I don't get it, how does it keeps failing with the formatter installed... time to create pre-commit hook I guess
All checks were successful
Format / formatting (push) Successful in 6s
CI / build (push) Successful in 10s
2025-02-28 11:47:45 +01:00
80b2d087e4 feat: implemented date filtration and a utils service to prevent code ducplication
Some checks failed
Format / formatting (push) Failing after 7s
CI / build (push) Successful in 11s
2025-02-28 11:45:55 +01:00
b5c03798fc fix: formatter now follow the same logic as idea, see https://github.com/google/google-java-format/issues/566
All checks were successful
Format / formatting (push) Successful in 5s
CI / build (push) Successful in 10s
2025-02-26 18:55:45 +01:00
1d970ce5f5 feat: continued to implement SharedApiService (if linter fails i don't understand)
Some checks failed
Format / formatting (push) Failing after 6s
CI / build (push) Successful in 11s
2025-02-26 18:33:09 +01:00
f9de5ed6bf feat: finished creating services from controllers, continued implementing entrepreneurServiceApi with some validation
Some checks failed
Format / formatting (push) Failing after 6s
CI / build (push) Successful in 11s
2025-02-26 17:35:52 +01:00
e75a5c9d2c fix: linter ??? pls idea be better
All checks were successful
Format / formatting (push) Successful in 5s
CI / build (push) Successful in 12s
2025-02-26 15:57:03 +01:00
5c3b2b138d feat: renamex title to sectionId and added a new shared API call
Some checks failed
Format / formatting (push) Failing after 5s
CI / build (push) Successful in 11s
2025-02-26 15:55:33 +01:00
8d4dc7916d feat: added most of shared API calls
Some checks failed
Format / formatting (push) Failing after 5s
CI / build (push) Successful in 10s
2025-02-26 15:31:02 +01:00
1a6db7c953 feat: added color in logs
All checks were successful
Format / formatting (push) Successful in 6s
CI / build (push) Successful in 11s
2025-02-26 15:29:55 +01:00
3890aed158 Merge branch 'backend-api' of ssh://gitea.piair.dev:2222/piair/MyINPulse into backend-api
All checks were successful
Format / formatting (push) Successful in 5s
CI / build (push) Successful in 11s
2025-02-26 14:51:52 +01:00
dd5ca2cbd7 feat: entrepreneur api and api service 2025-02-26 14:51:02 +01:00
024deeba41 fix: linter - how did this append ?
All checks were successful
Format / formatting (push) Successful in 6s
CI / build (push) Successful in 10s
2025-02-26 14:30:58 +01:00
067eeb9494 docs: added examples and changed some weird character
All checks were successful
CI / build (push) Successful in 11s
2025-02-26 11:31:45 +01:00
f48b570494 fix: to composition api 2025-02-26 03:39:59 +01:00
0733f8d5af Merge branch 'front_foundation' of https://gitea.piair.dev/piair/MyINPulse into front_foundation 2025-02-25 22:53:26 +01:00
8071c01c5d fix: fixing the issues regarding the use of href 2025-02-25 22:53:15 +01:00
b355463dd9 docs: added needed modications and reorganized yaml file
All checks were successful
CI / build (push) Successful in 11s
2025-02-25 22:34:33 +01:00
4ee3d9bc44 fix: data from api is now correctly displayed 2025-02-19 12:15:39 +01:00
d75d45e204 fix + feat: api test + error fix 2025-02-19 11:02:15 +01:00
79e949bdd4 feat: configured gradle file to generate code for api interface from swagger specification
All checks were successful
CI / build (push) Successful in 11s
2025-02-19 01:36:52 +01:00
9f3754776f fix: fixed the code for AdminMain 2025-02-18 22:17:57 +01:00
651fb2b1a1 feat: adding first fake api (use this cmd in fake_data reportory: json-server --watch db.json --port 5000) + fetching witch axios for now (npm install axios) 2025-02-18 07:20:58 +01:00
aa5988ce75 fix: emptyed the app.vue and did some code reorganisation 2025-02-17 23:48:28 +01:00
9ae18e1e4b feat : adding an agenda, not included to the main page yet 2025-02-17 21:51:27 +01:00
ef8c8e896d feat: added openapi documentation to the project
All checks were successful
CI / build (push) Successful in 11s
2025-02-12 20:59:13 +01:00
22ebb0e1f4 feat: enhancing canvass layout 2025-02-11 20:47:37 +01:00
09e4b3262f feat: switching to composition API standard 2025-02-11 19:56:52 +01:00
6a3d4239ab feat: canvas are now generic 2025-02-10 22:46:59 +01:00
9d71c93b5b feat: layout changes 2025-02-10 15:53:10 +01:00
b1df7421dc fix: commiting some minor changes before switching to front_foundation branch 2025-02-10 15:32:28 +01:00
7a03146bf8 fix: commiting minor changes before switching to front_foundation branch 2025-02-10 15:21:07 +01:00
5145b833ae feat: rendez-vous agenda for admin and user 2025-02-10 15:15:58 +01:00
f0a371dc52 Merge pull request 'main-revert' (#2) from main-revert into main
Reviewed-on: #2
2025-02-09 13:11:01 +01:00
ac19d33bdb Reapply "fix: fixed TS errors"
This reverts commit 3d4d5b90d1.
2025-02-09 13:05:28 +01:00
3d4d5b90d1 Revert "fix: fixed TS errors"
This reverts commit b5c9b40672.
2025-02-09 13:03:26 +01:00
4080cee818 Meeeerge...
Merge branch 'main' of https://gitea.piair.dev/piair/MyINPulse
2025-02-08 22:40:42 +01:00
f4d73654d1 some minor changes on the main page 2025-02-08 22:40:35 +01:00
4fda5513a9 error corrected 2025-02-08 20:33:03 +01:00
32407b0e8f conflits... 2025-02-08 20:23:43 +01:00
b30e1196f4 canvas included in the main page, still shiting with vue 2025-02-08 20:18:44 +01:00
126 changed files with 15309 additions and 705 deletions

View File

@ -15,4 +15,4 @@ jobs:
- uses: axel-op/googlejavaformat-action@v3 - uses: axel-op/googlejavaformat-action@v3
with: with:
args: "--set-exit-if-changed --skip-sorting-imports --aosp -n" args: "--set-exit-if-changed --skip-sorting-imports --skip-reflowing-long-strings --aosp -n"

View File

@ -0,0 +1,32 @@
name: Build
on:
push:
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout sources
uses: actions/checkout@v4
- name: Load .env file
uses: xom9ikk/dotenv@v2.3.0
with:
path: ./config/
mode: dev
load-mode: strict
- name: Setup Java
uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: 21
- name: Setup Gradle
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
View File

@ -1,5 +1,8 @@
.env .env
.idea .idea
keycloak/CAS/target keycloak/CAS/target
keycloak/.installed
docker-compose.yaml docker-compose.yaml
node_modules
.vscode
postgres/data postgres/data

View File

@ -19,8 +19,14 @@ front/MyINPulse-front/.installed:
vite: ./front/MyINPulse-front/.installed vite: ./front/MyINPulse-front/.installed
keycloak: ./keycloak/.installed
dev-front: clean vite keycloak/.installed:
@echo "running one time install"
@cd keycloak/CAS && sudo sh build.sh
@touch ./keycloak/.installed
dev-front: clean vite keycloak
@cp config/frontdev.env front/MyINPulse-front/.env @cp config/frontdev.env front/MyINPulse-front/.env
@cp config/frontdev.env .env @cp config/frontdev.env .env
@cp config/frontdev.env MyINPulse-back/.env @cp config/frontdev.env MyINPulse-back/.env
@ -28,7 +34,7 @@ dev-front: clean vite
@docker compose up -d --build @docker compose up -d --build
@cd ./front/MyINPulse-front/ && npm run dev @cd ./front/MyINPulse-front/ && npm run dev
prod: clean prod: clean keycloak
@cp config/prod.env front/MyINPulse-front/.env @cp config/prod.env front/MyINPulse-front/.env
@cp config/prod.env .env @cp config/prod.env .env
@cp config/prod.env .env @cp config/prod.env .env
@ -37,7 +43,7 @@ prod: clean
dev-back: dev-back: keycloak
@cp config/backdev.env front/MyINPulse-front/.env @cp config/backdev.env front/MyINPulse-front/.env
@cp config/backdev.env .env @cp config/backdev.env .env
@cp config/backdev.env MyINPulse-back/.env @cp config/backdev.env MyINPulse-back/.env
@ -46,7 +52,7 @@ dev-back:
@echo "cd MyINPulse-back" && echo 'export $$(cat .env | xargs)' @echo "cd MyINPulse-back" && echo 'export $$(cat .env | xargs)'
@echo "./gradlew bootRun --args='--server.port=8081'" @echo "./gradlew bootRun --args='--server.port=8081'"
dev: clean vite dev: clean vite keycloak
@cp config/dev.env front/MyINPulse-front/.env @cp config/dev.env front/MyINPulse-front/.env
@cp config/dev.env .env @cp config/dev.env .env
@cp config/dev.env MyINPulse-back/.env @cp config/dev.env MyINPulse-back/.env
@ -55,3 +61,13 @@ dev: clean vite
@echo "cd MyINPulse-back" && echo 'export $$(cat .env | xargs)' @echo "cd MyINPulse-back" && echo 'export $$(cat .env | xargs)'
@echo "./gradlew bootRun --args='--server.port=8081'" @echo "./gradlew bootRun --args='--server.port=8081'"
@cd ./front/MyINPulse-front/ && npm run dev & @cd ./front/MyINPulse-front/ && npm run dev &
test-back: clean keycloak
@cp config/dev.env front/MyINPulse-front/.env
@cp config/dev.env .env
@cp config/dev.env MyINPulse-back/.env
@cp config/dev.docker-compose.yaml docker-compose.yaml
@docker compose up -d --build
@echo "cd MyINPulse-back" && echo 'export $$(cat .env | xargs)'
@cd ./MyINPulse-back/ && ./gradlew test && ./gradlew jacocoTestReport
@firefox ./MyINPulse-back/build/jacocoHtml/index.html

View File

@ -2,6 +2,7 @@ plugins {
id 'java' id 'java'
id 'org.springframework.boot' version '3.4.2' id 'org.springframework.boot' version '3.4.2'
id 'io.spring.dependency-management' version '1.1.7' id 'io.spring.dependency-management' version '1.1.7'
id 'jacoco'
} }
group = 'enseirb' group = 'enseirb'
@ -23,13 +24,38 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-validation' implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation 'org.springframework.boot:spring-boot-starter-data-rest' implementation 'org.springframework.boot:spring-boot-starter-data-rest'
implementation group: 'org.apache.logging.log4j', name: 'log4j-api', version: '2.16.0' implementation group: 'org.apache.logging.log4j', name: 'log4j-api', version: '2.+'
implementation group: 'org.apache.logging.log4j', name: 'log4j-core', version: '2.16.0' implementation group: 'org.apache.logging.log4j', name: 'log4j-core', version: '2.+'
implementation 'org.postgresql:postgresql' 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 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'com.h2database:h2'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher' testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
} }
tasks.named('test') { tasks.named('test') {
useJUnitPlatform() useJUnitPlatform()
} }
test {
finalizedBy jacocoTestReport // report is always generated after tests run
}
jacocoTestReport {
dependsOn test // tests are required to run before generating the report
reports {
xml.required = false
csv.required = false
html.outputLocation = layout.buildDirectory.dir('jacocoHtml')
}
}
jacoco {
toolVersion = "0.8.12"
reportsDirectory = layout.buildDirectory.dir('customJacocoReportDir')
}

View File

@ -31,7 +31,7 @@ public class WebSecurityCustomConfiguration {
public CorsConfigurationSource corsConfigurationSource() { public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration(); CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(List.of(frontendUrl)); configuration.setAllowedOrigins(List.of(frontendUrl));
configuration.setAllowedMethods(Arrays.asList("GET", "OPTIONS")); configuration.setAllowedMethods(Arrays.asList("GET", "OPTIONS", "POST", "PUT", "DELETE"));
configuration.setAllowedHeaders( configuration.setAllowedHeaders(
Arrays.asList("authorization", "content-type", "x-auth-token")); Arrays.asList("authorization", "content-type", "x-auth-token"));
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
@ -56,14 +56,18 @@ public class WebSecurityCustomConfiguration {
http.authorizeHttpRequests( http.authorizeHttpRequests(
authorize -> authorize ->
authorize authorize
.requestMatchers("/entrepreneur/**", "/shared/**") .requestMatchers("/entrepreneur/**")
.access(hasRole("REALM_MyINPulse-entrepreneur")) .access(hasRole("REALM_MyINPulse-entrepreneur"))
.requestMatchers("/admin/**", "/shared/**") .requestMatchers("/admin/**")
.access(hasRole("REALM_MyINPulse-admin")) .access(hasRole("REALM_MyINPulse-admin"))
.requestMatchers("/shared/**")
.hasAnyRole(
"REALM_MyINPulse-admin",
"REALM_MyINPulse-entrepreneur")
.requestMatchers("/unauth/**") .requestMatchers("/unauth/**")
.permitAll() .authenticated()
.anyRequest() .anyRequest()
.authenticated()) .denyAll())
.oauth2ResourceServer( .oauth2ResourceServer(
oauth2 -> oauth2 ->
oauth2.jwt( oauth2.jwt(

View File

@ -57,7 +57,7 @@ public class AdminApi {
* *
* @return the status code of the request * @return the status code of the request
*/ */
@PostMapping("/admin/projects/decision") @PostMapping("/admin/projects/pending/decision")
public void validateProject(@RequestBody ProjectDecision decision) { public void validateProject(@RequestBody ProjectDecision decision) {
adminApiService.validateProject(decision); adminApiService.validateProject(decision);
} }
@ -67,7 +67,7 @@ public class AdminApi {
* *
* @return the status code of the request * @return the status code of the request
*/ */
@PostMapping("/admin/project/add") @PostMapping("/admin/project")
public void addNewProject(@RequestBody Project project) { public void addNewProject(@RequestBody Project project) {
adminApiService.addNewProject(project); adminApiService.addNewProject(project);
} }
@ -79,9 +79,9 @@ public class AdminApi {
* *
* @return the status code of the request * @return the status code of the request
*/ */
@PostMapping("/admin/appoitements/report/{appointmentId}") @PostMapping("/admin/appointments/report/{appointmentId}")
public void createAppointmentReport( public void createAppointmentReport(
@PathVariable String appointmentId, @PathVariable long appointmentId,
@RequestBody Report report, @RequestBody Report report,
@AuthenticationPrincipal Jwt principal) { @AuthenticationPrincipal Jwt principal) {
adminApiService.createAppointmentReport( adminApiService.createAppointmentReport(
@ -95,8 +95,35 @@ public class AdminApi {
* *
* @return the status code of the request * @return the status code of the request
*/ */
@DeleteMapping("/admin/projects/remove/{projectId}") @DeleteMapping("/admin/projects/{projectId}")
public void deleteProject(@PathVariable long projectId) { public void deleteProject(@PathVariable long projectId) {
adminApiService.deleteProject(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);
}
} }

View File

@ -1,14 +1,20 @@
package enseirb.myinpulse.controller; package enseirb.myinpulse.controller;
import enseirb.myinpulse.model.LCSection;
import enseirb.myinpulse.model.Project;
import enseirb.myinpulse.service.EntrepreneurApiService;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.oauth2.jwt.Jwt; 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 @SpringBootApplication
@RestController @RestController
@ -28,13 +34,26 @@ public class EntrepreneurApi {
* *
* @return status code * @return status code
*/ */
@PutMapping("/entrepreneur/lcsection/modify/{sectionId}") @PutMapping("/entrepreneur/sectionCells/{sectionCellId}")
public void editLCSection( public void editSectionCell(
@PathVariable String sectionId, @PathVariable Long sectionCellId,
@RequestBody LCSection section, @RequestBody String content,
@AuthenticationPrincipal Jwt principal) { @AuthenticationPrincipal Jwt principal) {
entrepreneurApiService.editLCSection( entrepreneurApiService.editSectionCell(
sectionId, section, 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 * @return status code
*/ */
@DeleteMapping("/entrepreneur/lcsection/remove/{sectionId}") @DeleteMapping("/entrepreneur/sectionCells/{sectionCellId}")
public void removeLCSection( public void removeSectionCell(
@PathVariable String sectionId, @AuthenticationPrincipal Jwt principal) { @PathVariable Long sectionCellId, @AuthenticationPrincipal Jwt principal) {
entrepreneurApiService.removeLCSection(sectionId, principal.getClaimAsString("email")); entrepreneurApiService.removeSectionCell(
sectionCellId, principal.getClaimAsString("email"));
} }
/** /**
@ -57,13 +77,10 @@ public class EntrepreneurApi {
* *
* @return status code * @return status code
*/ */
@PostMapping("/entrepreneur/lcsection/add/{sectionId}") @PostMapping("/entrepreneur/sectionCells")
public void addLCSection( public void addLCSection(
@PathVariable String sectionId, @RequestBody SectionCell sectionCell, @AuthenticationPrincipal Jwt principal) {
@RequestBody LCSection section, entrepreneurApiService.addSectionCell(sectionCell, principal.getClaimAsString("email"));
@AuthenticationPrincipal Jwt principal) {
entrepreneurApiService.addLCSection(
sectionId, section, principal.getClaimAsString("email"));
} }
/** /**
@ -73,9 +90,27 @@ public class EntrepreneurApi {
* *
* @return status code * @return status code
*/ */
@PostMapping("/entrepreneur/project/request") @PostMapping("/entrepreneur/projects/request")
public void requestNewProject( public void requestNewProject(
@RequestBody Project project, @AuthenticationPrincipal Jwt principal) { @RequestBody Project project, @AuthenticationPrincipal Jwt principal) {
entrepreneurApiService.requestNewProject(project, principal.getClaimAsString("email")); 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"));
}
} }

View File

@ -1,5 +1,7 @@
package enseirb.myinpulse.controller; package enseirb.myinpulse.controller;
import com.itextpdf.text.DocumentException;
import enseirb.myinpulse.model.*; import enseirb.myinpulse.model.*;
import enseirb.myinpulse.service.SharedApiService; import enseirb.myinpulse.service.SharedApiService;
@ -9,6 +11,9 @@ import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.oauth2.jwt.Jwt; import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import java.io.IOException;
import java.net.URISyntaxException;
@SpringBootApplication @SpringBootApplication
@RestController @RestController
public class SharedApi { public class SharedApi {
@ -21,22 +26,18 @@ public class SharedApi {
} }
/** /**
* TODO: It does not looks like a good id to have the title and the date in the url. What even * Endpoint used to get the data inside the lean canvas
* TODO: is the title btw ? if this is the LC section, wouldn't it be better to use an ID ?
* TODO: choose return type, cf comment in LCSection
*
* <p>Endpoint used to get the data inside the lean canvas
* *
* @return a list of lean canvas sections * @return a list of lean canvas sections
*/ */
@GetMapping("/shared/project/lcsection/{projectId}/{title}/{date}") @GetMapping("/shared/projects/sectionCells/{projectId}/{sectionId}/{date}")
public Iterable<SectionCell> getLCSection( public Iterable<SectionCell> getLCSection(
@PathVariable("projectId") String projectId, @PathVariable("projectId") Long projectId,
@PathVariable("title") String title, @PathVariable("sectionId") Long sectionId,
@PathVariable("date") String date, @PathVariable("date") String date,
@AuthenticationPrincipal Jwt principal) { @AuthenticationPrincipal Jwt principal) {
return sharedApiService.getLCSection( return sharedApiService.getSectionCells(
projectId, title, date, principal.getClaimAsString("email")); projectId, sectionId, date, principal.getClaimAsString("email"));
} }
/** /**
@ -44,7 +45,7 @@ public class SharedApi {
* *
* @return a list of all entrepreneurs in a project * @return a list of all entrepreneurs in a project
*/ */
@GetMapping("/shared/entrepreneurs/{projectId}") @GetMapping("/shared/projects/entrepreneurs/{projectId}")
public Iterable<Entrepreneur> getEntrepreneursByProjectId( public Iterable<Entrepreneur> getEntrepreneursByProjectId(
@PathVariable int projectId, @AuthenticationPrincipal Jwt principal) { @PathVariable int projectId, @AuthenticationPrincipal Jwt principal) {
return sharedApiService.getEntrepreneursByProjectId( return sharedApiService.getEntrepreneursByProjectId(
@ -54,10 +55,10 @@ public class SharedApi {
/** /**
* Endpoint used to get the administrator of a project. * Endpoint used to get the administrator of a project.
* *
* @return a list of all project managed by the current admin user * @return the admin of a project
*/ */
@GetMapping("/shared/projects/admin/{projectId}") @GetMapping("/shared/projects/admin/{projectId}")
public Iterable<Administrator> getAdminByProjectId( public Administrator getAdminByProjectId(
@PathVariable int projectId, @AuthenticationPrincipal Jwt principal) { @PathVariable int projectId, @AuthenticationPrincipal Jwt principal) {
return sharedApiService.getAdminByProjectId(projectId, principal.getClaimAsString("email")); return sharedApiService.getAdminByProjectId(projectId, principal.getClaimAsString("email"));
} }
@ -79,16 +80,24 @@ public class SharedApi {
* *
* @return a PDF file? TODO: how does that works ? * @return a PDF file? TODO: how does that works ?
*/ */
@GetMapping("/shared/projects/appointments/report/{appointmentId}") @GetMapping("/shared/appointments/report/{appointmentId}")
public void getPDFReport( public void getPDFReport(
@PathVariable int appointmentId, @AuthenticationPrincipal Jwt principal) { @PathVariable int appointmentId, @AuthenticationPrincipal Jwt principal) {
try {
sharedApiService.getPDFReport(appointmentId, principal.getClaimAsString("email")); sharedApiService.getPDFReport(appointmentId, principal.getClaimAsString("email"));
} catch (DocumentException e) {
System.out.println(e + "Document exception");
} catch (URISyntaxException e) {
System.out.println(e + "Error with URI");
} catch (IOException e) {
System.out.println(e + "Failed to access file");
}
} }
/** /**
* @return TODO * @return TODO
*/ */
@PostMapping("/shared/appointment/request") @PostMapping("/shared/appointments/request")
public void createAppointmentRequest( public void createAppointmentRequest(
@RequestBody Appointment appointment, @AuthenticationPrincipal Jwt principal) { @RequestBody Appointment appointment, @AuthenticationPrincipal Jwt principal) {
sharedApiService.createAppointmentRequest(appointment, principal.getClaimAsString("email")); sharedApiService.createAppointmentRequest(appointment, principal.getClaimAsString("email"));

View File

@ -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"));
}
}

View File

@ -13,14 +13,14 @@ import java.util.List;
public class Administrator extends User { public class Administrator extends User {
@OneToMany(mappedBy = "projectAdministrator", fetch = FetchType.LAZY, orphanRemoval = true) @OneToMany(mappedBy = "projectAdministrator", fetch = FetchType.LAZY, orphanRemoval = true)
private List<Project> listProject = new ArrayList<>(); private final List<Project> listProject = new ArrayList<>();
/*@OneToMany(mappedBy = "administratorSectionCell", fetch = FetchType.LAZY, orphanRemoval = true) /*@OneToMany(mappedBy = "administratorSectionCell", fetch = FetchType.LAZY, orphanRemoval = true)
private List<SectionCell> listSectionCell = new ArrayList<>();*/ private List<SectionCell> listSectionCell = new ArrayList<>();*/
// should now be useless // should now be useless
@OneToMany(mappedBy = "administratorAnnotation", fetch = FetchType.LAZY, orphanRemoval = true) @OneToMany(mappedBy = "administratorAnnotation", fetch = FetchType.LAZY, orphanRemoval = true)
private List<Annotation> listAnnotation = new ArrayList<>(); private final List<Annotation> listAnnotation = new ArrayList<>();
/*@OneToMany(mappedBy = "administratorAppointment", fetch = FetchType.LAZY, orphanRemoval = true) /*@OneToMany(mappedBy = "administratorAppointment", fetch = FetchType.LAZY, orphanRemoval = true)
private final List<Appointment> listAppointment = new ArrayList<>();*/ private final List<Appointment> listAppointment = new ArrayList<>();*/
@ -32,12 +32,35 @@ public class Administrator extends User {
public Administrator() {} public Administrator() {}
public Administrator( public Administrator(
Long idUser,
String userSurname, String userSurname,
String username, String username,
String mainMail, String primaryMail,
String secondaryMail, String secondaryMail,
String phoneNumber) { String phoneNumber) {
super(idUser, userSurname, username, mainMail, secondaryMail, phoneNumber); super(userSurname, username, primaryMail, secondaryMail, phoneNumber, false);
}
public List<Project> getListProject() {
return listProject;
}
public void updateListProject(Project project) {
listProject.add(project);
}
public List<Annotation> getListAnnotation() {
return listAnnotation;
}
public void updateListAnnotation(Annotation annotation) {
listAnnotation.add(annotation);
}
public MakeAppointment getMakeAppointment() {
return makeAppointment;
}
public void setMakeAppointment(MakeAppointment makeAppointment) {
this.makeAppointment = makeAppointment;
} }
} }

View File

@ -1,14 +1,12 @@
package enseirb.myinpulse.model; package enseirb.myinpulse.model;
import jakarta.persistence.*; import jakarta.persistence.*;
import jakarta.validation.constraints.NotNull;
@Entity @Entity
@Table(name = "annotation") @Table(name = "annotation")
public class Annotation { public class Annotation {
@Id @Id
@NotNull
@GeneratedValue(strategy = GenerationType.IDENTITY) @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long idAnnotation; private Long idAnnotation;
@ -33,7 +31,31 @@ public class Annotation {
return comment; return comment;
} }
public void setCommentary(String comment) { public void setComment(String comment) {
this.comment = comment; this.comment = comment;
} }
public Long getIdAnnotation() {
return idAnnotation;
}
public void setIdAnnotation(Long idAnnotation) {
this.idAnnotation = idAnnotation;
}
public SectionCell getSectionCellAnnotation() {
return sectionCellAnnotation;
}
public void setSectionCellAnnotation(SectionCell sectionCellAnnotation) {
this.sectionCellAnnotation = sectionCellAnnotation;
}
public Administrator getAdministratorAnnotation() {
return administratorAnnotation;
}
public void setAdministratorAnnotation(Administrator administratorAnnotation) {
this.administratorAnnotation = administratorAnnotation;
}
} }

View File

@ -1,7 +1,6 @@
package enseirb.myinpulse.model; package enseirb.myinpulse.model;
import jakarta.persistence.*; import jakarta.persistence.*;
import jakarta.validation.constraints.NotNull;
import java.time.LocalDate; import java.time.LocalDate;
import java.time.LocalTime; import java.time.LocalTime;
@ -17,9 +16,6 @@ public class Appointment {
new ArrayList<>(); */ new ArrayList<>(); */
// should now be useless // should now be useless
@OneToMany(mappedBy = "appointmentReport", fetch = FetchType.LAZY, orphanRemoval = true)
private final List<Report> listReport = new ArrayList<>();
@ManyToMany( @ManyToMany(
fetch = FetchType.LAZY, fetch = FetchType.LAZY,
cascade = {CascadeType.ALL}) cascade = {CascadeType.ALL})
@ -29,8 +25,10 @@ public class Appointment {
inverseJoinColumns = @JoinColumn(name = "idSectionCell")) inverseJoinColumns = @JoinColumn(name = "idSectionCell"))
List<SectionCell> listSectionCell = new ArrayList<>(); List<SectionCell> listSectionCell = new ArrayList<>();
@OneToOne(mappedBy = "appointmentReport", fetch = FetchType.LAZY, orphanRemoval = true)
private Report report;
@Id @Id
@NotNull
@GeneratedValue(strategy = GenerationType.IDENTITY) @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long idAppointment; private Long idAppointment;
@ -109,4 +107,20 @@ public class Appointment {
public void setAppointmentSubject(String appointmentSubject) { public void setAppointmentSubject(String appointmentSubject) {
this.appointmentSubject = appointmentSubject; this.appointmentSubject = appointmentSubject;
} }
public List<SectionCell> getAppointmentListSectionCell() {
return listSectionCell;
}
public void updateListSectionCell(SectionCell sectionCell) {
listSectionCell.add(sectionCell);
}
public Report getAppointmentReport() {
return report;
}
public void setAppointmentReport(Report report) {
this.report = report;
}
} }

View File

@ -1,8 +0,0 @@
package enseirb.myinpulse.model;
public class DelAppointment {
int validated;
int[] akserId;
int[] destId;
String date; // TODO: date type ?
}

View File

@ -1,7 +0,0 @@
package enseirb.myinpulse.model;
public class DelProject {
int projectId;
String projectName;
String projectDescription;
}

View File

@ -1,6 +0,0 @@
package enseirb.myinpulse.model;
public class DelReport {
int projectId;
String reportContent;
}

View File

@ -37,21 +37,58 @@ public class Entrepreneur extends User {
public Entrepreneur() {} public Entrepreneur() {}
public Entrepreneur( public Entrepreneur(
Long idUser,
String userSurname, String userSurname,
String username, String username,
String mainMail, String primaryMail,
String secondaryMail,
String phoneNumber,
String school,
String course,
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 secondaryMail,
String phoneNumber, String phoneNumber,
String school, String school,
String course, String course,
boolean sneeStatus) { boolean sneeStatus) {
super(idUser, userSurname, username, mainMail, secondaryMail, phoneNumber); super(userSurname, username, primaryMail, secondaryMail, phoneNumber, true);
this.school = school; this.school = school;
this.course = course; this.course = course;
this.sneeStatus = sneeStatus; this.sneeStatus = sneeStatus;
} }
public Entrepreneur(
String userSurname,
String userName,
String primaryMail,
String secondaryMail,
String phoneNumber,
String school,
String course,
boolean sneeStatus,
Project projectParticipation,
Project projectProposed,
MakeAppointment makeAppointment,
boolean pending) {
super(userSurname, userName, primaryMail, secondaryMail, phoneNumber, pending);
this.school = school;
this.course = course;
this.sneeStatus = sneeStatus;
this.projectParticipation = projectParticipation;
this.projectProposed = projectProposed;
this.makeAppointment = makeAppointment;
}
public String getSchool() { public String getSchool() {
return school; return school;
} }
@ -75,4 +112,28 @@ public class Entrepreneur extends User {
public void setSneeStatus(boolean statusSnee) { public void setSneeStatus(boolean statusSnee) {
this.sneeStatus = sneeStatus; this.sneeStatus = sneeStatus;
} }
public Project getProjectParticipation() {
return projectParticipation;
}
public void setProjectParticipation(Project projectParticipation) {
this.projectParticipation = projectParticipation;
}
public Project getProjectProposed() {
return projectProposed;
}
public void setProjectProposed(Project projectProposed) {
this.projectProposed = projectProposed;
}
public MakeAppointment getMakeAppointment() {
return makeAppointment;
}
public void setMakeAppointment(MakeAppointment makeAppointment) {
this.makeAppointment = makeAppointment;
}
} }

View File

@ -1,7 +0,0 @@
package enseirb.myinpulse.model;
// TODO: is this redundant with the Section class from the database ?
// TODO: In the one hand it represent the same data, and on the other it should be much lighter.
// TODO: btw why does a LC section have an administrator ?
public class LCSection {}

View File

@ -1,14 +1,12 @@
package enseirb.myinpulse.model; package enseirb.myinpulse.model;
import jakarta.persistence.*; import jakarta.persistence.*;
import jakarta.validation.constraints.NotNull;
@Entity @Entity
@Table(name = "make_appointment") @Table(name = "make_appointment")
public class MakeAppointment { public class MakeAppointment {
@Id @Id
@NotNull
@GeneratedValue(strategy = GenerationType.IDENTITY) @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long idMakeAppointment; private Long idMakeAppointment;

View File

@ -1,7 +1,6 @@
package enseirb.myinpulse.model; package enseirb.myinpulse.model;
import jakarta.persistence.*; import jakarta.persistence.*;
import jakarta.validation.constraints.NotNull;
import java.time.LocalDate; import java.time.LocalDate;
import java.util.ArrayList; import java.util.ArrayList;
@ -13,37 +12,67 @@ public class Project {
@OneToMany(mappedBy = "projectParticipation", fetch = FetchType.LAZY, orphanRemoval = true) @OneToMany(mappedBy = "projectParticipation", fetch = FetchType.LAZY, orphanRemoval = true)
private final List<Entrepreneur> listEntrepreneurParticipation = new ArrayList<>(); private final List<Entrepreneur> listEntrepreneurParticipation = new ArrayList<>();
@OneToMany(mappedBy = "projectSectionCell", fetch = FetchType.LAZY, orphanRemoval = true) @OneToMany(mappedBy = "projectSectionCell", fetch = FetchType.LAZY, orphanRemoval = true)
private final List<SectionCell> listSectionCell = new ArrayList<>(); private final List<SectionCell> listSectionCell = new ArrayList<>();
@Id @Id
@NotNull
@GeneratedValue(strategy = GenerationType.IDENTITY) @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long idProject; private Long idProject;
@Column(length = 255) @Column(length = 255)
private String projectName; private String projectName;
private byte[] logo; private byte[] logo;
private LocalDate creationDate; private LocalDate creationDate;
@Column(length = 255)
private String projectStatus; @Column private ProjectDecisionValue projectStatus;
@ManyToOne(fetch = FetchType.LAZY) @ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "idAdministrator") @JoinColumn(name = "idAdministrator")
private Administrator projectAdministrator; private Administrator projectAdministrator;
@OneToOne(mappedBy = "projectProposed", fetch = FetchType.LAZY, orphanRemoval = true) @OneToOne(mappedBy = "projectProposed", fetch = FetchType.LAZY, orphanRemoval = true)
private Entrepreneur entrepreneurProposed; private Entrepreneur entrepreneurProposed;
public Project() {} public Project() {}
public Project( public Project(
Long idProject,
String projectName, String projectName,
byte[] logo, byte[] logo,
LocalDate creationDate, LocalDate creationDate,
String projectStatus) { ProjectDecisionValue projectStatus,
this.idProject = idProject; Administrator projectAdministrator) {
this.projectName = projectName;
this.logo = logo;
this.creationDate = creationDate;
// this.projectStatus = (long) projectStatus.ordinal();
this.projectStatus = projectStatus;
this.projectAdministrator = projectAdministrator;
}
public Project(
String projectName,
byte[] logo,
LocalDate creationDate,
ProjectDecisionValue projectStatus,
Administrator projectAdministrator,
Entrepreneur entrepreneurProposed) {
this.projectName = projectName; this.projectName = projectName;
this.logo = logo; this.logo = logo;
this.creationDate = creationDate; this.creationDate = creationDate;
this.projectStatus = projectStatus; this.projectStatus = projectStatus;
this.projectAdministrator = projectAdministrator;
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() { public Long getIdProject() {
@ -78,15 +107,43 @@ public class Project {
this.creationDate = creationDate; this.creationDate = creationDate;
} }
public String getProjectStatus() { public ProjectDecisionValue getProjectStatus() {
return projectStatus; return projectStatus;
} }
public void setProjectStatus(String projectStatus) { public void setProjectStatus(ProjectDecisionValue projectStatus) {
this.projectStatus = projectStatus; this.projectStatus = projectStatus;
} }
public void setAdministrator(Administrator administrator) { public List<Entrepreneur> getListEntrepreneurParticipation() {
this.projectAdministrator = administrator; return listEntrepreneurParticipation;
}
public void updateListEntrepreneurParticipation(Entrepreneur projectParticipant) {
listEntrepreneurParticipation.add(projectParticipant);
}
public List<SectionCell> getListSectionCell() {
return listSectionCell;
}
public void updateListSectionCell(SectionCell projectSectionCell) {
listSectionCell.add(projectSectionCell);
}
public Administrator getProjectAdministrator() {
return projectAdministrator;
}
public void setProjectAdministrator(Administrator projectAdministrator) {
this.projectAdministrator = projectAdministrator;
}
public Entrepreneur getEntrepreneurProposed() {
return entrepreneurProposed;
}
public void setEntrepreneurProposed(Entrepreneur entrepreneurProposed) {
this.entrepreneurProposed = entrepreneurProposed;
} }
} }

View File

@ -4,4 +4,22 @@ public class ProjectDecision {
public long projectId; public long projectId;
public long adminId; public long adminId;
public long isAccepted; public long isAccepted;
public ProjectDecision(long projectId, long adminId, long isAccepted) {
this.projectId = projectId;
this.adminId = adminId;
this.isAccepted = isAccepted;
}
@Override
public String toString() {
return "ProjectDecision{"
+ "projectId="
+ projectId
+ ", adminId="
+ adminId
+ ", isAccepted="
+ isAccepted
+ '}';
}
} }

View File

@ -0,0 +1,9 @@
package enseirb.myinpulse.model;
public enum ProjectDecisionValue {
PENDING,
ACTIVE,
ENDED,
ABORTED,
REJECTED,
}

View File

@ -4,20 +4,18 @@ import jakarta.persistence.*;
import jakarta.persistence.Entity; import jakarta.persistence.Entity;
import jakarta.persistence.Id; import jakarta.persistence.Id;
import jakarta.persistence.Table; import jakarta.persistence.Table;
import jakarta.validation.constraints.NotNull;
@Entity @Entity
@Table(name = "report") @Table(name = "report")
public class Report { public class Report {
@Id @Id
@NotNull
@GeneratedValue(strategy = GenerationType.IDENTITY) @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long idReport; private Long idReport;
private String reportContent; private String reportContent;
@ManyToOne(fetch = FetchType.LAZY) @OneToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "idAppointment") @JoinColumn(name = "idAppointment")
private Appointment appointmentReport; private Appointment appointmentReport;
@ -28,6 +26,10 @@ public class Report {
this.reportContent = reportContent; this.reportContent = reportContent;
} }
public Long getIdReport() {
return idReport;
}
public String getReportContent() { public String getReportContent() {
return reportContent; return reportContent;
} }
@ -35,4 +37,12 @@ public class Report {
public void setReportContent(String reportContent) { public void setReportContent(String reportContent) {
this.reportContent = reportContent; this.reportContent = reportContent;
} }
public Appointment getAppointmentReport() {
return appointmentReport;
}
public void setAppointmentReport(Appointment appointmentReport) {
this.appointmentReport = appointmentReport;
}
} }

View File

@ -1,7 +1,9 @@
package enseirb.myinpulse.model; package enseirb.myinpulse.model;
import jakarta.persistence.*; import jakarta.persistence.*;
import jakarta.validation.constraints.NotNull;
import org.hibernate.annotations.Generated;
import org.hibernate.generator.EventType;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.ArrayList; import java.util.ArrayList;
@ -11,44 +13,46 @@ import java.util.List;
@Table(name = "section_cell") @Table(name = "section_cell")
public class SectionCell { public class SectionCell {
@ManyToMany(mappedBy = "listSectionCell")
private final List<Appointment> listAppointment = new ArrayList<>();
@OneToMany(mappedBy = "sectionCellAnnotation", fetch = FetchType.LAZY, orphanRemoval = true)
private final List<Annotation> listAnnotation = new ArrayList<>();
@Id @Id
@NotNull
@GeneratedValue(strategy = GenerationType.IDENTITY) @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long idSectionCell; private Long idSectionCell;
@Column(length = 255) @Column(columnDefinition = "serial")
private String title; @Generated(event = EventType.INSERT)
private Long idReference;
@Column() private long sectionId;
private String contentSectionCell; private String contentSectionCell;
/*@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "idAdministrator")
private Administrator administratorSectionCell;*/
// should now be useless
private LocalDateTime modificationDate; private LocalDateTime modificationDate;
@ManyToOne(fetch = FetchType.LAZY) @ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "idProject") @JoinColumn(name = "idProject")
private Project projectSectionCell; private Project projectSectionCell;
/*@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "idAdministrator")
private Administrator administratorSectionCell;*/
// should now be useless
@ManyToMany(mappedBy = "listSectionCell")
private List<Appointment> appointment = new ArrayList<>();
@OneToMany(mappedBy = "sectionCellAnnotation", fetch = FetchType.LAZY, orphanRemoval = true)
private List<Annotation> listAnnotation = new ArrayList<>();
public SectionCell() {} public SectionCell() {}
public SectionCell( public SectionCell(
Long idSectionCell, Long idSectionCell,
String title, Long sectionId,
String contentSectionCell, String contentSectionCell,
LocalDateTime modificationDate) { LocalDateTime modificationDate,
Project projectSectionCell) {
this.idSectionCell = idSectionCell; this.idSectionCell = idSectionCell;
this.title = title; this.sectionId = sectionId;
this.contentSectionCell = contentSectionCell; this.contentSectionCell = contentSectionCell;
this.modificationDate = modificationDate; this.modificationDate = modificationDate;
this.projectSectionCell = projectSectionCell;
} }
public Long getIdSectionCell() { public Long getIdSectionCell() {
@ -59,12 +63,20 @@ public class SectionCell {
this.idSectionCell = idSectionCell; this.idSectionCell = idSectionCell;
} }
public String getTitle() { public Long getIdReference() {
return title; return idReference;
} }
public void setTitle(String title) { public void setIdReference(Long idReference) {
this.title = title; this.idReference = idReference;
}
public Long getSectionId() {
return sectionId;
}
public void setSectionId(Long sectionId) {
this.sectionId = sectionId;
} }
public String getContentSectionCell() { public String getContentSectionCell() {
@ -82,4 +94,32 @@ public class SectionCell {
public void setModificationDate(LocalDateTime modificationDate) { public void setModificationDate(LocalDateTime modificationDate) {
this.modificationDate = modificationDate; this.modificationDate = modificationDate;
} }
public Project getProjectSectionCell() {
return projectSectionCell;
}
public List<Appointment> getAppointmentSectionCell() {
return listAppointment;
}
public void updateAppointmentSectionCell(Appointment appointment) {
listAppointment.add(appointment);
}
public List<Annotation> getListAnnotation() {
return listAnnotation;
}
public void updateListAnnotation(Annotation annotation) {
listAnnotation.add(annotation);
}
public void setSectionId(long sectionId) {
this.sectionId = sectionId;
}
public void setProjectSectionCell(Project projectSectionCell) {
this.projectSectionCell = projectSectionCell;
}
} }

View File

@ -1,7 +1,6 @@
package enseirb.myinpulse.model; package enseirb.myinpulse.model;
import jakarta.persistence.*; import jakarta.persistence.*;
import jakarta.validation.constraints.NotNull;
@Entity @Entity
@Table(name = "user_inpulse") @Table(name = "user_inpulse")
@ -9,7 +8,6 @@ import jakarta.validation.constraints.NotNull;
public class User { public class User {
@Id @Id
@NotNull
@GeneratedValue(strategy = GenerationType.IDENTITY) @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long idUser; private Long idUser;
@ -28,21 +26,23 @@ public class User {
@Column(length = 20) @Column(length = 20)
private String phoneNumber; private String phoneNumber;
@Column private boolean pending;
public User() {} public User() {}
public User( public User(
Long idUser,
String userSurname, String userSurname,
String userName, String userName,
String primaryMail, String primaryMail,
String secondaryMail, String secondaryMail,
String phoneNumber) { String phoneNumber,
this.idUser = idUser; boolean pending) {
this.userSurname = userSurname; this.userSurname = userSurname;
this.userName = userName; this.userName = userName;
this.primaryMail = primaryMail; this.primaryMail = primaryMail;
this.secondaryMail = secondaryMail; this.secondaryMail = secondaryMail;
this.phoneNumber = phoneNumber; this.phoneNumber = phoneNumber;
this.pending = pending;
} }
public Long getIdUser() { public Long getIdUser() {
@ -73,8 +73,8 @@ public class User {
return primaryMail; return primaryMail;
} }
public void setPrimaryMail(String mainMail) { public void setPrimaryMail(String primaryMail) {
this.primaryMail = mainMail; this.primaryMail = primaryMail;
} }
public String getSecondaryMail() { public String getSecondaryMail() {
@ -92,4 +92,12 @@ public class User {
public void setPhoneNumber(String phoneNumber) { public void setPhoneNumber(String phoneNumber) {
phoneNumber = phoneNumber; phoneNumber = phoneNumber;
} }
public boolean isPending() {
return pending;
}
public void setPending(boolean pending) {
this.pending = pending;
}
} }

View File

@ -5,10 +5,13 @@ import enseirb.myinpulse.model.Administrator;
import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.rest.core.annotation.RepositoryRestResource; import org.springframework.data.rest.core.annotation.RepositoryRestResource;
import java.util.Optional;
@RepositoryRestResource @RepositoryRestResource
public interface AdministratorRepository extends JpaRepository<Administrator, Long> { public interface AdministratorRepository extends JpaRepository<Administrator, Long> {
/* @Query("SELECT a from Administrators a") /* @Query("SELECT a from Administrators a")
Administrator findAllAdministrator(); */ Administrator findAllAdministrator(); */
Optional<Administrator> findByPrimaryMail(String PrimaryMail);
} }

View File

@ -1,6 +1,7 @@
package enseirb.myinpulse.repository; package enseirb.myinpulse.repository;
import enseirb.myinpulse.model.Entrepreneur; import enseirb.myinpulse.model.Entrepreneur;
import enseirb.myinpulse.model.Project;
import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.rest.core.annotation.RepositoryRestResource; import org.springframework.data.rest.core.annotation.RepositoryRestResource;
@ -8,6 +9,8 @@ import org.springframework.data.rest.core.annotation.RepositoryRestResource;
@RepositoryRestResource @RepositoryRestResource
public interface EntrepreneurRepository extends JpaRepository<Entrepreneur, Long> { public interface EntrepreneurRepository extends JpaRepository<Entrepreneur, Long> {
Iterable<Entrepreneur> getEntrepreneurByProjectParticipation(Project project);
/* @Query("SELECT e from Entrepreneur e") /* @Query("SELECT e from Entrepreneur e")
Entrepreneur findAllEntrepreneurl(); */ Entrepreneur findAllEntrepreneurl(); */

View File

@ -2,13 +2,18 @@ package enseirb.myinpulse.repository;
import enseirb.myinpulse.model.Administrator; import enseirb.myinpulse.model.Administrator;
import enseirb.myinpulse.model.Project; import enseirb.myinpulse.model.Project;
import enseirb.myinpulse.model.ProjectDecisionValue;
import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.rest.core.annotation.RepositoryRestResource; import org.springframework.data.rest.core.annotation.RepositoryRestResource;
import java.util.Optional;
@RepositoryRestResource @RepositoryRestResource
public interface ProjectRepository extends JpaRepository<Project, Long> { public interface ProjectRepository extends JpaRepository<Project, Long> {
Iterable<Project> findByProjectAdministrator(Administrator administrator); Iterable<Project> findByProjectAdministrator(Administrator administrator);
Iterable<Project> findByProjectStatus(String status); Iterable<Project> findByProjectStatus(ProjectDecisionValue status);
Optional<Project> findByProjectName(String projectName);
} }

View File

@ -6,4 +6,4 @@ import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.rest.core.annotation.RepositoryRestResource; import org.springframework.data.rest.core.annotation.RepositoryRestResource;
@RepositoryRestResource @RepositoryRestResource
public interface ReportRepository extends JpaRepository<Report, Integer> {} public interface ReportRepository extends JpaRepository<Report, Long> {}

View File

@ -1,9 +1,20 @@
package enseirb.myinpulse.repository; package enseirb.myinpulse.repository;
import enseirb.myinpulse.model.Project;
import enseirb.myinpulse.model.SectionCell; import enseirb.myinpulse.model.SectionCell;
import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.rest.core.annotation.RepositoryRestResource; import org.springframework.data.rest.core.annotation.RepositoryRestResource;
import java.time.LocalDateTime;
@RepositoryRestResource @RepositoryRestResource
public interface SectionCellRepository extends JpaRepository<SectionCell, Long> {} public interface SectionCellRepository extends JpaRepository<SectionCell, Long> {
Iterable<SectionCell> findByProjectSectionCellAndSectionId(Project project, long sectionId);
Iterable<SectionCell> findByProjectSectionCellAndSectionIdAndModificationDateBefore(
Project project, long sectionId, LocalDateTime date);
Iterable<SectionCell> findByProjectSectionCell(Project project);
}

View File

@ -9,8 +9,9 @@ import java.util.Optional;
@RepositoryRestResource @RepositoryRestResource
public interface UserRepository extends JpaRepository<User, Long> { 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") /* @Query("SELECT u from User u")
User findAllUser(); */ User findAllUser(); */

View File

@ -1,72 +1,224 @@
package enseirb.myinpulse.service; package enseirb.myinpulse.service;
import enseirb.myinpulse.model.*; import static enseirb.myinpulse.model.ProjectDecisionValue.ACTIVE;
import enseirb.myinpulse.service.database.AdministratorService; import static enseirb.myinpulse.model.ProjectDecisionValue.REJECTED;
import enseirb.myinpulse.service.database.ProjectService;
import enseirb.myinpulse.service.database.UserService;
import enseirb.myinpulse.model.*;
import enseirb.myinpulse.service.database.*;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.web.server.ResponseStatusException; import org.springframework.web.server.ResponseStatusException;
import java.util.ArrayList;
import java.util.List;
@Service @Service
public class AdminApiService { public class AdminApiService {
protected static final Logger logger = LogManager.getLogger();
private final ProjectService projectService; private final ProjectService projectService;
private final UserService userService; private final UserService userService;
private final AdministratorService administratorService; private final AdministratorService administratorService;
private final EntrepreneurService entrepreneurService;
private final UtilsService utilsService;
private final AppointmentService appointmentService;
private final ReportService reportService;
private final SectionCellService sectionCellService;
@Autowired @Autowired
AdminApiService( AdminApiService(
ProjectService projectService, ProjectService projectService,
UserService userService, UserService userService,
AdministratorService administratorService) { AdministratorService administratorService,
UtilsService utilsService,
EntrepreneurService entrepreneurService,
AppointmentService appointmentService,
ReportService reportService,
SectionCellService sectionCellService) {
this.projectService = projectService; this.projectService = projectService;
this.userService = userService; this.userService = userService;
this.administratorService = administratorService; this.administratorService = administratorService;
this.utilsService = utilsService;
this.appointmentService = appointmentService;
this.reportService = reportService;
this.sectionCellService = sectionCellService;
this.entrepreneurService = entrepreneurService;
} }
// TODO: test // TODO: check if tests are sufficient - peer verification required
public Iterable<Project> getProjectsOfAdmin(String email) { public Iterable<Project> getProjectsOfAdmin(String mail) {
return projectService.getProjectsByAdminId( return projectService.getProjectsByAdminId(
administratorService.getAdministratorById( administratorService.getAdministratorById(
this.userService.getIdUserByEmail(email))); this.userService.getUserByEmail(mail).getIdUser()));
} }
// TODO public Iterable<Appointment> getUpcomingAppointments(String mail) {
public Iterable<Appointment> getUpcomingAppointments(String email) { logger.info("User {} check their upcoming appointments", mail);
throw new ResponseStatusException(HttpStatus.NOT_IMPLEMENTED, "Not implemented yet"); User user = this.userService.getUserByEmail(mail);
List<Appointment> appointments = new ArrayList<>();
if (user instanceof Administrator) {
List<Project> projects = new ArrayList<>(((Administrator) user).getListProject());
projects.forEach(
project -> {
project.getListSectionCell()
.forEach(
sectionCell -> {
appointments.addAll(
this.sectionCellService
.getAppointmentsBySectionCellId(
sectionCell
.getIdSectionCell()));
});
});
}
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");
} }
// TODO: test project.getListSectionCell()
.forEach(
sectionCell -> {
appointments.addAll(
this.sectionCellService.getAppointmentsBySectionCellId(
sectionCell.getIdSectionCell()));
});
}
return appointments;
}
// TODO: check if tests are sufficient - peer verification required
public Iterable<Project> getPendingProjects() { public Iterable<Project> getPendingProjects() {
return this.projectService.getPendingProjects(); return this.projectService.getPendingProjects();
} }
// TODO: test // TODO: check if tests are sufficient - peer verification required
public void validateProject(ProjectDecision decision) { public void validateProject(ProjectDecision decision) {
projectService.updateProject( projectService.updateProject(
decision.projectId, decision.projectId,
null, null,
null, null,
(decision.isAccepted == 1) ? ACTIVE : REJECTED,
null, null,
"ACTIVE", null,
this.administratorService.getAdministratorById(decision.projectId)); this.administratorService.getAdministratorById(decision.adminId));
} }
// TODO: solve todo + test // TODO: check if tests are sufficient - peer verification required
public void addNewProject(Project project) { public Project addNewProject(Project project) {
projectService.addNewProject(project); // TODO: how can the user know the ID ? project.setIdProject(null);
// We remove the ID from the request to be sure that it will be auto generated
try {
this.projectService.getProjectByName(project.getProjectName(), true);
throw new ResponseStatusException(HttpStatus.CONFLICT, "Project already exists");
} catch (ResponseStatusException e) {
if (e.getStatusCode() == HttpStatus.CONFLICT) {
throw new ResponseStatusException(HttpStatus.CONFLICT, "Project already exists");
}
}
Project newProject = projectService.addNewProject(project);
if (project.getProjectAdministrator() != null) {
newProject.getProjectAdministrator().updateListProject(newProject);
}
if (newProject.getEntrepreneurProposed() != null) {
Entrepreneur proposed = newProject.getEntrepreneurProposed();
proposed.setProjectProposed(newProject);
proposed.setProjectParticipation(newProject);
}
newProject
.getListEntrepreneurParticipation()
.forEach(
participation -> {
participation.setProjectParticipation(newProject);
});
newProject
.getListSectionCell()
.forEach(
sectionCell -> {
sectionCell.setProjectSectionCell(newProject);
});
return newProject;
} }
// TODO public void createAppointmentReport(long appointmentId, Report report, String mail) {
public void createAppointmentReport(String appointmentId, Report report, String email) { long projectId =
throw new ResponseStatusException(HttpStatus.NOT_IMPLEMENTED, "Not implemented yet"); this.appointmentService
.getAppointmentById(appointmentId)
.getAppointmentListSectionCell()
.getFirst()
.getProjectSectionCell()
.getIdProject();
if (!utilsService.isAllowedToCheckProject(mail, projectId)) {
logger.warn(
"User {} tried to add an report for appointment {} but is not allowed to.",
mail,
projectId);
throw new ResponseStatusException(
HttpStatus.UNAUTHORIZED, "You're not allowed to check this project");
}
logger.info("User {} added a report for appointment {}", mail, projectId);
Report addedReport = this.reportService.addNewReport(report);
addedReport.setAppointmentReport(this.appointmentService.getAppointmentById(appointmentId));
this.appointmentService.getAppointmentById(appointmentId).setAppointmentReport(addedReport);
} }
// TODO: test // TODO: test
public void deleteProject(long projectId) { public void deleteProject(long projectId) {
this.projectService.deleteProjectById(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();
}
} }

View File

@ -1,29 +1,284 @@
package enseirb.myinpulse.service; package enseirb.myinpulse.service;
import enseirb.myinpulse.model.LCSection; import static enseirb.myinpulse.model.ProjectDecisionValue.PENDING;
import enseirb.myinpulse.model.Project; 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.model.User;
import enseirb.myinpulse.service.database.*;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.server.ResponseStatusException; import org.springframework.web.server.ResponseStatusException;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
@Service @Service
public class EntrepreneurApiService { public class EntrepreneurApiService {
public void editLCSection(String sectionId, LCSection section, String mail) { protected static final Logger logger = LogManager.getLogger();
throw new ResponseStatusException(HttpStatus.NOT_IMPLEMENTED, "Not implemented yet");
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,
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 removeLCSection(String sectionId, String mail) { public void editSectionCell(Long sectionCellId, String content, String mail) {
throw new ResponseStatusException(HttpStatus.NOT_IMPLEMENTED, "Not implemented yet"); 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(
"User {} tried to edit section cells {} of the project {} but is not allowed to.",
mail,
sectionCellId,
this.sectionCellService.getProjectId(sectionCellId));
throw new ResponseStatusException(
HttpStatus.UNAUTHORIZED, "You're not allowed to check this project");
}
logger.info(
"User {} edited section cell {} of the project with id {}",
mail,
sectionCellId,
this.sectionCellService.getProjectId(sectionCellId));
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 addLCSection(String sectionId, LCSection section, String mail) { public void removeSectionCell(Long sectionCellId, String mail) {
throw new ResponseStatusException(HttpStatus.NOT_IMPLEMENTED, "Not implemented yet"); 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.getProjectId(sectionCellId));
throw new ResponseStatusException(
HttpStatus.UNAUTHORIZED, "You're not allowed to check this project");
}
logger.info(
"User {} removed section cell {} of the project with id {}",
mail,
sectionCellId,
this.sectionCellService.getProjectId(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) {
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, sectionCell.getProjectSectionCell().getIdProject())) {
logger.warn(
"User {} tried to add a section cell to the project {} but is not allowed to.",
mail,
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 {}",
mail,
sectionCell.getIdSectionCell(),
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 -> {
this.appointmentService.updateAppointmentListSectionCell(
appointment.getIdAppointment(), newSectionCell);
});
newSectionCell
.getListAnnotation()
.forEach(
annotation -> {
this.annotationService.updateAnnotationSectionCell(
annotation.getIdAnnotation(), newSectionCell);
});
} }
public void requestNewProject(Project project, String mail) { public void requestNewProject(Project project, String mail) {
throw new ResponseStatusException(HttpStatus.NOT_IMPLEMENTED, "Not implemented yet"); if (project == null) {
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 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;
} }
} }

View File

@ -6,12 +6,17 @@ import enseirb.myinpulse.exception.UserNotFoundException;
import enseirb.myinpulse.model.RoleRepresentation; import enseirb.myinpulse.model.RoleRepresentation;
import enseirb.myinpulse.model.UserRepresentation; import enseirb.myinpulse.model.UserRepresentation;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.web.client.RestClient; import org.springframework.web.client.RestClient;
import java.util.List;
import javax.management.relation.RoleNotFoundException; import javax.management.relation.RoleNotFoundException;
public class KeycloakApi { public class KeycloakApi {
protected static final Logger logger = LogManager.getLogger();
static final String keycloakUrl; static final String keycloakUrl;
static final String realmName; static final String realmName;
@ -29,44 +34,48 @@ public class KeycloakApi {
realmName = System.getenv("VITE_KEYCLOAK_REALM"); 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 * Uses Keycloak API to retrieve a role representation of a role by its name
* *
* @param roleName name of the role * @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 { throws RoleNotFoundException {
RoleRepresentation[] response = RoleRepresentation response =
RestClient.builder() RestClient.builder()
.baseUrl(keycloakUrl) .baseUrl(keycloakUrl)
.defaultHeader("Authorization", bearer) .defaultHeader("Authorization", toBearer(token))
.build() .build()
.get() .get()
.uri("/admin/realms/{realmName}/roles/{roleName}", realmName, roleName) .uri("/admin/realms/{realmName}/roles/{roleName}", realmName, roleName)
.retrieve() .retrieve()
.body(RoleRepresentation[].class); .body(RoleRepresentation.class);
/*
if (response == null || response.length == 0) { {"id":"7a845f2e-c832-4465-8cd8-894d72bc13f1","name":"MyINPulse-entrepreneur","description":"Role for entrepreneur","composite":false,"clientRole":false,"containerId":"0d6f691b-e328-471a-b89e-c30bd7e5b6b0","attributes":{}}
throw new RoleNotFoundException("Role not found"); */
} // TODO: check what happens when role does not exist
return response[0]; return response;
} }
/** /**
* Use keycloak API to to retreive a userID via his name or email. * Use keycloak API to to retreive a userID via his name or email.
* *
* @param username username or mail of the user * @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 * @return the userid, as a String
* @throws UserNotFoundException * @throws UserNotFoundException
*/ */
public static String getUserIdByName(String username, String bearer) public static String getUserIdByName(String username, String token)
throws UserNotFoundException { throws UserNotFoundException {
UserRepresentation[] response = UserRepresentation[] response =
RestClient.builder() RestClient.builder()
.baseUrl(keycloakUrl) .baseUrl(keycloakUrl)
.defaultHeader("Authorization", bearer) .defaultHeader("Authorization", toBearer(token))
.build() .build()
.get() .get()
.uri( .uri(
@ -91,27 +100,26 @@ public class KeycloakApi {
* *
* @param username * @param username
* @param roleName * @param roleName
* @param bearer * @param token
* @throws RoleNotFoundException * @throws RoleNotFoundException
* @throws UserNotFoundException * @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 { throws RoleNotFoundException, UserNotFoundException {
RoleRepresentation roleRepresentation = getRoleRepresentationByName(roleName, bearer); RoleRepresentation roleRepresentation = getRoleRepresentationByName(roleName, token);
String userId = getUserIdByName(username, bearer); String userId = getUserIdByName(username, token);
List<RoleRepresentation> rolesToAdd = List.of(roleRepresentation);
logger.debug("Adding role {} to user {}", roleRepresentation.id, userId);
RestClient.builder() RestClient.builder()
.baseUrl(keycloakUrl) .baseUrl(keycloakUrl)
.defaultHeader("Authorization", bearer) .defaultHeader("Authorization", toBearer(token))
.build() .build()
.post() .post()
.uri( .uri("/admin/realms/" + realmName + "/users/" + userId + "/role-mappings/realm")
"/admin/realms/${realmName}/users/${userId}/role-mappings/realm", .body(rolesToAdd)
realmName,
userId)
.body(roleRepresentation)
.contentType(APPLICATION_JSON) .contentType(APPLICATION_JSON)
.retrieve(); .retrieve()
.toBodilessEntity();
} }
/** /**

View File

@ -1,36 +1,324 @@
package enseirb.myinpulse.service; 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.model.*;
import enseirb.myinpulse.service.database.*;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.web.server.ResponseStatusException; import org.springframework.web.server.ResponseStatusException;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.file.Files;
import java.nio.file.Paths;
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 @Service
public class SharedApiService { public class SharedApiService {
public Iterable<SectionCell> getLCSection( private final AdminApi adminApi;
String projectId, String title, String date, String mail) {
throw new ResponseStatusException(HttpStatus.NOT_IMPLEMENTED, "Not implemented yet"); private final EntrepreneurApi entrepreneurApi;
protected static final Logger logger = LogManager.getLogger();
private final ProjectService projectService;
private final EntrepreneurService entrepreneurService;
private final SectionCellService sectionCellService;
private final AppointmentService appointmentService;
private final UtilsService utilsService;
@Autowired
SharedApiService(
ProjectService projectService,
EntrepreneurService entrepreneurService,
SectionCellService sectionCellService,
AppointmentService appointmentService,
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;
} }
public Iterable<Entrepreneur> getEntrepreneursByProjectId(int projectId, String mail) { // TODO filter this with date
throw new ResponseStatusException(HttpStatus.NOT_IMPLEMENTED, "Not implemented yet"); public Iterable<SectionCell> getSectionCells(
long projectId, long sectionId, String date, 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");
} }
public Iterable<Administrator> getAdminByProjectId(int projectId, String mail) { DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
throw new ResponseStatusException(HttpStatus.NOT_IMPLEMENTED, "Not implemented yet"); LocalDateTime dateTime = LocalDateTime.parse(date, formatter);
Project project = this.projectService.getProjectById(projectId);
return this.sectionCellService.getLatestSectionCellsByIdReferenceBeforeDate(
project, sectionId, dateTime);
} }
public Iterable<Appointment> getAppointmentsByProjectId(int projectId, String mail) { // Retrieve all up to date (for every sectionId) sectionCells of a project
throw new ResponseStatusException(HttpStatus.NOT_IMPLEMENTED, "Not implemented yet"); 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");
} }
public void getPDFReport(int appointmentId, String mail) { Project project = this.projectService.getProjectById(projectId);
throw new ResponseStatusException(HttpStatus.NOT_IMPLEMENTED, "Not implemented yet");
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)) {
logger.warn(
"User {} tried to check the member 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);
return this.entrepreneurService.GetEntrepreneurByProject(project);
}
// TODO: test
public Administrator getAdminByProjectId(long projectId, String mail) {
if (!utilsService.isAllowedToCheckProject(mail, projectId)) {
logger.warn(
"User {} tried to check the admin 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);
return project.getProjectAdministrator();
}
public Iterable<Appointment> getAppointmentsByProjectId(long projectId, String mail) {
if (!utilsService.isAllowedToCheckProject(mail, projectId)) {
logger.warn(
"User {} tried to check the appointments related to the project {} but is not allowed to.",
mail,
projectId);
throw new ResponseStatusException(
HttpStatus.UNAUTHORIZED, "You're not allowed to check this project");
}
logger.info(
"User {} tried to check the appointments related to the project {}",
mail,
projectId);
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 -> {
List<Appointment> sectionAppointments =
this.sectionCellService.getAppointmentsBySectionCellId(
sectionCell.getIdSectionCell());
// Add all appointments from this section cell to the Set
uniqueAppointments.addAll(sectionAppointments);
});
// Convert the Set back to a List for the return value
return new ArrayList<>(uniqueAppointments);
}
public void getPDFReport(long appointmentId, String mail)
throws DocumentException, URISyntaxException, IOException {
long projectId =
this.appointmentService
.getAppointmentById(appointmentId)
.getAppointmentListSectionCell()
.getFirst()
.getProjectSectionCell()
.getIdProject();
if (!utilsService.isAllowedToCheckProject(mail, projectId)) {
logger.warn(
"User {} tried to generate the PDF report {} related to the appointment {} but is not allowed to.",
mail,
this.appointmentService
.getAppointmentById(appointmentId)
.getAppointmentReport()
.getIdReport(),
appointmentId);
throw new ResponseStatusException(
HttpStatus.UNAUTHORIZED, "You're not allowed to check this project");
}
logger.info(
"User {} generated the PDF report related to appointment {}", mail, appointmentId);
String reportContent =
this.appointmentService
.getAppointmentById(appointmentId)
.getAppointmentReport()
.getReportContent();
// PDF generation
Document document = new Document();
PdfWriter.getInstance(document, new FileOutputStream("Report" + appointmentId + ".pdf"));
document.open();
Paragraph title =
new Paragraph(
new Phrase(
"Compte Rendu - Réunion du "
+ this.appointmentService
.getAppointmentById(appointmentId)
.getAppointmentDate()
.toString(),
FontFactory.getFont(
FontFactory.HELVETICA,
20,
Font.BOLDITALIC,
BaseColor.BLACK)));
title.setAlignment(Element.ALIGN_CENTER);
document.add(title);
Font subsection =
FontFactory.getFont(FontFactory.HELVETICA, 14, Font.UNDERLINE, BaseColor.DARK_GRAY);
Font body = FontFactory.getFont(FontFactory.COURIER, 12, BaseColor.BLACK);
String[] split = reportContent.split(" ");
String tmp = "";
int counter = 1;
for (String s : split) {
if (s.equals("//")) {
Chunk chunk = new Chunk(tmp, body);
document.add(chunk);
document.add(new Paragraph("\n"));
tmp = "";
Paragraph paragraph = new Paragraph("Point n°" + counter + " : ", subsection);
document.add(paragraph);
document.add(new Paragraph("\n"));
counter++;
} else {
tmp = tmp.concat(s + " ");
}
}
Chunk chunk = new Chunk(tmp, body);
document.add(chunk);
document.add(new Paragraph("\n"));
document.close();
// Replace uri with website address
Files.copy(
new URI(
"http://localhost:8080/shared/projects/appointments/report/"
+ appointmentId)
.toURL()
.openStream(),
Paths.get("Report" + appointmentId + ".pdf"),
StandardCopyOption.REPLACE_EXISTING);
// delete file, we don't want to stock all reports on the server
File file = new File("Report" + appointmentId + ".pdf");
if (!file.delete()) {
logger.warn("Failed to delete report {}", file.getAbsolutePath());
}
} }
public void createAppointmentRequest(Appointment appointment, String mail) { public void createAppointmentRequest(Appointment appointment, String mail) {
throw new ResponseStatusException(HttpStatus.NOT_IMPLEMENTED, "Not implemented yet"); long projectId =
appointment
.getAppointmentListSectionCell()
.getFirst()
.getProjectSectionCell()
.getIdProject();
if (!utilsService.isAllowedToCheckProject(mail, projectId)) {
logger.warn(
"User {} tried to create for the project {} but is not allowed to.",
mail,
projectId);
throw new ResponseStatusException(
HttpStatus.UNAUTHORIZED, "You're not allowed to check this project");
}
logger.info("User {} tried to create an appointment for project {}", mail, projectId);
Appointment newAppointment = this.appointmentService.addNewAppointment(appointment);
newAppointment
.getAppointmentListSectionCell()
.forEach(
sectionCell -> {
sectionCell.updateAppointmentSectionCell(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);
}
} }
} }

View File

@ -0,0 +1,81 @@
package enseirb.myinpulse.service;
import enseirb.myinpulse.model.Administrator;
import enseirb.myinpulse.model.Entrepreneur;
import enseirb.myinpulse.model.Project;
import enseirb.myinpulse.model.User;
import enseirb.myinpulse.service.database.AdministratorService;
import enseirb.myinpulse.service.database.EntrepreneurService;
import enseirb.myinpulse.service.database.ProjectService;
import enseirb.myinpulse.service.database.UserService;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
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 {
protected static final Logger logger = LogManager.getLogger();
private final UserService userService;
private final ProjectService projectService;
private final EntrepreneurService entrepreneurService;
private final AdministratorService administratorService;
@Autowired
UtilsService(
ProjectService projectService,
UserService userService,
EntrepreneurService entrepreneurService,
AdministratorService administratorService) {
this.userService = userService;
this.projectService = projectService;
this.entrepreneurService = entrepreneurService;
this.administratorService = administratorService;
}
// TODO: test?
public Boolean isAllowedToCheckProject(String mail, long projectId) {
if (isAnAdmin(mail)) {
return true;
}
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);
// We compare the ID instead of the project themselves
return Objects.equals(
entrepreneur.getProjectParticipation().getIdProject(), project.getIdProject());
}
// TODO: test
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();
}
}

View File

@ -1,6 +1,9 @@
package enseirb.myinpulse.service.database; package enseirb.myinpulse.service.database;
import enseirb.myinpulse.model.Administrator; 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 enseirb.myinpulse.repository.AdministratorRepository;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
@ -37,7 +40,67 @@ public class AdministratorService {
return administrator.get(); return administrator.get();
} }
public Administrator getAdministratorByPrimaryMain(String primaryMail) {
Optional<Administrator> administrator =
this.administratorRepository.findByPrimaryMail(primaryMail);
if (administrator.isEmpty()) {
logger.error("No administrator found with the mail {}", primaryMail);
throw new ResponseStatusException(
HttpStatus.NOT_FOUND, "Cet administrateur n'existe pas");
}
return administrator.get();
}
public Administrator addAdministrator(Administrator administrator) { public Administrator addAdministrator(Administrator administrator) {
return this.administratorRepository.save(administrator); 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
}
*/
} }

View File

@ -0,0 +1,81 @@
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;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.web.server.ResponseStatusException;
import java.util.Optional;
@Service
public class AnnotationService {
protected static final Logger logger = LogManager.getLogger();
private final AnnotationRepository annotationRepository;
@Autowired
AnnotationService(AnnotationRepository annotationRepository) {
this.annotationRepository = annotationRepository;
}
public Iterable<Annotation> getAllAnnotations() {
return annotationRepository.findAll();
}
public Annotation getAnnotationById(Long id) {
Optional<Annotation> annotation = annotationRepository.findById(id);
if (annotation.isEmpty()) {
logger.error("getAnnotationById : No annotation found with id {}", id);
throw new ResponseStatusException(
HttpStatus.NOT_FOUND, "Cette annotation n'existe pas");
}
return annotation.get();
}
public Annotation addNewAnnotation(Annotation annotation) {
return this.annotationRepository.save(annotation);
}
public void deleteAnnotationById(Long id) {
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()) {
logger.error("updateAnnotation : No annotation found with id {}", id);
throw new ResponseStatusException(
HttpStatus.NOT_FOUND, "Cette annotation n'existe pas");
}
if (comment != null) {
annotation.get().setComment(comment);
}
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);
}
}

View File

@ -0,0 +1,120 @@
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;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.web.server.ResponseStatusException;
import java.time.LocalDate;
import java.time.LocalTime;
import java.util.Optional;
@Service
public class AppointmentService {
private static final Logger logger = LogManager.getLogger(AppointmentService.class);
private AppointmentRepository appointmentRepository;
@Autowired
AppointmentService(AppointmentRepository appointmentRepository) {
this.appointmentRepository = appointmentRepository;
}
public Iterable<Appointment> getallAppointments() {
return this.appointmentRepository.findAll();
}
public Appointment getAppointmentById(Long id) {
Optional<Appointment> appointment = this.appointmentRepository.findById(id);
if (appointment.isEmpty()) {
logger.error("getAppointmentById : No appointment found with id {}", id);
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Ce rendez vous n'existe pas");
}
return appointment.get();
}
public Appointment addNewAppointment(Appointment appointment) {
return this.appointmentRepository.save(appointment);
}
public void deleteAppointmentById(Long id) {
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,
SectionCell sectionCell) {
Optional<Appointment> appointment = this.appointmentRepository.findById(id);
if (appointment.isEmpty()) {
logger.error("updateAppointment : No appointment found with id {}", id);
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Ce rendez vous n'existe pas");
}
if (appointmentDate != null) {
appointment.get().setAppointmentDate(appointmentDate);
}
if (appointmentTime != null) {
appointment.get().setAppointmentTime(appointmentTime);
}
if (appointmentDuration != null) {
appointment.get().setAppointmentDuration(appointmentDuration);
}
if (appointmentPlace != null) {
appointment.get().setAppointmentPlace(appointmentPlace);
}
if (appointmentSubject != null) {
appointment.get().setAppointmentSubject(appointmentSubject);
}
if (sectionCell != null) {
appointment.get().updateListSectionCell(sectionCell);
}
return this.appointmentRepository.save(appointment.get());
}
}

View File

@ -0,0 +1,135 @@
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;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.web.server.ResponseStatusException;
import java.util.Optional;
@Service
public class EntrepreneurService {
protected static final Logger logger = LogManager.getLogger();
private final EntrepreneurRepository entrepreneurRepository;
EntrepreneurService(EntrepreneurRepository entrepreneurRepository) {
this.entrepreneurRepository = entrepreneurRepository;
}
public Iterable<Entrepreneur> getAllEntrepreneurs() {
return this.entrepreneurRepository.findAll();
}
public Entrepreneur getEntrepreneurById(Long id) {
Optional<Entrepreneur> entrepreneur = entrepreneurRepository.findById(id);
if (entrepreneur.isEmpty()) {
logger.error("getEntrepreneurById : No entrepreneur found with id {}", id);
throw new ResponseStatusException(
HttpStatus.NOT_FOUND, "Cet entrepreneur n'existe pas");
}
return entrepreneur.get();
}
public Entrepreneur addEntrepreneur(Entrepreneur entrepreneur) {
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,
Project projectParticipation,
Project projectProposed,
MakeAppointment makeAppointment) {
Optional<Entrepreneur> entrepreneur = entrepreneurRepository.findById(id);
if (entrepreneur.isEmpty()) {
logger.error("updateEntrepreneur : No entrepreneur found with id {}", id);
throw new ResponseStatusException(
HttpStatus.NOT_FOUND, "Cet entrepreneur n'existe pas");
}
if (school != null) {
entrepreneur.get().setSchool(school);
}
if (course != null) {
entrepreneur.get().setCourse(course);
}
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());
}
}

View File

@ -1,7 +1,8 @@
package enseirb.myinpulse.service.database; package enseirb.myinpulse.service.database;
import enseirb.myinpulse.model.Administrator; import static enseirb.myinpulse.model.ProjectDecisionValue.PENDING;
import enseirb.myinpulse.model.Project;
import enseirb.myinpulse.model.*;
import enseirb.myinpulse.repository.ProjectRepository; import enseirb.myinpulse.repository.ProjectRepository;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
@ -11,7 +12,6 @@ import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.web.server.ResponseStatusException; import org.springframework.web.server.ResponseStatusException;
import java.time.LocalDate;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
@ -34,7 +34,7 @@ public class ProjectService {
public Project getProjectById(Long id) { public Project getProjectById(Long id) {
Optional<Project> project = this.projectRepository.findById(id); Optional<Project> project = this.projectRepository.findById(id);
if (project.isEmpty()) { if (project.isEmpty()) {
System.err.println("Project with id " + id + " not found"); logger.error("No project found with id {}", id);
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Ce projet n'existe pas"); throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Ce projet n'existe pas");
} }
return project.get(); return project.get();
@ -49,12 +49,50 @@ public class ProjectService {
return this.projectRepository.save(project); 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( public Project updateProject(
Long id, Long id,
String projectName, String projectName,
byte[] logo, byte[] logo,
LocalDate creationDate, ProjectDecisionValue projectStatus,
String projectStatus, Entrepreneur entrepreneurParticipation,
SectionCell sectionCell,
Administrator administrator) { Administrator administrator) {
Optional<Project> project = this.projectRepository.findById(id); Optional<Project> project = this.projectRepository.findById(id);
@ -70,22 +108,25 @@ public class ProjectService {
if (logo != null) { if (logo != null) {
project.get().setLogo(logo); project.get().setLogo(logo);
} }
if (creationDate != null) {
project.get().setCreationDate(creationDate);
}
if (projectStatus != null) { if (projectStatus != null) {
// TODO: check if this is really useful
/*
if (!validateStatus(projectStatus)) { if (!validateStatus(projectStatus)) {
System.err.println("updateProjectStatus: Invalid status " + projectStatus); logger.error("updateProjectStatus: Invalid status {}", projectStatus);
throw new ResponseStatusException( throw new ResponseStatusException(
HttpStatus.NOT_ACCEPTABLE, "Ce status n'est pas accepté"); HttpStatus.NOT_ACCEPTABLE, "Ce status n'est pas accepté");
} }
*/
project.get().setProjectStatus(projectStatus); project.get().setProjectStatus(projectStatus);
} }
if (entrepreneurParticipation != null) {
project.get().updateListEntrepreneurParticipation(entrepreneurParticipation);
}
if (sectionCell != null) {
project.get().updateListSectionCell(sectionCell);
}
if (administrator != null) { if (administrator != null) {
project.get().setAdministrator(administrator); project.get().setProjectAdministrator(administrator);
} }
return this.projectRepository.save(project.get()); return this.projectRepository.save(project.get());
@ -96,10 +137,23 @@ public class ProjectService {
} }
public Iterable<Project> getPendingProjects() { public Iterable<Project> getPendingProjects() {
return this.projectRepository.findByProjectStatus("PENDING"); return this.projectRepository.findByProjectStatus(PENDING);
} }
public void deleteProjectById(Long id) { public void deleteProjectById(Long id) {
this.projectRepository.deleteById(id); this.projectRepository.deleteById(id);
} }
public Project getProjectByName(String name, boolean noerror) {
Optional<Project> project = this.projectRepository.findByProjectName(name);
if (project.isEmpty()) {
if (noerror) logger.error("No project found with name {}", name);
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Ce projet n'existe pas");
}
return project.get();
}
public Project getProjectByName(String name) {
return getProjectByName(name, false);
}
} }

View File

@ -1,8 +1,11 @@
package enseirb.myinpulse.service.database; package enseirb.myinpulse.service.database;
import enseirb.myinpulse.model.Appointment;
import enseirb.myinpulse.model.Report; import enseirb.myinpulse.model.Report;
import enseirb.myinpulse.repository.ReportRepository; import enseirb.myinpulse.repository.ReportRepository;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@ -12,6 +15,9 @@ import java.util.Optional;
@Service @Service
public class ReportService { public class ReportService {
protected static final Logger logger = LogManager.getLogger();
private final ReportRepository reportRepository; private final ReportRepository reportRepository;
@Autowired @Autowired
@ -19,16 +25,52 @@ public class ReportService {
this.reportRepository = reportRepository; this.reportRepository = reportRepository;
} }
Report getReportById(int id) { public Iterable<Report> getAllReports() {
Optional<Report> report = reportRepository.findById(id); return this.reportRepository.findAll();
}
public Report getReportById(Long id) {
Optional<Report> report = this.reportRepository.findById(id);
if (report.isEmpty()) { if (report.isEmpty()) {
logger.error("getReportById : No report found with id {}", id);
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Ce compte rendu n'existe pas"); throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Ce compte rendu n'existe pas");
} }
return report.get(); return report.get();
} }
// TODO: do some validation // TODO: do some validation
void saveReport(Report report) { public Report addNewReport(Report report) {
reportRepository.save(report); return this.reportRepository.save(report);
}
public void deleteReportById(Long id) {
this.reportRepository.deleteById(id);
}
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);
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Ce compte rendu n'existe pas");
}
if (reportContent != null) {
report.get().setReportContent(reportContent);
}
if (appointment != null) {
report.get().setAppointmentReport(appointment);
}
return this.reportRepository.save(report.get());
} }
} }

View File

@ -0,0 +1,183 @@
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;
import enseirb.myinpulse.repository.SectionCellRepository;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
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.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
@Service
public class SectionCellService {
protected static final Logger logger = LogManager.getLogger();
private final SectionCellRepository sectionCellRepository;
@Autowired
SectionCellService(SectionCellRepository sectionCellRepository) {
this.sectionCellRepository = sectionCellRepository;
}
public Iterable<SectionCell> getAllSectionCells() {
return this.sectionCellRepository.findAll();
}
public SectionCell getSectionCellById(Long id) {
Optional<SectionCell> sectionCell = this.sectionCellRepository.findById(id);
if (sectionCell.isEmpty()) {
logger.error("getSectionCellById : No sectionCell found with id {}", id);
throw new ResponseStatusException(
HttpStatus.NOT_FOUND, "Cette cellule de section n'existe pas");
}
return sectionCell.get();
}
public SectionCell addNewSectionCell(SectionCell sectionCell) {
return this.sectionCellRepository.save(sectionCell);
}
public void removeSectionCellById(Long id) {
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,
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 (contentSectionCell != null) {
sectionCell.get().setContentSectionCell(contentSectionCell);
sectionCell.get().setModificationDate(LocalDateTime.now());
}
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());
}
public Iterable<SectionCell> getSectionCellsByProject(Project project, Long sectionId) {
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();
return sectionProject.getIdProject();
}
public List<Appointment> getAppointmentsBySectionCellId(Long sectionCellId) {
SectionCell sectionCell = getSectionCellById(sectionCellId);
return sectionCell.getAppointmentSectionCell();
}
public Iterable<SectionCell> getSectionCellsByProjectAndSectionIdBeforeDate(
Project project, long sectionId, LocalDateTime date) {
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();
}
}

View File

@ -3,6 +3,8 @@ package enseirb.myinpulse.service.database;
import enseirb.myinpulse.model.User; import enseirb.myinpulse.model.User;
import enseirb.myinpulse.repository.UserRepository; import enseirb.myinpulse.repository.UserRepository;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@ -14,6 +16,9 @@ import java.util.Optional;
@Service @Service
public class UserService { public class UserService {
protected static final Logger logger = LogManager.getLogger();
private final UserRepository userRepository; private final UserRepository userRepository;
@Autowired @Autowired
@ -25,16 +30,24 @@ public class UserService {
return this.userRepository.findAll(); 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 // TODO
public long getIdUserByEmail(String email) { public User getUserByEmail(String email) {
Optional<User> opt_user = this.userRepository.findByPrimaryMail(email); Optional<User> opt_user = this.userRepository.findByPrimaryMail(email);
if (opt_user.isEmpty()) { if (opt_user.isEmpty()) {
System.err.println("Couldn't find user with email " + email); logger.error("getUserByEmail : No user found with email {}", email);
throw new ResponseStatusException(HttpStatus.NOT_FOUND); throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Cet utilisateur n'existe pas");
} }
User user = opt_user.get(); return opt_user.get();
return user.getIdUser();
} }
public Iterable<User> allUsers() { public Iterable<User> allUsers() {
@ -45,15 +58,46 @@ public class UserService {
return this.userRepository.save(user); 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( public User updateUser(
@PathVariable Long id, @PathVariable Long id,
String userSurname, String userSurname,
String userName, String userName,
String mainMail, String primaryMail,
String secondaryMail, String secondaryMail,
String phoneNumber) { String phoneNumber) {
Optional<User> user = userRepository.findById(id); Optional<User> user = userRepository.findById(id);
if (user.isEmpty()) { if (user.isEmpty()) {
logger.error("updateUser : No user found with id {}", id);
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Cet utilisateur n'existe pas"); throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Cet utilisateur n'existe pas");
} }
if (userName != null) { if (userName != null) {
@ -62,8 +106,8 @@ public class UserService {
if (userSurname != null) { if (userSurname != null) {
user.get().setUserSurname(userSurname); user.get().setUserSurname(userSurname);
} }
if (mainMail != null) { if (primaryMail != null) {
user.get().setPrimaryMail(mainMail); user.get().setPrimaryMail(primaryMail);
} }
if (secondaryMail != null) { if (secondaryMail != null) {
user.get().setSecondaryMail(secondaryMail); user.get().setSecondaryMail(secondaryMail);
@ -73,4 +117,8 @@ public class UserService {
} }
return this.userRepository.save(user.get()); return this.userRepository.save(user.get());
} }
public Iterable<User> getPendingAccounts() {
return this.userRepository.findAllByPendingEquals(true);
}
} }

View File

@ -1,69 +0,0 @@
package enseirb.myinpulse.service.database.old_controllers_to_convert_to_services;
import enseirb.myinpulse.model.Appointment;
import enseirb.myinpulse.repository.AppointmentRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.server.ResponseStatusException;
import java.time.LocalDate;
import java.time.LocalTime;
import java.util.Optional;
@RestController
public class AppointmentController {
@Autowired AppointmentRepository appointmentRepository;
@GetMapping("/Appointment")
@ResponseBody
public Iterable<Appointment> allAppointments() {
return this.appointmentRepository.findAll();
}
@GetMapping("/Appointment/{id}")
public Appointment getAppointmentById(@PathVariable Long id) {
Optional<Appointment> appointment = this.appointmentRepository.findById(id);
if (appointment.isEmpty()) {
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Ce rendez vous n'existe pas");
}
return appointment.get();
}
@PostMapping("/Appointment")
public Appointment addAppointment(@RequestBody Appointment appointment) {
return this.appointmentRepository.save(appointment);
}
@PostMapping("/Appointment/{id}")
public Appointment updateAppointment(
@PathVariable Long id,
LocalDate appointmentDate,
LocalTime appointmentTime,
LocalTime appointmentDuration,
String appointmentPlace,
String appointmentSubject) {
Optional<Appointment> appointment = this.appointmentRepository.findById(id);
if (appointment.isEmpty()) {
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Ce rendez vous n'existe pas");
}
if (appointmentDate != null) {
appointment.get().setAppointmentDate(appointmentDate);
}
if (appointmentTime != null) {
appointment.get().setAppointmentTime(appointmentTime);
}
if (appointmentDuration != null) {
appointment.get().setAppointmentDuration(appointmentDuration);
}
if (appointmentPlace != null) {
appointment.get().setAppointmentPlace(appointmentPlace);
}
if (appointmentSubject != null) {
appointment.get().setAppointmentSubject(appointmentSubject);
}
return this.appointmentRepository.save(appointment.get());
}
}

View File

@ -1,58 +0,0 @@
package enseirb.myinpulse.service.database.old_controllers_to_convert_to_services;
import enseirb.myinpulse.model.Entrepreneur;
import enseirb.myinpulse.repository.EntrepreneurRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.server.ResponseStatusException;
import java.util.Optional;
@RestController
public class EntrepreneurController {
@Autowired EntrepreneurRepository entrepreneurRepository;
@GetMapping("/Entrepreneur")
@ResponseBody
public Iterable<Entrepreneur> allEntrepreneurs() {
return this.entrepreneurRepository.findAll();
}
@GetMapping("/Entrepreneur/{id}")
public Entrepreneur getEntrepreneurById(@PathVariable Long id) {
Optional<Entrepreneur> entrepreneur = entrepreneurRepository.findById(id);
if (entrepreneur.isEmpty()) {
throw new ResponseStatusException(
HttpStatus.NOT_FOUND, "Cet entrepreneur n'existe pas");
}
return entrepreneur.get();
}
@PostMapping("/Entrepreneur")
public Entrepreneur addEntrepreneur(@RequestBody Entrepreneur entrepreneur) {
return this.entrepreneurRepository.save(entrepreneur);
}
@PostMapping("/Entrepreneur/{id}")
public Entrepreneur updateEntrepreneur(
@PathVariable Long id, String school, String course, Boolean sneeStatus) {
Optional<Entrepreneur> entrepreneur = entrepreneurRepository.findById(id);
if (entrepreneur.isEmpty()) {
throw new ResponseStatusException(
HttpStatus.NOT_FOUND, "Cet entrepreneur n'existe pas");
}
if (school != null) {
entrepreneur.get().setSchool(school);
}
if (course != null) {
entrepreneur.get().setCourse(course);
}
if (sneeStatus != null) {
entrepreneur.get().setSneeStatus(sneeStatus);
}
return this.entrepreneurRepository.save(entrepreneur.get());
}
}

View File

@ -1,43 +0,0 @@
package enseirb.myinpulse.service.database.old_controllers_to_convert_to_services;
import org.springframework.web.bind.annotation.*;
@RestController
public class ReportController {
/*
private final ReportRepository reportRepository;
@Autowired
public ReportController(ReportRepository reportRepository) {
this.reportRepository = reportRepository;
}
@GetMapping("/Report")
@ResponseBody
public Iterable<Report> allReports() {
System.out.println("\n\n");
System.out.println(ReportRepository);
System.out.println("\n\n");
return this.reportRepository.findAll();
}
@PostMapping("/Report")
public Report addReport(@RequestBody Report report) {
return this.reportRepository.save(report);
}
@PostMapping("/Report/{id}")
public Report updateProject(@PathVariable Long id, String reportContent) {
Optional<Report> report = this.reportRepository.findById(id);
if (report.isEmpty()) {
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Ce compte rendu n'existe pas");
}
if (reportContent != null) {
report.get().setReportContent(reportContent);
}
return this.reportRepository.save(report.get());
}
*/
}

View File

@ -1,62 +0,0 @@
package enseirb.myinpulse.service.database.old_controllers_to_convert_to_services;
import enseirb.myinpulse.model.SectionCell;
import enseirb.myinpulse.repository.SectionCellRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.server.ResponseStatusException;
import java.time.LocalDateTime;
import java.util.Optional;
@RestController
public class SectionCellController {
@Autowired SectionCellRepository sectionCellRepository;
@GetMapping("/SectionCell")
@ResponseBody
public Iterable<SectionCell> allSectionCells() {
return this.sectionCellRepository.findAll();
}
@GetMapping("/SectionCell/{id}")
public SectionCell getSectionCellById(@PathVariable Long id) {
Optional<SectionCell> sectionCell = this.sectionCellRepository.findById(id);
if (sectionCell.isEmpty()) {
throw new ResponseStatusException(
HttpStatus.NOT_FOUND, "Cette cellule de section n'existe pas");
}
return sectionCell.get();
}
@PostMapping("/SectionCell")
public SectionCell addSectionCell(@RequestBody SectionCell sectionCell) {
return this.sectionCellRepository.save(sectionCell);
}
@PostMapping("/SectionCell/{id}")
public SectionCell updateSectionCell(
@PathVariable Long id,
String title,
String contentSectionCell,
LocalDateTime modificationDate) {
Optional<SectionCell> sectionCell = this.sectionCellRepository.findById(id);
if (sectionCell.isEmpty()) {
throw new ResponseStatusException(
HttpStatus.NOT_FOUND, "Cette cellule de section n'existe pas");
}
if (title != null) {
sectionCell.get().setTitle(title);
}
if (contentSectionCell != null) {
sectionCell.get().setContentSectionCell(contentSectionCell);
}
if (modificationDate != null) {
sectionCell.get().setModificationDate(modificationDate);
}
return this.sectionCellRepository.save(sectionCell.get());
}
}

View File

@ -1,8 +1,8 @@
spring.application.name=myinpulse 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.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/test spring.security.oauth2.resourceserver.jwt.issuer-uri=http://localhost:7080/realms/${VITE_KEYCLOAK_REALM}
logging.level.org.springframework.security=DEBUG
spring.datasource.url=jdbc:postgresql://${DATABASE_URL}/${BACKEND_DB} spring.datasource.url=jdbc:postgresql://${DATABASE_URL}/${BACKEND_DB}
spring.datasource.username=${BACKEND_USER} spring.datasource.username=${BACKEND_USER}
spring.datasource.password=${BACKEND_PASSWORD} spring.datasource.password=${BACKEND_PASSWORD}
spring.jpa.hibernate.ddl-auto=update spring.jpa.hibernate.ddl-auto=update
logging.pattern.console=%d{yyyy-MMM-dd HH:mm:ss.SSS} [%thread] %highlight(%-5level) %cyan(%logger{15}) - %msg %n

View File

@ -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

View File

@ -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);

View File

@ -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;

View 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.

View File

@ -1,14 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="INFO">
<Appenders>
<Console name="console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
</Console>
</Appenders>
<Loggers>
<Root level="trace">
<AppenderRef ref="console"/>
</Root>
</Loggers>
</Configuration>

View File

@ -0,0 +1,331 @@
package enseirb.myinpulse;
import static enseirb.myinpulse.model.ProjectDecisionValue.*;
import static org.junit.jupiter.api.Assertions.*;
import enseirb.myinpulse.model.*;
import enseirb.myinpulse.service.AdminApiService;
import enseirb.myinpulse.service.UtilsService;
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 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 AppointmentService appoitmentService,
@Autowired SectionCellService sectionCellService) {
administratorService.addAdministrator(
new Administrator(
"admin",
"admin",
"testAdminEmpty@example.com",
"testAdmin@example.com",
""));
administrator =
administratorService.addAdministrator(
new Administrator(
"admin2",
"admin2",
"testAdminFull@example.com",
"testAdmin@example.com",
""));
administratorid = administrator.getIdUser();
entrepreneur =
new Entrepreneur(
"JeSuisUnEntrepreneurDeCompet",
"EtUé",
"Entrepreneur@inpulse.com",
"mail2",
"phone",
"Ensimag nan jdeconne ENSEIRB (-matmeca mais on s'en fout)",
"info ofc",
false);
entrepreneurService.addEntrepreneur(entrepreneur);
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(),
LocalTime.now(),
LocalTime.now(),
"Salle TD 03",
"Discussion importante");
}
private <T> List<T> IterableToList(Iterable<T> iterable) {
List<T> l = new ArrayList<>();
iterable.forEach(l::add);
return l;
}
@Test
void getProjectOfAdminIsEmpty() {
Iterable<Project> projects =
adminApiService.getProjectsOfAdmin("testAdminEmpty@example.com");
assertEquals(0, IterableToList(projects).size());
}
@Test
void getProjectOfInexistantAdminFails() {
String nonExistentAdminEmail = "testInexistantAdmin@example.com";
assertThrows(
ResponseStatusException.class,
() -> {
adminApiService.getProjectsOfAdmin(nonExistentAdminEmail);
});
}
@Test
void getProjectOfAdminNotEmpty() {
Iterable<Project> projects =
adminApiService.getProjectsOfAdmin("testAdminFull@example.com");
List<Project> l = IterableToList(projects);
assertEquals(1, l.size());
Project p = l.getFirst();
assertEquals("sampleProjectAdminApiService", p.getProjectName());
}
@Test
void getPendingProjectsEmpty() {
assertEquals(0, IterableToList(this.adminApiService.getPendingProjects()).size());
}
@Test
void getPendingProjectsNotEmpty() {
this.projectService.addNewProject(
new Project(
"PendingProjectAdminApiService1", null, LocalDate.now(), PENDING, null));
this.projectService.addNewProject(
new Project(
"PendingProjectAdminApiService2", null, LocalDate.now(), PENDING, null));
Iterable<Project> pendingProjects = this.adminApiService.getPendingProjects();
List<Project> pendingProjectsList = IterableToList(pendingProjects);
assertEquals(2, pendingProjectsList.size());
assertTrue(
List.of("PendingProjectAdminApiService1", "PendingProjectAdminApiService2")
.contains(pendingProjectsList.getFirst().getProjectName()));
assertTrue(
List.of("PendingProjectAdminApiService1", "PendingProjectAdminApiService2")
.contains(pendingProjectsList.getLast().getProjectName()));
}
@Test
void validateInexistantProject() {
ProjectDecision d = new ProjectDecision(-1, 0, 1);
assertThrows(ResponseStatusException.class, () -> this.adminApiService.validateProject(d));
}
@Test
void validateExistantProject() {
Project p =
new Project("PendingProjectAdminApiService2", null, LocalDate.now(), PENDING, null);
this.projectService.addNewProject(p);
assertEquals(PENDING, p.getProjectStatus());
ProjectDecision d = new ProjectDecision(p.getIdProject(), administratorid, 1);
this.adminApiService.validateProject(d);
assertEquals(ACTIVE, p.getProjectStatus());
// Check if the project was really updated in the database
assertEquals(0, IterableToList(this.adminApiService.getPendingProjects()).size());
}
@Test
void refuseExistantProject() {
Project p =
new Project("PendingProjectAdminApiService2", null, LocalDate.now(), PENDING, null);
this.projectService.addNewProject(p);
assertEquals(PENDING, p.getProjectStatus());
ProjectDecision d = new ProjectDecision(p.getIdProject(), administratorid, 0);
this.adminApiService.validateProject(d);
assertEquals(REJECTED, p.getProjectStatus());
// Check if the project was really updated in the database
assertEquals(0, IterableToList(this.adminApiService.getPendingProjects()).size());
}
@Test
void addProject() {
assertEquals(0, IterableToList(this.adminApiService.getPendingProjects()).size());
Project p1 =
new Project("PendingProjectAdminApiService2", null, LocalDate.now(), PENDING, null);
this.adminApiService.addNewProject(p1);
assertEquals(1, IterableToList(this.adminApiService.getPendingProjects()).size());
}
@Test
void addProjectToAdmin() {
assertEquals(0, administrator.getListProject().size());
Project p1 = new Project("addProjectToAdmin", null, LocalDate.now(), ACTIVE, administrator);
this.adminApiService.addNewProject(p1);
assertEquals(1, administrator.getListProject().size());
}
@Test
void addProjectToUser() {
assertNull(entrepreneur.getProjectParticipation());
Project p1 =
new Project("addProjectToAdmin", null, LocalDate.now(), ACTIVE, null, entrepreneur);
this.adminApiService.addNewProject(p1);
assertEquals(p1, entrepreneur.getProjectParticipation());
}
@Test
void addProjectWithManyUsers() {
Entrepreneur e1 = new Entrepreneur();
Entrepreneur e2 = new Entrepreneur();
Entrepreneur e3 = new Entrepreneur();
assertNull(e1.getProjectParticipation());
assertNull(e2.getProjectParticipation());
assertNull(e3.getProjectParticipation());
Project p1 = new Project("addProjectToAdmin", null, LocalDate.now(), ACTIVE, null, null);
p1.updateListEntrepreneurParticipation(e1);
p1.updateListEntrepreneurParticipation(e2);
p1.updateListEntrepreneurParticipation(e3);
this.adminApiService.addNewProject(p1);
assertEquals(p1, e1.getProjectParticipation());
assertEquals(p1, e2.getProjectParticipation());
assertEquals(p1, e3.getProjectParticipation());
}
@Test
void addDuplicateProject() {
Project p1 =
new Project("PendingProjectAdminApiService2", null, LocalDate.now(), PENDING, null);
Project p2 =
new Project("PendingProjectAdminApiService2", null, LocalDate.now(), PENDING, null);
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()));
}
}

View File

@ -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());
}
}

View File

@ -1,5 +1,6 @@
package enseirb.myinpulse; package enseirb.myinpulse;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest;
@ -7,5 +8,6 @@ import org.springframework.boot.test.context.SpringBootTest;
class MyinpulseApplicationTests { class MyinpulseApplicationTests {
@Test @Test
@DisplayName("contextLoad => Test if the context can load, i.e. the application can start")
void contextLoads() {} void contextLoads() {}
} }

View File

@ -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())));
}
}

View File

@ -0,0 +1,10 @@
spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.url=jdbc:h2:mem:db;DB_CLOSE_DELAY=-1
spring.datasource.username=sa
spring.datasource.password=sa
spring.sql.init.mode=never
spring.application.name=myinpulse-test
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.jpa.hibernate.ddl-auto=update
logging.pattern.console=%d{yyyy-MMM-dd HH:mm:ss.SSS} [%thread] %highlight(%-5level) %cyan(%logger{15}) - %msg %n

22
config/.env.dev Normal file
View File

@ -0,0 +1,22 @@
POSTGRES_DB=postgres_db
POSTGRES_USER=postgres
POSTGRES_PASSWORD=postgres_db_user_password
KEYCLOAK_ADMIN=admin
KEYCLOAK_ADMIN_PASSWORD=admin
KEYCLOAK_HOSTNAME=localhost
KEYCLOAK_DB=keycloak_db
KEYCLOAK_USER=keycloak_db_user
KEYCLOAK_PASSWORD=keycloak_db_user_password
BACKEND_DB=backend_db
BACKEND_USER=backend_db_user
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_APP_URL=http://localhost:5173
VITE_BACKEND_URL=http://localhost:8081/

View File

@ -16,7 +16,7 @@ BACKEND_PASSWORD=backend_db_user_password
DATABASE_URL=localhost:5433 DATABASE_URL=localhost:5433
VITE_KEYCLOAK_URL=http://localhost:7080 VITE_KEYCLOAK_URL=http://localhost:7080
VITE_KEYCLOAK_CLIENT_ID=myinpulse-dev VITE_KEYCLOAK_CLIENT_ID=MyINPulse-vite
VITE_KEYCLOAK_REALM=test VITE_KEYCLOAK_REALM=MyINPulse
VITE_APP_URL=http://localhost:5173 VITE_APP_URL=http://localhost:5173
VITE_BACKEND_URL=http://localhost:8081/ VITE_BACKEND_URL=http://localhost:8081/

12
documentation/Doc.txt Normal file
View File

@ -0,0 +1,12 @@
Format des comptes rendus de réunion :
Texte organisé par bullet point, chaque bullet point est séparé par "//" pour pouvoir être correctement généré.
Exemple :
Le texte "// blablabla // oui bonjour"
donne le résultat
Point n°1 :
blablabla
Point n°2 :
oui bonjour

View 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

View File

@ -0,0 +1,11 @@
#!/bin/bash
cd ./swagger-ui
if [ ! -d "./node_modules/" ]
then
npm install
npm install swagger-cli
fi
npm start

View 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.

View 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.

View 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"

View 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"

View 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.

View 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.

View 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");
});

File diff suppressed because it is too large Load Diff

View 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"
}
}

0
front/Dockerfile Normal file → Executable file
View File

View File

@ -1,9 +1,9 @@
<!DOCTYPE html> <!doctype html>
<html lang=""> <html lang="">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico"> <link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite App</title> <title>Vite App</title>
</head> </head>
<body> <body>

View File

@ -10,6 +10,7 @@
"dependencies": { "dependencies": {
"axios": "^1.7.9", "axios": "^1.7.9",
"cors": "^2.8.5", "cors": "^2.8.5",
"jwt-decode": "^4.0.0",
"keycloak-js": "^26.1.0", "keycloak-js": "^26.1.0",
"pinia": "^2.3.1", "pinia": "^2.3.1",
"pinia-plugin-persistedstate": "^4.2.0", "pinia-plugin-persistedstate": "^4.2.0",
@ -3588,6 +3589,15 @@
"graceful-fs": "^4.1.6" "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": { "node_modules/keycloak-js": {
"version": "26.1.0", "version": "26.1.0",
"resolved": "https://registry.npmjs.org/keycloak-js/-/keycloak-js-26.1.0.tgz", "resolved": "https://registry.npmjs.org/keycloak-js/-/keycloak-js-26.1.0.tgz",

View File

@ -18,7 +18,8 @@
"pinia": "^2.3.1", "pinia": "^2.3.1",
"pinia-plugin-persistedstate": "^4.2.0", "pinia-plugin-persistedstate": "^4.2.0",
"vue": "^3.5.13", "vue": "^3.5.13",
"vue-router": "^4.5.0" "vue-router": "^4.5.0",
"jwt-decode": "^4.0.0"
}, },
"devDependencies": { "devDependencies": {
"@playwright/test": "^1.49.1", "@playwright/test": "^1.49.1",

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View File

@ -1,47 +1,12 @@
<script setup lang="ts"> <script setup lang="ts">
import { RouterView } from "vue-router"; import { /*RouterLink,*/ RouterView } from "vue-router";
import ErrorWrapper from "@/views/errorWrapper.vue"; import ErrorWrapper from "@/views/errorWrapper.vue";
import ProjectComponent from "@/components/ProjectComponent.vue";
</script> </script>
<template> <template>
<HeaderComponent /> <Header />
<error-wrapper></error-wrapper> <ErrorWrapper />
<div id="main"> <!--<RouterLink to="/">Home</RouterLink> | -->
<ProjectComponent <!--<RouterLink to="/canvas">Canvas</RouterLink> -->
v-for="(project, index) in projects"
:key="index"
:project-name="project.name"
/>
</div>
<RouterView /> <RouterView />
</template> </template>
<script lang="ts">
import HeaderComponent from "@/components/HeaderComponent.vue";
export default {
name: "App",
components: {
HeaderComponent,
},
data() {
return {
projects: [
{
name: "Projet Alpha",
//link: './project-alpha.html',
//members: ['Alice', 'Bob', 'Charlie'],
},
{
name: "Projet Beta",
//link: './project-beta.html',
//members: ['David', 'Eve', 'Frank'],
},
],
};
},
};
</script>
<style scoped></style>

View 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>

View 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>

View 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>

View 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>

View 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>

View File

@ -1,26 +1,64 @@
<template> <template>
<header> <header class="header">
<img src="./icons/logo inpulse.png" alt="INPulse" /> <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> </header>
</template> </template>
<script lang="ts"> <script setup lang="ts">
export default { import { store } from "@/main.ts";
name: "HeaderComponent",
};
</script> </script>
<style scoped> <style scoped>
header img { @import "@/components/canvas/style-project.css";
width: 100px;
}
header { .header {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
padding: 10px 20px; padding: 15px 30px;
background-color: #fff; background-color: #f9f9f9;
border-bottom: 2px solid #ddd; 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> </style>

View 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>

View 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>

View 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>

View File

@ -1,21 +1,306 @@
<template> <template>
<div class="project"> <div class="project">
<div class="project-header"> <div class="project-header">
<h2>{{ projectName }}</h2> <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>
<!-- Toute cette partie est cliquable -->
<div class="project-body" @click="goToLink">
<ul>
<li v-for="(name, index) in listName" :key="index">
{{ name }}
</li>
</ul>
</div> </div>
</div> </div>
</template> </template>
<script lang="ts"> <script setup lang="ts">
import type { PropType } from "vue"; 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";
export default { const IS_MOCK_MODE = false;
name: "ProjectComponent", const dropdownRef = ref<HTMLElement | null>(null);
props: {
projectName: { const props = defineProps<{
type: Object as PropType<string>, projectName: string;
required: true, 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);
}
}; };
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> </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);
transition: box-shadow 0.3s ease;
cursor: pointer;
}
.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: 0.5rem;
}
.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>

View File

@ -0,0 +1,687 @@
<template>
<div :class="['cell', { expanded }]" @click="handleClick">
<h3 class="fs-5 fw-medium">{{ titleText }}</h3>
<div class="tooltip-explain">{{ description }}</div>
<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>
</div>
</template>
<script setup lang="ts">
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;
titleText: string;
description: string;
isAdmin: boolean;
}>();
const IS_MOCK_MODE = true;
const IS_ADMIN = props.isAdmin;
const expanded = ref(false);
const currentDescriptions = ref<SectionCell[]>([]);
const editedDescriptions = ref<SectionCell[]>([]);
const isEditing = ref<boolean[]>([]);
const startEditing = (index: number) => {
isEditing.value[index] = true;
};
const cancelEdit = (index: number) => {
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 lorigine des produits.",
"Peu dalternatives 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);
}
.expanded-content {
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);
}
.cell h3 {
font-size: 15px;
font-weight: 500;
font-family: "Arial", sans-serif;
}
.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);
}
.description {
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;
}
.edit-input {
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%;
}
.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;
}
.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;
}
.save-button {
background-color: #28a745;
color: white;
}
.cancel-button {
background-color: #dc3545;
color: white;
}
.edit-button:hover {
background-color: #0056b3;
}
.save-button:hover {
background-color: #218838;
}
.cancel-button:hover {
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>

View File

@ -0,0 +1,239 @@
<template>
<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>
</header>
</template>
<script setup lang="ts">
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<string[]>([]);
const entrepreneurs = ref<UserEntrepreneur[]>([]);
const IS_MOCK_MODE = false;
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 {
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 = () => {
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(() => {
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: center;
padding: 10px 20px;
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>

Some files were not shown because too many files have changed in this diff Show More