Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -118,4 +118,6 @@
<data name="Status" xml:space="preserve"><value>الحالة</value></data>
<data name="Date" xml:space="preserve"><value>التاريخ</value></data>
<data name="Error" xml:space="preserve"><value>خطأ</value></data>
<data name="NoLayersAvailable" xml:space="preserve"><value>لا توجد طبقات متاحة. يرجى إنشاء طبقة أولاً قبل الاستيراد.</value></data>
<data name="PleaseSelectTargetLayer" xml:space="preserve"><value>يرجى تحديد طبقة الهدف قبل الاستيراد. أنشئ طبقة أولاً إذا لم تكن موجودة.</value></data>
</root>
Original file line number Diff line number Diff line change
Expand Up @@ -365,4 +365,10 @@
<data name="Error" xml:space="preserve">
<value>Error</value>
</data>
<data name="NoLayersAvailable" xml:space="preserve">
<value>No layers available. Please create a layer first before importing.</value>
</data>
<data name="PleaseSelectTargetLayer" xml:space="preserve">
<value>Please select a target layer before importing. Create a layer first if none exist.</value>
</data>
</root>
2 changes: 2 additions & 0 deletions Core/Resgrid.Model/Helpers/SerializerHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ public static void WarmUpProtobufSerializer()
Serializer.PrepareSerializer<Address>();
Serializer.PrepareSerializer<DepartmentMember>();
Serializer.PrepareSerializer<Payment>();
Serializer.PrepareSerializer<PaymentAddon>();
Serializer.PrepareSerializer<Plan>();
Serializer.PrepareSerializer<PlanAddon>();
Serializer.PrepareSerializer<PlanLimit>();
Serializer.PrepareSerializer<IdentityUser>();
Serializer.PrepareSerializer<UserProfile>();
Expand Down
18 changes: 15 additions & 3 deletions Core/Resgrid.Model/PlanAddon.cs
Original file line number Diff line number Diff line change
@@ -1,25 +1,34 @@
using Newtonsoft.Json;
using ProtoBuf;
using Resgrid.Framework;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;

