Skip to content

Commit bd8b44b

Browse files
committed
RE1-T112 Weather fixes, calendar fixes and comm test fixes
1 parent 7f99e3d commit bd8b44b

18 files changed

Lines changed: 272 additions & 23 deletions

File tree

Core/Resgrid.Model/DepartmentSettingTypes.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,5 +46,6 @@ public enum DepartmentSettingTypes
4646
WeatherAlertCallIntegration = 42,
4747
WeatherAlertCacheMinutes = 43,
4848
WeatherAlertAutoMessageSchedule = 44,
49+
WeatherAlertExcludedEvents = 45,
4950
}
5051
}

Core/Resgrid.Model/PermissionTypes.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,8 @@ public enum PermissionTypes
2929
ViewWorkflowRuns = 24,
3030
ViewUdfFields = 25,
3131
ManageRoutes = 26,
32-
DeleteLog = 27
32+
DeleteLog = 27,
33+
UseCalendarSync = 28
3334
}
3435

3536
}

Core/Resgrid.Services/WeatherAlertService.cs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -340,6 +340,17 @@ public async Task SendPendingNotificationsAsync(CancellationToken ct = default)
340340
legacyThreshold = parsed;
341341
}
342342

343+
// Load excluded event titles
344+
HashSet<string> excludedEvents = null;
345+
var excludedSetting = await _departmentSettingsRepository.GetDepartmentSettingByIdTypeAsync(
346+
departmentId, DepartmentSettingTypes.WeatherAlertExcludedEvents);
347+
if (excludedSetting != null && !string.IsNullOrWhiteSpace(excludedSetting.Setting))
348+
{
349+
excludedEvents = new HashSet<string>(
350+
excludedSetting.Setting.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries),
351+
StringComparer.OrdinalIgnoreCase);
352+
}
353+
343354
// Load department for sender info and time conversion
344355
Department department = null;
345356
try
@@ -353,6 +364,15 @@ public async Task SendPendingNotificationsAsync(CancellationToken ct = default)
353364

