Compare commits

...
Sign in to create a new pull request.

203 commits

Author SHA1 Message Date
K Muller
5634d4d232 Merge branch 'trunk' 2026-10-15 09:12:44 +02:00
K Muller
9a1a07307b Merge branch 'develop' into trunk 2026-10-15 09:12:16 +02:00
Karl M'muller
8410447a29 Merge branch '134-restore-soft-delete' into 'develop'
feat: ✨ restore soft-deleted user on create

See merge request ldc-web/projets/support-web!138
2026-10-15 07:11:08 +00:00
Corentin Huet
f0fb7a26f3 feat: ✨ restore soft-deleted user on create 2026-10-08 15:31:11 +02:00
Noah Lancien
f55ca959b8 Merge branch 'trunk' 2026-10-07 15:30:35 +02:00
Noah Lancien
d622f543e9 Merge branch 'develop' into trunk 2026-10-07 15:29:51 +02:00
Noix
1f1c684008 Merge branch '133-export-file' into 'develop'
feat: ✨ change ExportTicketsState to Friday 14:00

See merge request ldc-web/projets/support-web!137
2026-10-07 13:20:45 +00:00
Corentin Huet
e8c5472d45 feat: ✨ change ExportTicketsState to Friday 14:00 2026-10-07 09:30:01 +02:00
Noah Lancien
4ff19d9277 Merge branch 'trunk' 2026-10-01 10:07:26 +02:00
Noah Lancien
7dfd80cf23 Merge branch 'develop' into trunk 2026-10-01 10:07:12 +02:00
Noix
e19d810a2f Merge branch '132-create-gitlab-issue' into 'develop'
feat: ✨ allow creating gitlab issues for mailing type

See merge request ldc-web/projets/support-web!136
2026-10-01 08:06:30 +00:00
Corentin Huet
ab998c975d feat: ✨ allow creating gitlab issues for mailing type 2026-09-01 16:51:04 +02:00
Vincent BRIDIER
9706922312 Merge branch 'trunk' 2026-08-27 20:56:21 +02:00
Vincent BRIDIER
ebb52b5968 Merge branch 'develop' into trunk 2026-08-27 20:56:07 +02:00
Vincent BRIDIER
6c783247a7 fix: Alpine www-data = 82 2026-08-27 20:54:40 +02:00
K Muller
6cf790feab Merge branch 'trunk' 2026-08-26 12:58:54 +02:00
K Muller
57c7613455 Merge branch 'develop' into trunk 2026-08-26 12:58:41 +02:00
Karl M'muller
eddeb4f7f6 Merge branch '128-archived-project' into 'develop'
fix: 🐛 add error message for archived project

See merge request ldc-web/projets/support-web!135
2026-08-26 10:58:10 +00:00
Karl M'muller
d9d1fea624 Merge branch '127-error-sentry' into 'develop'
fix: 🐛 update regex to match both issue and work items

See merge request ldc-web/projets/support-web!134
2026-08-26 10:57:57 +00:00
Corentin Huet
22d79cfb11 fix: 🐛 add error message for archived project 2026-08-25 13:16:11 +02:00
Corentin Huet
22bc49baab fix: 🐛 update regex to match both issue and work items 2026-08-24 17:50:16 +02:00
K Muller
adb2a8b21c Merge branch 'trunk' 2026-07-23 09:26:12 +02:00
K Muller
7246d7894d Merge branch 'develop' into trunk 2026-07-23 09:25:13 +02:00
Karl M'muller
10b75031e7 Merge branch 'issue-126' into 'develop'
Fix: 🐛 duplicate emails sent when a ticket's modif is changed

See merge request ldc-web/projets/support-web!133
2026-07-23 07:23:38 +00:00
Abdel LAMRANI
68a48fdf5a Fix: 🐛 duplicate emails sent when a ticket's modif is changed 2026-07-20 12:45:46 +02:00
Jeremy Munsch
a7debacd65
Merge branch 'trunk' 2026-06-26 15:36:53 +02:00
Jeremy Munsch
dd10d4ba3a
Merge branch 'develop' into trunk 2026-06-26 15:36:43 +02:00
Jeremy Munsch
a7bd6d6420
fixed: NotifyAdmin event listener 2026-06-26 15:36:34 +02:00
Jeremy Munsch
8cfc409d5c
Merge branch 'trunk' 2026-06-26 15:28:54 +02:00
Jeremy Munsch
bc7a1020bc
Merge branch 'develop' into trunk 2026-06-26 15:28:45 +02:00
Jeremy Munsch
e828c8bf66
fix: TicketCreated event 2026-06-26 15:28:31 +02:00
Jeremy Munsch
f28dbdd1df
Merge branch 'trunk' 2026-06-26 15:15:10 +02:00
Jeremy Munsch
965226fae6
Fixed ticket creation 2026-06-26 15:14:32 +02:00
Jeremy Munsch
bd9b7cd32e
fix: new ticket creation failed 2026-06-26 15:13:48 +02:00
Noah Lancien
8c67989526 Merge branch 'trunk' 2026-06-26 09:50:56 +02:00
Noah Lancien
2212b0ae08 Merge branch 'develop' into trunk 2026-06-26 09:50:41 +02:00
Noix
c2b25c3503 Merge branch 'brevo-fix' into 'develop'
fix: brevo in (content commit that were missed)

See merge request ldc-web/projets/support-web!132
2026-06-26 07:46:25 +00:00
Jeremy Munsch
f106c17588
fix: brevo in (content commit that were missed) 2026-06-26 09:33:50 +02:00
Noah Lancien
8e9d117918 Merge branch 'trunk' 2026-06-25 11:07:30 +02:00
Noah Lancien
0dbb483fc4 Merge branch 'develop' into trunk 2026-06-25 08:47:02 +02:00
Noah Lancien
980717f37c ci: swicth to docker multi-stage 2026-06-25 08:46:44 +02:00
Noah Lancien
020fcdaa03 Merge branch 'develop' into trunk 2026-06-25 08:37:04 +02:00
Noix
095f80eac1 Merge branch 'brevo-webhook-fix' into 'develop'
Brevo webhook fix Closes #106

See merge request ldc-web/projets/support-web!130
2026-06-25 06:36:32 +00:00
Noix
6a973703c4 Merge branch 'issue-124' into 'develop'
refactor: change ldc logo Closes #124

See merge request ldc-web/projets/support-web!131
2026-06-25 06:36:22 +00:00
Jeremy Munsch
9a853088b1
refactor: change ldc logo Closes #124 2026-06-24 17:22:58 +02:00
Jeremy Munsch
0ec3be521d
refactor: Fixes #106 create ticket automatically from brevo webhook manager 2026-06-24 16:46:12 +02:00
Jeremy Munsch
6854af708b
fix: fix project migrations 2026-06-24 13:22:36 +02:00
Noah Lancien
8f1c920e26 Merge branch 'trunk' 2026-05-18 10:28:44 +02:00
Noah Lancien
efe5e8ace6 Merge branch 'develop' into trunk 2026-05-18 10:28:29 +02:00
Noah Lancien
91f4773c60 chore: 📦 Added meow npm package 2026-05-18 10:28:21 +02:00
Noah Lancien
6b5b4ec619 Merge branch 'trunk' 2026-05-18 10:18:39 +02:00
Noah Lancien
4edd92e572 Merge branch 'develop' into trunk 2026-05-18 10:18:20 +02:00
Noix
da03fbe273 Merge branch '115-project-not-found' into 'develop'
fix: 🐛 handle 404 when gitlab project is not found

See merge request ldc-web/projets/support-web!128
2026-05-18 08:12:44 +00:00
Noah Lancien
b7f7b7c55d Merge branch 'trunk' 2026-04-29 16:28:13 +02:00
Noah Lancien
49d37a8fa6 Merge branch 'develop' into trunk 2026-04-29 16:18:44 +02:00
Noah Lancien
195a5fcb3e chore: arrow_up: Bump PHP 8.5 2026-04-29 16:18:25 +02:00
Noah Lancien
e0af4a243a Merge branch 'trunk' 2026-04-29 16:10:44 +02:00
Noah Lancien
0c4d376c22 Merge branch 'develop' into trunk 2026-04-29 16:10:11 +02:00
Noix
6fabb998ed Merge branch 'support-to-glab-labels' into 'develop'
fix: 🐛 update to-dispatch label to g-to-dispatch

See merge request ldc-web/projets/support-web!129
2026-04-29 14:09:36 +00:00
Corentin Huet
04bd156849 fix: 🐛 update to-dispatch label to g-to-dispatch 2026-04-28 15:16:20 +02:00
Corentin Huet
a43037d2f9 fix: 🐛 handle 404 when gitlab project is not found 2026-03-30 11:59:42 +02:00
Noah Lancien
ef57fe9fe5 Merge branch 'trunk' 2026-03-25 11:52:33 +01:00
Noah Lancien
62901a015e Merge branch 'develop' into trunk 2026-03-25 11:52:18 +01:00
Noah Lancien
cbabb40f83 Merge branch 'develop' into trunk 2026-02-26 12:10:44 +01:00
Noah Lancien
870986faa4 Merge branch 'develop' into trunk 2026-02-26 11:15:14 +01:00
Noah Lancien
33ea75a5ed Merge branch 'trunk' 2026-01-29 12:38:38 +01:00
Noah Lancien
3e09baf6f3 Merge branch 'develop' into trunk 2026-01-29 12:38:20 +01:00
Noah Lancien
d5f1891764 Merge branch 'trunk' 2026-01-19 09:39:57 +01:00
Noah Lancien
098e40576e Merge branch 'develop' into trunk 2026-01-19 09:39:06 +01:00
Noah Lancien
18b66793a0 Merge branch 'trunk' 2026-01-16 15:56:26 +01:00
Noah Lancien
7ee8a2be82 Merge branch 'develop' into trunk 2026-01-16 15:51:31 +01:00
Noah Lancien
87cc2dec86 Merge branch 'trunk' 2026-01-12 09:32:52 +01:00
Noah Lancien
ea41e19724 Merge branch 'develop' into trunk 2026-01-12 09:32:32 +01:00
Vincent BRIDIER
65772aeb1d Merge branch 'trunk' 2026-01-02 15:31:49 +01:00
Vincent BRIDIER
ea6ac998d8 Merge branch 'develop' into trunk 2026-01-02 15:31:35 +01:00
Noah Lancien
9bc3d9e5e0 Merge branch 'trunk' 2025-11-28 09:33:55 +01:00
Noah Lancien
f99dac8aab Merge branch 'develop' into trunk 2025-11-21 09:07:52 +01:00
Noah Lancien
3f4342f3bb Merge branch 'develop' into trunk 2025-11-20 15:54:16 +01:00
Noah Lancien
481c732f3e Merge branch 'trunk' 2025-10-30 12:32:32 +01:00
Noah Lancien
887837dcaf Merge branch 'develop' into trunk 2025-10-30 12:22:06 +01:00
K Muller
4bdcf60dfa Merge branch 'develop' into trunk 2025-10-23 10:32:31 +02:00
Noah Lancien
435d5626ee Merge branch 'trunk' 2025-10-09 11:50:24 +02:00
Noah Lancien
50ba8eaf8a Merge branch 'develop' into trunk 2025-10-09 11:50:12 +02:00
Noah Lancien
f2a65c85b0 Merge branch 'trunk' 2025-10-09 11:43:15 +02:00
Noah Lancien
1a2fe21212 Merge branch 'develop' into trunk 2025-10-09 11:42:45 +02:00
Noah Lancien
d702c02bdb Merge branch 'trunk' 2025-09-18 08:59:45 +02:00
Noah Lancien
b1a6dfb72c Merge branch 'develop' into trunk 2025-09-18 08:58:20 +02:00
Vincent BRIDIER
844928e945 Merge branch 'trunk' 2025-08-22 12:51:32 +02:00
Vincent BRIDIER
3560e6c7f6 Merge branch 'develop' into trunk 2025-08-22 10:28:08 +02:00
Vincent BRIDIER
aa3f507483
Merge branch 'trunk' 2025-08-18 14:16:25 +02:00
Vincent BRIDIER
8a34a30ab4
Merge branch 'develop' into trunk 2025-08-18 14:16:17 +02:00
Vincent BRIDIER
4a0c556937
Merge branch 'trunk' 2025-08-18 14:03:06 +02:00
Vincent BRIDIER
085028a465
Merge branch 'develop' into trunk 2025-08-18 12:40:48 +02:00
Vincent BRIDIER
f9ed83ff60
Merge branch 'develop' into trunk 2025-08-18 12:13:24 +02:00
Noah Lancien
1ffb5e5bdd Merge branch 'trunk' 2025-07-10 14:54:56 +02:00
Noah Lancien
c6fcd7cfa6 Merge branch 'develop' into trunk 2025-07-10 13:05:54 +02:00
Noah Lancien
3d81cd77ce Merge branch 'trunk' 2025-07-09 15:31:20 +02:00
Noah Lancien
fba178b598 Merge branch 'develop' into trunk 2025-07-09 15:23:22 +02:00
Noah Lancien
1f02f86610 Merge branch 'trunk' 2025-06-24 10:31:22 +02:00
Noah Lancien
efb3914e4f Merge branch 'develop' into trunk 2025-06-24 10:31:03 +02:00
Noah Lancien
b0f4c37153 Merge branch 'trunk' 2025-06-19 12:09:03 +02:00
Noah Lancien
25ab578c93 Merge branch 'develop' into trunk 2025-06-19 12:01:08 +02:00
Noah Lancien
52386bbfd4 Merge branch 'develop' into trunk 2025-06-19 11:48:17 +02:00
Noah Lancien
d6ed7c1f04 Merge branch 'trunk' 2025-06-16 12:00:47 +02:00
Noah Lancien
5477affb97 Merge branch 'develop' into trunk 2025-06-16 11:52:26 +02:00
Vincent BRIDIER
3e199d0f68
Merge branch 'trunk' 2025-05-27 10:30:17 +02:00
Vincent BRIDIER
7564600ace
Merge branch 'develop' into trunk 2025-05-27 10:30:05 +02:00
Vincent BRIDIER
ba6f7f8e7b
Merge branch 'trunk' 2025-05-12 15:11:13 +02:00
Vincent BRIDIER
f1eddb1cc2
Merge branch 'develop' into trunk 2025-05-12 15:10:59 +02:00
Vincent BRIDIER
658fedd3eb
Merge branch 'trunk' 2025-05-12 11:42:21 +02:00
Vincent BRIDIER
31510149af
Merge branch 'develop' into trunk 2025-05-12 11:41:46 +02:00
Noah Lancien
fb8720d41b Merge branch 'trunk' 2025-04-24 15:02:27 +02:00
Noah Lancien
08afe201f8 Merge branch 'develop' into trunk 2025-04-24 15:02:07 +02:00
Noah Lancien
afd8f09d02 Merge branch 'trunk' 2025-04-23 12:11:56 +02:00
Noah Lancien
33cf4f1c93 Merge branch 'develop' into trunk 2025-04-23 12:11:30 +02:00
Vincent BRIDIER
e1944924c1
Merge branch 'trunk' 2025-04-18 10:28:51 +02:00
Vincent BRIDIER
3a0cc47fa8
Merge branch 'develop' into trunk 2025-04-18 10:14:27 +02:00
Vincent BRIDIER
3a2b5a0af9
Merge branch 'develop' into trunk 2025-04-18 09:59:53 +02:00
Noah Lancien
ca7057ce77 Merge branch 'develop' into trunk 2025-04-08 14:11:31 +02:00
Noah Lancien
0dd283b68d Merge branch 'trunk' 2025-04-07 15:01:11 +02:00
Noah Lancien
7c028a4505 Merge branch 'develop' into trunk 2025-04-07 15:00:52 +02:00
Karl Muller
b21dd4d260 Merge branch 'trunk' 2025-03-07 14:27:01 +00:00
Karl Muller
4838b54b4c Merge branch 'develop' into trunk 2025-03-07 14:26:49 +00:00
Karl Muller
911ee5df4b Merge branch 'trunk' 2025-03-07 14:10:23 +00:00
Karl Muller
bbfd4360f2 Merge branch 'develop' into trunk 2025-03-07 14:09:58 +00:00
Vincent BRIDIER
3a51ccb0f3
Merge branch 'trunk' 2025-03-04 09:58:05 +01:00
Vincent BRIDIER
1c3f20782b
Merge branch 'develop' into trunk 2025-03-04 09:57:56 +01:00
Noah Lancien
673494aeda Merge branch 'trunk' 2025-02-20 14:41:26 +01:00
Noah Lancien
1bd04e2e52 Merge branch 'develop' into trunk 2025-02-20 14:41:11 +01:00
Noah Lancien
aa17aab45f Merge branch 'trunk' 2025-02-14 13:42:22 +01:00
Noah Lancien
ec47d2610f Merge branch 'develop' into trunk 2025-02-14 13:42:08 +01:00
Noah Lancien
560ef30cd8 Merge branch 'trunk' 2025-02-14 13:31:00 +01:00
Noah Lancien
b2908aac9f Merge branch 'develop' into trunk 2025-02-14 13:30:20 +01:00
Karl Muller
0da4a6fa13 Merge branch 'trunk' 2025-01-13 13:56:41 +00:00
Karl Muller
b6720bae3d Merge branch 'develop' into trunk 2025-01-13 13:46:09 +00:00
Karl Muller
a58bf2ff3b fix: change manifest for cron issue 2025-01-13 13:45:04 +00:00
Noah Lancien
2b94d780b6 Merge branch 'trunk' 2024-11-26 09:18:29 +01:00
Noah Lancien
74edc5c785 Merge branch 'develop' into trunk 2024-11-26 09:17:42 +01:00
Noah Lancien
781161c5e4 Merge branch 'trunk' 2024-11-25 17:19:55 +01:00
Noah Lancien
2d70ff9f29 Merge branch 'develop' into trunk 2024-11-25 17:18:41 +01:00
Noah Lancien
17a01e1f7a Merge branch 'trunk' 2024-11-25 17:12:45 +01:00
Karl Muller
e3d828a9e5 Merge branch 'develop' into trunk 2024-11-25 15:50:32 +00:00
Noah Lancien
d17678342e Merge branch 'trunk' 2024-11-12 14:32:29 +01:00
Noah Lancien
b646cb41fe Merge branch 'develop' into trunk 2024-11-12 14:07:35 +01:00
Noah Lancien
5bd7673a18 Merge branch 'trunk' 2024-07-31 12:21:21 +02:00
Noah Lancien
2faf043f68 Merge branch 'develop' into trunk 2024-07-31 12:12:17 +02:00
Vincent BRIDIER
66c889b02a
Merge branch 'trunk' 2024-07-26 15:33:24 +02:00
Vincent BRIDIER
74600ef3a7
Merge branch 'develop' into trunk 2024-07-26 15:06:54 +02:00
Vincent BRIDIER
35e18112b9
Merge branch 'develop' into trunk 2024-07-25 17:29:35 +02:00
Vincent BRIDIER
f10b46a012
Merge branch 'trunk' 2024-07-20 23:18:59 +02:00
Vincent BRIDIER
523715a3c1
Merge branch 'develop' into trunk 2024-07-20 23:01:04 +02:00
Vincent BRIDIER
c8b4f97c88
Merge branch 'trunk' 2024-07-10 15:23:32 +02:00
Vincent BRIDIER
65b92edc99
Merge branch 'develop' into trunk 2024-07-10 15:23:17 +02:00
Thibault Raquet
2ee245be4c Merge branch 'trunk' into 'master'
Trunk