namespace Resgrid.Model
{
[ProtoContract]
public class PlanAddon : IEntity
{
[ProtoMember(1)]
public string PlanAddonId { get; set; }

[ProtoMember(2)]
public int? PlanId { get; set; }

[ProtoMember(3)]
public virtual Plan Plan { get; set; }

[ProtoMember(4)]
public int AddonType { get; set; }

[ProtoMember(5)]
public double Cost { get; set; }

[ProtoMember(6)]
public string ExternalId { get; set; }

[ProtoMember(7)]
public string TestExternalId { get; set; }
Comment thread
coderabbitai[bot] marked this conversation as resolved.

[NotMapped]
Expand All @@ -33,10 +42,13 @@ public class PlanAddon : IEntity

public string GetExternalKey()
{
if (Config.PaymentProviderConfig.IsTestMode)
return TestExternalId;
else
if (!string.IsNullOrEmpty(ExternalId))
return ExternalId;

if (Config.PaymentProviderConfig.IsTestMode && !string.IsNullOrEmpty(TestExternalId))
return TestExternalId;

return null;
}

[NotMapped]
Expand Down
6 changes: 6 additions & 0 deletions Core/Resgrid.Services/CustomMapService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,9 @@ public async Task<CustomMapTile> GetTileAsync(string layerId, int z, int x, int

public async Task<CustomMapImport> ImportGeoJsonAsync(string mapId, string layerId, string geoJsonString, string userId, CancellationToken cancellationToken = default(CancellationToken))
{
if (string.IsNullOrWhiteSpace(layerId))
throw new ArgumentException("A target layer must be specified for import.", nameof(layerId));

var import = new CustomMapImport
{
CustomMapId = mapId,
Expand Down Expand Up @@ -353,6 +356,9 @@ public async Task<CustomMapTile> GetTileAsync(string layerId, int z, int x, int

public async Task<CustomMapImport> ImportKmlAsync(string mapId, string layerId, Stream kmlStream, bool isKmz, string userId, CancellationToken cancellationToken = default(CancellationToken))
{
if (string.IsNullOrWhiteSpace(layerId))
throw new ArgumentException("A target layer must be specified for import.", nameof(layerId));

var import = new CustomMapImport
{
CustomMapId = mapId,
Expand Down
3 changes: 3 additions & 0 deletions Core/Resgrid.Services/UsersService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,9 @@ public async Task<bool> DoesUserHaveAnyActiveDepartments(string userName)

public IdentityUser GetUserById(string userId, bool bypassCache = true)
{
if (string.IsNullOrWhiteSpace(userId))
return null;

if (!bypassCache && Config.SystemBehaviorConfig.CacheEnabled)
{
Func<IdentityUser> getUser = delegate ()
Expand Down
28 changes: 24 additions & 4 deletions Providers/Resgrid.Providers.Weather/NwsWeatherAlertProvider.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using Resgrid.Framework;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
Expand Down Expand Up @@ -79,16 +80,35 @@ public async Task<List<WeatherAlert>> FetchAlertsAsync(WeatherAlertSource source
if (response.StatusCode == System.Net.HttpStatusCode.NotModified)
return alerts; // No changes since last poll

response.EnsureSuccessStatusCode();
// Read response body before checking status for diagnostic context
var json = response.Content != null ? await response.Content.ReadAsStringAsync() : string.Empty;

if (!response.IsSuccessStatusCode)
{
// For client errors (4xx), the request is malformed — retrying won't help.
// Log the full diagnostic context and return empty rather than crashing the poller.
var snippet = json.Length > 500 ? json.Substring(0, 500) : json;
var errorMsg = $"NWS API returned {(int)response.StatusCode} ({response.ReasonPhrase}) " +
$"for URL '{url}', departmentId='{source.DepartmentId}', areaFilter='{source.AreaFilter}'. " +
$"Response body: {snippet}";

if ((int)response.StatusCode >= 500)
{
// Server errors are transient — throw so the poller can retry
throw new HttpRequestException(errorMsg);
}

// Client errors (400, etc.) are permanent — log and return empty
Logging.LogError(errorMsg);
return alerts;
}

// Update ETag on source
if (response.Headers.ETag != null)
source.LastETag = response.Headers.ETag.Tag;

var json = await response.Content.ReadAsStringAsync();

// Validate response content-type is JSON before parsing
var contentType = response.Content.Headers.ContentType?.MediaType ?? "";
var contentType = response.Content?.Headers.ContentType?.MediaType ?? "";
if (!contentType.Contains("json", StringComparison.OrdinalIgnoreCase))
{
var snippet = json.Length > 200 ? json.Substring(0, 200) : json;
Expand Down
16 changes: 15 additions & 1 deletion Web/Resgrid.Web/Areas/User/Controllers/CustomMapsController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
using Resgrid.Model;
using Resgrid.Model.Services;
using Resgrid.Web.Areas.User.Models.CustomMaps;
using Microsoft.Extensions.Localization;
using Resgrid.Localization.Areas.User.CustomMaps;
using Resgrid.Web.Helpers;

namespace Resgrid.Web.Areas.User.Controllers
Expand All @@ -17,10 +19,12 @@ namespace Resgrid.Web.Areas.User.Controllers
public class CustomMapsController : SecureBaseController
{
private readonly ICustomMapService _customMapService;
private readonly IStringLocalizer<CustomMaps> _localizer;

public CustomMapsController(ICustomMapService customMapService)
public CustomMapsController(ICustomMapService customMapService, IStringLocalizer<CustomMaps> localizer)
{
_customMapService = customMapService;
_localizer = localizer;
}

#region Map CRUD
Expand Down Expand Up @@ -277,6 +281,16 @@ public async Task<IActionResult> ImportUpload(string mapId, string layerId, IFor
if (map == null || map.DepartmentId != DepartmentId)
return RedirectToAction("Index");

if (string.IsNullOrWhiteSpace(layerId))
{
var model = new CustomMapImportView();
model.Map = map;
model.Layers = await _customMapService.GetLayersForMapAsync(mapId);
model.Imports = await _customMapService.GetImportsForMapAsync(mapId);
model.Message = _localizer["PleaseSelectTargetLayer"];
return View("Import", model);
}

if (importFile == null || importFile.Length == 0)
return RedirectToAction("Import", new { id = mapId });

Expand Down
6 changes: 6 additions & 0 deletions Web/Resgrid.Web/Areas/User/Controllers/PersonnelController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -649,6 +649,9 @@ public async Task<IActionResult> AddPerson(AddPersonModel model, IFormCollection
[Authorize(Policy = ResgridResources.Personnel_Delete)]
public async Task<IActionResult> DeletePerson(string userId)
{
if (string.IsNullOrWhiteSpace(userId))
return RedirectToAction("Index", "Personnel", new { area = "User" });

if (!await _authorizationService.CanUserDeleteUserAsync(DepartmentId, UserId, userId))
return Unauthorized();

Expand All @@ -666,6 +669,9 @@ public async Task<IActionResult> DeletePerson(string userId)
[RequiresRecentTwoFactor]
public async Task<IActionResult> DeletePerson(DeletePersonModel model, CancellationToken cancellationToken)
{
if (string.IsNullOrWhiteSpace(model?.UserId))
return RedirectToAction("Index", "Personnel", new { area = "User" });

if (!await _authorizationService.CanUserDeleteUserAsync(DepartmentId, UserId, model.UserId))
return Unauthorized();

Expand Down
17 changes: 16 additions & 1 deletion Web/Resgrid.Web/Areas/User/Controllers/TemplatesController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ public async Task<IActionResult> New()
model.Template = new CallQuickTemplate();

var priorites = await _callsService.GetActiveCallPrioritiesForDepartmentAsync(DepartmentId);
model.CallPriorities = new SelectList(priorites, "DepartmentCallPriorityId", "Name", priorites.FirstOrDefault(x => x.IsDefault));
model.CallPriorities = new SelectList(priorites, "DepartmentCallPriorityId", "Name", priorites.FirstOrDefault(x => x.IsDefault)?.DepartmentCallPriorityId);


List<CallType> types = new List<CallType>();
Expand All @@ -70,13 +70,25 @@ public async Task<IActionResult> New()
return View(model);
}

private async Task PopulateDropdowns(NewTemplateModel model)
{
var priorites = await _callsService.GetActiveCallPrioritiesForDepartmentAsync(DepartmentId);
model.CallPriorities = new SelectList(priorites, "DepartmentCallPriorityId", "Name", priorites.FirstOrDefault(x => x.IsDefault)?.DepartmentCallPriorityId);

Comment thread
coderabbitai[bot] marked this conversation as resolved.
List<CallType> types = new List<CallType>();
types.Add(new CallType { CallTypeId = 0, Type = "No Type" });
types.AddRange(await _callsService.GetCallTypesForDepartmentAsync(DepartmentId));
model.CallTypes = new SelectList(types, "Type", "Type");
}

[HttpPost]
[Authorize(Policy = ResgridResources.Department_Update)]
public async Task<IActionResult> New(NewTemplateModel model, CancellationToken cancellationToken)
{
if (String.IsNullOrWhiteSpace(model.Template.CallName) &&
String.IsNullOrWhiteSpace(model.Template.CallNature))
{
await PopulateDropdowns(model);
model.Message = "You must specify a call name and/or call nature to set to save the template";
return View(model);
}
Expand All @@ -91,6 +103,7 @@ public async Task<IActionResult> New(NewTemplateModel model, CancellationToken c
return RedirectToAction("Index");
}

await PopulateDropdowns(model);
return View(model);
}

Expand Down Expand Up @@ -125,6 +138,7 @@ public async Task<IActionResult> Edit(NewTemplateModel model, CancellationToken
if (String.IsNullOrWhiteSpace(model.Template.CallName) &&
String.IsNullOrWhiteSpace(model.Template.CallNature))
{
await PopulateDropdowns(model);
model.Message = "You must specify a call name and/or call nature to set to save the template";
return View(model);
}
Expand All @@ -139,6 +153,7 @@ public async Task<IActionResult> Edit(NewTemplateModel model, CancellationToken
return RedirectToAction("Index");
}

await PopulateDropdowns(model);
return View(model);
}

Expand Down
18 changes: 11 additions & 7 deletions Web/Resgrid.Web/Areas/User/Views/CustomMaps/Import.cshtml
Original file line number Diff line number Diff line change
Expand Up @@ -34,15 +34,19 @@
<div class="form-group">
<label class="col-sm-2 control-label">@localizer["TargetLayer"]</label>
<div class="col-sm-4">
<select class="form-control" name="layerId">
@if (Model.Layers != null)
{
foreach (var layer in Model.Layers)
@if (Model.Layers == null || !Model.Layers.Any())
{
<p class="text-danger">@localizer["NoLayersAvailable"]</p>
}
else
{
<select class="form-control" name="layerId">
@foreach (var layer in Model.Layers)
{
<option value="@layer.IndoorMapFloorId">@layer.Name</option>
}
}
</select>
</select>
}
</div>
</div>
<div class="form-group">
Expand All @@ -52,7 +56,7 @@
<span class="help-block">@localizer["ImportFileHelp"]</span>
</div>
<div class="col-sm-2">
<button class="btn btn-primary" type="submit">@localizer["ImportButton"]</button>
<button class="btn btn-primary" type="submit" @(Model.Layers == null || !Model.Layers.Any() ? "disabled" : null)>@localizer["ImportButton"]</button>
</div>
</div>
</form>
Expand Down
Loading