Skip to content

Commit 65919bc

Browse files
Add canton-based email routing for approval notifications
1 parent 01a54fd commit 65919bc

6 files changed

Lines changed: 312 additions & 1 deletion

File tree

.env.example

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,3 +44,36 @@ KEYCLOAK_CACHE_OPENID=true # set to false for testing
4444

4545
CROWDIN_USER=crowdinuser
4646
CROWDIN_PASS=crowdinpass
47+
48+
# Canton Email Addresses (Testing)
49+
CANTON_EMAIL_ZH="test-zh@example.com"
50+
CANTON_EMAIL_VD="test-vd@example.com"
51+
CANTON_EMAIL_BE="test-be@example.com"
52+
53+
# # Canton Email Addresses (German-speaking cantons)
54+
# CANTON_EMAIL_AG="ag@example.com"
55+
# CANTON_EMAIL_AR="ar@example.com"
56+
# CANTON_EMAIL_BS="bs@example.com"
57+
# CANTON_EMAIL_BL="bl@example.com"
58+
# CANTON_EMAIL_BE="be@example.com"
59+
# CANTON_EMAIL_GL="gl@example.com"
60+
# CANTON_EMAIL_GR="gr@example.com"
61+
# CANTON_EMAIL_LU="lu@example.com"
62+
# CANTON_EMAIL_NW="nw@example.com"
63+
# CANTON_EMAIL_OW="ow@example.com"
64+
# CANTON_EMAIL_SH="sh@example.com"
65+
# CANTON_EMAIL_SZ="sz@example.com"
66+
# CANTON_EMAIL_SO="so@example.com"
67+
# CANTON_EMAIL_SG="sg@example.com"
68+
# CANTON_EMAIL_TG="tg@example.com"
69+
# CANTON_EMAIL_UR="ur@example.com"
70+
# CANTON_EMAIL_TI="ti@example.com"
71+
# CANTON_EMAIL_ZG="zg@example.com"
72+
# CANTON_EMAIL_ZH="zh@example.com"
73+
74+
# # Canton Email Addresses (French-speaking cantons)
75+
# CANTON_EMAIL_FR="fr@example.com"
76+
# CANTON_EMAIL_VD="vd@example.com"
77+
# CANTON_EMAIL_NE="ne@example.com"
78+
# CANTON_EMAIL_GE="ge@example.com"
79+
# CANTON_EMAIL_JU="ju@example.com"
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<?php
2+
3+
namespace App\Console\Commands;
4+
5+
use App\Services\CantonEmailService;
6+
use Illuminate\Console\Command;
7+
8+
class TestCantonEmails extends Command
9+
{
10+
protected $signature = 'test:canton-emails';
11+
protected $description = 'Test canton email routing functionality';
12+
13+
public function handle()
14+
{
15+
$this->info('Testing Canton Email Service...');
16+
$this->line(str_repeat('-', 50));
17+
18+
$testCases = [
19+
'Zurich (ZH)' => ['/CH/ZH/'],
20+
'Vaud (VD)' => ['/CH/VD/'],
21+
'Bern (BE)' => ['/CH/BE/'],
22+
'Switzerland (CH)' => ['/CH/'],
23+
'Unknown (XX)' => ['/CH/XX/'],
24+
'Empty groups' => [],
25+
];
26+
27+
foreach ($testCases as $name => $groups) {
28+
$email = CantonEmailService::getCantonEmail($groups);
29+
$language = CantonEmailService::getCantonLanguage($groups);
30+
31+
$this->info("{$name}:");
32+
$this->line(" Groups: " . json_encode($groups));
33+
$this->line(" Email: {$email}");
34+
$this->line(" Language: {$language}");
35+
$this->line('');
36+
}
37+
38+
$this->info('Test completed!');
39+
return 0;
40+
}
41+
}

app/Http/Controllers/UserController.php

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -275,7 +275,12 @@ public function register(Request $request, UserFederationService $federationServ
275275
'lang' => app()->getLocale()
276276
]);
277277

