Skip to content

Commit 713677c

Browse files
authored
Merge pull request #255 from Resgrid/develop
Develop
2 parents 298b09b + e2aeecc commit 713677c

15 files changed

Lines changed: 262 additions & 53 deletions

File tree

Core/Resgrid.Model/MobileCarriers.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -302,7 +302,8 @@ public static class Carriers
302302
MobileCarriers.Vodacom,
303303
MobileCarriers.MTN,
304304
MobileCarriers.TelkomMobile,
305-
MobileCarriers.CellC
305+
MobileCarriers.CellC,
306+
MobileCarriers.TMobile
306307
};
307308

308309
public static HashSet<MobileCarriers> OnPremSmsGatewayCarriers = new HashSet<MobileCarriers>()

Core/Resgrid.Services/MessageService.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ public async Task<Message> GetMessageByIdAsync(int messageId)
5050
public async Task<List<Message>> GetInboxMessagesByUserIdAsync(string userId)
5151
{
5252
var list = await _messageRepository.GetInboxMessagesByUserIdAsync(userId);
53-
return list.ToList();
53+
return list.OrderByDescending(x => x.SentOn).ToList();
5454
}
5555

5656
public async Task<List<Message>> GetUnreadInboxMessagesByUserIdAsync(string userId)
@@ -65,7 +65,7 @@ public async Task<List<Message>> GetSentMessagesByUserIdAsync(string userId)
6565
var items = await _messageRepository.GetSentMessagesByUserIdAsync(userId);
6666

6767
if (items != null && items.Any())
68-
return items.ToList();
68+
return items.OrderByDescending(x => x.SentOn).ToList();
6969

7070
return new List<Message>();
7171
}

Providers/Resgrid.Providers.Claims/ClaimsLogic.cs