See merge request ldc-web/projets/support-web!86
2024-06-27 08:58:58 +00:00
Thibault Raquet
766a990361 Merge branch 'develop' into 'trunk'
Develop

See merge request ldc-web/projets/support-web!85
2024-06-27 08:58:33 +00:00
Vincent BRIDIER
a23509fa09
Merge branch 'trunk' 2024-06-11 10:22:48 +02:00
Vincent BRIDIER
6c8cc25bae
Merge branch 'develop' into trunk 2024-06-11 10:07:26 +02:00
Vincent BRIDIER
3c529a7bc0
Merge branch 'trunk' 2024-02-05 12:17:39 +01:00
Vincent BRIDIER
c8d179e7b3
Merge branch 'develop' into trunk 2024-02-05 12:09:45 +01:00
Vincent BRIDIER
8e4d6d4198
Merge branch 'develop' into trunk 2024-02-02 11:34:37 +01:00
Vincent BRIDIER
a3c2697c86
Merge branch 'trunk' 2024-01-31 18:03:13 +01:00
Vincent BRIDIER
c6d89101b4
Merge branch 'develop' into trunk 2024-01-31 17:55:33 +01:00
Vincent BRIDIER
ad43378b52
Merge branch 'trunk' 2023-10-13 15:23:19 +02:00
Vincent BRIDIER
f40f2e2aa5
Merge branch 'develop' into trunk 2023-10-13 15:16:29 +02:00
Vincent BRIDIER
b55d812aaa Merge branch 'develop' into trunk 2023-10-12 16:55:19 +02:00
Vincent BRIDIER
ab08bad87e Merge branch 'trunk' 2023-10-12 11:47:11 +02:00
Vincent BRIDIER
e8b920a8cd Merge branch 'develop' into trunk 2023-10-12 11:18:37 +02:00
Vincent BRIDIER
cb779c83b8
Merge branch 'trunk' 2023-10-09 15:09:26 +02:00
Vincent BRIDIER
a393b24392
Merge branch 'develop' into trunk 2023-10-04 11:38:28 +02:00
Vincent BRIDIER
cd89f6b443 Merge branch 'trunk' 2023-08-24 16:21:39 +02:00
Vincent BRIDIER
d9b316eae4 Merge branch 'develop' into trunk 2023-08-24 16:01:54 +02:00
Vincent BRIDIER
722369cf61 Merge branch 'develop' into trunk 2023-08-24 12:37:14 +02:00
Vincent BRIDIER
0cc315d2ef Merge branch 'develop' into trunk 2023-08-24 12:35:08 +02:00
Vincent BRIDIER
7559a47044
Merge branch 'trunk' 2023-08-18 12:37:30 +02:00
Vincent BRIDIER
55fef3421e
Merge branch 'develop' into trunk 2023-08-18 12:37:14 +02:00
Vincent BRIDIER
97d8111f4b
Merge branch 'trunk' 2023-08-17 11:21:13 +02:00
Vincent BRIDIER
4354f6a445
Merge branch 'develop' into trunk 2023-08-17 11:18:26 +02:00
Vincent BRIDIER
abdbbae843
Merge branch 'develop' into trunk 2023-08-16 12:24:45 +02:00
Vincent BRIDIER
f150e96d39
Merge branch 'develop' into trunk 2023-08-16 10:30:48 +02:00
Vincent BRIDIER
d2324145a4
Merge branch 'develop' into trunk 2023-08-14 11:24:22 +02:00
Vincent BRIDIER
b6c74efd3d
Merge branch 'trunk' 2023-07-20 15:31:37 +02:00
Vincent BRIDIER
57a437db48
Merge branch 'develop' into trunk 2023-07-20 15:31:24 +02:00
Vincent BRIDIER
f3cb8e7974
Merge branch 'trunk' 2023-06-30 09:14:08 +02:00
Vincent BRIDIER
91c1fe5532
Merge branch 'develop' into trunk 2023-06-30 09:13:52 +02:00
Vincent BRIDIER
29a837ff61
Merge branch 'trunk' 2023-06-29 10:12:31 +02:00
Vincent BRIDIER
bbe4478c09
Merge branch 'develop' into trunk 2023-06-29 10:12:14 +02:00
Vincent BRIDIER
28ee9f593d
Merge branch 'trunk' 2023-06-22 14:58:09 +02:00
Vincent BRIDIER
eb2d4e5267
Merge branch 'develop' into trunk 2023-06-22 14:57:42 +02:00
Vincent BRIDIER
c19d5829e6
Merge branch 'trunk' 2023-04-25 11:35:01 +02:00
Vincent BRIDIER
c1fb743e34
Merge branch 'develop' into trunk 2023-04-25 11:34:24 +02:00
Vincent BRIDIER
1d07c93799
Merge branch 'trunk' 2023-04-18 17:27:35 +02:00
Vincent BRIDIER
32cb11622c
Merge branch 'develop' into trunk 2023-04-18 17:27:21 +02:00
Vincent BRIDIER
911d7a2f6d
Merge branch 'trunk' 2023-04-17 17:21:58 +02:00
Vincent BRIDIER
32be1746b5
Merge branch 'develop' into trunk 2023-04-17 16:22:06 +02:00
Vincent BRIDIER
a5d4c58604
Merge branch 'develop' into trunk 2023-04-12 15:47:13 +02:00
Vincent BRIDIER
0d6f34b05a
Merge branch 'trunk' 2022-12-15 09:10:57 +01:00
Vincent BRIDIER
606ca09608
Merge branch 'develop' into trunk 2022-12-15 09:10:38 +01:00
Jérémy Munsch
6523875860
Merge branch 'trunk' 2022-11-04 17:04:58 +01:00
Jérémy Munsch
83b7e42b90
Merge branch 'develop' into trunk 2022-11-04 17:03:49 +01:00
Jérémy Munsch
6f20a6b8db
Merge branch 'develop' into trunk 2022-10-13 10:15:36 +02:00
Jérémy Munsch
88f3da4c92
Merge branch 'hotfix' 2022-10-12 14:22:46 +02:00
Vincent BRIDIER
bd3133f06c
Merge branch 'trunk' 2022-10-11 12:06:30 +02:00
Vincent BRIDIER
bb2cbc5ad8
Merge branch 'develop' into trunk 2022-10-11 12:06:06 +02:00
75 changed files with 7913 additions and 1795 deletions

View file

@ -7,7 +7,7 @@ include:
file: 'security/security.yml' file: 'security/security.yml'
- project: 'ldc-web/shared-ci' - project: 'ldc-web/shared-ci'
ref: 'main' ref: 'main'
file: 'build/build.yml' file: "build/docker-multistage.yml"
- project: 'ldc-web/shared-ci' - project: 'ldc-web/shared-ci'
ref: 'main' ref: 'main'
file: 'test/test.yml' file: 'test/test.yml'

View file

@ -1,5 +1,43 @@
FROM registry.s-web.fr/ldc-web/projets/ldc-web-images-docker/ldcweb-production-8.3:170625-101047 FROM composer:2.9.2 AS vendor
COPY --chown=www-data:www-data . /app/srcs
ARG GITLAB_USER
ARG GITLAB_PASSWORD
COPY database/ database/
COPY composer.json composer.json
COPY composer.lock composer.lock
RUN composer config gitlab-token.lab.s-web.fr ${GITLAB_USER} ${GITLAB_PASSWORD}
RUN composer install \
--ignore-platform-reqs \
--no-interaction \
--no-plugins \
--no-scripts \
--no-dev \
--prefer-dist
FROM node:20.18 AS frontend
ARG GITLAB_PASSWORD
RUN mkdir -p /app/public/build
COPY package*.json tsconfig.json vite.config.ts .eslintrc.json .stylelintrc.json /app/
COPY resources/ /app/resources/
WORKDIR /app
RUN npm config set @uwil:registry https://lab.s-web.fr/api/v4/groups/ldc-web/-/packages/npm/
RUN npm config set -- '//lab.s-web.fr/api/v4/groups/ldc-web/-/packages/npm/:_authToken' "${GITLAB_PASSWORD}"
RUN npm ci --no-audit --no-fund --loglevel=error && npm run prod
FROM registry.s-web.fr/ldc-web/projets/ldc-web-images-docker/uwil-production-8.5:${PHP_8_5_TAG}
ARG USER=www-data
COPY --chown=${USER}:${USER} . /app/srcs
COPY --from=vendor --chown=${USER}:${USER} /app/vendor/ /app/srcs/vendor/
COPY --from=frontend --chown=${USER}:${USER} /app/public/build/ /app/srcs/public/build/
USER root USER root
RUN chmod -R 755 /app/srcs/storage RUN chmod -R 755 /app/srcs/storage
USER www-data USER ${USER}

4633
_ide_helper.php Normal file

File diff suppressed because it is too large Load diff

View file