278-
Mail::to(config('app.admin_email'))
278+
// Route email based on canton and use appropriate language
279+
$recipientEmail = \App\Services\CantonEmailService::getCantonEmail($keycloakUser->groups);
280+
$cantonLanguage = \App\Services\CantonEmailService::getCantonLanguage($keycloakUser->groups);
281+
282+
Mail::to($recipientEmail)
283+
->locale($cantonLanguage)
279284
->send(new PendingApproval(
280285
$localUser,
281286
$keycloakUser->groups
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
<?php
2+
3+
namespace App\Services;
4+
5+
class CantonEmailService
6+
{
7+
public static function getCantonEmail(array $groups): string
8+
{
9+
$cantonConfig = config('app.cantons', []);
10+
11+
// Extract canton from groups path
12+
$canton = self::extractCantonFromGroups($groups);
13+
14+
// If canton is "CH" or not found, return admin email
15+
if ($canton === 'CH' || !isset($cantonConfig[$canton])) {
16+
return config('app.admin_email');
17+
}
18+
19+
$email = $cantonConfig[$canton]['email'] ?? null;
20+
21+
// Fallback to admin email if email is not configured
22+
return $email ?: config('app.admin_email');
23+
}
24+
25+
public static function getCantonLanguage(array $groups): string
26+
{
27+
$cantonConfig = config('app.cantons', []);
28+
29+
// Extract canton from groups path
30+
$canton = self::extractCantonFromGroups($groups);
31+
32+
// If canton is "CH" or not found, return default language
33+
if ($canton === 'CH' || !isset($cantonConfig[$canton])) {
34+
return 'de'; // Default to German
35+
}
36+
37+
return $cantonConfig[$canton]['lang'] ?? 'de';
38+
}
39+
40+
private static function extractCantonFromGroups(array $groups): string
41+
{
42+
if (empty($groups)) {
43+
return 'CH';
44+
}
45+
46+
// Look for a path like "/CH/VD/" and extract the last segment
47+
foreach ($groups as $group) {
48+
if (is_string($group) && str_starts_with($group, '/CH/')) {
49+
$parts = explode('/', trim($group, '/'));
50+
return end($parts); // Return the last part (VD, ZH, etc.)
51+
}
52+
}
53+
54+
// If no specific canton found, assume CH
55+
return 'CH';
56+
}
57+
}

config/app.php

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -408,6 +408,123 @@
408408

409409
'admin_email' => env('APP_ADMIN_EMAIL'),
410410

411+
/*
412+
|--------------------------------------------------------------------------
413+
| Canton Configuration
414+
|--------------------------------------------------------------------------
415+
|
416+
| Email addresses and languages for Swiss cantons. This allows routing
417+
| approval emails to the appropriate cantonal office and sends them
418+
| in the correct language.
419+
|
420+
*/
421+
422+
'cantons' => [
423+
// German-speaking cantons
424+
'AG' => [
425+
'email' => env('CANTON_EMAIL_AG'),
426+
'lang' => 'de',
427+
],
428+
'AR' => [
429+
'email' => env('CANTON_EMAIL_AR'),
430+
'lang' => 'de',
431+
],
432+
'BS' => [
433+
'email' => env('CANTON_EMAIL_BS'),
434+
'lang' => 'de',
435+
],
436+
'BL' => [
437+
'email' => env('CANTON_EMAIL_BL'),
438+
'lang' => 'de',
439+
],
440+
'BE' => [
441+
'email' => env('CANTON_EMAIL_BE'),
442+
'lang' => 'de',
443+
],
444+
'GL' => [
445+
'email' => env('CANTON_EMAIL_GL'),
446+
'lang' => 'de',
447+
],
448+
'GR' => [
449+
'email' => env('CANTON_EMAIL_GR'),
450+
'lang' => 'de',
451+
],
452+
'LU' => [
453+
'email' => env('CANTON_EMAIL_LU'),
454+
'lang' => 'de',
455+
],
456+
'NW' => [
457+
'email' => env('CANTON_EMAIL_NW'),
458+
'lang' => 'de',
459+
],
460+
'OW' => [
461+
'email' => env('CANTON_EMAIL_OW'),
462+
'lang' => 'de',
463+
],
464+
'VS' => [
465+
'email' => env('CANTON_EMAIL_VS'),
466+
'lang' => 'de',
467+
],
468+
'SH' => [
469+
'email' => env('CANTON_EMAIL_SH'),
470+
'lang' => 'de',
471+
],
472+
'SZ' => [
473+
'email' => env('CANTON_EMAIL_SZ'),
474+
'lang' => 'de',
475+
],
476+
'SO' => [
477+
'email' => env('CANTON_EMAIL_SO'),
478+
'lang' => 'de',
479+
],
480+
'SG' => [
481+
'email' => env('CANTON_EMAIL_SG'),
482+
'lang' => 'de',
483+
],
484+
'TG' => [
485+
'email' => env('CANTON_EMAIL_TG'),
486+
'lang' => 'de',
487+
],
488+
'UR' => [
489+
'email' => env('CANTON_EMAIL_UR'),
490+
'lang' => 'de',
491+
],
492+
'TI' => [
493+
'email' => env('CANTON_EMAIL_TI'),
494+
'lang' => 'de',
495+
],
496+
'ZG' => [
497+
'email' => env('CANTON_EMAIL_ZG'),
498+
'lang' => 'de',
499+
],
500+
'ZH' => [
501+
'email' => env('CANTON_EMAIL_ZH'),
502+
'lang' => 'de',
503+
],
504+
505+
// French-speaking cantons
506+
'FR' => [
507+
'email' => env('CANTON_EMAIL_FR'),
508+
'lang' => 'fr',
509+
],
510+
'VD' => [
511+
'email' => env('CANTON_EMAIL_VD'),
512+
'lang' => 'fr',
513+
],
514+
'NE' => [
515+
'email' => env('CANTON_EMAIL_NE'),
516+
'lang' => 'fr',
517+
],
518+
'GE' => [
519+
'email' => env('CANTON_EMAIL_GE'),
520+
'lang' => 'fr',
521+
],
522+
'JU' => [
523+
'email' => env('CANTON_EMAIL_JU'),
524+
'lang' => 'fr',
525+
],
526+
],
527+
411528
/*
412529
|--------------------------------------------------------------------------
413530
| Crowdin username
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
<?php
2+
3+
namespace Tests\Unit;
4+
5+
use App\Services\CantonEmailService;
6+
use Tests\TestCase;
7+
8+
class CantonEmailServiceTest extends TestCase
9+
{
10+
protected function setUp(): void
11+
{
12+
parent::setUp();
13+
14+
// Set up test environment variables
15+
config(['app.cantons' => [
16+
'ZH' => ['email' => 'test-zh@example.com', 'lang' => 'de'],
17+
'VD' => ['email' => 'test-vd@example.com', 'lang' => 'fr'],
18+
'BE' => ['email' => 'test-be@example.com', 'lang' => 'de'],
19+
]]);
20+
}
21+
22+
public function testExtractsCantonFromGroups()
23+
{
24+
// Test German canton
25+
$groupsZh = ['/CH/ZH/'];
26+
$this->assertEquals('test-zh@example.com', CantonEmailService::getCantonEmail($groupsZh));
27+
$this->assertEquals('de', CantonEmailService::getCantonLanguage($groupsZh));
28+
29+
// Test French canton
30+
$groupsVd = ['/CH/VD/'];
31+
$this->assertEquals('test-vd@example.com', CantonEmailService::getCantonEmail($groupsVd));
32+
$this->assertEquals('fr', CantonEmailService::getCantonLanguage($groupsVd));
33+
34+
// Test CH fallback
35+
$groupsCh = ['/CH/'];
36+
$this->assertEquals(config('app.admin_email'), CantonEmailService::getCantonEmail($groupsCh));
37+
$this->assertEquals('de', CantonEmailService::getCantonLanguage($groupsCh));
38+
39+
// Test unknown canton fallback
40+
$groupsUnknown = ['/CH/XX/'];
41+
$this->assertEquals(config('app.admin_email'), CantonEmailService::getCantonEmail($groupsUnknown));
42+
$this->assertEquals('de', CantonEmailService::getCantonLanguage($groupsUnknown));
43+
}
44+
45+
public function testHandlesEmptyGroups()
46+
{
47+
$this->assertEquals(config('app.admin_email'), CantonEmailService::getCantonEmail([]));
48+
$this->assertEquals('de', CantonEmailService::getCantonLanguage([]));
49+
}
50+
51+
public function testHandlesMultipleGroups()
52+
{
53+
$groups = ['/CH/VD/', '/CH/ZH/'];
54+
// Should use the first matching canton
55+
$this->assertEquals('test-vd@example.com', CantonEmailService::getCantonEmail($groups));
56+
$this->assertEquals('fr', CantonEmailService::getCantonLanguage($groups));
57+
}
58+
}

0 commit comments

Comments
 (0)