354365
foreach (var alert in group)
355366
{
367+
// Skip excluded event titles
368+
if (excludedEvents != null && !string.IsNullOrWhiteSpace(alert.Event) && excludedEvents.Contains(alert.Event))
369+
{
370+
alert.NotificationSent = true;
371+
alert.LastUpdatedUtc = DateTime.UtcNow;
372+
await _weatherAlertRepository.UpdateAsync(alert, ct, true);
373+
continue;
374+
}
375+
356376
bool shouldSend = ShouldSendAutoMessage(alert.Severity, schedule, legacyThreshold, department);
357377
if (shouldSend)
358378
{

Web/Resgrid.Web.Services/Controllers/v4/CalendarExportController.cs

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,15 +23,27 @@ public class CalendarExportController : V4AuthenticatedApiControllerbase
2323
private readonly ICalendarExportService _calendarExportService;
2424
private readonly ICalendarService _calendarService;
2525
private readonly IUserProfileService _userProfileService;
26+
private readonly IPermissionsService _permissionsService;
27+
private readonly IPersonnelRolesService _personnelRolesService;
28+
private readonly IDepartmentsService _departmentsService;
29+
private readonly IDepartmentGroupsService _departmentGroupsService;
2630

2731
public CalendarExportController(
2832
ICalendarExportService calendarExportService,
2933
ICalendarService calendarService,
30-
IUserProfileService userProfileService)
34+
IUserProfileService userProfileService,
35+
IPermissionsService permissionsService,
36+
IPersonnelRolesService personnelRolesService,
37+
IDepartmentsService departmentsService,
38+
IDepartmentGroupsService departmentGroupsService)
3139
{
3240
_calendarExportService = calendarExportService;
3341
_calendarService = calendarService;
3442
_userProfileService = userProfileService;
43+
_permissionsService = permissionsService;
44+
_personnelRolesService = personnelRolesService;
45+
_departmentsService = departmentsService;
46+
_departmentGroupsService = departmentGroupsService;
3547
}
3648

3749
/// <summary>
@@ -132,6 +144,19 @@ public async Task<IActionResult> CalendarFeed(string token)
132144
if (validated == null)
133145
return Unauthorized();
134146

147+
// Check UseCalendarSync permission for the user who owns this token
148+
var calSyncPerm = await _permissionsService.GetPermissionByDepartmentTypeAsync(validated.Value.DepartmentId, Resgrid.Model.PermissionTypes.UseCalendarSync);
149+
if (calSyncPerm != null)
150+
{
151+
var dept = await _departmentsService.GetDepartmentByIdAsync(validated.Value.DepartmentId, false);
152+
var isAdmin = dept != null && dept.IsUserAnAdmin(validated.Value.UserId);
153+
var grp = await _departmentGroupsService.GetGroupForUserAsync(validated.Value.UserId, validated.Value.DepartmentId);
154+
var isGroupAdmin = grp != null && grp.IsUserGroupAdmin(validated.Value.UserId);
155+
var roles = await _personnelRolesService.GetRolesForUserAsync(validated.Value.UserId, validated.Value.DepartmentId);
156+
if (!_permissionsService.IsUserAllowed(calSyncPerm, isAdmin, isGroupAdmin, roles))
157+
return Unauthorized();
158+
}
159+
135160
var icsContent = await _calendarExportService.GenerateICalForDepartmentAsync(validated.Value.DepartmentId);
136161
var bytes = Encoding.UTF8.GetBytes(icsContent);
137162

Web/Resgrid.Web.Services/Controllers/v4/WeatherAlertsController.cs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -363,14 +363,17 @@ public async Task<ActionResult<GetWeatherAlertSettingsResult>> SaveSettings([Fro
363363
await _departmentSettingsService.SaveOrUpdateSettingAsync(DepartmentId, scheduleJson, DepartmentSettingTypes.WeatherAlertAutoMessageSchedule);
364364
}
365365

366+
await _departmentSettingsService.SaveOrUpdateSettingAsync(DepartmentId, input.ExcludedEvents ?? "", DepartmentSettingTypes.WeatherAlertExcludedEvents);
367+
366368
var result = new GetWeatherAlertSettingsResult();
367369
result.Data = new WeatherAlertSettingsData
368370
{
369371
WeatherAlertsEnabled = input.WeatherAlertsEnabled,
370372
MinimumSeverity = input.MinimumSeverity,
371373
AutoMessageSeverity = input.AutoMessageSeverity,
372374
CallIntegrationEnabled = input.CallIntegrationEnabled,
373-
AutoMessageSchedule = input.AutoMessageSchedule
375+
AutoMessageSchedule = input.AutoMessageSchedule,
376+
ExcludedEvents = input.ExcludedEvents
374377
};
375378

376379
ResponseHelper.PopulateV4ResponseData(result);
@@ -386,6 +389,7 @@ private async Task<WeatherAlertSettingsData> GetWeatherAlertSettingsDataAsync()
386389
var autoMsgSetting = await _departmentSettingsService.GetSettingByTypeAsync(DepartmentId, DepartmentSettingTypes.WeatherAlertAutoMessageSeverity);
387390
var callIntSetting = await _departmentSettingsService.GetSettingByTypeAsync(DepartmentId, DepartmentSettingTypes.WeatherAlertCallIntegration);
388391
var scheduleSetting = await _departmentSettingsService.GetSettingByTypeAsync(DepartmentId, DepartmentSettingTypes.WeatherAlertAutoMessageSchedule);
392+
var excludedEventsSetting = await _departmentSettingsService.GetSettingByTypeAsync(DepartmentId, DepartmentSettingTypes.WeatherAlertExcludedEvents);
389393

390394
if (enabledSetting != null && !string.IsNullOrWhiteSpace(enabledSetting.Setting))
391395
settings.WeatherAlertsEnabled = bool.TryParse(enabledSetting.Setting, out var enabled) && enabled;
@@ -408,6 +412,9 @@ private async Task<WeatherAlertSettingsData> GetWeatherAlertSettingsDataAsync()
408412
catch { }
409413
}
410414

415+
if (excludedEventsSetting != null && !string.IsNullOrWhiteSpace(excludedEventsSetting.Setting))
416+
settings.ExcludedEvents = excludedEventsSetting.Setting;
417+
411418
return settings;
412419
}
413420

Web/Resgrid.Web.Services/Models/v4/WeatherAlerts/SaveWeatherAlertSettingsInput.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,6 @@ public class SaveWeatherAlertSettingsInput
99
public int AutoMessageSeverity { get; set; }
1010
public bool CallIntegrationEnabled { get; set; }
1111
public List<WeatherAlertSeverityScheduleData> AutoMessageSchedule { get; set; }
12+
public string ExcludedEvents { get; set; }
1213
}
1314
}

Web/Resgrid.Web.Services/Models/v4/WeatherAlerts/WeatherAlertSettingsData.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ public class WeatherAlertSettingsData
99
public int AutoMessageSeverity { get; set; }
1010
public bool CallIntegrationEnabled { get; set; }
1111
public List<WeatherAlertSeverityScheduleData> AutoMessageSchedule { get; set; }
12+
public string ExcludedEvents { get; set; }
1213
}
1314