@ -2,16 +2,17 @@
namespace App\Console\Commands; namespace App\Console\Commands;
use Carbon\Carbon; use App\Enums\Tickets\TicketStatusEnum;
use App\Models\Ticket;
use App\Models\GitlabIssues; use App\Models\GitlabIssues;
use App\Models\GitlabLabels; use App\Models\GitlabLabels;
use App\Models\GitlabProjects;
use Illuminate\Console\Command;
use App\Models\GitlabMilestones; use App\Models\GitlabMilestones;
use Illuminate\Support\Facades\Cache; use App\Models\GitlabProjects;
use App\Enums\Tickets\TicketStatusEnum; use App\Models\Ticket;
use Carbon\Carbon;
use GrahamCampbell\GitLab\Facades\GitLab; use GrahamCampbell\GitLab\Facades\GitLab;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Log;
class UpdateGitlabDatas extends Command class UpdateGitlabDatas extends Command
{ {
@ -226,10 +227,36 @@ class UpdateGitlabDatas extends Command
$page = 1; $page = 1;
do { do {
$milestones = GitLab::milestones()->all($projectId, [ $milestones = [];
'per_page' => 100,
'page' => $page try {
]); $milestones = GitLab::milestones()->all($projectId, [
'per_page' => 100,
'page' => $page
]);
} catch (\Exception $e) {
if ($e->getCode() === 404) {
if ($project->delete()) {
Log::warning(sprintf(
"Project not found (%s), it has been deleted.",
$projectId
));
} else {
Log::warning(sprintf(
"Project not found (%s).",
$projectId
));
}
} else {
Log::error(sprintf(
'API Error for project %s, Message: %d',
$projectId,
$e->getMessage()
));
report($e);
}
}
$allMilestones = array_merge($allMilestones, $milestones); $allMilestones = array_merge($allMilestones, $milestones);
$page++; $page++;
} while (!empty($milestones)); } while (!empty($milestones));

View file

@ -25,7 +25,7 @@ class Kernel extends ConsoleKernel
*/ */
protected function schedule(Schedule $schedule) protected function schedule(Schedule $schedule)
{ {
$schedule->job(ExportTicketsState::class)->weekly()->mondays()->at('07:00'); $schedule->job(ExportTicketsState::class)->weekly()->fridays()->at('14:00');
$schedule->job(AutoCloseOldTickets::class)->dailyAt('08:00'); $schedule->job(AutoCloseOldTickets::class)->dailyAt('08:00');
$schedule->job(WarnBeforeAutoCloseTicket::class)->dailyAt('08:05'); $schedule->job(WarnBeforeAutoCloseTicket::class)->dailyAt('08:05');
$schedule->command('app:update-gitlab-datas --is-cron')->hourly()->between('7:00', '19:00'); $schedule->command('app:update-gitlab-datas --is-cron')->hourly()->between('7:00', '19:00');

View file

@ -0,0 +1,13 @@
<?php
declare(strict_types=1);
namespace App\Dto;
final readonly class BrevoWebHookDto
{
public function __construct(
public readonly string $plainTextContent,
public readonly ?string $brand
) {}
}

View file

@ -0,0 +1,30 @@
<?php
namespace App\Events;
use Domain\Models\ValueObjects\Email;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Foundation\Events\Dispatchable;
readonly class BrevoWebhookCallReceived
{
use Dispatchable;
public function __construct(
public Email $from,
public Email $to,
public string $htmlRawContent
) {}
/**
* Get the channels the event should broadcast on.
*
* @return array<int, \Illuminate\Broadcasting\Channel>
*/
public function broadcastOn(): array
{
return [
new PrivateChannel('default'),
];
}
}

View file

@ -0,0 +1,34 @@
<?php
namespace App\Events;
use App\Models\Site;
use App\Models\Ticket;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class TicketCreated implements ShouldQueue
{
use Dispatchable;
use SerializesModels;
public function __construct(
public readonly Site $siteReceivingTicket,
public readonly Ticket $ticket,
public readonly string $byUserName
) {}
/**
* Get the channels the event should broadcast on.
*
* @return array<int, \Illuminate\Broadcasting\Channel>
*/
public function broadcastOn(): array
{
return [
new PrivateChannel('default'),
];
}
}

View file

@ -1,77 +0,0 @@
<?php
namespace App\Http\Controllers\Bo;
use Carbon\Carbon;
use App\Models\Site;
use App\Models\Ticket;
use App\Mail\NewTicket;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Mail;
use App\Enums\Tickets\TicketTypeEnum;
use App\Enums\Tickets\TicketStatusEnum;
use App\Http\Requests\Bo\BrevoWehbookRequest;
class BrevoController extends Controller
{
public function createTicket(BrevoWehbookRequest $request)
{
$data = [];
$site = Site::where('name', 'Mailing en masse')->firstOrFail();
$formContent = $request->items[0]["ExtractedMarkdownMessage"];
$tblRegexPatterns = [
'user' => '/Matricule et Nom du demandeur\s*\|\s*([^|]+?)\s*\|/i',
'email' => '/Email du demandeur\s*\|\s*([^|]+?)\s*\|/i',
'brand' => '/Marque\s*\/\s*Filiale\s*\|\s*([^|]+?)\s*\|/i',
'subject' => '/Pour quels besoins avez-vous besoin d\'envoyer des emails en masse\s*\?\s*\|\s*([^|]+?)\s*\|/is',
'date_mep' => '/Date de première envoi souhaité\s*\|\s*([^|]+?)\s*\|/i',
'comment' => '/Commentaire\s*\|\s*([^|]+?)(?=\s*\|)/i',
];
foreach ($tblRegexPatterns as $field => $regex) {
if (preg_match($regex, $formContent, $matches)) {
$data[$field] = trim($matches[1]);
} else {
return response()->json(['reason' => "Champ manquant $field"], 422);
}
}
if ($data['date_mep']) {
$dateMep = Carbon::createFromFormat('d/m/Y', $data['date_mep'])->format('Y-m-d');
}
$ticket = new Ticket([
'site_id' => $site->id,
'type' => TicketTypeEnum::mailing->value,
'status' => TicketStatusEnum::new->value,
'date_mep' => $dateMep ?? null,
'text' => "
Bonjour,\n
Nom du demandeur : {$data['user']},
Adresse mail : {$data['email']},
Marque / Filiale concernée : {$data['brand']},
Sujet : {$data['subject']},
Date d'envoi souhaitée : {$data['date_mep']},
Commentaire : {$data['comment']},\n
Cordialement.
",
'title' => "Demande de mail en masse pour {$data['brand']}",
'is_internal' => false
]);
$ticket->user()->associate($site->projectManager);
$ticket->site()->associate($site);
$ticket->assignedUser()->associate($site->projectManager);
$ticket->saveOrFail();
if (config('mail.MAIL_ADMIN')) {
Mail::to(config('mail.MAIL_ADMIN'))->send(new NewTicket($ticket));
} else {
throw new \ErrorException('Missing MAIL_ADMIN in .env');
}
return response()->json(['ok' => true]);
}
}

View file

@ -0,0 +1,58 @@
<?php
namespace App\Http\Controllers\Bo;
use App\Events\BrevoWebhookCallReceived;
use App\Http\Requests\Bo\BrevoWebhookRequest;
use Domain\Models\ValueObjects\Email;
use Psr\Log\LoggerInterface;
use Illuminate\Routing\Controller as BaseController;
use \Illuminate\Contracts\Events\Dispatcher;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Response;
class BrevoWebhookController extends BaseController
{
public function __construct(
private readonly LoggerInterface $logger,
private readonly Dispatcher $dispatcher
) {}
public function webhookCallReceived(BrevoWebhookRequest $request): JsonResponse
{
$fromEmail = $request->getFromAddress();
$toEmail = $request->getToAddress();
$formContent = $request->getRawHtmlBody();
if (!$this->isFromLdc($fromEmail)) {
$this->logger->warning(
'Received a brevo webhook call from a non ldc email.',
[
'from' => $fromEmail,
'to' => $toEmail,
'content' => base64_encode($formContent)
]
);
return response()->json(
['ok' => false],
Response::HTTP_BAD_REQUEST
);
}
$this->dispatcher->dispatch(
new BrevoWebhookCallReceived(
from: $fromEmail,
to: $toEmail,
htmlRawContent: $formContent,
)
);
return response()->json(['ok' => true]);
}
private function isFromLdc(Email $email): bool
{
return substr(strrchr($email->value, "@"), 1) === 'ldc.fr';
}
}

View file

@ -24,6 +24,7 @@ use App\Http\Requests\Bo\StoreTicket;
use App\Http\Requests\Bo\UpdateTicket; use App\Http\Requests\Bo\UpdateTicket;
use App\Lib\Helpers\FileStorageHelper; use App\Lib\Helpers\FileStorageHelper;
use App\Enums\Tickets\TicketStatusEnum; use App\Enums\Tickets\TicketStatusEnum;
use App\Events\TicketCreated;
use App\Http\Requests\Bo\CreateMessage; use App\Http\Requests\Bo\CreateMessage;
use App\Http\Requests\Bo\UpdateMessage; use App\Http\Requests\Bo\UpdateMessage;
use Illuminate\Support\Facades\Session; use Illuminate\Support\Facades\Session;
@ -33,14 +34,16 @@ use App\Http\Requests\Bo\ChangeTicketStatus;
use App\Http\Requests\Bo\UpdateChangeOrderRequest; use App\Http\Requests\Bo\UpdateChangeOrderRequest;
use App\Http\Requests\Bo\AssignTicketToUserRequest; use App\Http\Requests\Bo\AssignTicketToUserRequest;
use App\Http\Requests\Bo\UpdateTicketPriorityRequest; use App\Http\Requests\Bo\UpdateTicketPriorityRequest;
use Illuminate\Contracts\Events\Dispatcher;
class TicketController extends Controller class TicketController extends Controller
{ {
/** /**
* TicketController * TicketController
*/ */
public function __construct() public function __construct(
{ public readonly Dispatcher $dispatcher
) {
parent::__construct(); parent::__construct();
$this->authorizeResource(Ticket::class, 'ticket'); $this->authorizeResource(Ticket::class, 'ticket');
} }
@ -213,7 +216,7 @@ class TicketController extends Controller
} }
} }
$ticket->user()->associate(auth()->user()); $ticket->user()->associate($user);
$ticket->site()->associate($site); $ticket->site()->associate($site);
$ticket->saveOrFail(); $ticket->saveOrFail();
@ -223,11 +226,11 @@ class TicketController extends Controller
$user->subscriptions()->toggle($ticket->id); $user->subscriptions()->toggle($ticket->id);
if (config('mail.MAIL_ADMIN')) { $this->dispatcher->dispatch(new TicketCreated(
Mail::to(config('mail.MAIL_ADMIN'))->send(new NewTicket($ticket)); siteReceivingTicket: $site,
} else { ticket: $ticket,
throw new \ErrorException('Missing MAIL_ADMIN in .env'); byUserName: $user->name
} ));
return redirect()->route('bo.ticket.show', $ticket) return redirect()->route('bo.ticket.show', $ticket)
->with('success', trans('Ticket enregistré !')); ->with('success', trans('Ticket enregistré !'));
@ -751,16 +754,23 @@ class TicketController extends Controller
{ {
$userGitlabId = auth()->user()->gitlab_id; $userGitlabId = auth()->user()->gitlab_id;
$gitlabRepoId = $ticket->site->gitlab_project_id; $gitlabRepoId = $ticket->site->gitlab_project_id;
$description = \sprintf("%s<br>", $ticket->text);
$cLabel = match ($ticket->type) { if (GitLab::projects()->show($gitlabRepoId)['archived']) {
TicketTypeEnum::bug => 'c-bug,to-dispatch', return back()->with('error', \trans("Ce projet GitLab est archivé et ne peut pas être utilisé."));
TicketTypeEnum::evolution => 'c-feature', }
$description = \sprintf("%s<br>", $ticket->text);
$cLabel = match ($ticket->type) {
TicketTypeEnum::bug => 'c-bug,g-to-dispatch',
TicketTypeEnum::evolution,
TicketTypeEnum::mailing => 'c-feature',
TicketTypeEnum::newsletter => 'c-feature,g-to-estimate', TicketTypeEnum::newsletter => 'c-feature,g-to-estimate',
}; };
$assigneeId = match ($ticket->type) { $assigneeId = match ($ticket->type) {
TicketTypeEnum::bug => $userGitlabId, TicketTypeEnum::bug => $userGitlabId,
TicketTypeEnum::evolution, TicketTypeEnum::evolution,
TicketTypeEnum::mailing,
TicketTypeEnum::newsletter => $ticket->site->projectManager?->gitlab_id TicketTypeEnum::newsletter => $ticket->site->projectManager?->gitlab_id
}; };

View file

@ -73,8 +73,9 @@ class UserController extends Controller
public function store(StoreUserRequest $request): \Illuminate\Http\RedirectResponse public function store(StoreUserRequest $request): \Illuminate\Http\RedirectResponse
{ {
return DB::transaction(function () use ($request) { return DB::transaction(function () use ($request) {
$user = new User(); $user = User::query()->onlyTrashed()->where('email', $request->validated('email'))->first() ?? new User();
$user->fill($request->safe()->except('sites')); $user->fill($request->safe()->except('sites'));
$user->deleted_at = null;
if ($user->saveOrFail()) { if ($user->saveOrFail()) {
$user->sites()->attach(collect($request->validated('sites'))->pluck('id')); $user->sites()->attach(collect($request->validated('sites'))->pluck('id'));

View file

@ -28,7 +28,7 @@ class RedirectIfAuthenticated
switch ($guard) { switch ($guard) {
case 'backend': case 'backend':
if (!$request->routeIs('bo.*') && !$request->routeIs('login')) { if (!$request->routeIs('bo.*') && !$request->routeIs('login')) {
continue; continue 2;
} }
return \redirect()->route('bo.dashboard'); return \redirect()->route('bo.dashboard');
default: default:

View file

@ -0,0 +1,50 @@
<?php
namespace App\Http\Requests\Bo;
use Domain\Models\ValueObjects\Email;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Config;
class BrevoWebhookRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return true;
}
public function getFromAddress(): Email
{
return new Email($this->string('items.0.From.Address'));
}
public function getToAddress(): Email
{
return new Email((string) $this->input('items.0.To.0.Address'));
}
public function getRawHtmlBody(): string
{
return $this->string('items.0.RawHtmlBody');
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
*/
public function rules(): array
{
$expectedFormAddress = Config::string('mail.brevo_webook.expected_from_address');
return [
'items' => ['required', 'array', 'min:1'],
'items.0.From.Address' => ['required', 'email:rfc,spoof'],
'items.0.To.*.Address' => ['required', 'email:rfc,spoof', "in:{$expectedFormAddress}"],
'items.0.RawHtmlBody' => ['required', 'string', 'min:10'],
];
}
}

View file

@ -1,31 +0,0 @@
<?php
namespace App\Http\Requests\Bo;
use Illuminate\Foundation\Http\FormRequest;
class BrevoWehbookRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
*/
public function rules(): array
{
return [
'items' => 'required|array|min:1',
'items.0.From.Address' => 'required|email',
'items.0.To.*.Address' => 'required|email|in:mailing@reply.uwil.fr',
'items.0.ExtractedMarkdownMessage' => 'required|string|min:10',
];
}
}

View file

@ -40,7 +40,11 @@ class StoreUserRequest extends FormRequest
{ {
return [ return [
'name' => 'required|string|min:3|max:255', 'name' => 'required|string|min:3|max:255',
'email' => 'email|required|unique:users,email', 'email' => [
'email',
'required',
Rule::unique('users', 'email')->whereNull('deleted_at')
],
'role' => [ 'role' => [
'required', 'required',
Rule::in(collect(RoleEnum::cases())->pluck('value')->all()), Rule::in(collect(RoleEnum::cases())->pluck('value')->all()),

View file

@ -0,0 +1,75 @@
<?php
namespace App\Listeners;
use App\Events\BrevoWebhookCallReceived;
use App\Events\TicketCreated;
use App\Mail\NewTicket;
use App\Repositories\SiteRepository;
use App\Services\AutomaticTicketCreator;
use Domain\Models\ValueObjects\Email;
use Illuminate\Contracts\Events\Dispatcher;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\Mail;
use Psr\Log\LoggerInterface;
// NOTE: this should be queued in order to be eventually retried or at least keep info data.
class CreateTicketAutomatically implements ShouldQueue
{
private const string SITE_NAME_WHERE_TO_CREATE_TICKET = 'Mailing en masse';
public function __construct(
private readonly LoggerInterface $logger,
private readonly AutomaticTicketCreator $automaticTicketCreator,
private readonly SiteRepository $siteRepository,
private readonly Dispatcher $dispatcher
) {}
public function handle(BrevoWebhookCallReceived $event): void
{
$fromEmail = $event->from;
$toEmail = $event->to;
$htmlRawContent = $event->htmlRawContent;
$this->logger->info('Received a ticket creation event from brevo');
$this->logger->debug(
'Payload for ticket creation form brevo',
[
'from_email' => $fromEmail->value,
'to_email' => $toEmail->value,
'html_raw_content' => base64_encode($htmlRawContent)
]
);
$siteReceivingTickets = $this->siteRepository->findByName(self::SITE_NAME_WHERE_TO_CREATE_TICKET);
if ($siteReceivingTickets === null) {
throw new \UnexpectedValueException(
'No site found with name \'' .
self::SITE_NAME_WHERE_TO_CREATE_TICKET . '\''
);
}
$projectManager = $siteReceivingTickets->projectManager()->first();
if ($projectManager === null) {
throw new \UnexpectedValueException(
'No project manager found for site \'' .
self::SITE_NAME_WHERE_TO_CREATE_TICKET . '\''
);
}
$ticket = $this->automaticTicketCreator->createTicket(
$siteReceivingTickets,
$projectManager,
$htmlRawContent,
);
$this->dispatcher->dispatch(new TicketCreated(
siteReceivingTicket: $siteReceivingTickets,
ticket: $ticket,
byUserName: $fromEmail->value
));
}
}

View file

@ -3,21 +3,51 @@
namespace App\Listeners; namespace App\Listeners;
use App\Events\NotifyAdmin; use App\Events\NotifyAdmin;
use App\Events\TicketCreated;
use App\Mail\NewTicket;
use App\Mail\TicketUpdate; use App\Mail\TicketUpdate;
use Domain\Models\ValueObjects\Email;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\Mail; use Illuminate\Support\Facades\Mail;
class SendNotificationMailToAdmin class SendNotificationMailToAdmin
{ {
private readonly Email $mailAdmin;
public function __construct()
{
$this->mailAdmin = new Email(
Config::string('mail.MAIL_ADMIN')
);
}
/** /**
* Handle the event. * Handle the event.
* *
* @param \App\Events\NotifyAdmin $event * @param \App\Events\NotifyAdmin $event
* @return void * @return void
*/ */
public function handle(NotifyAdmin $event) public function handle(NotifyAdmin | TicketCreated $event)
{ {
$ticket = $event->ticket; if ($event instanceof NotifyAdmin) {
Mail::to(config('mail.MAIL_ADMIN')) $ticket = $event->ticket;
->send(new TicketUpdate($ticket, $event->corpsMaj)); Mail::to($this->mailAdmin->value)
->send(new TicketUpdate($ticket, $event->corpsMaj));
}
if ($event instanceof TicketCreated) {
$ticket = $event->ticket;
$siteReceivingTickets = $event->siteReceivingTicket;
$userName = $event->byUserName;
Mail::to($this->mailAdmin->value)
->send(new NewTicket(
ticketId: $ticket->id,
siteName: $siteReceivingTickets->name,
ticketTitle: $ticket->title,
ticketType: $ticket->type,
userName: $userName,
ticketDescription: $ticket->text,
));
}
} }
} }

View file

@ -2,29 +2,23 @@
namespace App\Mail; namespace App\Mail;
use App\Models\Ticket; use App\Enums\Tickets\TicketTypeEnum;
use Illuminate\Bus\Queueable; use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Mail\Mailable; use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;
class NewTicket extends Mailable class NewTicket extends Mailable implements ShouldQueue
{ {
use Queueable; use Queueable;
use SerializesModels;
/** @var \App\Models\Ticket */ public function __construct(
private $ticket; private readonly int $ticketId,
private readonly string $siteName,
/** private readonly string $ticketTitle,
* Create a new message instance. private readonly TicketTypeEnum $ticketType,
* private readonly string $userName,
* @param \App\Models\Ticket $ticket private readonly string $ticketDescription,
* @return void ) {}
*/
public function __construct(Ticket $ticket)
{
$this->ticket = $ticket;
}
/** /**
* Build the message. * Build the message.
@ -33,8 +27,14 @@ class NewTicket extends Mailable
*/ */
public function build() public function build()
{ {
$ticket = $this->ticket; return $this->subject("[Support] Nouveau ticket n° {$this->ticketId} - {$this->ticketTitle}")
return $this->subject("[Support] Nouveau ticket n° $ticket->id - $ticket->title") ->view('emails.new-ticket', [
->view('emails.new-ticket', compact('ticket')); 'ticketId' => $this->ticketId,
'siteName' => $this->siteName,
'ticketTitle' => $this->ticketTitle,
'ticketType' => $this->ticketType,
'userName' => $this->userName,
'ticketDescription' => $this->ticketDescription,
]);
} }
} }

View file

@ -7,7 +7,13 @@ use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
/** /**
* @property string $name * @property int $id Doit correspondre aux ids marques de la CRM
* @property string $name Nom de la marque
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @property-read \Illuminate\Database\Eloquent\Collection<int, Account> $accounts
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Site> $sites
* @mixin \Eloquent
*/ */
class Brand extends Model class Brand extends Model
{ {

View file

@ -7,7 +7,14 @@ use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Casts\Attribute;
/** /**
* @property int $id
* @property string $name * @property string $name
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Invoice> $invoices
* @property-read mixed $value
* @method static \Database\Factories\CompanyFactory factory($count = null, $state = [])
* @mixin \Eloquent
*/ */
class Company extends Model class Company extends Model
{ {

View file

@ -7,10 +7,16 @@ use App\Models\Brand;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
/** /**
* @property string $label * @property int $id
* @property AccountTypeEnum $type * @property AccountTypeEnum $type Enum Credit\AccountTypeEnum
* @property integer $balance * @property string $label Custom account label
* @property Brand $brand_id * @property float $balance Account balance
* @property int $brand_id
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @property-read Brand $brand
* @property-read \App\Models\Credit\CreditLog|null $creditlogs
* @mixin \Eloquent
*/ */
class Account extends Model class Account extends Model
{ {

View file

@ -6,10 +6,15 @@ use App\Enums\Credit\BalanceOperationEnum;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
/** /**
* @property BalanceOperationEnum $type * @property int $id
* @property double $amount * @property BalanceOperationEnum $type Enum Credit\BalanceOperationEnum
* @property Account $credit_account_id * @property float $amount Montant de l'opération
* @property date $created_at * @property int $credit_account_id
* @property string $created_at
* @property-read \App\Models\Credit\Account $account
* @property-read \App\Models\Credit\EstimationLog|null $pivot
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Credit\Estimation> $estimation
* @mixin \Eloquent
*/ */
class CreditLog extends Model class CreditLog extends Model
{ {

View file

@ -9,6 +9,22 @@ use App\Models\User;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Validation\ValidationException; use Illuminate\Validation\ValidationException;
/**
* @property int $id
* @property EstimationStatusEnum $status Enum Credit\EstimationStatusEnum
* @property numeric $amount Estimation amount
* @property int $ticket_id
* @property int $credit_account_id The account to debit.
* @property int $estimator_id The person who has done the estimate.
* @property int|null $validator_id The person who has validate the estimate.
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @property-read \App\Models\Credit\Account $account
* @property-read User|null $estimator
* @property-read Ticket|null $ticket
* @property-read User|null $validator
* @mixin \Eloquent
*/
class Estimation extends Model class Estimation extends Model
{ {
/** /**

View file

@ -4,6 +4,14 @@ namespace App\Models\Credit;
use Illuminate\Database\Eloquent\Relations\Pivot; use Illuminate\Database\Eloquent\Relations\Pivot;
/**
* @property int $id
* @property int $credit_log_id
* @property int $credit_estimation_id
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @mixin \Eloquent
*/
class EstimationLog extends Pivot class EstimationLog extends Pivot
{ {
/** /**

View file

@ -4,6 +4,26 @@ namespace App\Models;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
/**
* @property int $id
* @property string $title Gitlab issue title
* @property string $url Gitlab URL
* @property int $estimate_time Gitlab issue estimate time in seconds
* @property int $spent_time Gitlab issue spent time in seconds
* @property int $is_closed Gitlab issue status
* @property int $gitlab_id Gitlab issue id
* @property int $gitlab_iid Gitlab issue iid in project
* @property int|null $milestone_iid Gitlab issue linked milestone iid in project
* @property int $gitlab_project_id Gitlab project id
* @property string $g_created_at Gitlab issue created at
* @property string $g_updated_at Gitlab issue updated at
* @property string|null $g_closed_at Gitlab issue closed at
* @property string|null $g_due_date Gitlab issue due date
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\GitlabLabels> $gitlabLabels
* @mixin \Eloquent
*/
class GitlabIssues extends Model class GitlabIssues extends Model
{ {
/** /**

View file

@ -4,6 +4,15 @@ namespace App\Models;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
/**
* @property int $id
* @property string $name Label name
* @property int $gitlab_id Label Gitlab id
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\GitlabIssues> $issues
* @mixin \Eloquent
*/
class GitlabLabels extends Model class GitlabLabels extends Model
{ {
/** /**

View file

@ -4,6 +4,22 @@ namespace App\Models;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
/**
* @property int $id
* @property string $title Milestone title
* @property string $url Gitlab URL
* @property int $is_closed Gitlab milestone status
* @property int $gitlab_id Gitlab milestone id
* @property int $gitlab_iid Gitlab milestone iid in project
* @property int $gitlab_project_id Gitlab project id
* @property string $g_created_at Gitlab milestone created at
* @property string $g_updated_at Gitlab milestone updated at
* @property string|null $g_start_date Gitlab milestone start date
* @property string|null $g_due_date Gitlab milestone due date
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @mixin \Eloquent
*/
class GitlabMilestones extends Model class GitlabMilestones extends Model
{ {
/** /**

View file

@ -4,6 +4,16 @@ namespace App\Models;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
/**
* @property int $id
* @property string $name Gitlab project name
* @property string $url Gitlab URL
* @property int $is_archived Gitlab archive status
* @property int $gitlab_id Gitlab unique ID
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @mixin \Eloquent
*/
class GitlabProjects extends Model class GitlabProjects extends Model
{ {
/** /**

View file

@ -7,9 +7,13 @@ use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
/** /**
* @property int $id
* @property string $title * @property string $title
* @property string $message * @property string $message
* @property InfoColorsEnum $type * @property InfoColorsEnum $type Defined by the InfoColorsEnum enum.
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @mixin \Eloquent
*/ */
class Information extends Model class Information extends Model
{ {

View file

@ -13,22 +13,34 @@ use Mccarlosen\LaravelMpdf\LaravelMpdf;
use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Database\Eloquent\SoftDeletes;
/** /**
* @property string $ref * @property int $id
* @property string $firstname * @property string $ref The invoice reference built as YYYY_MM_INCREMENT_USER. INCREMENT is the N invoice of the month. USER is the first two letters of the user who last edited the invoice. This field has to generated only on create.
* @property string $lastname * @property string $firstname The person to which the invoice is addressed.
* @property string $object * @property string $lastname The person to which the invoice is addressed.
* @property string $tasks * @property string $object The invoice object.
* @property double $total * @property array<array-key, mixed> $tasks A JSON object, each line has to be contructed using these fields description, days, type Invoices\TaskTypeEnum, amount
* @property double $total_days * @property float $total The total calculated using all tasks
* @property string $comment * @property float $total_days The total days calculated using all tasks
* @property boolean $show_days * @property string|null $comment Any comment joined to the invoice
* @property date $signed_date * @property int $show_days Show days on the printed invoice
* @property date $sent_client_date * @property Carbon|null $signed_date The date the invoice was signed
* @property date $sent_accounting_date * @property Carbon|null $sent_client_date The date the invoice was send to the client
* @property Site $site_id * @property Carbon|null $sent_accounting_date The date the invoice was send to the client
* @property Company $company_id * @property int $site_id
* @property Company $invoiced_to_id * @property int $company_id
* @property InvoiceStatusEnum $status * @property int $invoiced_to_id
* @property InvoiceStatusEnum $status Status of the invoice.
* @property Carbon|null $created_at
* @property Carbon|null $updated_at
* @property Carbon|null $deleted_at
* @property-read \App\Models\Company $company
* @property-read \App\Models\Company $companyInvoiced
* @property-read \App\Models\Site|null $site
* @method static \Database\Factories\InvoiceFactory factory($count = null, $state = [])
* @method static \Illuminate\Database\Eloquent\Builder<static>|Invoice onlyTrashed()
* @method static \Illuminate\Database\Eloquent\Builder<static>|Invoice withTrashed(bool $withTrashed = true)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Invoice withoutTrashed()
* @mixin \Eloquent
*/ */
class Invoice extends Model class Invoice extends Model
{ {

View file

@ -7,6 +7,14 @@ use App\Lib\Helpers\ToolboxHelper;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
/**
* @property int $id
* @property string $name Un ou deux mot désignant le label
* @property string $color Cast HTML color
* @property-read \App\Models\Site|null $site
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Ticket> $tickets
* @mixin \Eloquent
*/
class Label extends Model class Label extends Model
{ {
use HasFactory; use HasFactory;

View file

@ -9,12 +9,20 @@ use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
/** /**
*
* @property string $message The message content.
* @property \App\Enums\MessageTypeEnum $type The message is not visible for clients.
* @property string $file The message joined file path.
*
* @package App\Models * @package App\Models
* @property int $id
* @property int $ticket_id
* @property int|null $user_id
* @property string $message
* @property MessageTypeEnum $type Message Type defined by an enum.
* @property string|null $file
* @property string|null $file2 Pièce jointes n°2
* @property string|null $file3 Pièce jointes n°3
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @property-read \App\Models\Ticket|null $ticket
* @property-read \App\Models\User|null $user
* @mixin \Eloquent
*/ */
class Message extends Model class Message extends Model
{ {

View file

@ -6,8 +6,12 @@ use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
/** /**
* @property int $id
* @property string $name * @property string $name
* @property double $amount * @property float $amount The cost per day
* @property bool $published
* @method static \Database\Factories\PackageFactory factory($count = null, $state = [])
* @mixin \Eloquent
*/ */
class Package extends Model class Package extends Model
{ {

View file

@ -10,11 +10,26 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Database\Eloquent\SoftDeletes;
/** /**
* @property string $name * @property int $id
* @property string $url_re7 * @property string $name
* @property string $url_prod * @property string|null $url_re7
* @property integer $gitlab_project_id * @property string|null $url_prod
* @property Brand $brand_id * @property int|null $gitlab_project_id
* @property int|null $brand_id Relation is nullable, since not all site would be affiliate with a brand.
* @property int|null $project_manager_id Project manager relationship
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @property \Illuminate\Support\Carbon|null $deleted_at
* @property-read \App\Models\Brand|null $brand
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Invoice> $invoices
* @property-read \App\Models\User|null $projectManager
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Ticket> $tickets
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\User> $users
* @property-read mixed $value
* @method static \Illuminate\Database\Eloquent\Builder<static>|Site onlyTrashed()
* @method static \Illuminate\Database\Eloquent\Builder<static>|Site withTrashed(bool $withTrashed = true)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Site withoutTrashed()
* @mixin \Eloquent
*/ */
class Site extends Model class Site extends Model
{ {

View file

@ -4,6 +4,11 @@ namespace App\Models;
use Illuminate\Database\Eloquent\Relations\Pivot; use Illuminate\Database\Eloquent\Relations\Pivot;
/**
* @property int $site_id
* @property int $user_id
* @mixin \Eloquent
*/
class SiteUser extends Pivot class SiteUser extends Pivot
{ {
/** /**

View file

@ -22,6 +22,44 @@ use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\ModelNotFoundException; use Illuminate\Database\Eloquent\ModelNotFoundException;
/**
* @property int $id
* @property int $created_by
* @property int|null $assigned_to La personne a qui est assigné le ticket.
* @property int $site_id
* @property TicketTypeEnum $type
* @property string $title Titre du ticket
* @property string $text
* @property string|null $file
* @property string|null $file2
* @property string|null $file3 Pièce jointes n°3
* @property string|null $file4 Pièce jointes n°4
* @property string|null $file5 Pièce jointes n°5
* @property TicketStatusEnum $status
* @property int $order Ticket order.
* @property bool $is_internal
* @property string|null $gitlab_issue URL de l'issue Gitlab
* @property \Illuminate\Support\Carbon|null $date_mep Date MEP souhaitée
* @property int $last_activity Last activity on the ticket.
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @property \Illuminate\Support\Carbon|null $deleted_at
* @property-read \App\Models\User|null $assignedUser
* @property-read Estimation|null $estimation
* @property-read string $full_name
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Label> $labels
* @property-read \Illuminate\Database\Eloquent\Collection<int, Ticket> $linkedTickets
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Message> $messages
* @property-read \App\Models\User|null $projectManager
* @property-read \App\Models\Site|null $site
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\User> $subscriptions
* @property-read TicketType|null $ticketType
* @property-read \App\Models\User|null $user
* @method static \Illuminate\Database\Eloquent\Builder<static>|Ticket onlyTrashed()
* @method static \Illuminate\Database\Eloquent\Builder<static>|Ticket withTrashed(bool $withTrashed = true)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Ticket withoutTrashed()
* @mixin \Eloquent
*/
class Ticket extends Model class Ticket extends Model
{ {
use HasFactory; use HasFactory;

View file

@ -9,8 +9,13 @@ use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
/** /**
* @property TicketTypeEnum $type * @property int $id
* @property string $template * @property TicketTypeEnum $type TicketType Enum
* @property string $template Le template du type de ticket
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @property-read \Illuminate\Database\Eloquent\Collection<int, Ticket> $tickets
* @mixin \Eloquent
*/ */
class TicketType extends Model class TicketType extends Model
{ {

View file

@ -15,12 +15,27 @@ use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Str; use Illuminate\Support\Str;
/** /**
* @property int $id
* @property string $name * @property string $name
* @property string $email * @property string $email
* @property string $password * @property string $password
* @property string $remember_token
* @property RoleEnum $role * @property RoleEnum $role
* @property Collection $sites * @property string|null $remember_token
* @property int|null $gitlab_id ID du user Gitlab
* @property int|null $favourite_site_id
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @property \Illuminate\Support\Carbon|null $deleted_at
* @property-read Collection<int, \App\Models\Message> $messages
* @property-read \Illuminate\Notifications\DatabaseNotificationCollection<int, \Illuminate\Notifications\DatabaseNotification> $notifications
* @property-read Collection<int, \App\Models\Site> $sites
* @property-read Collection<int, \App\Models\Ticket> $subscriptions
* @property-read Collection<int, \App\Models\Ticket> $tickets
* @method static \Database\Factories\UserFactory factory($count = null, $state = [])
* @method static \Illuminate\Database\Eloquent\Builder<static>|User onlyTrashed()
* @method static \Illuminate\Database\Eloquent\Builder<static>|User withTrashed(bool $withTrashed = true)
* @method static \Illuminate\Database\Eloquent\Builder<static>|User withoutTrashed()
* @mixin \Eloquent
*/ */
class User extends Authenticatable class User extends Authenticatable
{ {

View file

@ -287,7 +287,8 @@ class TicketPolicy
$ticket->type->equals(TicketTypeEnum::bug) $ticket->type->equals(TicketTypeEnum::bug)
|| $ticket->type->equals(TicketTypeEnum::evolution) || $ticket->type->equals(TicketTypeEnum::evolution)
|| $ticket->type->equals(TicketTypeEnum::newsletter) || $ticket->type->equals(TicketTypeEnum::newsletter)
) || $ticket->type->equals(TicketTypeEnum::mailing)
)
&& !\is_null($ticket->site->gitlab_project_id) && !\is_null($ticket->site->gitlab_project_id)
&& \is_null($ticket->gitlab_issue); && \is_null($ticket->gitlab_issue);
} }

View file

@ -2,12 +2,24 @@
namespace App\Providers; namespace App\Providers;
use App\Repositories\EloquentSiteRepository;
use App\Repositories\SiteRepository;
use Illuminate\Support\Facades\App; use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\URL; use Illuminate\Support\Facades\URL;
use Illuminate\Support\ServiceProvider; use Illuminate\Support\ServiceProvider;
use Override;
class AppServiceProvider extends ServiceProvider class AppServiceProvider extends ServiceProvider
{ {
#[Override]
public function register()
{
$this->app->singleton(
SiteRepository::class,
fn() => new EloquentSiteRepository()
);
}
/** /**
* Bootstrap any application services. * Bootstrap any application services.
* *
@ -24,7 +36,7 @@ class AppServiceProvider extends ServiceProvider
return request()->getClientIp(); return request()->getClientIp();
} else { } else {
return request()->server('HTTP_X_FORWARDED_FOR') ?? return request()->server('HTTP_X_FORWARDED_FOR') ??
request()->getClientIp(); request()->getClientIp();
} }
}); });

View file

@ -2,14 +2,6 @@
namespace App\Providers; namespace App\Providers;
use App\Events\NotifyAdmin;
use App\Events\NotifySubscriptions;
use App\Events\NotifyTicketCreator;
use App\Events\TicketStatusChanged;
use App\Listeners\AddStatusMessages;
use App\Listeners\SendNotificationMailToAdmin;
use App\Listeners\SendNotificationMailToTicketCreator;
use App\Listeners\SendSubscriptionsNotificationsMail;
use Illuminate\Auth\Events\Registered; use Illuminate\Auth\Events\Registered;
use Illuminate\Auth\Listeners\SendEmailVerificationNotification; use Illuminate\Auth\Listeners\SendEmailVerificationNotification;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider; use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
@ -23,10 +15,6 @@ class EventServiceProvider extends ServiceProvider
*/ */
protected $listen = [ protected $listen = [
Registered::class => [SendEmailVerificationNotification::class], Registered::class => [SendEmailVerificationNotification::class],
TicketStatusChanged::class => [AddStatusMessages::class],
NotifySubscriptions::class => [SendSubscriptionsNotificationsMail::class],
NotifyTicketCreator::class => [SendNotificationMailToTicketCreator::class],
NotifyAdmin::class => [SendNotificationMailToAdmin::class],
]; ];
/** /**
@ -36,6 +24,6 @@ class EventServiceProvider extends ServiceProvider
*/ */
public function shouldDiscoverEvents(): bool public function shouldDiscoverEvents(): bool
{ {
return false; return true;
} }
} }

View file

@ -0,0 +1,19 @@
<?php
declare(strict_types=1);
namespace App\Repositories;
use App\Models\Site;
use Override;
class EloquentSiteRepository implements SiteRepository
{
#[Override]
public function findByName(string $name): ?Site
{
return Site::query()
->where('name', $name)
->first();
}
}

View file

@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
namespace App\Repositories;
use App\Models\Site;
interface SiteRepository
{
public function findByName(string $name): ?Site;
}

View file

@ -0,0 +1,56 @@
<?php
declare(strict_types=1);
namespace App\Services;
use App\Enums\Tickets\TicketStatusEnum;
use App\Enums\Tickets\TicketTypeEnum;
use App\Models\Site;
use App\Models\Ticket;
use App\Models\User;
class AutomaticTicketCreator
{
public function __construct(
private readonly BrevoWebookParser $parser
) {}
public function createTicket(
Site $forSite,
User $withProjectManager,
string $withRawHtmlContent,
): Ticket {
$parsedContent = $this->parser->parseRawHtml($withRawHtmlContent);
$textContent = "Bonjour,\n";
$textContent .= " Contenu :\n\n";
$textContent .= " {$parsedContent->plainTextContent},\n\n";
$textContent .= " Cordialement.";
$title = "Demande de mail en masse";
if (!empty($parsedContent->brand)) {
$title .= " pour {$parsedContent->brand}";
}
$ticket = new Ticket([
'site_id' => $forSite->id,
'type' => TicketTypeEnum::mailing->value,
'status' => TicketStatusEnum::estimateTodo->value,
'date_mep' => null,
'text' => $textContent,
'title' => $title,
'is_internal' => false
]);
$ticket->site()->associate($forSite);
$ticket->user()->associate($withProjectManager);
$ticket->assignedUser()->associate($withProjectManager);
$ticket->saveOrFail();
return $ticket;
}
}

View file

@ -0,0 +1,35 @@
<?php
declare(strict_types=1);
namespace App\Services;
use App\Dto\BrevoWebHookDto;
use App\Exceptions\BrevoParseFailure;
class BrevoWebookParser
{
/** @throws BrevoParseFailure */
public function parseRawHtml(string $rawHtml): BrevoWebHookDto
{
$data = [];
$plainText = strip_tags($rawHtml);
$tblRegexPatterns = [
'user' => '/^Matricule et Nom du demandeur\s*\r?\n(.*)$/im',
'email' => '/^Email du demandeur\s*\r?\n(.*)$/im',
'brand' => '/^Marque\s*\/\s*Filiale\s*\r?\n(.*)$/im',
];
foreach ($tblRegexPatterns as $field => $regex) {
if (preg_match($regex, $plainText, $matches)) {
$data[$field] = trim($matches[1]);
}
}
return new BrevoWebHookDto(
plainTextContent: $plainText,
brand: $data['brand'] ?? null
);
}
}

View file

@ -17,7 +17,7 @@ trait HasGitlabIssue
*/ */
public function getGitlabIssue(int $projectId, string $issueUrl): array public function getGitlabIssue(int $projectId, string $issueUrl): array
{ {
if (!preg_match('/\/issues\/(\d+)/', $issueUrl, $matches)) { return []; } if (!preg_match('/\/(?:issues|work_items)\/(\d+)/', $issueUrl, $matches)) { return []; }
$gitlabIssue = GitLab::issues()->show($projectId, (int) $matches[1]); $gitlabIssue = GitLab::issues()->show($projectId, (int) $matches[1]);

View file

@ -12,7 +12,7 @@
], ],
"license": "MIT", "license": "MIT",
"require": { "require": {
"php": "^8.3", "php": "^8.5",
"carlos-meneses/laravel-mpdf": "^2.1", "carlos-meneses/laravel-mpdf": "^2.1",
"codegreencreative/laravel-samlidp": "^5.2", "codegreencreative/laravel-samlidp": "^5.2",
"diglactic/laravel-breadcrumbs": "^9.0", "diglactic/laravel-breadcrumbs": "^9.0",
@ -28,6 +28,7 @@
"uwil/k8s-laravel": "^1.2" "uwil/k8s-laravel": "^1.2"
}, },
"require-dev": { "require-dev": {
"barryvdh/laravel-ide-helper": "^3.7",
"fakerphp/faker": "^1.9.1", "fakerphp/faker": "^1.9.1",
"laravel-lang/common": "^4.0", "laravel-lang/common": "^4.0",
"laravel/pint": "^1.0", "laravel/pint": "^1.0",
@ -43,7 +44,8 @@
"psr-4": { "psr-4": {
"App\\": "app/", "App\\": "app/",
"Database\\Factories\\": "database/factories/", "Database\\Factories\\": "database/factories/",
"Database\\Seeders\\": "database/seeders/" "Database\\Seeders\\": "database/seeders/",
"Domain\\": "domain/"
} }
}, },
"repositories": [ "repositories": [
@ -67,7 +69,11 @@
"@php artisan package:discover --ansi" "@php artisan package:discover --ansi"
], ],
"post-update-cmd": [ "post-update-cmd": [
"@php artisan vendor:publish --tag=laravel-assets --ansi --force" "@php artisan vendor:publish --tag=laravel-assets --ansi --force",
"Illuminate\\Foundation\\ComposerScripts::postUpdate",
"@php artisan lang:update",
"@php artisan ide-helper:generate",
"@php artisan ide-helper:models -RW"
], ],
"post-root-package-install": [ "post-root-package-install": [
"@php -r \"file_exists('.env') || copy('.env.example', '.env');\"" "@php -r \"file_exists('.env') || copy('.env.example', '.env');\""

2951
composer.lock generated

File diff suppressed because it is too large Load diff

379
config/ide-helper.php Normal file
View file

@ -0,0 +1,379 @@
<?php
declare(strict_types=1);
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\Client\Factory;
use Illuminate\Http\Client\PendingRequest;
use Illuminate\Session\Store;
return [
/*
|--------------------------------------------------------------------------
| Filename
|--------------------------------------------------------------------------
|
| The default filename.
|
*/
'filename' => '_ide_helper.php',
/*
|--------------------------------------------------------------------------
| Models filename
|--------------------------------------------------------------------------
|
| The default filename for the models helper file.
|
*/
'models_filename' => '_ide_helper_models.php',
/*
|--------------------------------------------------------------------------
| PhpStorm meta filename
|--------------------------------------------------------------------------
|
| PhpStorm also supports the directory `.phpstorm.meta.php/` with arbitrary
| files in it, should you need additional files for your project; e.g.
| `.phpstorm.meta.php/laravel_ide_Helper.php'.
|
*/
'meta_filename' => '.phpstorm.meta.php',
/*
|--------------------------------------------------------------------------
| Fluent helpers
|--------------------------------------------------------------------------
|
| Set to true to generate commonly used Fluent methods.
|
*/
'include_fluent' => false,
/*
|--------------------------------------------------------------------------
| Write model query methods
|--------------------------------------------------------------------------
|
| Set to false to disable generated docs for the 'query()', 'newQuery()' and 'newModelQuery()' methods.
|
*/
'write_query_methods' => false,
/*
|--------------------------------------------------------------------------
| Write model magic methods
|--------------------------------------------------------------------------
|
| Set to false to disable write magic methods of model.
|
*/
'write_model_magic_where' => false,
/*
|--------------------------------------------------------------------------
| Write model external Eloquent builder methods
|--------------------------------------------------------------------------
|
| Set to false to disable write external Eloquent builder methods.
|
*/
'write_model_external_builder_methods' => false,
/*
|--------------------------------------------------------------------------
| Write model relation count and exists properties
|--------------------------------------------------------------------------
|
| Set to false to disable writing of relation count and exists properties
| to model DocBlocks.
|
*/
'write_model_relation_count_properties' => false,
'write_model_relation_exists_properties' => false,
/*
|--------------------------------------------------------------------------
| Write Eloquent model mixins
|--------------------------------------------------------------------------
|
| This will add the necessary DocBlock mixins to the model class
| contained in the Laravel framework. This helps the IDE with
| auto-completion.
|
| Please be aware that this setting changes a file within the /vendor directory.
|
*/
'write_eloquent_model_mixins' => false,
/*
|--------------------------------------------------------------------------
| Helper files to include
|--------------------------------------------------------------------------
|
| Include helper files. By default not included, but can be toggled with the
| -- helpers (-H) option. Extra helper files can be included.
|
*/
'include_helpers' => false,
'helper_files' => [
base_path() . '/vendor/laravel/framework/src/Illuminate/Support/helpers.php',
base_path() . '/vendor/laravel/framework/src/Illuminate/Foundation/helpers.php',
],
/*
|--------------------------------------------------------------------------
| Model locations to include
|--------------------------------------------------------------------------
|
| Define in which directories the ide-helper:models command should look
| for models.
|
| glob patterns are supported to easier reach models in sub-directories,
| e.g. `app/Services/* /Models` (without the space).
|
*/
'model_locations' => [
'app',
],
/*
|--------------------------------------------------------------------------
| Models to ignore
|--------------------------------------------------------------------------
|
| Define which models should be ignored.
|
*/
'ignored_models' => [],
/*
|--------------------------------------------------------------------------
| Models hooks
|--------------------------------------------------------------------------
|
| Define which hook classes you want to run for models to add custom information.
|
| Hooks should implement Barryvdh\LaravelIdeHelper\Contracts\ModelHookInterface.
|
*/
'model_hooks' => [
// App\Support\IdeHelper\MyModelHook::class
],
/*
|--------------------------------------------------------------------------
| Extra classes
|--------------------------------------------------------------------------
|
| These implementations are not really extended, but called with magic functions.
|
*/
'extra' => [
'Eloquent' => [Builder::class, \Illuminate\Database\Query\Builder::class],
'Session' => [Store::class],
],
'magic' => [],
/*
|--------------------------------------------------------------------------
| Interface implementations
|--------------------------------------------------------------------------
|
| These interfaces will be replaced with the implementing class. Some interfaces
| are detected by the helpers, others can be listed below.
|
*/
'interfaces' => [
// App\MyInterface::class => App\MyImplementation::class,
],
/*
|--------------------------------------------------------------------------
| Support for camel cased models
|--------------------------------------------------------------------------
|
| There are some Laravel packages (such as Eloquence) that allow for accessing
| Eloquent model properties via camel case, instead of snake case.
|
| Enabling this option will support these packages by saving all model
| properties as camel case, instead of snake case.
|
| For example, normally you would see this:
|
| * @property \Illuminate\Support\Carbon $created_at
| * @property \Illuminate\Support\Carbon $updated_at
|
| With this enabled, the properties will be this:
|
| * @property \Illuminate\Support\Carbon $createdAt
| * @property \Illuminate\Support\Carbon $updatedAt
|
| Note, it is currently an all-or-nothing option.
|
*/
'model_camel_case_properties' => false,
/*
|--------------------------------------------------------------------------
| Property casts
|--------------------------------------------------------------------------
|
| Cast the given "real type" to the given "type".
|
*/
'type_overrides' => [
'integer' => 'int',
'boolean' => 'bool',
],
/*
|--------------------------------------------------------------------------
| Include DocBlocks from classes
|--------------------------------------------------------------------------
|
| Include DocBlocks from classes to allow additional code inspection for
| magic methods and properties.
|
*/
'include_class_docblocks' => false,
/*
|--------------------------------------------------------------------------
| Force FQN usage
|--------------------------------------------------------------------------
|
| Use the fully qualified (class) name in DocBlocks,
| even if the class exists in the same namespace
| or there is an import (use className) of the class.
|
*/
'force_fqn' => false,
/*
|--------------------------------------------------------------------------
| Use generics syntax
|--------------------------------------------------------------------------
|
| Use generics syntax within DocBlocks,
| e.g. `Collection<User>` instead of `Collection|User[]`.
|
*/
'use_generics_annotations' => true,
/*
|--------------------------------------------------------------------------
| Default return types for macros
|--------------------------------------------------------------------------
|
| Define default return types for macros without explicit return types.
| e.g. `\Illuminate\Database\Query\Builder::class => 'static'`,
| `\Illuminate\Support\Str::class => 'string'`
|
*/
'macro_default_return_types' => [
Factory::class => PendingRequest::class,
],
/*
|--------------------------------------------------------------------------
| Additional relation types
|--------------------------------------------------------------------------
|
| Sometimes it's needed to create custom relation types. The key of the array
| is the relationship method name. The value of the array is the fully-qualified
| class name of the relationship, e.g. `'relationName' => RelationShipClass::class`.
|
*/
'additional_relation_types' => [],
/*
|--------------------------------------------------------------------------
| Additional relation return types
|--------------------------------------------------------------------------
|
| When using custom relation types its possible for the class name to not contain
| the proper return type of the relation. The key of the array is the relationship
| method name. The value of the array is the return type of the relation ('many'
| or 'morphTo').
| e.g. `'relationName' => 'many'`.
|
*/
'additional_relation_return_types' => [],
/*
|--------------------------------------------------------------------------
| Enforce nullable Eloquent relationships on not null columns
|--------------------------------------------------------------------------
|
| When set to true (default), this option enforces nullable Eloquent relationships.
| However, in cases where the application logic ensures the presence of related
| records it may be desirable to set this option to false to avoid unwanted null warnings.
|
| Default: true
| A not null column with no foreign key constraint will have a "nullable" relationship.
| * @property int $not_null_column_with_no_foreign_key_constraint
| * @property-read BelongsToVariation|null $notNullColumnWithNoForeignKeyConstraint
|
| Option: false
| A not null column with no foreign key constraint will have a "not nullable" relationship.
| * @property int $not_null_column_with_no_foreign_key_constraint
| * @property-read BelongsToVariation $notNullColumnWithNoForeignKeyConstraint
|
*/
'enforce_nullable_relationships' => true,
/*
|--------------------------------------------------------------------------
| Make soft deletable relations nullable
|--------------------------------------------------------------------------
|
| When set to true (default), relationships to models using SoftDeletes trait
| will be marked as nullable. This is because soft-deleted records are excluded
| from queries by default, meaning even non-nullable foreign keys can return
| null when the related model is soft-deleted.
|
| Default: true
| A relationship to a soft-deletable model will include |null in the type:
| * @property-read Team|null $team
|
| Option: false
| A relationship to a soft-deletable model will NOT include |null (unless
| nullable for other reasons such as nullable foreign key column):
| * @property-read Team $team
|
*/
'soft_deletes_force_nullable' => true,
/*
|--------------------------------------------------------------------------
| Run artisan commands after migrations to generate model helpers
|--------------------------------------------------------------------------
|
| The specified commands should run after migrations are finished running.
|
*/
'post_migrate' => [
// 'ide-helper:models --nowrite',
],
];

View file

@ -18,6 +18,10 @@ return [
// * Mail admin // * Mail admin
'MAIL_ADMIN' => env('MAIL_ADMIN', 'digital@ldc.fr'), 'MAIL_ADMIN' => env('MAIL_ADMIN', 'digital@ldc.fr'),
'brevo_webook' => [
'expected_from_address' => 'mailing@reply.uwil.fr'
],
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| Mailer Configurations | Mailer Configurations

View file

@ -0,0 +1,46 @@
<?php
declare(strict_types=1);
namespace Database\Factories;
use App\Models\Site;
use App\Models\User;
use Illuminate\Database\Eloquent\Factories\Factory;
class SiteFactory extends Factory
{
/**
* The name of the factory's corresponding model.
*
* @var string
*/
protected $model = Site::class;
/**
* Define the model's default state.
*
* @return array<string, mixed>
*/
public function definition(): array
{
return [
'name' => fake()->company(),
'url_re7' => fake()->url(),
'url_prod' => fake()->url(),
'gitlab_project_id' => fake()->optional()->randomNumber(4),
'brand_id' => null,
'project_manager_id' => null,
];
}
/**
* Indique que le site a un chef de projet assigné.
*/
public function withProjectManager(): static
{
return $this->state(fn(array $attributes) => [
'project_manager_id' => User::factory(),
]);
}
}

View file

@ -1,10 +1,12 @@
<?php <?php
declare(strict_types=1);
namespace Database\Factories; namespace Database\Factories;
use App\Models\User; use App\Models\User;
use App\Enums\Users\RoleEnum;
use Illuminate\Database\Eloquent\Factories\Factory; use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Str;
class UserFactory extends Factory class UserFactory extends Factory
{ {
@ -18,17 +20,23 @@ class UserFactory extends Factory
/** /**
* Define the model's default state. * Define the model's default state.
* *
* @return array * @return array<string, mixed>
*/ */
public function definition() public function definition(): array
{ {
return [ return [
'name' => $this->faker->name, 'name' => fake()->name(),
'email' => $this->faker->unique()->safeEmail, 'email' => fake()->unique()->safeEmail(),
'email_verified_at' => now(), 'role' => RoleEnum::developper,
// * password 'gitlab_id' => fake()->optional()->randomNumber(5),
'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', 'favourite_site_id' => null,
'remember_token' => Str::random(10),
]; ];
} }
public function admin(): static
{
return $this->state(fn(array $attributes) => [
'role' => RoleEnum::admin,
]);
}
} }

View file

@ -16,12 +16,12 @@ class CreateCreditLogsTable extends Migration
Schema::create('credit_logs', function (Blueprint $table) { Schema::create('credit_logs', function (Blueprint $table) {
$table->id(); $table->id();
$table->unsignedTinyInteger('type')->comment('Enum Credit\\BalanceOperationEnum'); $table->tinyInteger('type')->unsigned()->comment('Enum Credit\\BalanceOperationEnum');
$table->unsignedFloat('amount')->comment('Montant de l\'opération'); $table->float('amount')->unsigned()->comment('Montant de l\'opération');
$table->foreignId('credit_account_id') $table->foreignId('credit_account_id')
->constrained('credit_accounts') ->constrained('credit_accounts')
->cascadeOnDelete(); ->cascadeOnDelete();
$table->timestamp('created_at'); $table->timestamp('created_at');
}); });

View file

@ -18,24 +18,24 @@ class CreateCreditEstimationsTable extends Migration
Schema::create('credit_estimations', function (Blueprint $table) { Schema::create('credit_estimations', function (Blueprint $table) {
$table->id(); $table->id();
$table->unsignedTinyInteger('status')->default(EstimationStatusEnum::pending) $table->tinyInteger('status')->unsigned()->default(EstimationStatusEnum::pending)
->comment('Enum Credit\\EstimationStatusEnum'); ->comment('Enum Credit\\EstimationStatusEnum');
$table->unsignedFloat('amount')->comment('Estimation amount'); $table->float('amount')->unsigned()->comment('Estimation amount');
$table->foreignIdFor(Ticket::class) $table->foreignIdFor(Ticket::class)
->constrained(); ->constrained();
$table->foreignId('credit_account_id') $table->foreignId('credit_account_id')
->comment('The account to debit.') ->comment('The account to debit.')
->constrained('credit_accounts'); ->constrained('credit_accounts');
$table->foreignId('estimator_id') $table->foreignId('estimator_id')
->comment('The person who has done the estimate.') ->comment('The person who has done the estimate.')
->constrained('users'); ->constrained('users');
$table->foreignId('validator_id') $table->foreignId('validator_id')
->nullable() ->nullable()
->comment('The person who has validate the estimate.') ->comment('The person who has validate the estimate.')
->constrained('users'); ->constrained('users');
$table->timestamps(); $table->timestamps();
}); });

View file

@ -1,6 +1,7 @@
<?php <?php
use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema; use Illuminate\Support\Facades\Schema;
class ChangeAmoutFloatingPointOnCreditEstimations extends Migration class ChangeAmoutFloatingPointOnCreditEstimations extends Migration
@ -12,8 +13,8 @@ class ChangeAmoutFloatingPointOnCreditEstimations extends Migration
*/ */
public function up() public function up()
{ {
Schema::table('credit_estimations', function ($table) { Schema::table('credit_estimations', function (Blueprint $table) {
$table->unsignedFloat('amount', 8, 3) $table->decimal('amount', 8, 3)->unsigned()
->comment('Estimation amount')->change(); ->comment('Estimation amount')->change();
}); });
} }
@ -25,8 +26,8 @@ class ChangeAmoutFloatingPointOnCreditEstimations extends Migration
*/ */
public function down() public function down()
{ {
Schema::table('credit_estimations', function ($table) { Schema::table('credit_estimations', function (Blueprint $table) {
$table->unsignedFloat('amount') $table->float('amount')->unsigned()
->comment('Estimation amount')->change(); ->comment('Estimation amount')->change();
}); });
} }

View file

@ -17,7 +17,7 @@ class CreatePackagesTable extends Migration
$table->id(); $table->id();
$table->string('name'); $table->string('name');
$table->unsignedFloat('amount')->comment('The cost per day'); $table->float('amount')->unsigned()->comment('The cost per day');
}); });
} }

View file

@ -37,8 +37,8 @@ class CreateInvoicesTable extends Migration
'A JSON object, each line has to be contructed using these fields description, days, type Invoices\TaskTypeEnum, amount' 'A JSON object, each line has to be contructed using these fields description, days, type Invoices\TaskTypeEnum, amount'
); );
$table->unsignedFloat('total')->comment('The total calculated using all tasks'); $table->float('total')->unsigned()->comment('The total calculated using all tasks');
$table->unsignedFloat('total_days')->comment('The total days calculated using all tasks'); $table->float('total_days')->unsigned()->comment('The total days calculated using all tasks');
$table->text('comment')->nullable() $table->text('comment')->nullable()
->comment('Any comment joined to the invoice'); ->comment('Any comment joined to the invoice');

View file

@ -1,6 +1,6 @@
<?php <?php
use App\Models\Ticket; use Illuminate\Support\Facades\DB;
use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Migrations\Migration;
class ChangeTicketsStatus extends Migration class ChangeTicketsStatus extends Migration
@ -12,17 +12,22 @@ class ChangeTicketsStatus extends Migration
*/ */
public function up() public function up()
{ {
Ticket::whereIn('status', [4, 5, 7, 10]) DB::table('tickets')->whereIn('status', [4, 5, 7, 10])
->update(['status' => 3]); ->update(['status' => 3]);
Ticket::where('status', 9)
DB::table('tickets')->where('status', 9)
->update(['status' => 4]); ->update(['status' => 4]);
Ticket::where('status', 17)
DB::table('tickets')->where('status', 17)
->update(['status' => 5]); ->update(['status' => 5]);
Ticket::where('status', 12)
DB::table('tickets')->where('status', 12)
->update(['status' => 6]); ->update(['status' => 6]);
Ticket::where('status', 13)
DB::table('tickets')->where('status', 13)
->update(['status' => 7]); ->update(['status' => 7]);
Ticket::where('status', 15)
DB::table('tickets')->where('status', 15)
->update(['status' => 8]); ->update(['status' => 8]);
} }
@ -33,17 +38,22 @@ class ChangeTicketsStatus extends Migration
*/ */
public function down() public function down()
{ {
Ticket::where('status', 3) DB::table('tickets')->where('status', 3)
->update(['status' => 10]); ->update(['status' => 10]);
Ticket::where('status', 4)
DB::table('tickets')->where('status', 4)
->update(['status' => 9]); ->update(['status' => 9]);
Ticket::where('status', 5)
DB::table('tickets')->where('status', 5)
->update(['status' => 17]); ->update(['status' => 17]);
Ticket::where('status', 6)
DB::table('tickets')->where('status', 6)
->update(['status' => 12]); ->update(['status' => 12]);
Ticket::where('status', 7)
DB::table('tickets')->where('status', 7)
->update(['status' => 13]); ->update(['status' => 13]);
Ticket::where('status', 8)
DB::table('tickets')->where('status', 8)
->update(['status' => 15]); ->update(['status' => 15]);
} }
} }

View file

@ -1,9 +1,8 @@
<?php <?php
use App\Models\Ticket;
use Carbon\Carbon;
use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema; use Illuminate\Support\Facades\Schema;
return new class extends Migration return new class extends Migration
@ -17,15 +16,32 @@ return new class extends Migration
{ {
Schema::table('tickets', function (Blueprint $table) { Schema::table('tickets', function (Blueprint $table) {
$table->timestamp('last_activity') $table->timestamp('last_activity')
->default(Carbon::now()) ->useCurrent()
->comment('Last activity on the ticket.') ->comment('Last activity on the ticket.')
->after('is_internal'); ->after('is_internal');
}); });
Ticket::get()->map(function (object $ticket) {
$lastMessage = $ticket->messages->whereNotNull('user_id')->sortByDesc('updated_at')->first(); DB::table('tickets')->orderBy('id')->chunk(500, function ($tickets) {
$ticket->last_activity = $ticketIds = $tickets->pluck('id')->toArray();
$lastMessage->updated_at ?? $ticket->updated_at;
$ticket->saveQuietly(); $messages = DB::table('messages')
->whereIn('ticket_id', $ticketIds)
->whereNotNull('user_id')
->orderBy('updated_at', 'desc')
->get()
->groupBy('ticket_id');
foreach ($tickets as $ticket) {
$lastMessageDate = $messages->has($ticket->id)
? $messages->get($ticket->id)->first()->updated_at
: null;
DB::table('tickets')
->where('id', $ticket->id)
->update([
'last_activity' => $lastMessageDate ?? $ticket->updated_at
]);
}
}); });
} }

View file

@ -1,10 +1,9 @@
<?php <?php
use App\Models\Ticket;
use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
return new class extends Migration return new class extends Migration
{ {
@ -22,11 +21,15 @@ return new class extends Migration
}); });
DB::transaction(function () { DB::transaction(function () {
$tickets = Ticket::all(); DB::table('tickets')->orderBy('id')->chunkById(1000, function ($tickets) {
foreach ($tickets as $ticket) { foreach ($tickets as $ticket) {
$ticket->title = 'ts-' . $ticket->id; DB::table('tickets')
$ticket->saveQuietly(); ->where('id', $ticket->id)
} ->update([
'title' => 'ts-' . $ticket->id
]);
}
});
}); });
} }

View file

@ -1,8 +1,7 @@
<?php <?php
use App\Models\Ticket;
use App\Models\Ticket\TicketType;
use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Migrations\Migration;
use Illuminate\Support\Facades\DB;
return new class extends Migration return new class extends Migration
{ {
@ -13,13 +12,19 @@ return new class extends Migration
*/ */
public function up(): void public function up(): void
{ {
Ticket::query()->get()->map(function (Ticket $model) { DB::transaction(function () {
$model->type = $model->getRawOriginal('type') + 1; DB::table('tickets')->orderBy('id')->chunkById(1000, function ($tickets) {
$model->saveQuietly(); $ids = $tickets->pluck('id')->toArray();
}); DB::table('tickets')->whereIn('id', $ids)->increment('type');
TicketType::query()->orderBy('type', 'DESC')->get()->map(function (TicketType $model) { });
$model->type = $model->getRawOriginal('type') + 1;
$model->saveQuietly(); $ticketTypes = DB::table('ticket_types')->orderBy('type', 'desc')->get();
foreach ($ticketTypes as $type) {
DB::table('ticket_types')
->where('id', $type->id)
->update(['type' => $type->type + 1]);
}
}); });
} }
@ -30,13 +35,20 @@ return new class extends Migration
*/ */
public function down(): void public function down(): void
{ {
Ticket::query()->get()->map(function (Ticket $model) { DB::transaction(function () {
$model->type = $model->getRawOriginal('type') - 1; // 1. Restauration des tickets
$model->saveQuietly(); DB::table('tickets')->orderBy('id')->chunkById(1000, function ($tickets) {
}); $ids = $tickets->pluck('id')->toArray();
TicketType::query()->orderBy('type', 'DESC')->get()->map(function (TicketType $model) { DB::table('tickets')->whereIn('id', $ids)->decrement('type');
$model->type = $model->getRawOriginal('type') - 1; });
$model->saveQuietly();
$ticketTypes = DB::table('ticket_types')->orderBy('type', 'asc')->get();
foreach ($ticketTypes as $type) {
DB::table('ticket_types')
->where('id', $type->id)
->update(['type' => $type->type - 1]);
}
}); });
} }
}; };

View file

@ -1,7 +1,7 @@
<?php <?php
use App\Models\Ticket;
use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Migrations\Migration;
use Illuminate\Support\Facades\DB;
return new class extends Migration return new class extends Migration
{ {
@ -12,10 +12,9 @@ return new class extends Migration
*/ */
public function up(): void public function up(): void
{ {
Ticket::query()->where('status', '>', 6)->get()->map(function (Ticket $model) { DB::table('tickets')
$model->status = $model->getRawOriginal('status') + 1; ->where('status', '>', 6)
$model->saveQuietly(); ->increment('status');
});
} }
/** /**
@ -25,9 +24,8 @@ return new class extends Migration
*/ */
public function down(): void public function down(): void
{ {
Ticket::query()->where('status', '>', 6)->get()->map(function (Ticket $model) { DB::table('tickets')
$model->status = $model->getRawOriginal('status') - 1; ->where('status', '>', 6)
$model->saveQuietly(); ->decrement('status');
});
} }
}; };

View file

@ -1,9 +1,9 @@
<?php <?php
use Illuminate\Support\Facades\Schema;
use App\Models\Site;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
return new class extends Migration return new class extends Migration
{ {
@ -15,14 +15,31 @@ return new class extends Migration
public function up(): void public function up(): void
{ {
Schema::table('tickets', function (Blueprint $table) { Schema::table('tickets', function (Blueprint $table) {
$table->integer('order')->after('status')->comment('Ticket order.'); $table->integer('order')->default(0)->after('status')->comment('Ticket order.');
}); });
Site::query()->get()->map(function ($site) { DB::transaction(function () {
$site->tickets->map(function ($ticket, $ticketIndex) { $currentSiteId = null;
$ticket->order = $ticketIndex + 1; $orderCounter = 1;
$ticket->saveQuietly();
}); DB::table('tickets')
->orderBy('site_id')
->orderBy('id')
->chunk(1000, function ($tickets) use (&$currentSiteId, &$orderCounter) {
foreach ($tickets as $ticket) {
if ($ticket->site_id !== $currentSiteId) {
$currentSiteId = $ticket->site_id;
$orderCounter = 1;
}
DB::table('tickets')
->where('id', $ticket->id)
->update(['order' => $orderCounter]);
$orderCounter++;
}
});
}); });
} }

View file

@ -0,0 +1,21 @@
<?php
declare(strict_types=1);
namespace Domain\Models\ValueObjects;
final readonly class Email
{
public function __construct(
public string $value
) {
$this->validate($value);
}
private function validate(string $email): void
{
if (! filter_var($email, FILTER_VALIDATE_EMAIL)) {
throw new \DomainException('Invalid email address');
}
}
}

View file

@ -75,9 +75,9 @@ spec:
imagePullSecrets: imagePullSecrets:
- name: docker-registry-credentials - name: docker-registry-credentials
securityContext: securityContext:
runAsUser: 33 runAsUser: 82
runAsGroup: 33 runAsGroup: 82
fsGroup: 33 fsGroup: 82
fsGroupChangePolicy: "OnRootMismatch" fsGroupChangePolicy: "OnRootMismatch"
volumes: volumes:
- name: storage - name: storage
@ -161,8 +161,8 @@ spec:
persistentVolumeClaim: persistentVolumeClaim:
claimName: ${CI_PROJECT_PATH_SLUG}-pvc-storage claimName: ${CI_PROJECT_PATH_SLUG}-pvc-storage
securityContext: securityContext:
runAsUser: 33 runAsUser: 82
runAsGroup: 33 runAsGroup: 82
fsGroup: 33 fsGroup: 82
fsGroupChangePolicy: "OnRootMismatch" fsGroupChangePolicy: "OnRootMismatch"
restartPolicy: OnFailure restartPolicy: OnFailure

188
package-lock.json generated
View file

@ -4,6 +4,9 @@
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"dependencies": {
"meow": "^9.0.0"
},
"devDependencies": { "devDependencies": {
"@eastdesire/jscolor": "^2.5.1", "@eastdesire/jscolor": "^2.5.1",
"@fortawesome/fontawesome-svg-core": "latest", "@fortawesome/fontawesome-svg-core": "latest",
@ -76,7 +79,6 @@
"version": "7.22.13", "version": "7.22.13",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz",
"integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==", "integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==",
"dev": true,
"dependencies": { "dependencies": {
"@babel/highlight": "^7.22.13", "@babel/highlight": "^7.22.13",
"chalk": "^2.4.2" "chalk": "^2.4.2"
@ -416,7 +418,6 @@
"version": "7.22.20", "version": "7.22.20",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz",
"integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==",
"dev": true,
"engines": { "engines": {
"node": ">=6.9.0" "node": ">=6.9.0"
} }
@ -462,7 +463,6 @@
"version": "7.22.20", "version": "7.22.20",
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz", "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz",
"integrity": "sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==", "integrity": "sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==",
"dev": true,
"dependencies": { "dependencies": {
"@babel/helper-validator-identifier": "^7.22.20", "@babel/helper-validator-identifier": "^7.22.20",
"chalk": "^2.4.2", "chalk": "^2.4.2",
@ -2542,9 +2542,7 @@
"node_modules/@types/minimist": { "node_modules/@types/minimist": {
"version": "1.2.3", "version": "1.2.3",
"resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.3.tgz", "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.3.tgz",
"integrity": "sha512-ZYFzrvyWUNhaPomn80dsMNgMeXxNWZBdkuG/hWlUvXvbdUH8ZERNBGXnU87McuGcWDsyzX2aChCv/SVN348k3A==", "integrity": "sha512-ZYFzrvyWUNhaPomn80dsMNgMeXxNWZBdkuG/hWlUvXvbdUH8ZERNBGXnU87McuGcWDsyzX2aChCv/SVN348k3A=="
"dev": true,
"peer": true
}, },
"node_modules/@types/node": { "node_modules/@types/node": {
"version": "20.8.4", "version": "20.8.4",
@ -2558,9 +2556,7 @@
"node_modules/@types/normalize-package-data": { "node_modules/@types/normalize-package-data": {
"version": "2.4.2", "version": "2.4.2",
"resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.2.tgz", "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.2.tgz",
"integrity": "sha512-lqa4UEhhv/2sjjIQgjX8B+RBjj47eo0mzGasklVJ78UKGQY1r0VpB9XHDaZZO9qzEFDdy4MrXLuEaSmPrPSe/A==", "integrity": "sha512-lqa4UEhhv/2sjjIQgjX8B+RBjj47eo0mzGasklVJ78UKGQY1r0VpB9XHDaZZO9qzEFDdy4MrXLuEaSmPrPSe/A=="
"dev": true,
"peer": true
}, },
"node_modules/@types/semver": { "node_modules/@types/semver": {
"version": "7.5.3", "version": "7.5.3",
@ -3222,7 +3218,6 @@
"version": "3.2.1", "version": "3.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
"dev": true,
"dependencies": { "dependencies": {
"color-convert": "^1.9.0" "color-convert": "^1.9.0"
}, },
@ -3262,8 +3257,6 @@
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz",
"integrity": "sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==", "integrity": "sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==",
"dev": true,
"peer": true,
"engines": { "engines": {
"node": ">=0.10.0" "node": ">=0.10.0"
} }
@ -3447,9 +3440,7 @@
"version": "5.3.1", "version": "5.3.1",
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
"integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
"dev": true, "license": "MIT",
"optional": true,
"peer": true,
"engines": { "engines": {
"node": ">=6" "node": ">=6"
} }
@ -3458,9 +3449,7 @@
"version": "6.2.2", "version": "6.2.2",
"resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-6.2.2.tgz", "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-6.2.2.tgz",
"integrity": "sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==", "integrity": "sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==",
"dev": true, "license": "MIT",
"optional": true,
"peer": true,
"dependencies": { "dependencies": {
"camelcase": "^5.3.1", "camelcase": "^5.3.1",
"map-obj": "^4.0.0", "map-obj": "^4.0.0",
@ -3497,7 +3486,6 @@
"version": "2.4.2", "version": "2.4.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
"dev": true,
"dependencies": { "dependencies": {
"ansi-styles": "^3.2.1", "ansi-styles": "^3.2.1",
"escape-string-regexp": "^1.0.5", "escape-string-regexp": "^1.0.5",
@ -3562,7 +3550,6 @@
"version": "1.9.3", "version": "1.9.3",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
"integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
"dev": true,
"dependencies": { "dependencies": {
"color-name": "1.1.3" "color-name": "1.1.3"
} }
@ -3570,8 +3557,7 @@
"node_modules/color-name": { "node_modules/color-name": {
"version": "1.1.3", "version": "1.1.3",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
"integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw=="
"dev": true
}, },
"node_modules/colord": { "node_modules/colord": {
"version": "2.9.3", "version": "2.9.3",
@ -3750,8 +3736,6 @@
"version": "1.2.0", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
"integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==",
"dev": true,
"peer": true,
"engines": { "engines": {
"node": ">=0.10.0" "node": ">=0.10.0"
} }
@ -3760,8 +3744,6 @@
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/decamelize-keys/-/decamelize-keys-1.1.1.tgz", "resolved": "https://registry.npmjs.org/decamelize-keys/-/decamelize-keys-1.1.1.tgz",
"integrity": "sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==", "integrity": "sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==",
"dev": true,
"peer": true,
"dependencies": { "dependencies": {
"decamelize": "^1.1.0", "decamelize": "^1.1.0",
"map-obj": "^1.0.0" "map-obj": "^1.0.0"
@ -3777,8 +3759,6 @@
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz",
"integrity": "sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==", "integrity": "sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==",
"dev": true,
"peer": true,
"engines": { "engines": {
"node": ">=0.10.0" "node": ">=0.10.0"
} }
@ -3839,8 +3819,6 @@
"version": "1.3.2", "version": "1.3.2",
"resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
"integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==",
"dev": true,
"peer": true,
"dependencies": { "dependencies": {
"is-arrayish": "^0.2.1" "is-arrayish": "^0.2.1"
} }
@ -3896,7 +3874,6 @@
"version": "1.0.5", "version": "1.0.5",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
"integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
"dev": true,
"engines": { "engines": {
"node": ">=0.8.0" "node": ">=0.8.0"
} }
@ -4544,8 +4521,6 @@
"version": "2.1.0", "version": "2.1.0",
"resolved": "https://registry.npmjs.org/hard-rejection/-/hard-rejection-2.1.0.tgz", "resolved": "https://registry.npmjs.org/hard-rejection/-/hard-rejection-2.1.0.tgz",
"integrity": "sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==", "integrity": "sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==",
"dev": true,
"peer": true,
"engines": { "engines": {
"node": ">=6" "node": ">=6"
} }
@ -4554,7 +4529,6 @@
"version": "1.0.4", "version": "1.0.4",
"resolved": "https://registry.npmjs.org/has/-/has-1.0.4.tgz", "resolved": "https://registry.npmjs.org/has/-/has-1.0.4.tgz",
"integrity": "sha512-qdSAmqLF6209RFj4VVItywPMbm3vWylknmB3nvNiUIs72xAimcM8nVYxYr7ncvZq5qzk9MKIZR8ijqD/1QuYjQ==", "integrity": "sha512-qdSAmqLF6209RFj4VVItywPMbm3vWylknmB3nvNiUIs72xAimcM8nVYxYr7ncvZq5qzk9MKIZR8ijqD/1QuYjQ==",
"dev": true,
"engines": { "engines": {
"node": ">= 0.4.0" "node": ">= 0.4.0"
} }
@ -4563,7 +4537,6 @@
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
"integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
"dev": true,
"engines": { "engines": {
"node": ">=4" "node": ">=4"
} }
@ -4581,8 +4554,6 @@
"version": "4.1.0", "version": "4.1.0",
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz",
"integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==", "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==",
"dev": true,
"peer": true,
"dependencies": { "dependencies": {
"lru-cache": "^6.0.0" "lru-cache": "^6.0.0"
}, },
@ -4594,8 +4565,6 @@
"version": "6.0.0", "version": "6.0.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
"dev": true,
"peer": true,
"dependencies": { "dependencies": {
"yallist": "^4.0.0" "yallist": "^4.0.0"
}, },
@ -4606,9 +4575,7 @@
"node_modules/hosted-git-info/node_modules/yallist": { "node_modules/hosted-git-info/node_modules/yallist": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
"dev": true,
"peer": true
}, },
"node_modules/html-tags": { "node_modules/html-tags": {
"version": "3.3.1", "version": "3.3.1",
@ -4677,9 +4644,7 @@
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz",
"integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==",
"dev": true, "license": "MIT",
"optional": true,
"peer": true,
"engines": { "engines": {
"node": ">=8" "node": ">=8"
} }
@ -4710,9 +4675,7 @@
"node_modules/is-arrayish": { "node_modules/is-arrayish": {
"version": "0.2.1", "version": "0.2.1",
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
"integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg=="
"dev": true,
"peer": true
}, },
"node_modules/is-binary-path": { "node_modules/is-binary-path": {
"version": "2.1.0", "version": "2.1.0",
@ -4730,7 +4693,6 @@
"version": "2.13.0", "version": "2.13.0",
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.0.tgz", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.0.tgz",
"integrity": "sha512-Z7dk6Qo8pOCp3l4tsX2C5ZVas4V+UxwQodwZhLopL91TX8UyyHEXafPcyoeeWuLrwzHcr3igO78wNLwHJHsMCQ==", "integrity": "sha512-Z7dk6Qo8pOCp3l4tsX2C5ZVas4V+UxwQodwZhLopL91TX8UyyHEXafPcyoeeWuLrwzHcr3igO78wNLwHJHsMCQ==",
"dev": true,
"dependencies": { "dependencies": {
"has": "^1.0.3" "has": "^1.0.3"
}, },
@ -4791,8 +4753,6 @@
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz",
"integrity": "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==", "integrity": "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==",
"dev": true,
"peer": true,
"engines": { "engines": {
"node": ">=0.10.0" "node": ">=0.10.0"
} }
@ -4816,8 +4776,7 @@
"node_modules/js-tokens": { "node_modules/js-tokens": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
"dev": true
}, },
"node_modules/js-yaml": { "node_modules/js-yaml": {
"version": "4.1.0", "version": "4.1.0",
@ -4852,9 +4811,7 @@
"node_modules/json-parse-even-better-errors": { "node_modules/json-parse-even-better-errors": {
"version": "2.3.1", "version": "2.3.1",
"resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
"integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w=="
"dev": true,
"peer": true
}, },
"node_modules/json-schema-traverse": { "node_modules/json-schema-traverse": {
"version": "0.4.1", "version": "0.4.1",
@ -4905,8 +4862,6 @@
"version": "6.0.3", "version": "6.0.3",
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
"integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==",
"dev": true,
"peer": true,
"engines": { "engines": {
"node": ">=0.10.0" "node": ">=0.10.0"
} }
@ -4949,9 +4904,7 @@
"node_modules/lines-and-columns": { "node_modules/lines-and-columns": {
"version": "1.2.4", "version": "1.2.4",
"resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
"integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="
"dev": true,
"peer": true
}, },
"node_modules/locate-path": { "node_modules/locate-path": {
"version": "6.0.0", "version": "6.0.0",
@ -5024,8 +4977,6 @@
"version": "4.3.0", "version": "4.3.0",
"resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.3.0.tgz", "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.3.0.tgz",
"integrity": "sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==", "integrity": "sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==",
"dev": true,
"peer": true,
"engines": { "engines": {
"node": ">=8" "node": ">=8"
}, },
@ -5055,9 +5006,7 @@
"version": "9.0.0", "version": "9.0.0",
"resolved": "https://registry.npmjs.org/meow/-/meow-9.0.0.tgz", "resolved": "https://registry.npmjs.org/meow/-/meow-9.0.0.tgz",
"integrity": "sha512-+obSblOQmRhcyBt62furQqRAQpNyWXo8BuQ5bN7dG8wmwQ+vwHKp/rCFD4CrTP8CsDQD1sjoZ94K417XEUk8IQ==", "integrity": "sha512-+obSblOQmRhcyBt62furQqRAQpNyWXo8BuQ5bN7dG8wmwQ+vwHKp/rCFD4CrTP8CsDQD1sjoZ94K417XEUk8IQ==",
"dev": true, "license": "MIT",
"optional": true,
"peer": true,
"dependencies": { "dependencies": {
"@types/minimist": "^1.2.0", "@types/minimist": "^1.2.0",
"camelcase-keys": "^6.2.2", "camelcase-keys": "^6.2.2",
@ -5083,9 +5032,7 @@
"version": "0.18.1", "version": "0.18.1",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.18.1.tgz", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.18.1.tgz",
"integrity": "sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw==", "integrity": "sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw==",
"dev": true, "license": "(MIT OR CC0-1.0)",
"optional": true,
"peer": true,
"engines": { "engines": {
"node": ">=10" "node": ">=10"
}, },
@ -5140,8 +5087,6 @@
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz",
"integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==",
"dev": true,
"peer": true,
"engines": { "engines": {
"node": ">=4" "node": ">=4"
} }
@ -5162,8 +5107,6 @@
"version": "4.1.0", "version": "4.1.0",
"resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-4.1.0.tgz", "resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-4.1.0.tgz",
"integrity": "sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==", "integrity": "sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==",
"dev": true,
"peer": true,
"dependencies": { "dependencies": {
"arrify": "^1.0.1", "arrify": "^1.0.1",
"is-plain-obj": "^1.1.0", "is-plain-obj": "^1.1.0",
@ -5228,8 +5171,6 @@
"version": "3.0.3", "version": "3.0.3",
"resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.3.tgz", "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.3.tgz",
"integrity": "sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==", "integrity": "sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==",
"dev": true,
"peer": true,
"dependencies": { "dependencies": {
"hosted-git-info": "^4.0.1", "hosted-git-info": "^4.0.1",
"is-core-module": "^2.5.0", "is-core-module": "^2.5.0",
@ -5244,8 +5185,6 @@
"version": "6.0.0", "version": "6.0.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
"dev": true,
"peer": true,
"dependencies": { "dependencies": {
"yallist": "^4.0.0" "yallist": "^4.0.0"
}, },
@ -5257,8 +5196,6 @@
"version": "7.5.4", "version": "7.5.4",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
"integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
"dev": true,
"peer": true,
"dependencies": { "dependencies": {
"lru-cache": "^6.0.0" "lru-cache": "^6.0.0"
}, },
@ -5272,9 +5209,7 @@
"node_modules/normalize-package-data/node_modules/yallist": { "node_modules/normalize-package-data/node_modules/yallist": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
"dev": true,
"peer": true
}, },
"node_modules/normalize-path": { "node_modules/normalize-path": {
"version": "3.0.0", "version": "3.0.0",
@ -5369,9 +5304,7 @@
"version": "2.2.0", "version": "2.2.0",
"resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
"integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
"dev": true, "license": "MIT",
"optional": true,
"peer": true,
"engines": { "engines": {
"node": ">=6" "node": ">=6"
} }
@ -5392,8 +5325,6 @@
"version": "5.2.0", "version": "5.2.0",
"resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz",
"integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==",
"dev": true,
"peer": true,
"dependencies": { "dependencies": {
"@babel/code-frame": "^7.0.0", "@babel/code-frame": "^7.0.0",
"error-ex": "^1.3.1", "error-ex": "^1.3.1",
@ -5411,7 +5342,6 @@
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
"integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
"dev": true,
"engines": { "engines": {
"node": ">=8" "node": ">=8"
} }
@ -5437,8 +5367,7 @@
"node_modules/path-parse": { "node_modules/path-parse": {
"version": "1.0.7", "version": "1.0.7",
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="
"dev": true
}, },
"node_modules/path-type": { "node_modules/path-type": {
"version": "4.0.0", "version": "4.0.0",
@ -5659,9 +5588,7 @@
"version": "4.0.1", "version": "4.0.1",
"resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-4.0.1.tgz", "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-4.0.1.tgz",
"integrity": "sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==", "integrity": "sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==",
"dev": true, "license": "MIT",
"optional": true,
"peer": true,
"engines": { "engines": {
"node": ">=8" "node": ">=8"
} }
@ -5670,9 +5597,7 @@
"version": "5.2.0", "version": "5.2.0",
"resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz",
"integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==",
"dev": true, "license": "MIT",
"optional": true,
"peer": true,
"dependencies": { "dependencies": {
"@types/normalize-package-data": "^2.4.0", "@types/normalize-package-data": "^2.4.0",
"normalize-package-data": "^2.5.0", "normalize-package-data": "^2.5.0",
@ -5687,9 +5612,7 @@
"version": "7.0.1", "version": "7.0.1",
"resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz",
"integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==",
"dev": true, "license": "MIT",
"optional": true,
"peer": true,
"dependencies": { "dependencies": {
"find-up": "^4.1.0", "find-up": "^4.1.0",
"read-pkg": "^5.2.0", "read-pkg": "^5.2.0",
@ -5706,9 +5629,7 @@
"version": "4.1.0", "version": "4.1.0",
"resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
"integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
"dev": true, "license": "MIT",
"optional": true,
"peer": true,
"dependencies": { "dependencies": {
"locate-path": "^5.0.0", "locate-path": "^5.0.0",
"path-exists": "^4.0.0" "path-exists": "^4.0.0"
@ -5721,9 +5642,7 @@
"version": "5.0.0", "version": "5.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
"integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
"dev": true, "license": "MIT",
"optional": true,
"peer": true,
"dependencies": { "dependencies": {
"p-locate": "^4.1.0" "p-locate": "^4.1.0"
}, },
@ -5735,9 +5654,7 @@
"version": "2.3.0", "version": "2.3.0",
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
"integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
"dev": true, "license": "MIT",
"optional": true,
"peer": true,
"dependencies": { "dependencies": {
"p-try": "^2.0.0" "p-try": "^2.0.0"
}, },
@ -5752,9 +5669,7 @@
"version": "4.1.0", "version": "4.1.0",
"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
"integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
"dev": true, "license": "MIT",
"optional": true,
"peer": true,
"dependencies": { "dependencies": {
"p-limit": "^2.2.0" "p-limit": "^2.2.0"
}, },
@ -5766,9 +5681,7 @@
"version": "0.8.1", "version": "0.8.1",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz",
"integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==",
"dev": true, "license": "(MIT OR CC0-1.0)",
"optional": true,
"peer": true,
"engines": { "engines": {
"node": ">=8" "node": ">=8"
} }
@ -5777,17 +5690,13 @@
"version": "2.8.9", "version": "2.8.9",
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz",
"integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==",
"dev": true, "license": "ISC"
"optional": true,
"peer": true
}, },
"node_modules/read-pkg/node_modules/normalize-package-data": { "node_modules/read-pkg/node_modules/normalize-package-data": {
"version": "2.5.0", "version": "2.5.0",
"resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz",
"integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==",
"dev": true, "license": "BSD-2-Clause",
"optional": true,
"peer": true,
"dependencies": { "dependencies": {
"hosted-git-info": "^2.1.4", "hosted-git-info": "^2.1.4",
"resolve": "^1.10.0", "resolve": "^1.10.0",
@ -5799,9 +5708,7 @@
"version": "5.7.2", "version": "5.7.2",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz",
"integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==",
"dev": true, "license": "ISC",
"optional": true,
"peer": true,
"bin": { "bin": {
"semver": "bin/semver" "semver": "bin/semver"
} }
@ -5810,9 +5717,7 @@
"version": "0.6.0", "version": "0.6.0",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz",
"integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==", "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==",
"dev": true, "license": "(MIT OR CC0-1.0)",
"optional": true,
"peer": true,
"engines": { "engines": {
"node": ">=8" "node": ">=8"
} }
@ -5833,9 +5738,7 @@
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz",
"integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==",
"dev": true, "license": "MIT",
"optional": true,
"peer": true,
"dependencies": { "dependencies": {
"indent-string": "^4.0.0", "indent-string": "^4.0.0",
"strip-indent": "^3.0.0" "strip-indent": "^3.0.0"
@ -5929,7 +5832,6 @@
"version": "1.22.8", "version": "1.22.8",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz",
"integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==",
"dev": true,
"dependencies": { "dependencies": {
"is-core-module": "^2.13.0", "is-core-module": "^2.13.0",
"path-parse": "^1.0.7", "path-parse": "^1.0.7",
@ -6180,8 +6082,6 @@
"version": "3.2.0", "version": "3.2.0",
"resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz",
"integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==",
"dev": true,
"peer": true,
"dependencies": { "dependencies": {
"spdx-expression-parse": "^3.0.0", "spdx-expression-parse": "^3.0.0",
"spdx-license-ids": "^3.0.0" "spdx-license-ids": "^3.0.0"
@ -6190,16 +6090,12 @@
"node_modules/spdx-exceptions": { "node_modules/spdx-exceptions": {
"version": "2.3.0", "version": "2.3.0",
"resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz",
"integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A=="
"dev": true,
"peer": true
}, },
"node_modules/spdx-expression-parse": { "node_modules/spdx-expression-parse": {
"version": "3.0.1", "version": "3.0.1",
"resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz",
"integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==",
"dev": true,
"peer": true,
"dependencies": { "dependencies": {
"spdx-exceptions": "^2.1.0", "spdx-exceptions": "^2.1.0",
"spdx-license-ids": "^3.0.0" "spdx-license-ids": "^3.0.0"
@ -6208,9 +6104,7 @@
"node_modules/spdx-license-ids": { "node_modules/spdx-license-ids": {
"version": "3.0.16", "version": "3.0.16",
"resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.16.tgz", "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.16.tgz",
"integrity": "sha512-eWN+LnM3GR6gPu35WxNgbGl8rmY1AEmoMDvL/QD6zYmPWgywxWqJWNdLGT+ke8dKNWrcYgYjPpG5gbTfghP8rw==", "integrity": "sha512-eWN+LnM3GR6gPu35WxNgbGl8rmY1AEmoMDvL/QD6zYmPWgywxWqJWNdLGT+ke8dKNWrcYgYjPpG5gbTfghP8rw=="
"dev": true,
"peer": true
}, },
"node_modules/string-width": { "node_modules/string-width": {
"version": "4.2.3", "version": "4.2.3",
@ -6243,9 +6137,7 @@
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz",
"integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==",
"dev": true, "license": "MIT",
"optional": true,
"peer": true,
"dependencies": { "dependencies": {
"min-indent": "^1.0.0" "min-indent": "^1.0.0"
}, },
@ -6688,7 +6580,6 @@
"version": "5.5.0", "version": "5.5.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
"dev": true,
"dependencies": { "dependencies": {
"has-flag": "^3.0.0" "has-flag": "^3.0.0"
}, },
@ -6737,7 +6628,6 @@
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
"integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
"dev": true,
"engines": { "engines": {
"node": ">= 0.4" "node": ">= 0.4"
}, },
@ -6870,9 +6760,7 @@
"version": "3.0.1", "version": "3.0.1",
"resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.1.tgz", "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.1.tgz",
"integrity": "sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==", "integrity": "sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==",
"dev": true, "license": "MIT",
"optional": true,
"peer": true,
"engines": { "engines": {
"node": ">=8" "node": ">=8"
} }
@ -7030,8 +6918,6 @@
"version": "3.0.4", "version": "3.0.4",
"resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz",
"integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==",
"dev": true,
"peer": true,
"dependencies": { "dependencies": {
"spdx-correct": "^3.0.0", "spdx-correct": "^3.0.0",
"spdx-expression-parse": "^3.0.0" "spdx-expression-parse": "^3.0.0"
@ -7656,8 +7542,6 @@
"version": "20.2.9", "version": "20.2.9",
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz",
"integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==",
"dev": true,
"peer": true,
"engines": { "engines": {
"node": ">=10" "node": ">=10"
} }

View file

@ -55,5 +55,8 @@
"vue": "latest", "vue": "latest",
"vue-select": "^4.0.0-beta.6", "vue-select": "^4.0.0-beta.6",
"vue-tsc": "1.6.0" "vue-tsc": "1.6.0"
},
"dependencies": {
"meow": "^9.0.0"
} }
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 98 KiB

After

Width:  |  Height:  |  Size: 130 KiB

View file

@ -1,13 +1,13 @@
<p>Bonjour,</p> <p>Bonjour,</p>
<p>Un nouveau ticket (n°{{ $ticket->id }}) a été créé sur le support :</p> <p>Un nouveau ticket (n°{{ $ticketId }}) a été créé sur le support :</p>
<ul> <ul>
<li>Projet : {{ $ticket->site->name }}</li> <li>Projet : {{ $siteName }}</li>
<li>Titre : {{ $ticket->title }}</li> <li>Titre : {{ $ticketTitle }}</li>
<li>Type : {{ $ticket->type->label() }}</li> <li>Type : {{ $ticketType->label() }}</li>
<li>Créé par : {{ $ticket->user()->withTrashed()->firstOrFail()->name }}</li> <li>Créé par : {{ $userName }}</li>
</ul> </ul>
<p> <p>
Description :<br> Description :<br>
{!! clean(nl2br($ticket->text)) !!} {!! clean(nl2br($ticketDescription)) !!}
</p> </p>
<p>Lien vers le ticket : <a href="{{ route('bo.ticket.show', $ticket->id) }}" traget="_blank">Ouvrir dans un nouvel onglet</a></p> <p>Lien vers le ticket : <a href="{{ route('bo.ticket.show', $ticketId) }}" traget="_blank">Ouvrir dans un nouvel onglet</a></p>

View file

@ -12,13 +12,13 @@ use App\Http\Controllers\Bo\PackageController;
use App\Http\Controllers\Bo\SiteController; use App\Http\Controllers\Bo\SiteController;
use App\Http\Controllers\Bo\TicketController; use App\Http\Controllers\Bo\TicketController;
use App\Http\Controllers\Bo\TicketTypeController; use App\Http\Controllers\Bo\TicketTypeController;
use App\Http\Controllers\Bo\BrevoController; use App\Http\Controllers\Bo\BrevoWebhookController;
use App\Http\Controllers\Bo\UserController; use App\Http\Controllers\Bo\UserController;
use App\Http\Middleware\SecureHeaders; use App\Http\Middleware\SecureHeaders;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Route; use Illuminate\Support\Facades\Route;
Route::post('/brevo-in', [BrevoController::class, 'createTicket']); Route::post('/brevo-in', [BrevoWebhookController::class, 'webhookCallReceived']);
Route::middleware('admin-secure') Route::middleware('admin-secure')
->group(function () { ->group(function () {
@ -130,7 +130,8 @@ Route::middleware('admin-secure')
->middleware('can:estimate,ticket') ->middleware('can:estimate,ticket')
->name('estimateSubmit'); ->name('estimateSubmit');
Route::post('/{ticket}/estimate/answer', [ Route::post('/{ticket}/estimate/answer', [
EstimationController::class, 'estimatePendingSubmit' EstimationController::class,
'estimatePendingSubmit'
]) ])
->middleware('can:validateEstimate,ticket') ->middleware('can:validateEstimate,ticket')
->name('estimatePendingSubmit'); ->name('estimatePendingSubmit');

View file

@ -15,6 +15,6 @@ class ExampleTest extends TestCase
{ {
$response = $this->get('/'); $response = $this->get('/');
$response->assertStatus(200); $response->assertStatus(302);
} }
} }

View file

@ -1,18 +0,0 @@
<?php
namespace Tests\Unit;
use PHPUnit\Framework\TestCase;
class ExampleTest extends TestCase
{
/**
* A basic test example.
*
* @return void
*/
public function testBasicTest()
{
$this->assertTrue(true);
}
}

View file

@ -0,0 +1,78 @@
<?php
declare(strict_types=1);
namespace Tests\Unit\Services;
use App\Dto\BrevoWebHookDto;
use App\Enums\Tickets\TicketStatusEnum;
use App\Enums\Tickets\TicketTypeEnum;
use App\Models\Site;
use App\Models\Ticket;
use App\Models\User;
use App\Services\AutomaticTicketCreator;
use App\Services\BrevoWebookParser;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Mockery;
use Mockery\MockInterface;
use Tests\TestCase;
class AutomaticTicketCreatorTest extends TestCase
{
use RefreshDatabase;
private AutomaticTicketCreator $creator;
private BrevoWebookParser|MockInterface $parserMock;
protected function setUp(): void
{
parent::setUp();
$this->parserMock = Mockery::mock(BrevoWebookParser::class);
$this->creator = new AutomaticTicketCreator($this->parserMock);
}
public function testCanCreateTicketWithProjectManagerAndBrand(): void
{
// GIVEN
$site = Site::factory()->create();
$manager = User::factory()->create();
$rawHtml = 'Simili HTML dynamique';
$dto = new BrevoWebHookDto(
plainTextContent: 'Contenu parsé',
brand: 'LDC Service'
);
$this->parserMock
->shouldReceive('parseRawHtml')
->once()
->with($rawHtml)
->andReturn($dto);
// WHEN
$ticket = $this->creator->createTicket($site, $manager, $rawHtml);
// THEN
$this->assertInstanceOf(Ticket::class, $ticket);
$this->assertEquals($site->id, $ticket->site_id);
$this->assertEquals(TicketTypeEnum::mailing, $ticket->type);
$this->assertEquals(TicketStatusEnum::estimateTodo, $ticket->status);
$this->assertEquals('Demande de mail en masse pour LDC Service', $ticket->title);
$this->assertStringContainsString('Contenu :', $ticket->text);
$this->assertStringContainsString('Contenu parsé', $ticket->text);
$this->assertFalse($ticket->is_internal);
$this->assertTrue($ticket->user->is($manager));
$this->assertTrue($ticket->assignedUser->is($manager));
$this->assertDatabaseHas('tickets', [
'id' => $ticket->id,
'created_by' => $manager->id,
'assigned_to' => $manager->id,
'title' => 'Demande de mail en masse pour LDC Service'
]);
}
}

View file

@ -0,0 +1,49 @@
<?php
declare(strict_types=1);
namespace Tests\Unit\Services;
use App\Dto\BrevoWebHookDto;
use App\Services\BrevoWebookParser;
use PHPUnit\Framework\TestCase;
class BrevoWebookParserTest extends TestCase
{
private BrevoWebookParser $parser;
protected function setUp(): void
{
parent::setUp();
$this->parser = new BrevoWebookParser();
}
public function testCanExtractDataWithRegexUsingPipedFormat(): void
{
// GIVEN
$rawHtml = <<<TEXT
Détails de la demande :\r\n\r\nOutil envoi de mails en masse\r\n\r\n\r\nDescription\r\nReponse\r\n\r\n\r\nMatricule et Nom du demandeur\r\nK00112323 : toto jean\r\n\r\n\r\nEmail du demandeur\r\ntoto.jean@ldc.fr : Jean\r\n\r\n\r\nMarque / Filiale\r\nLDC Service\r\n\r\n\r\nPour quels besoins avez-vous besoin d'envoyer des emails en masse ?\r\nL'aigüe ambiguïté exige qu'on s'y penche. Œil, cœur, bœuf: la naïveté est un mythe. Test accents: àéèïôùç. Maman a acheté 15,99&euro; de pâte (soit 10%)! Symboles: & ~ # | \\ ^ @ % * $ £ ¥ ¤ + = - _ Parenthèses: (test) [test] {test} &lt;test&gt;. Cyrillique: Добрый день. Arabe: مرحبا. Emojis: 😀🚀🐛. Test XSS: &lt;script&gt;alert('XSS');&lt;/script&gt; Test SQL: SELECT * FROM users WHERE nom = 'O\\'Brian'; DROP TABLE test; -- Fin du texte 1. 1234567890. Remplissage pour atteindre un peu moins de 500 chars.\r\n\r\n\r\nDate de première envoi souhaité\r\n30/06/2026\r\n\r\n\r\nCommentaire\r\nTest de sauts de ligne pour le second champ.\r\n\r\n\tTabulation et espaces multiples.\r\nJSON :\r\n{'id': 999, 'name': 'L'Étranger', 'tags': ['&lt;img src=x onerror=alert(1)&gt;']}\r\n\r\nVérification Regex :\r\nEmail: jean.dupont+test@exemple.co.uk\r\nURL: https://www.exemple.com/api/v1/data?p=1#ancre\r\nTél: +33(0)123456789\r\nIPv4: 192.168.1.254 | IPv6: 2001:db8::8a2e:370:7334\r\n\r\nCe second bloc vérifie que les données structurées et les liens survivent au transfert. Remplissage pour atteindre la limite de sept cents caractères sans la dépasser. Chinois: 这是一个测试. Japonais: こんにちは世界. Test des retours chariots invisibles. Fin du second bloc de test.\r\n\r\n\r\n
TEXT;
// WHEN
$dto = $this->parser->parseRawHtml($rawHtml);
// THEN
$this->assertInstanceOf(BrevoWebHookDto::class, $dto);
$this->assertEquals('LDC Service', $dto->brand);
$this->assertEquals($rawHtml, $dto->plainTextContent);
}
public function testHandlesMissingFieldsGracefully(): void
{
// GIVEN
$rawHtml = "<p>Contenu classique sans le formalisme du tableau markdown</p>";
// WHEN
$dto = $this->parser->parseRawHtml($rawHtml);
// THEN
$this->assertNull($dto->brand);
$this->assertEquals("Contenu classique sans le formalisme du tableau markdown", $dto->plainTextContent);
}
}