Lines changed: 31 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1101,16 +1101,18 @@ public static void AddContactsClaims(ClaimsIdentity identity, bool isAdmin, List
11011101
}
11021102
else if (permission.Action == (int)PermissionActions.DepartmentAdminsAndSelectRoles && !isAdmin)
11031103
{
1104-
var roleIds = permission.Data.Split(char.Parse(",")).Select(int.Parse);
1105-
var role = from r in roles
1106-
where roleIds.Contains(r.PersonnelRoleId)
1107-
select r;
1108-
1109-
if (role.Any())
1104+
if (!String.IsNullOrWhiteSpace(permission.Data))
11101105
{
1111-
identity.AddClaim(new Claim(ResgridClaimTypes.Resources.Contacts, ResgridClaimTypes.Actions.View));
1112-
}
1106+
var roleIds = permission.Data.Split(char.Parse(",")).Select(int.Parse);
1107+
var role = from r in roles
1108+
where roleIds.Contains(r.PersonnelRoleId)
1109+
select r;
11131110

1111+
if (role.Any())
1112+
{
1113+
identity.AddClaim(new Claim(ResgridClaimTypes.Resources.Contacts, ResgridClaimTypes.Actions.View));
1114+
}
1115+
}
11141116
}
11151117
else if (permission.Action == (int)PermissionActions.Everyone)
11161118
{
@@ -1143,17 +1145,19 @@ where roleIds.Contains(r.PersonnelRoleId)
11431145
}
11441146
else if (permission.Action == (int)PermissionActions.DepartmentAdminsAndSelectRoles && !isAdmin)
11451147
{
1146-
var roleIds = permission.Data.Split(char.Parse(",")).Select(int.Parse);
1147-
var role = from r in roles
1148-
where roleIds.Contains(r.PersonnelRoleId)
1149-
select r;
1150-
1151-
if (role.Any())
1148+
if (!String.IsNullOrWhiteSpace(permission.Data))
11521149
{
1153-
identity.AddClaim(new Claim(ResgridClaimTypes.Resources.Contacts, ResgridClaimTypes.Actions.Update));
1154-
identity.AddClaim(new Claim(ResgridClaimTypes.Resources.Contacts, ResgridClaimTypes.Actions.Create));
1155-
}
1150+
var roleIds = permission.Data.Split(char.Parse(",")).Select(int.Parse);
1151+
var role = from r in roles
1152+
where roleIds.Contains(r.PersonnelRoleId)
1153+
select r;
11561154

1155+
if (role.Any())
1156+
{
1157+
identity.AddClaim(new Claim(ResgridClaimTypes.Resources.Contacts, ResgridClaimTypes.Actions.Update));
1158+
identity.AddClaim(new Claim(ResgridClaimTypes.Resources.Contacts, ResgridClaimTypes.Actions.Create));
1159+
}
1160+
}
11571161
}
11581162
else if (permission.Action == (int)PermissionActions.Everyone)
11591163
{
@@ -1186,16 +1190,18 @@ where roleIds.Contains(r.PersonnelRoleId)
11861190
}
11871191
else if (permission.Action == (int)PermissionActions.DepartmentAdminsAndSelectRoles && !isAdmin)
11881192
{
1189-
var roleIds = permission.Data.Split(char.Parse(",")).Select(int.Parse);
1190-
var role = from r in roles
1191-
where roleIds.Contains(r.PersonnelRoleId)
1192-
select r;
1193-
1194-
if (role.Any())
1193+
if (!String.IsNullOrWhiteSpace(permission.Data))
11951194
{
1196-
identity.AddClaim(new Claim(ResgridClaimTypes.Resources.Contacts, ResgridClaimTypes.Actions.Delete));
1197-
}
1195+
var roleIds = permission.Data.Split(char.Parse(",")).Select(int.Parse);
1196+
var role = from r in roles
1197+
where roleIds.Contains(r.PersonnelRoleId)
1198+
select r;
11981199

1200+
if (role.Any())
1201+
{
1202+
identity.AddClaim(new Claim(ResgridClaimTypes.Resources.Contacts, ResgridClaimTypes.Actions.Delete));
1203+
}
1204+
}
11991205
}
12001206
else if (permission.Action == (int)PermissionActions.Everyone)
12011207
{
Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
using Microsoft.AspNetCore.Http;
2+
using Microsoft.AspNetCore.Mvc;
3+
using Resgrid.Framework;
4+
using Resgrid.Model;
5+
using Resgrid.Model.Services;
6+
using System;
7+
using System.IO;
8+
using System.Net.Mime;
9+
using System.Threading.Tasks;
10+
11+
using Resgrid.Web.Services.Models;
12+
using Resgrid.Web.ServicesCore.Helpers;
13+
using SixLabors.ImageSharp;
14+
using SixLabors.ImageSharp.Processing;
15+
16+
17+
namespace Resgrid.Web.Services.Controllers.v4
18+
{
19+
/// <summary>
20+
/// Used to interact with the user avatars (profile pictures) in the Resgrid system. The authentication header isn't required to access this method.
21+
/// </summary>
22+
[Route("api/v{VersionId:apiVersion}/[controller]")]
23+
[ApiVersion("4.0")]
24+
[ApiExplorerSettings(GroupName = "v4")]
25+
//[EnableCors("_resgridWebsiteAllowSpecificOrigins")]
26+
public class AvatarsController : ControllerBase
27+
{
28+
private readonly IImageService _imageService;
29+
private static byte[] _defaultProfileImage;
30+
31+
public AvatarsController(IImageService imageService)
32+
{
33+
_imageService = imageService;
34+
}
35+
36+
/// <summary>
37+
/// Get a users avatar from the Resgrid system based on their ID
38+
/// </summary>
39+
/// <param name="id">ID of the user</param>
40+
/// <returns></returns>
41+
[HttpGet("Get")]
42+
[Produces(MediaTypeNames.Image.Jpeg)]
43+
[ProducesResponseType(StatusCodes.Status200OK)]
44+
[ProducesResponseType(StatusCodes.Status404NotFound)]
45+
public async Task<ActionResult> Get(string id, int? type)
46+
{
47+
byte[] data = null;
48+
if (type == null)
49+
data = await _imageService.GetImageAsync(ImageTypes.Avatar, id);
50+
else
51+
data = await _imageService.GetImageAsync((ImageTypes)type.Value, id);
52+
53+
if (data == null || data.Length <= 0)
54+
return File(GetDefaultProfileImage(), "image/png");
55+
56+
return File(data, "image/jpeg");
57+
}
58+
59+
[HttpPost("Upload")]
60+
[ProducesResponseType(StatusCodes.Status200OK)]
61+
[ProducesResponseType(StatusCodes.Status201Created)]
62+
[ProducesResponseType(StatusCodes.Status400BadRequest)]
63+
public async Task<ActionResult> Upload([FromQuery] string id, int? type)
64+
{
65+
var img = HttpContext.Request.Form.Files.Count > 0 ?
66+
HttpContext.Request.Form.Files[0] : null;
67+
68+
// check for a valid mediatype
69+
if (!img.ContentType.StartsWith("image/"))
70+
return BadRequest();
71+
72+
// load the image from the upload and generate a new filename
73+
//var image = Image.FromStream(img.OpenReadStream());
74+
var extension = Path.GetExtension(img.FileName);
75+
byte[] imgArray;
76+
int width = 0;
77+
int height = 0;
78+
79+
using (Image image = Image.Load(img.OpenReadStream()))
80+
{
81+
//image.Mutate(x => x
82+
// .Resize(image.Width / 2, image.Height / 2)
83+
// .Grayscale());
84+
85+
width = image.Width;
86+
height = image.Height;
87+
88+
MemoryStream ms = new MemoryStream();
89+
await image.SaveAsPngAsync(ms);
90+
imgArray = ms.ToArray();
91+
92+
//image.Save()"output/fb.png"); // Automatic encoder selected based on extension.
93+
}
94+
95+
//ImageConverter converter = new ImageConverter();
96+
//byte[] imgArray = (byte[])converter.ConvertTo(image, typeof(byte[]));
97+
98+
if (type == null)
99+
await _imageService.SaveImageAsync(ImageTypes.Avatar, id, imgArray);
100+
else
101+
await _imageService.SaveImageAsync((ImageTypes)type.Value, id, imgArray);
102+
103+
var baseUrl = Config.SystemBehaviorConfig.ResgridApiBaseUrl;
104+
105+
string url;
106+
107+
if (type == null)
108+
url = baseUrl + "/api/v4/Avatars/Get?id=" + id;
109+
else
110+
url = baseUrl + "/api/v4/Avatars/Get?id=" + id + "&type=" + type.Value;
111+
112+
var obj = new
113+
{
114+
status = CroppicStatuses.Success,
115+
url = url,
116+
width = width,
117+
height = height
118+
};
119+
120+
return CreatedAtAction(nameof(Upload), new { id = obj.url }, obj);
121+
}
122+
123+
[HttpPut("Crop")]
124+
[ProducesResponseType(StatusCodes.Status200OK)]
125+
[ProducesResponseType(StatusCodes.Status201Created)]
126+
[ProducesResponseType(StatusCodes.Status400BadRequest)]
127+
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
128+
public async Task<ActionResult> Crop([FromBody] CropRequest model)
129+
{
130+
// extract original image ID and generate a new filename for the cropped result
131+
var originalUri = new Uri(model.imgUrl);
132+
var originalId = originalUri.Query.Replace("?id=", "");
133+
134+
try
135+
{
136+
byte[] imgArray;
137+
138+
using (var ms = new MemoryStream(await _imageService.GetImageAsync(ImageTypes.Avatar, originalId)))
139+
using (var image = Image.Load(ms))
140+
{
141+
// load the original picture and resample it to the scaled values
142+
var bitmap = ImageUtils.Resize(image, (int)model.imgW, (int)model.imgH);
143+
144+
var croppedBitmap = ImageUtils.Crop(bitmap, model.imgX1, model.imgY1, model.cropW, model.cropH);
145+
146+
using (var ms2 = new MemoryStream())
147+
{
148+
await croppedBitmap.SaveAsPngAsync(ms2);
149+
imgArray = ms2.ToArray();
150+
}
151+
}
152+
153+
await _imageService.SaveImageAsync(ImageTypes.Avatar, originalId, imgArray);
154+
}
155+
catch (Exception ex)
156+
{
157+
Logging.LogException(ex, $"Error cropping avatar image for ID: {originalId}");
158+
return StatusCode(StatusCodes.Status500InternalServerError, "An error occurred while cropping the image");
159+
}
160+
161+
var obj = new
162+
{
163+
status = CroppicStatuses.Success,
164+
url = originalId
165+
};
166+
167+
return CreatedAtAction(nameof(Crop), new { id = obj.url }, obj);
168+
}
169+
170+
private byte[] GetDefaultProfileImage()
171+
{
172+
if (_defaultProfileImage == null)
173+
_defaultProfileImage = EmbeddedResources.GetApiRequestFile(typeof(AvatarsController), "Resgrid.Web.Services.Properties.Resources.defaultProfile.png");
174+
175+
return _defaultProfileImage;
176+
}
177+
}
178+
179+
internal static class CroppicStatuses
180+
{
181+
public const string Success = "success";
182+
public const string Error = "error";
183+
}
184+
}

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

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -312,13 +312,19 @@ public async Task<ActionResult<SendMessageResult>> SendMessage([FromBody] NewMes
312312
// Add all the explict people
313313
foreach (var person in newMessageInput.Recipients.Where(x => x.Type == 1))
314314
{
315-
if (usersToSendTo.All(x => x != person.Id) && person.Id != UserId)
315+
if (!String.IsNullOrWhiteSpace(person.Id))
316316
{
317-
// Ensure the user is in the same department
318-
if (departmentUsers.Any(x => x.UserId == person.Id))
317+
// New RN Apps add a prefix to ID's from the Recipients list, guard against the prefix here.
318+
var userIdToSendTo = person.Id.Replace("P:", "").Trim();
319+
320+
if (usersToSendTo.All(x => x != userIdToSendTo) && userIdToSendTo != UserId)
319321
{
320-
usersToSendTo.Add(person.Id);
321-
message.AddRecipient(person.Id);
322+
// Ensure the user is in the same department
323+
if (departmentUsers.Any(x => x.UserId == userIdToSendTo))
324+
{
325+
usersToSendTo.Add(userIdToSendTo);
326+
message.AddRecipient(userIdToSendTo);
327+
}
322328
}
323329
}
324330
}
@@ -328,8 +334,11 @@ public async Task<ActionResult<SendMessageResult>> SendMessage([FromBody] NewMes
328334
{
329335
if (!String.IsNullOrWhiteSpace(group.Id))
330336
{
337+
// New RN Apps add a prefix to ID's from the Recipients list, guard against the prefix here.
338+
var groupIdToSendTo = group.Id.Replace("G:", "").Trim();
339+
331340
int groupId = 0;
332-
if (int.TryParse(group.Id.Trim(), out groupId))
341+
if (int.TryParse(groupIdToSendTo, out groupId))
333342
{
334343
if (departmentGroups.Any(x => x.DepartmentGroupId == groupId))
335344
{
@@ -356,8 +365,11 @@ public async Task<ActionResult<SendMessageResult>> SendMessage([FromBody] NewMes
356365
{
357366
if (!String.IsNullOrWhiteSpace(role.Id))
358367
{
368+
// New RN Apps add a prefix to ID's from the Recipients list, guard against the prefix here.
369+
var roleIdToSendTo = role.Id.Replace("R:", "").Trim();
370+
359371
int roleId = 0;
360-
if (int.TryParse(role.Id.Trim(), out roleId))
372+
if (int.TryParse(roleIdToSendTo, out roleId))
361373
{
362374
if (departmentRoles.Any(x => x.PersonnelRoleId == roleId))
363375
{

Web/Resgrid.Web.Services/Resgrid.Web.Services.xml

Lines changed: 12 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ public async Task<IActionResult> Index()
4343
var model = new IndexView();
4444
model.Department = await _departmentsService.GetDepartmentByIdAsync(DepartmentId);
4545
model.Profile = _departmentProfileService.GetOrInitializeDepartmentProfile(DepartmentId);
46-
model.ImageUrl = $"{Config.SystemBehaviorConfig.ResgridApiBaseUrl}/api/v3/Avatars/Get?id={model.Profile.DepartmentId}&type=1";
46+
model.ImageUrl = $"{Config.SystemBehaviorConfig.ResgridApiBaseUrl}/api/v4/Avatars/Get?id={model.Profile.DepartmentId}&type=1";
4747

4848
var posts = _departmentProfileService.GetArticlesForDepartment(model.Profile.DepartmentProfileId);
4949
var visiblePosts = _departmentProfileService.GetVisibleArticlesForDepartment(model.Profile.DepartmentProfileId);
@@ -67,7 +67,7 @@ public async Task<IActionResult> Profile()
6767

6868
model.ApiUrl = Config.SystemBehaviorConfig.ResgridApiBaseUrl;
6969
model.Department = await _departmentsService.GetDepartmentByUserIdAsync(UserId);
70-
model.ImageUrl = $"{model.ApiUrl}/api/v3/Avatars/Get?id={model.Department.DepartmentId}&type=1";
70+
model.ImageUrl = $"{model.ApiUrl}/api/v4/Avatars/Get?id={model.Department.DepartmentId}&type=1";
7171

7272

7373
var profile = _departmentProfileService.GetOrInitializeDepartmentProfile(DepartmentId);
@@ -98,7 +98,7 @@ public async Task<IActionResult> Profile(ProfileView model)
9898
{
9999
model.ApiUrl = Config.SystemBehaviorConfig.ResgridApiBaseUrl;
100100
model.Department = await _departmentsService.GetDepartmentByUserIdAsync(UserId);
101-
model.ImageUrl = $"{model.ApiUrl}/api/v3/Avatars/Get?id={model.Department.DepartmentId}&type=1";
101+
model.ImageUrl = $"{model.ApiUrl}/api/v4/Avatars/Get?id={model.Department.DepartmentId}&type=1";
102102

103103
if (ModelState.IsValid)
104104
{

0 commit comments

Comments
 (0)