1415
public class WeatherAlertSeverityScheduleData

Web/Resgrid.Web/Areas/User/Controllers/CalendarController.cs

Lines changed: 61 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,13 @@ public class CalendarController : SecureBaseController
3434
private readonly IEventAggregator _eventAggregator;
3535
private readonly IAuthorizationService _authorizationService;
3636
private readonly IUserProfileService _userProfileService;
37+
private readonly IPermissionsService _permissionsService;
38+
private readonly IPersonnelRolesService _personnelRolesService;
3739

3840
public CalendarController(IDepartmentsService departmentsService, IUsersService usersService, ICalendarService calendarService,
3941
IDepartmentGroupsService departmentGroupsService, IGeoLocationProvider geoLocationProvider, IEventAggregator eventAggregator,
40-
IAuthorizationService authorizationService, IUserProfileService userProfileService)
42+
IAuthorizationService authorizationService, IUserProfileService userProfileService,
43+
IPermissionsService permissionsService, IPersonnelRolesService personnelRolesService)
4144
{
4245
_departmentsService = departmentsService;
4346
_usersService = usersService;
@@ -47,6 +50,8 @@ public CalendarController(IDepartmentsService departmentsService, IUsersService
4750
_eventAggregator = eventAggregator;
4851
_authorizationService = authorizationService;
4952
_userProfileService = userProfileService;
53+
_permissionsService = permissionsService;
54+
_personnelRolesService = personnelRolesService;
5055
}
5156
#endregion Private Members and Constructors
5257

@@ -69,13 +74,25 @@ public async Task<IActionResult> Index()
6974
model.UpcomingItems = new List<CalendarItem>();
7075
model.UpcomingItems = await _calendarService.GetUpcomingCalendarItemsAsync(DepartmentId, DateTime.UtcNow);
7176

77+
// Check calendar sync permission
78+
var calSyncPermission = await _permissionsService.GetPermissionByDepartmentTypeAsync(DepartmentId, PermissionTypes.UseCalendarSync);
79+
var department = model.Department;
80+
var isAdmin = department.IsUserAnAdmin(UserId);
81+
var group = await _departmentGroupsService.GetGroupForUserAsync(UserId, DepartmentId);
82+
var isGroupAdmin = group != null && group.IsUserGroupAdmin(UserId);
83+
var roles = await _personnelRolesService.GetRolesForUserAsync(UserId, DepartmentId);
84+
model.CanUseCalendarSync = _permissionsService.IsUserAllowed(calSyncPermission, isAdmin, isGroupAdmin, roles);
85+
7286
// Populate calendar sync token for the subscribe panel.
73-
var profile = await _userProfileService.GetProfileByUserIdAsync(UserId);
74-
if (profile != null && !String.IsNullOrWhiteSpace(profile.CalendarSyncToken))
87+
if (model.CanUseCalendarSync)
7588
{
76-
model.CalendarSyncToken = profile.CalendarSyncToken;
77-
var feedToken = await _calendarService.GetCalendarFeedTokenAsync(DepartmentId, UserId);
78-
model.CalendarSubscriptionUrl = $"{SystemBehaviorConfig.ResgridApiBaseUrl}/api/v4/CalendarExport/CalendarFeed/{feedToken}";
89+
var profile = await _userProfileService.GetProfileByUserIdAsync(UserId);
90+
if (profile != null && !String.IsNullOrWhiteSpace(profile.CalendarSyncToken))
91+
{
92+
model.CalendarSyncToken = profile.CalendarSyncToken;
93+
var feedToken = await _calendarService.GetCalendarFeedTokenAsync(DepartmentId, UserId);
94+
model.CalendarSubscriptionUrl = $"{SystemBehaviorConfig.ResgridApiBaseUrl}/api/v4/CalendarExport/CalendarFeed/{feedToken}";
95+
}
7996
}
8097

8198
return View(model);
@@ -895,6 +912,13 @@ public async Task<IActionResult> GetMapDataForItem(int calendarItemId)
895912
[ValidateAntiForgeryToken]
896913
public async Task<IActionResult> ActivateCalendarSync(CancellationToken cancellationToken)
897914
{
915+
var permission = await _permissionsService.GetPermissionByDepartmentTypeAsync(DepartmentId, PermissionTypes.UseCalendarSync);
916+
var dept = await _departmentsService.GetDepartmentByIdAsync(DepartmentId, false);
917+
var grp = await _departmentGroupsService.GetGroupForUserAsync(UserId, DepartmentId);
918+
var roles = await _personnelRolesService.GetRolesForUserAsync(UserId, DepartmentId);
919+
if (!_permissionsService.IsUserAllowed(permission, dept.IsUserAnAdmin(UserId), grp != null && grp.IsUserGroupAdmin(UserId), roles))
920+
return Unauthorized();
921+
898922
await _calendarService.ActivateCalendarSyncAsync(DepartmentId, UserId, cancellationToken);
899923
return RedirectToAction("Index");
900924
}
@@ -908,10 +932,41 @@ public async Task<IActionResult> ActivateCalendarSync(CancellationToken cancella
908932
[ValidateAntiForgeryToken]
909933
public async Task<IActionResult> RegenerateCalendarSync(CancellationToken cancellationToken)
910934
{
935+
var permission = await _permissionsService.GetPermissionByDepartmentTypeAsync(DepartmentId, PermissionTypes.UseCalendarSync);
936+
var dept = await _departmentsService.GetDepartmentByIdAsync(DepartmentId, false);
937+
var grp = await _departmentGroupsService.GetGroupForUserAsync(UserId, DepartmentId);
938+
var roles = await _personnelRolesService.GetRolesForUserAsync(UserId, DepartmentId);
939+
if (!_permissionsService.IsUserAllowed(permission, dept.IsUserAnAdmin(UserId), grp != null && grp.IsUserGroupAdmin(UserId), roles))
940+
return Unauthorized();
941+
911942
await _calendarService.RegenerateCalendarSyncAsync(DepartmentId, UserId, cancellationToken);
912943
return RedirectToAction("Index");
913944
}
914945

946+
/// <summary>
947+
/// Admin action: regenerates calendar sync tokens for ALL users in the department who have one provisioned.
948+
/// </summary>
949+
[HttpPost]
950+
[Authorize(Policy = ResgridResources.Department_Update)]
951+
[ValidateAntiForgeryToken]
952+
public async Task<IActionResult> RegenerateAllCalendarSyncTokens(CancellationToken cancellationToken)
953+
{
954+
var members = await _departmentsService.GetAllMembersForDepartmentAsync(DepartmentId);
955+
if (members != null)
956+
{
957+
foreach (var member in members)
958+
{
959+
var profile = await _userProfileService.GetProfileByUserIdAsync(member.UserId);
960+
if (profile != null && !string.IsNullOrWhiteSpace(profile.CalendarSyncToken))
961+
{
962+
await _calendarService.RegenerateCalendarSyncAsync(DepartmentId, member.UserId, cancellationToken);
963+
}
964+
}
965+
}
966+
967+
return RedirectToAction("Index");
968+
}
969+
915970
// -- Check-In Attendance -------------------------------------------------------
916971

917972
[HttpPost]

Web/Resgrid.Web/Areas/User/Controllers/CommunicationTestController.cs

Lines changed: 56 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,24 +22,40 @@ public class CommunicationTestController : Resgrid.Web.SecureBaseController
2222
private readonly ICommunicationTestService _communicationTestService;
2323
private readonly IUserProfileService _userProfileService;
2424
private readonly IEventAggregator _eventAggregator;
25+
private readonly IDepartmentGroupsService _departmentGroupsService;
26+
private readonly IDepartmentsService _departmentsService;
2527

2628
public CommunicationTestController(
2729
ICommunicationTestService communicationTestService,
2830
IUserProfileService userProfileService,
29-
IEventAggregator eventAggregator)
31+
IEventAggregator eventAggregator,
32+
IDepartmentGroupsService departmentGroupsService,
33+
IDepartmentsService departmentsService)
3034
{
3135
_communicationTestService = communicationTestService;
3236
_userProfileService = userProfileService;
3337
_eventAggregator = eventAggregator;
38+
_departmentGroupsService = departmentGroupsService;
39+
_departmentsService = departmentsService;
3440
}
3541

3642
[HttpGet]
3743
public async Task<IActionResult> Index()
3844
{
39-
if (!ClaimsAuthorizationHelper.IsUserDepartmentAdmin())
45+
bool isDeptAdmin = ClaimsAuthorizationHelper.IsUserDepartmentAdmin();
46+
bool isGroupAdmin = false;
47+
48+
if (!isDeptAdmin)
49+
{
50+
var group = await _departmentGroupsService.GetGroupForUserAsync(UserId, DepartmentId);
51+
isGroupAdmin = group != null && group.IsUserGroupAdmin(UserId);
52+
}
53+
54+
if (!isDeptAdmin && !isGroupAdmin)
4055
return Unauthorized();
4156

4257
var model = new CommunicationTestIndexView();
58+
model.IsDepartmentAdmin = isDeptAdmin;
4359

4460
var tests = await _communicationTestService.GetTestsByDepartmentIdAsync(DepartmentId);
4561
if (tests != null)
@@ -287,7 +303,17 @@ public async Task<IActionResult> StartRun(string testId, CancellationToken cance
287303
[HttpGet]
288304
public async Task<IActionResult> Report(string runId)
289305
{
290-
if (!ClaimsAuthorizationHelper.IsUserDepartmentAdmin())
306+
bool isDeptAdmin = ClaimsAuthorizationHelper.IsUserDepartmentAdmin();
307+
bool isGroupAdmin = false;
308+
DepartmentGroup userGroup = null;
309+
310+
if (!isDeptAdmin)
311+
{
312+
userGroup = await _departmentGroupsService.GetGroupForUserAsync(UserId, DepartmentId);
313+
isGroupAdmin = userGroup != null && userGroup.IsUserGroupAdmin(UserId);
314+
}
315+
316+
if (!isDeptAdmin && !isGroupAdmin)
291317
return Unauthorized();
292318

293319
if (!Guid.TryParse(runId, out var id))
@@ -299,13 +325,39 @@ public async Task<IActionResult> Report(string runId)
299325

300326
var test = await _communicationTestService.GetTestByIdAsync(run.CommunicationTestId);
301327
var results = await _communicationTestService.GetResultsByRunIdAsync(id);
328+
var resultList = results?.ToList() ?? new List<CommunicationTestResult>();
302329
var profiles = await _userProfileService.GetAllProfilesForDepartmentAsync(DepartmentId);
303330

331+
// Group admins only see results for members in their group and child groups
332+
if (!isDeptAdmin && isGroupAdmin && userGroup != null)
333+
{
334+
var allowedUserIds = new HashSet<string>();
335+
336+
// Add members of the admin's own group
337+
var groupMembers = await _departmentGroupsService.GetAllMembersForGroupAsync(userGroup.DepartmentGroupId);
338+
foreach (var m in groupMembers)
339+
allowedUserIds.Add(m.UserId);
340+
341+
// Add members of child groups
342+
var childGroups = await _departmentGroupsService.GetAllChildDepartmentGroupsAsync(userGroup.DepartmentGroupId);
343+
if (childGroups != null)
344+
{
345+
foreach (var childGroup in childGroups)
346+
{
347+
var childMembers = await _departmentGroupsService.GetAllMembersForGroupAsync(childGroup.DepartmentGroupId);
348+
foreach (var m in childMembers)
349+
allowedUserIds.Add(m.UserId);
350+
}
351+
}
352+
353+
resultList = resultList.Where(r => allowedUserIds.Contains(r.UserId)).ToList();
354+
}
355+
304356
var model = new CommunicationTestReportView
305357
{
306358
Run = run,
307359
Test = test,
308-
Results = results?.ToList() ?? new List<CommunicationTestResult>(),
360+
Results = resultList,
309361
Profiles = profiles ?? new Dictionary<string, UserProfile>()
310362
};
311363

Web/Resgrid.Web/Areas/User/Controllers/SecurityController.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -382,6 +382,19 @@ public async Task<IActionResult> Index()
382382
model.ViewWorkflowRunsPermissions = new SelectList(viewWorkflowRunsPermissions, "Id", "Name");
383383

384384
// 2FA enforcement scope � only managingUser can change this
385+
if (permissions.Any(x => x.PermissionType == (int)PermissionTypes.UseCalendarSync))
386+
model.UseCalendarSync = permissions.First(x => x.PermissionType == (int)PermissionTypes.UseCalendarSync).Action;
387+
else
388+
model.UseCalendarSync = 3;
389+
390+
var useCalendarSyncPermissions = new List<dynamic>();
391+
useCalendarSyncPermissions.Add(new { Id = 3, Name = "Everyone" });
392+
useCalendarSyncPermissions.Add(new { Id = 0, Name = "Department Admins" });
393+
useCalendarSyncPermissions.Add(new { Id = 1, Name = "Department and Group Admins" });
394+
useCalendarSyncPermissions.Add(new { Id = 2, Name = "Department Admins and Select Roles" });
395+
model.UseCalendarSyncPermissions = new SelectList(useCalendarSyncPermissions, "Id", "Name");
396+
397+
385398
var department = await _departmentsService.GetDepartmentByIdAsync(DepartmentId);
386399
model.IsManagingUser = department.ManagingUserId == UserId;
387400
model.Require2FAForAdmins = await _departmentSettingsService.GetRequire2FAForAdminsAsync(DepartmentId);

0 commit comments

Comments
 (0)